@nicia-ai/typegraph 0.36.0 → 0.37.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/backend/postgres/index.cjs +24 -24
- package/dist/backend/postgres/index.d.cts +6 -6
- package/dist/backend/postgres/index.d.ts +6 -6
- package/dist/backend/postgres/index.js +11 -11
- package/dist/backend/postgres/pglite.cjs +16 -16
- package/dist/backend/postgres/pglite.d.cts +3 -3
- package/dist/backend/postgres/pglite.d.ts +3 -3
- package/dist/backend/postgres/pglite.js +11 -11
- package/dist/backend/sqlite/index.cjs +22 -21
- package/dist/backend/sqlite/index.d.cts +11 -9
- package/dist/backend/sqlite/index.d.ts +11 -9
- package/dist/backend/sqlite/index.js +10 -9
- package/dist/backend/sqlite/libsql.cjs +15 -14
- package/dist/backend/sqlite/libsql.cjs.map +1 -1
- package/dist/backend/sqlite/libsql.d.cts +3 -3
- package/dist/backend/sqlite/libsql.d.ts +3 -3
- package/dist/backend/sqlite/libsql.js +11 -10
- package/dist/backend/sqlite/libsql.js.map +1 -1
- package/dist/backend/sqlite/local.cjs +30 -23
- package/dist/backend/sqlite/local.cjs.map +1 -1
- package/dist/backend/sqlite/local.d.cts +3 -3
- package/dist/backend/sqlite/local.d.ts +3 -3
- package/dist/backend/sqlite/local.js +18 -11
- package/dist/backend/sqlite/local.js.map +1 -1
- package/dist/{chunk-TCOCYN2P.cjs → chunk-27SSDCEP.cjs} +14 -14
- package/dist/{chunk-TCOCYN2P.cjs.map → chunk-27SSDCEP.cjs.map} +1 -1
- package/dist/{chunk-5AQIKK56.cjs → chunk-2N6EOBTH.cjs} +16 -16
- package/dist/{chunk-5AQIKK56.cjs.map → chunk-2N6EOBTH.cjs.map} +1 -1
- package/dist/{chunk-FN35DLCN.cjs → chunk-3C2RYI6L.cjs} +13 -107
- package/dist/chunk-3C2RYI6L.cjs.map +1 -0
- package/dist/{chunk-HW3LW7DH.cjs → chunk-3DS7Z4T4.cjs} +39 -39
- package/dist/{chunk-HW3LW7DH.cjs.map → chunk-3DS7Z4T4.cjs.map} +1 -1
- package/dist/{chunk-RSSQKPSZ.cjs → chunk-3GH67G4Z.cjs} +5 -5
- package/dist/{chunk-RSSQKPSZ.cjs.map → chunk-3GH67G4Z.cjs.map} +1 -1
- package/dist/{chunk-M75SUBFG.js → chunk-3RWZPJKV.js} +24 -9
- package/dist/chunk-3RWZPJKV.js.map +1 -0
- package/dist/{chunk-SKCOUWAX.js → chunk-3SLPDKHQ.js} +4 -4
- package/dist/chunk-3SLPDKHQ.js.map +1 -0
- package/dist/{chunk-5A6FNS6N.cjs → chunk-3XXGYU62.cjs} +11 -11
- package/dist/{chunk-5A6FNS6N.cjs.map → chunk-3XXGYU62.cjs.map} +1 -1
- package/dist/{chunk-IU46OHVW.js → chunk-44TA22QT.js} +58 -11
- package/dist/chunk-44TA22QT.js.map +1 -0
- package/dist/{chunk-5DIPR3L3.js → chunk-552KNYQJ.js} +2392 -279
- package/dist/chunk-552KNYQJ.js.map +1 -0
- package/dist/{chunk-2THZ3TQI.cjs → chunk-5KEQ74YU.cjs} +14 -14
- package/dist/chunk-5KEQ74YU.cjs.map +1 -0
- package/dist/{chunk-Q5TEXCTB.js → chunk-5KPNWT6X.js} +3 -3
- package/dist/{chunk-Q5TEXCTB.js.map → chunk-5KPNWT6X.js.map} +1 -1
- package/dist/{chunk-WI7ISNQF.js → chunk-5PNMDJ7J.js} +94 -5
- package/dist/chunk-5PNMDJ7J.js.map +1 -0
- package/dist/{chunk-AW4A3P5A.cjs → chunk-5VQM6VWP.cjs} +2 -2
- package/dist/{chunk-AW4A3P5A.cjs.map → chunk-5VQM6VWP.cjs.map} +1 -1
- package/dist/{chunk-A3W7QG7K.js → chunk-5WWVZMVX.js} +2 -2
- package/dist/{chunk-A3W7QG7K.js.map → chunk-5WWVZMVX.js.map} +1 -1
- package/dist/{chunk-FACJJWGF.cjs → chunk-6EEO3OVU.cjs} +7 -7
- package/dist/{chunk-FACJJWGF.cjs.map → chunk-6EEO3OVU.cjs.map} +1 -1
- package/dist/{chunk-DIK74MXX.cjs → chunk-BVRDGYXQ.cjs} +97 -4
- package/dist/chunk-BVRDGYXQ.cjs.map +1 -0
- package/dist/{chunk-4BCU6VFZ.cjs → chunk-DEFQM7JU.cjs} +7 -7
- package/dist/{chunk-4BCU6VFZ.cjs.map → chunk-DEFQM7JU.cjs.map} +1 -1
- package/dist/{chunk-456Z2U7Y.cjs → chunk-DIQ3AV5W.cjs} +574 -268
- package/dist/chunk-DIQ3AV5W.cjs.map +1 -0
- package/dist/{chunk-FPIZFYHE.js → chunk-EXU3RASM.js} +74 -15
- package/dist/chunk-EXU3RASM.js.map +1 -0
- package/dist/{chunk-H7YNSMA7.cjs → chunk-HAHNRASW.cjs} +20 -2
- package/dist/chunk-HAHNRASW.cjs.map +1 -0
- package/dist/{chunk-J3LEGNQB.cjs → chunk-IP3CGGPA.cjs} +8 -8
- package/dist/{chunk-J3LEGNQB.cjs.map → chunk-IP3CGGPA.cjs.map} +1 -1
- package/dist/{chunk-X4JBT642.cjs → chunk-IPSVVTWT.cjs} +94 -35
- package/dist/chunk-IPSVVTWT.cjs.map +1 -0
- package/dist/{chunk-VQEXF5D4.js → chunk-IXGYRP2P.js} +184 -92
- package/dist/chunk-IXGYRP2P.js.map +1 -0
- package/dist/{chunk-BJVEU3ZL.js → chunk-J4IUSYFF.js} +251 -121
- package/dist/chunk-J4IUSYFF.js.map +1 -0
- package/dist/{chunk-JFMEONLG.cjs → chunk-JBNOGRKW.cjs} +318 -188
- package/dist/chunk-JBNOGRKW.cjs.map +1 -0
- package/dist/{chunk-4GX2JJXS.js → chunk-K3Q3LJY3.js} +520 -221
- package/dist/chunk-K3Q3LJY3.js.map +1 -0
- package/dist/{chunk-OR5ZA5WP.js → chunk-K5UBVGJN.js} +4 -4
- package/dist/{chunk-OR5ZA5WP.js.map → chunk-K5UBVGJN.js.map} +1 -1
- package/dist/{chunk-UZV3AZKF.js → chunk-KQJ54OG5.js} +3 -3
- package/dist/{chunk-UZV3AZKF.js.map → chunk-KQJ54OG5.js.map} +1 -1
- package/dist/{chunk-SXGE7ITL.js → chunk-L7HQ2OSI.js} +4 -4
- package/dist/{chunk-SXGE7ITL.js.map → chunk-L7HQ2OSI.js.map} +1 -1
- package/dist/{chunk-RAXU3RMB.js → chunk-M6VES7V6.js} +4 -4
- package/dist/{chunk-RAXU3RMB.js.map → chunk-M6VES7V6.js.map} +1 -1
- package/dist/{chunk-7BQLPXK6.cjs → chunk-NBP2WTJW.cjs} +3011 -898
- package/dist/chunk-NBP2WTJW.cjs.map +1 -0
- package/dist/{chunk-PWGX5DF5.cjs → chunk-NUIG3BCV.cjs} +100 -85
- package/dist/chunk-NUIG3BCV.cjs.map +1 -0
- package/dist/{chunk-ZAMFOJ5P.cjs → chunk-NXQFATMF.cjs} +269 -177
- package/dist/chunk-NXQFATMF.cjs.map +1 -0
- package/dist/{chunk-KBGQIPRQ.js → chunk-PC2UBAUB.js} +20 -3
- package/dist/chunk-PC2UBAUB.js.map +1 -0
- package/dist/chunk-QARBLBXT.cjs +219 -0
- package/dist/chunk-QARBLBXT.cjs.map +1 -0
- package/dist/{chunk-6FR2MMS6.js → chunk-QERAIDRJ.js} +45 -4
- package/dist/chunk-QERAIDRJ.js.map +1 -0
- package/dist/chunk-QSXTWU4U.cjs +12 -0
- package/dist/{chunk-4BMPGT35.cjs.map → chunk-QSXTWU4U.cjs.map} +1 -1
- package/dist/{chunk-DN5VP4JC.js → chunk-RCVLFTCT.js} +9 -10
- package/dist/chunk-RCVLFTCT.js.map +1 -0
- package/dist/{chunk-ZIGXMKQQ.js → chunk-RRSNR3TP.js} +3 -3
- package/dist/{chunk-ZIGXMKQQ.js.map → chunk-RRSNR3TP.js.map} +1 -1
- package/dist/{chunk-4CAXEB2F.cjs → chunk-TGOMW4MB.cjs} +91 -92
- package/dist/chunk-TGOMW4MB.cjs.map +1 -0
- package/dist/{chunk-PZKNKG5M.js → chunk-TKT2XHED.js} +8 -8
- package/dist/{chunk-PZKNKG5M.js.map → chunk-TKT2XHED.js.map} +1 -1
- package/dist/{chunk-S5F7ZLP2.js → chunk-TPF25IG6.js} +3 -3
- package/dist/{chunk-S5F7ZLP2.js.map → chunk-TPF25IG6.js.map} +1 -1
- package/dist/{chunk-5SEGFTZX.js → chunk-UGZUAYAR.js} +3 -3
- package/dist/{chunk-5SEGFTZX.js.map → chunk-UGZUAYAR.js.map} +1 -1
- package/dist/{chunk-VHMPG6UL.js → chunk-UXLSNRYL.js} +11 -105
- package/dist/chunk-UXLSNRYL.js.map +1 -0
- package/dist/{chunk-FRV5Q5IO.js → chunk-UYQMBBWJ.js} +3 -3
- package/dist/{chunk-FRV5Q5IO.js.map → chunk-UYQMBBWJ.js.map} +1 -1
- package/dist/{chunk-UUT7UN2N.cjs → chunk-VGDDG3XJ.cjs} +7 -7
- package/dist/{chunk-UUT7UN2N.cjs.map → chunk-VGDDG3XJ.cjs.map} +1 -1
- package/dist/{chunk-5VTR2F2K.js → chunk-VKGKDOKR.js} +12 -10
- package/dist/chunk-VKGKDOKR.js.map +1 -0
- package/dist/{chunk-5W4L6DLF.cjs → chunk-VL2V6PEN.cjs} +11 -11
- package/dist/{chunk-5W4L6DLF.cjs.map → chunk-VL2V6PEN.cjs.map} +1 -1
- package/dist/{chunk-ZUNQ5BEP.cjs → chunk-WOJMKB3N.cjs} +82 -80
- package/dist/chunk-WOJMKB3N.cjs.map +1 -0
- package/dist/{chunk-UMDVATGY.cjs → chunk-WWFHO375.cjs} +6 -6
- package/dist/{chunk-UMDVATGY.cjs.map → chunk-WWFHO375.cjs.map} +1 -1
- package/dist/{chunk-JFUPDJXD.js → chunk-XGPUQ56G.js} +3 -3
- package/dist/{chunk-JFUPDJXD.js.map → chunk-XGPUQ56G.js.map} +1 -1
- package/dist/{chunk-D7ZEOS56.cjs → chunk-XGU7DZDA.cjs} +74 -14
- package/dist/chunk-XGU7DZDA.cjs.map +1 -0
- package/dist/{chunk-S55OV6PW.cjs → chunk-XPCZADMT.cjs} +54 -11
- package/dist/chunk-XPCZADMT.cjs.map +1 -0
- package/dist/{chunk-T2WUSPDA.js → chunk-XWV4JT34.js} +3 -3
- package/dist/{chunk-T2WUSPDA.js.map → chunk-XWV4JT34.js.map} +1 -1
- package/dist/chunk-YTSN73ND.js +212 -0
- package/dist/chunk-YTSN73ND.js.map +1 -0
- package/dist/{ddl-CZz7a0E8.d.cts → ddl-BD-ZfKeX.d.cts} +3 -3
- package/dist/{ddl-C2rZ-Aen.d.ts → ddl-CS0KUVwB.d.ts} +3 -3
- package/dist/{extension-types-ixRpJVUp.d.ts → extension-types-BM2ACfYO.d.cts} +6 -1
- package/dist/{extension-types-ixRpJVUp.d.cts → extension-types-BM2ACfYO.d.ts} +6 -1
- package/dist/{fulltext-strategy-CDkSSYhq.d.cts → fulltext-strategy-C27vXaeB.d.cts} +127 -8
- package/dist/{fulltext-strategy-B57yvC5Q.d.ts → fulltext-strategy-DebsMrqg.d.ts} +127 -8
- package/dist/graph-extension/index.cjs +19 -19
- package/dist/graph-extension/index.d.cts +4 -4
- package/dist/graph-extension/index.d.ts +4 -4
- package/dist/graph-extension/index.js +4 -4
- package/dist/graph-merge/index.cjs +88 -88
- package/dist/graph-merge/index.d.cts +6 -6
- package/dist/graph-merge/index.d.ts +6 -6
- package/dist/graph-merge/index.js +19 -19
- package/dist/{index-BLtQWKRj.d.cts → index-BPM59vsg.d.cts} +52 -7
- package/dist/{index-BFvdmBPV.d.ts → index-BeacbPoe.d.ts} +52 -7
- package/dist/index.cjs +224 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +53 -40
- package/dist/index.js.map +1 -1
- package/dist/indexes/index.cjs +30 -21
- package/dist/indexes/index.d.cts +5 -5
- package/dist/indexes/index.d.ts +5 -5
- package/dist/indexes/index.js +8 -7
- package/dist/interchange/index.cjs +207 -30
- package/dist/interchange/index.cjs.map +1 -1
- package/dist/interchange/index.d.cts +25 -8
- package/dist/interchange/index.d.ts +25 -8
- package/dist/interchange/index.js +190 -11
- package/dist/interchange/index.js.map +1 -1
- package/dist/{manager-DwQi8eV0.d.cts → manager-D00_NXvT.d.ts} +11 -2
- package/dist/{manager-ejUCB52T.d.ts → manager-nU1vTJLy.d.cts} +11 -2
- package/dist/{materialize-indexes-B4e9360H.d.cts → materialize-indexes-CuR3gAZb.d.cts} +8 -2
- package/dist/{materialize-indexes-te9TPMMJ.d.ts → materialize-indexes-zK2J_flP.d.ts} +8 -2
- package/dist/{postgres-CfEqsRMp.d.cts → postgres-IaBA4RgE.d.ts} +1 -1
- package/dist/{postgres-DQ5R6rzV.d.ts → postgres-YrYmPlaU.d.cts} +1 -1
- package/dist/profiler/index.cjs +3 -3
- package/dist/profiler/index.d.cts +7 -7
- package/dist/profiler/index.d.ts +7 -7
- package/dist/profiler/index.js +2 -2
- package/dist/provenance/index.cjs +32 -33
- package/dist/provenance/index.cjs.map +1 -1
- package/dist/provenance/index.d.cts +5 -5
- package/dist/provenance/index.d.ts +5 -5
- package/dist/provenance/index.js +5 -6
- package/dist/provenance/index.js.map +1 -1
- package/dist/schema/index.cjs +35 -35
- package/dist/schema/index.d.cts +7 -7
- package/dist/schema/index.d.ts +7 -7
- package/dist/schema/index.js +11 -11
- package/dist/{sqlite-DvQndLky.d.ts → sqlite-BUOHPx7p.d.ts} +1 -1
- package/dist/{sqlite-D7zBinNR.d.cts → sqlite-Pp8O19WE.d.cts} +1 -1
- package/dist/{store-view-DplHC3Nt.d.ts → store-view-ANL6mTph.d.ts} +213 -27
- package/dist/{store-view-9KDOpZzF.d.cts → store-view-X96Rd00T.d.cts} +213 -27
- package/dist/{types-Bv9DcS_E.d.cts → types-r-Zyq2Hz.d.cts} +2 -2
- package/dist/{types-CoxuV7EJ.d.ts → types-wsvuBgPK.d.ts} +2 -2
- package/dist/where-CJOpJj3a.d.ts +36 -0
- package/dist/where-K3uBP33l.d.cts +36 -0
- package/package.json +1 -1
- package/dist/chunk-2THZ3TQI.cjs.map +0 -1
- package/dist/chunk-456Z2U7Y.cjs.map +0 -1
- package/dist/chunk-4BMPGT35.cjs +0 -12
- package/dist/chunk-4CAXEB2F.cjs.map +0 -1
- package/dist/chunk-4GX2JJXS.js.map +0 -1
- package/dist/chunk-5DIPR3L3.js.map +0 -1
- package/dist/chunk-5VTR2F2K.js.map +0 -1
- package/dist/chunk-6FR2MMS6.js.map +0 -1
- package/dist/chunk-7BQLPXK6.cjs.map +0 -1
- package/dist/chunk-BJVEU3ZL.js.map +0 -1
- package/dist/chunk-D7ZEOS56.cjs.map +0 -1
- package/dist/chunk-DIK74MXX.cjs.map +0 -1
- package/dist/chunk-DN5VP4JC.js.map +0 -1
- package/dist/chunk-EOLERH7Y.cjs +0 -40
- package/dist/chunk-EOLERH7Y.cjs.map +0 -1
- package/dist/chunk-FN35DLCN.cjs.map +0 -1
- package/dist/chunk-FPIZFYHE.js.map +0 -1
- package/dist/chunk-H7YNSMA7.cjs.map +0 -1
- package/dist/chunk-IU46OHVW.js.map +0 -1
- package/dist/chunk-JFMEONLG.cjs.map +0 -1
- package/dist/chunk-KBGQIPRQ.js.map +0 -1
- package/dist/chunk-M75SUBFG.js.map +0 -1
- package/dist/chunk-PWGX5DF5.cjs.map +0 -1
- package/dist/chunk-S55OV6PW.cjs.map +0 -1
- package/dist/chunk-SKCOUWAX.js.map +0 -1
- package/dist/chunk-UT3S4SEN.js +0 -31
- package/dist/chunk-UT3S4SEN.js.map +0 -1
- package/dist/chunk-VHMPG6UL.js.map +0 -1
- package/dist/chunk-VQEXF5D4.js.map +0 -1
- package/dist/chunk-WI7ISNQF.js.map +0 -1
- package/dist/chunk-X4JBT642.cjs.map +0 -1
- package/dist/chunk-ZAMFOJ5P.cjs.map +0 -1
- package/dist/chunk-ZUNQ5BEP.cjs.map +0 -1
- package/dist/where-7RKFOsXt.d.ts +0 -11
- package/dist/where-DdMIXOtH.d.cts +0 -11
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { createEdgeRowMapper, createNodeRowMapper, POSTGRES_ROW_MAPPER_CONFIG, SQLITE_ROW_MAPPER_CONFIG, quoteIdentifier, EMPTY_REQUIRED_COLUMNS, addRequiredColumn, mapSelectiveSystemFieldToColumn, markFieldRefAsRequired, findSelectivePropsFieldForFieldRef, markSelectiveFieldAsRequired, NODE_COLUMNS, shouldProjectColumn, isIdFieldRef, isAggregateExpr, EDGE_COLUMNS } from './chunk-
|
|
2
|
-
import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDelete, nodeUpsertDirtyCheck, executeNodeUpsertUpdate, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, runInWriteTransaction, checkCardinalityConstraint, runHookedWriteOperation, rowToNodeMeta, rowToEdgeMeta } from './chunk-
|
|
3
|
-
import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, buildKindRegistry, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, serializeIndexDeclaration } from './chunk-
|
|
4
|
-
import { isKnownKind, validateEdgeProps } from './chunk-
|
|
1
|
+
import { createEdgeRowMapper, createNodeRowMapper, POSTGRES_ROW_MAPPER_CONFIG, SQLITE_ROW_MAPPER_CONFIG, quoteIdentifier, EMPTY_REQUIRED_COLUMNS, addRequiredColumn, mapSelectiveSystemFieldToColumn, markFieldRefAsRequired, findSelectivePropsFieldForFieldRef, markSelectiveFieldAsRequired, NODE_COLUMNS, shouldProjectColumn, isIdFieldRef, isAggregateExpr, EDGE_COLUMNS } from './chunk-M6VES7V6.js';
|
|
2
|
+
import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDelete, nodeUpsertDirtyCheck, executeNodeUpsertUpdate, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, runInWriteTransaction, checkCardinalityConstraint, runHookedWriteOperation, rowToNodeMeta, rowToEdgeMeta } from './chunk-RCVLFTCT.js';
|
|
3
|
+
import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, buildKindRegistry, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, serializeIndexDeclaration } from './chunk-3RWZPJKV.js';
|
|
4
|
+
import { isKnownKind, validateEdgeProps } from './chunk-3SLPDKHQ.js';
|
|
5
5
|
import { canonicalEqual, sortedReplacer } from './chunk-UR7W5YQK.js';
|
|
6
|
-
import { generateIndexDDL } from './chunk-
|
|
7
|
-
import {
|
|
6
|
+
import { generateIndexDDL } from './chunk-KQJ54OG5.js';
|
|
7
|
+
import { SYSTEM_INDEX_DECLARATIONS, resolveSystemIndexTableName, systemIndexName, resolveSystemIndexNames, generateSystemIndexDDL } from './chunk-YTSN73ND.js';
|
|
8
|
+
import { NODE_META_KEYS, EDGE_META_KEYS } from './chunk-K5UBVGJN.js';
|
|
8
9
|
import { sha256Hex, fnv1aBase36 } from './chunk-W63OVYY4.js';
|
|
9
|
-
import { getDialect, assertVectorMinScore, vectorScoreExpression, vectorMinScoreCondition } from './chunk-
|
|
10
|
-
import { createSchemaIntrospector } from './chunk-
|
|
11
|
-
import { IncompatibleChangeError, RemoveCompileTimeKindError, KindHasReferentsError } from './chunk-
|
|
12
|
-
import { validateProjectionField } from './chunk-
|
|
10
|
+
import { getDialect, assertVectorMinScore, vectorScoreExpression, vectorMinScoreCondition } from './chunk-EXU3RASM.js';
|
|
11
|
+
import { createSchemaIntrospector } from './chunk-UGZUAYAR.js';
|
|
12
|
+
import { IncompatibleChangeError, RemoveCompileTimeKindError, KindHasReferentsError } from './chunk-L7HQ2OSI.js';
|
|
13
|
+
import { validateProjectionField } from './chunk-TPF25IG6.js';
|
|
13
14
|
import { compareStrings, compareCodePoints } from './chunk-BYPOGQWK.js';
|
|
14
|
-
import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, DEFAULT_SQL_SCHEMA, requireSqlSchema, recordedReadSchemaFor, assertRevisionTrackableBackend, createSqlSchema, requireExternalRecordedReadSource, createRecordedReadBinding, createRecordedBackend, readRecordedClock, asRecordedInstant, ensureRevisionOrigin, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, createRecordedTransactionScope, lockRecordedGraphWrite, createHistoryUnsafeSqlRef, createRevisionTrackingUnsafeSqlRef, generateId, vectorSlotKey, computeFulltextContent, advanceRevisionClock, recordedReadSqlSchema, requireRecordedReadBinding, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, getEmbeddingFields, getNodeRowsByIds, getEdgeRowsByIds,
|
|
15
|
-
import { markAnnIndexScan, asCompiledSelectSql, isRawExecutable, annIndexScanTypes, asCompiledRowsSql, markForceCustomPlan, asCompiledStatementSql } from './chunk-
|
|
16
|
-
import {
|
|
17
|
-
import { withRecordedRelationsPrecondition, nowIso, validateOptionalCanonicalIsoDate, isMissingTableError } from './chunk-JFUPDJXD.js';
|
|
15
|
+
import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, DEFAULT_SQL_SCHEMA, requireSqlSchema, recordedReadSchemaFor, assertRevisionTrackableBackend, createSqlSchema, requireExternalRecordedReadSource, createRecordedReadBinding, createRecordedBackend, readRecordedClock, asRecordedInstant, ensureRevisionOrigin, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, createRecordedTransactionScope, lockRecordedGraphWrite, createHistoryUnsafeSqlRef, createRevisionTrackingUnsafeSqlRef, generateId, vectorSlotKey, computeFulltextContent, advanceRevisionClock, recordedReadSqlSchema, requireRecordedReadBinding, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, getEmbeddingFields, getNodeRowsByIds, getEdgeRowsByIds, validateEdgeEndpoints, recordedBindParamBudget, assertValidRecordedInstant, closeRecordedHardDeletedKind, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-VKGKDOKR.js';
|
|
16
|
+
import { isParameterRef, fieldRef, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, markAnnIndexScan, asCompiledSelectSql, isRawExecutable, runOptionallyInTransaction, baseField, objectField, arrayField, dateField, numberField, annIndexScanTypes, rowPropsToObject, asCompiledRowsSql, sqlValueList, createBackendOverlay, compileKindFilter, currentReadInstant, compileTemporalFilter, markForceCustomPlan, chunk, INTERNAL_TEMPORARY_WRITES, buildPredicateIndex, compileFieldValue, asCompiledTemporaryStatementSql, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getNodeKindsForAlias, getHybridTargetAlias, asCompiledStatementSql, compilePredicateClauses, isPresent } from './chunk-44TA22QT.js';
|
|
17
|
+
import { withRecordedRelationsPrecondition, nowIso, validateOptionalCanonicalIsoDate, isMissingTableError } from './chunk-XGPUQ56G.js';
|
|
18
18
|
import { DEFAULT_PAGINATION_LIMIT, DEFAULT_STREAM_BATCH_SIZE, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, MAX_PG_IDENTIFIER_LENGTH } from './chunk-OHMP6N2J.js';
|
|
19
|
-
import { getSearchableFields, resolveEmbeddingFields, resolveDeclaredFulltextLanguage } from './chunk-
|
|
20
|
-
import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, EndpointNotFoundError } from './chunk-
|
|
19
|
+
import { getSearchableFields, resolveEmbeddingFields, resolveGraphVectorSlots, resolveDeclaredFulltextLanguage } from './chunk-PC2UBAUB.js';
|
|
20
|
+
import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, UnsupportedBackendCapabilityError, GraphAlgorithmConvergenceError, EndpointNotFoundError, InvalidEdgeWeightError } from './chunk-5PNMDJ7J.js';
|
|
21
21
|
import { jsonPointer, parseJsonPointer, resolveJsonPointer } from './chunk-G6Q5UM7U.js';
|
|
22
22
|
import { Placeholder, sql } from 'drizzle-orm';
|
|
23
23
|
|
|
@@ -394,7 +394,8 @@ function recordedSearch(coordinate) {
|
|
|
394
394
|
var CoordinatePinnedView = class {
|
|
395
395
|
store;
|
|
396
396
|
coordinate;
|
|
397
|
-
#
|
|
397
|
+
#algorithmFacade;
|
|
398
|
+
#internalAlgorithms;
|
|
398
399
|
constructor(store, coordinate) {
|
|
399
400
|
this.store = store;
|
|
400
401
|
this.coordinate = coordinate;
|
|
@@ -415,9 +416,24 @@ var CoordinatePinnedView = class {
|
|
|
415
416
|
query() {
|
|
416
417
|
return this.store.sealedQuery(this.coordinate);
|
|
417
418
|
}
|
|
418
|
-
|
|
419
|
-
this.#
|
|
420
|
-
|
|
419
|
+
internalAlgorithms() {
|
|
420
|
+
this.#internalAlgorithms ??= this.store.algorithmsAtCoordinate(
|
|
421
|
+
this.coordinate
|
|
422
|
+
);
|
|
423
|
+
return this.#internalAlgorithms;
|
|
424
|
+
}
|
|
425
|
+
/** Graph algorithms pinned to this view's immutable temporal coordinate. */
|
|
426
|
+
get algorithms() {
|
|
427
|
+
this.#algorithmFacade ??= Object.freeze({
|
|
428
|
+
shortestPath: (from, to, options) => this.shortestPath(from, to, options),
|
|
429
|
+
weightedShortestPath: (from, to, options) => this.weightedShortestPath(from, to, options),
|
|
430
|
+
reachable: (from, options) => this.reachable(from, options),
|
|
431
|
+
canReach: (from, to, options) => this.canReach(from, to, options),
|
|
432
|
+
neighbors: (node, options) => this.neighbors(node, options),
|
|
433
|
+
degree: (node, options) => this.degree(node, options),
|
|
434
|
+
weaklyConnectedComponents: (options) => this.weaklyConnectedComponents(options)
|
|
435
|
+
});
|
|
436
|
+
return this.#algorithmFacade;
|
|
421
437
|
}
|
|
422
438
|
/** Extracts a subgraph at this view's pinned coordinate. */
|
|
423
439
|
subgraph(rootId, options) {
|
|
@@ -429,35 +445,49 @@ var CoordinatePinnedView = class {
|
|
|
429
445
|
}
|
|
430
446
|
/** Shortest path between two nodes at this view's pinned coordinate. */
|
|
431
447
|
shortestPath(from, to, options) {
|
|
432
|
-
return this.
|
|
448
|
+
return this.internalAlgorithms().shortestPath(from, to, {
|
|
449
|
+
...options,
|
|
450
|
+
...withCoordinate(this.coordinate)
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/** Minimum-total-weight path at this view's pinned coordinate. */
|
|
454
|
+
weightedShortestPath(from, to, options) {
|
|
455
|
+
return this.internalAlgorithms().weightedShortestPath(from, to, {
|
|
433
456
|
...options,
|
|
434
457
|
...withCoordinate(this.coordinate)
|
|
435
458
|
});
|
|
436
459
|
}
|
|
437
460
|
/** Nodes reachable from `from` at this view's pinned coordinate. */
|
|
438
461
|
reachable(from, options) {
|
|
439
|
-
return this.
|
|
462
|
+
return this.internalAlgorithms().reachable(from, {
|
|
440
463
|
...options,
|
|
441
464
|
...withCoordinate(this.coordinate)
|
|
442
465
|
});
|
|
443
466
|
}
|
|
444
467
|
/** Whether `to` is reachable from `from` at this view's pinned coordinate. */
|
|
445
468
|
canReach(from, to, options) {
|
|
446
|
-
return this.
|
|
469
|
+
return this.internalAlgorithms().canReach(from, to, {
|
|
447
470
|
...options,
|
|
448
471
|
...withCoordinate(this.coordinate)
|
|
449
472
|
});
|
|
450
473
|
}
|
|
451
474
|
/** The k-hop neighborhood of `node` at this view's pinned coordinate. */
|
|
452
475
|
neighbors(node, options) {
|
|
453
|
-
return this.
|
|
476
|
+
return this.internalAlgorithms().neighbors(node, {
|
|
454
477
|
...options,
|
|
455
478
|
...withCoordinate(this.coordinate)
|
|
456
479
|
});
|
|
457
480
|
}
|
|
458
481
|
/** Counts active edges incident to `node` at this view's pinned coordinate. */
|
|
459
482
|
degree(node, options) {
|
|
460
|
-
return this.
|
|
483
|
+
return this.internalAlgorithms().degree(node, {
|
|
484
|
+
...options,
|
|
485
|
+
...withCoordinate(this.coordinate)
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/** Exact WCC memberships at this view's pinned coordinate. */
|
|
489
|
+
weaklyConnectedComponents(options) {
|
|
490
|
+
return this.internalAlgorithms().weaklyConnectedComponents({
|
|
461
491
|
...options,
|
|
462
492
|
...withCoordinate(this.coordinate)
|
|
463
493
|
});
|
|
@@ -1457,6 +1487,97 @@ function buildStandardOrderBy(input) {
|
|
|
1457
1487
|
}
|
|
1458
1488
|
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
1459
1489
|
}
|
|
1490
|
+
var LATE_MAT_TOPK_CTE_ALIAS = "cte_lm_topk";
|
|
1491
|
+
var LATE_MAT_SORT_KEY_PREFIX = "__lm_sk";
|
|
1492
|
+
var LATE_MAT_PHYSICAL_ALIAS_PREFIX = "lm_";
|
|
1493
|
+
function lateMaterializedPhysicalAlias(alias) {
|
|
1494
|
+
return `${LATE_MAT_PHYSICAL_ALIAS_PREFIX}${alias}`;
|
|
1495
|
+
}
|
|
1496
|
+
function lateMaterializedNodeAliases(ast) {
|
|
1497
|
+
return [
|
|
1498
|
+
ast.start.alias,
|
|
1499
|
+
...ast.traversals.map((traversal) => traversal.nodeAlias)
|
|
1500
|
+
];
|
|
1501
|
+
}
|
|
1502
|
+
function lateMaterializedProjectedNodeAliases(ast) {
|
|
1503
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1504
|
+
for (const field2 of ast.selectiveFields ?? []) {
|
|
1505
|
+
referenced.add(field2.alias);
|
|
1506
|
+
}
|
|
1507
|
+
return lateMaterializedNodeAliases(ast).filter(
|
|
1508
|
+
(alias) => referenced.has(alias)
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
function buildLateMaterializedTopKCte(input) {
|
|
1512
|
+
const { ast, collapsedTraversalCteAlias, dialect, fromClause } = input;
|
|
1513
|
+
const aliasToCte = buildAliasToCteMap(ast);
|
|
1514
|
+
const cteAliasFor = (alias) => collapsedTraversalCteAlias ?? aliasToCte.get(alias) ?? `${ALIAS_CTE_PREFIX}${alias}`;
|
|
1515
|
+
const columns = [];
|
|
1516
|
+
for (const alias of lateMaterializedNodeAliases(ast)) {
|
|
1517
|
+
const cteAlias = cteAliasFor(alias);
|
|
1518
|
+
columns.push(
|
|
1519
|
+
sql`${qualifyColumn(cteAlias, `${alias}_id`)} AS ${sql.raw(`${alias}_id`)}`,
|
|
1520
|
+
sql`${qualifyColumn(cteAlias, `${alias}_kind`)} AS ${sql.raw(`${alias}_kind`)}`
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
for (const [index, orderSpec] of (ast.orderBy ?? []).entries()) {
|
|
1524
|
+
const value = compileOrderFieldValue(
|
|
1525
|
+
ast,
|
|
1526
|
+
orderSpec.field,
|
|
1527
|
+
dialect,
|
|
1528
|
+
cteAliasFor(orderSpec.field.alias)
|
|
1529
|
+
);
|
|
1530
|
+
columns.push(
|
|
1531
|
+
sql`${value} AS ${sql.raw(`${LATE_MAT_SORT_KEY_PREFIX}${index}`)}`
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
const innerOrderBy = buildStandardOrderBy({
|
|
1535
|
+
ast,
|
|
1536
|
+
dialect,
|
|
1537
|
+
...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
|
|
1538
|
+
});
|
|
1539
|
+
const limitOffset = buildLimitOffsetClause({
|
|
1540
|
+
limit: input.limit,
|
|
1541
|
+
offset: input.offset
|
|
1542
|
+
});
|
|
1543
|
+
const parts = [sql`SELECT ${sql.join(columns, sql`, `)}`, fromClause];
|
|
1544
|
+
if (innerOrderBy !== void 0) parts.push(innerOrderBy);
|
|
1545
|
+
if (limitOffset !== void 0) parts.push(limitOffset);
|
|
1546
|
+
return sql`${sql.raw(LATE_MAT_TOPK_CTE_ALIAS)} AS (${sql.join(parts, sql` `)})`;
|
|
1547
|
+
}
|
|
1548
|
+
function buildLateMaterializedOuterProjection(ast, dialect) {
|
|
1549
|
+
const columns = (ast.selectiveFields ?? []).map((field2) => {
|
|
1550
|
+
const physicalAlias = lateMaterializedPhysicalAlias(field2.alias);
|
|
1551
|
+
if (field2.isSystemField) {
|
|
1552
|
+
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
1553
|
+
return sql`${compileColumnReference(physicalAlias, dbColumn)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1554
|
+
}
|
|
1555
|
+
const extracted = compileSelectivePropsExtraction(
|
|
1556
|
+
field2,
|
|
1557
|
+
compileColumnReference(physicalAlias, "props"),
|
|
1558
|
+
dialect
|
|
1559
|
+
);
|
|
1560
|
+
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1561
|
+
});
|
|
1562
|
+
return sql.join(columns, sql`, `);
|
|
1563
|
+
}
|
|
1564
|
+
function buildLateMaterializedOuterOrderBy(ast) {
|
|
1565
|
+
const orderBy = ast.orderBy ?? [];
|
|
1566
|
+
if (orderBy.length === 0) return void 0;
|
|
1567
|
+
const topk = sql.raw(LATE_MAT_TOPK_CTE_ALIAS);
|
|
1568
|
+
const parts = [];
|
|
1569
|
+
for (const [index, orderSpec] of orderBy.entries()) {
|
|
1570
|
+
const column = sql`${topk}.${sql.raw(`${LATE_MAT_SORT_KEY_PREFIX}${index}`)}`;
|
|
1571
|
+
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
1572
|
+
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
1573
|
+
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
1574
|
+
parts.push(
|
|
1575
|
+
sql`(${column} IS NULL) ${nullsDirection}`,
|
|
1576
|
+
sql`${column} ${direction}`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
1580
|
+
}
|
|
1460
1581
|
function fieldRefKey(field2) {
|
|
1461
1582
|
const pointer = field2.jsonPointer ?? "";
|
|
1462
1583
|
return `${field2.alias}:${field2.path.join(".")}:${pointer}`;
|
|
@@ -3611,31 +3732,34 @@ function markPredicateFieldsAsRequired(requiredColumnsByAlias, expression) {
|
|
|
3611
3732
|
}
|
|
3612
3733
|
}
|
|
3613
3734
|
}
|
|
3614
|
-
function collectRequiredColumnsByAlias2(ast) {
|
|
3735
|
+
function collectRequiredColumnsByAlias2(ast, options) {
|
|
3736
|
+
const includeProjection = options?.includeProjection ?? true;
|
|
3615
3737
|
const requiredColumnsByAlias = /* @__PURE__ */ new Map();
|
|
3616
3738
|
addRequiredColumn(requiredColumnsByAlias, ast.start.alias, "id");
|
|
3617
3739
|
for (const traversal of ast.traversals) {
|
|
3618
3740
|
addRequiredColumn(requiredColumnsByAlias, traversal.nodeAlias, "id");
|
|
3619
3741
|
}
|
|
3620
3742
|
const hasSelectiveFields = (ast.selectiveFields?.length ?? 0) > 0;
|
|
3621
|
-
if (
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3743
|
+
if (includeProjection) {
|
|
3744
|
+
if (hasSelectiveFields) {
|
|
3745
|
+
for (const field2 of ast.selectiveFields ?? []) {
|
|
3746
|
+
if (field2.isSystemField) {
|
|
3747
|
+
addRequiredColumn(
|
|
3748
|
+
requiredColumnsByAlias,
|
|
3749
|
+
field2.alias,
|
|
3750
|
+
mapSelectiveSystemFieldToColumn(field2.field)
|
|
3751
|
+
);
|
|
3752
|
+
}
|
|
3629
3753
|
}
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3754
|
+
} else {
|
|
3755
|
+
for (const projectedField of ast.projection.fields) {
|
|
3756
|
+
const source = projectedField.source;
|
|
3757
|
+
if (source.__type === "field_ref") {
|
|
3758
|
+
markFieldRefAsRequired(requiredColumnsByAlias, source);
|
|
3759
|
+
} else {
|
|
3760
|
+
addRequiredColumn(requiredColumnsByAlias, source.field.alias, "id");
|
|
3761
|
+
markFieldRefAsRequired(requiredColumnsByAlias, source.field);
|
|
3762
|
+
}
|
|
3639
3763
|
}
|
|
3640
3764
|
}
|
|
3641
3765
|
}
|
|
@@ -4318,6 +4442,169 @@ function stripLimitOffsetFromPlanNode(node) {
|
|
|
4318
4442
|
}
|
|
4319
4443
|
return node;
|
|
4320
4444
|
}
|
|
4445
|
+
function buildStandardStartAndTraversalCtes(input) {
|
|
4446
|
+
const {
|
|
4447
|
+
ast,
|
|
4448
|
+
carryForwardPreviousColumns,
|
|
4449
|
+
ctx,
|
|
4450
|
+
graphId,
|
|
4451
|
+
predicateIndex,
|
|
4452
|
+
requiredColumnsByAlias,
|
|
4453
|
+
temporalFilterPass,
|
|
4454
|
+
traversalLimit
|
|
4455
|
+
} = input;
|
|
4456
|
+
const ctes = [
|
|
4457
|
+
buildStandardStartCte({
|
|
4458
|
+
ast,
|
|
4459
|
+
ctx,
|
|
4460
|
+
graphId,
|
|
4461
|
+
predicateIndex,
|
|
4462
|
+
requiredColumnsByAlias,
|
|
4463
|
+
temporalFilterPass
|
|
4464
|
+
})
|
|
4465
|
+
];
|
|
4466
|
+
for (let index = 0; index < ast.traversals.length; index++) {
|
|
4467
|
+
ctes.push(
|
|
4468
|
+
buildStandardTraversalCte({
|
|
4469
|
+
ast,
|
|
4470
|
+
carryForwardPreviousColumns,
|
|
4471
|
+
ctx,
|
|
4472
|
+
graphId,
|
|
4473
|
+
materializeCte: shouldMaterializeTraversalCte(
|
|
4474
|
+
ctx.dialect,
|
|
4475
|
+
ast.traversals.length,
|
|
4476
|
+
index
|
|
4477
|
+
),
|
|
4478
|
+
predicateIndex,
|
|
4479
|
+
requiredColumnsByAlias,
|
|
4480
|
+
temporalFilterPass,
|
|
4481
|
+
traversalIndex: index,
|
|
4482
|
+
traversalLimit
|
|
4483
|
+
})
|
|
4484
|
+
);
|
|
4485
|
+
}
|
|
4486
|
+
return ctes;
|
|
4487
|
+
}
|
|
4488
|
+
function selectiveFieldIsIdentity(field2) {
|
|
4489
|
+
if (!field2.isSystemField) {
|
|
4490
|
+
return false;
|
|
4491
|
+
}
|
|
4492
|
+
const column = mapSelectiveSystemFieldToColumn(field2.field);
|
|
4493
|
+
return column === "id" || column === "kind";
|
|
4494
|
+
}
|
|
4495
|
+
function resolveLateMaterializationPlan(ast) {
|
|
4496
|
+
if (ast.recordedAsOf !== void 0) {
|
|
4497
|
+
return void 0;
|
|
4498
|
+
}
|
|
4499
|
+
const selectiveFields = ast.selectiveFields;
|
|
4500
|
+
if (selectiveFields === void 0 || selectiveFields.length === 0) {
|
|
4501
|
+
return void 0;
|
|
4502
|
+
}
|
|
4503
|
+
const orderBy = ast.orderBy;
|
|
4504
|
+
if (orderBy === void 0 || orderBy.length === 0) {
|
|
4505
|
+
return void 0;
|
|
4506
|
+
}
|
|
4507
|
+
if (ast.limit === void 0 || ast.limit <= 0) {
|
|
4508
|
+
return void 0;
|
|
4509
|
+
}
|
|
4510
|
+
if (ast.groupBy !== void 0 || ast.having !== void 0) {
|
|
4511
|
+
return void 0;
|
|
4512
|
+
}
|
|
4513
|
+
if (ast.aggregateOrderBy !== void 0 && ast.aggregateOrderBy.length > 0) {
|
|
4514
|
+
return void 0;
|
|
4515
|
+
}
|
|
4516
|
+
if (ast.traversals.some((traversal) => traversal.optional)) {
|
|
4517
|
+
return void 0;
|
|
4518
|
+
}
|
|
4519
|
+
const nodeAliases = /* @__PURE__ */ new Set([
|
|
4520
|
+
ast.start.alias,
|
|
4521
|
+
...ast.traversals.map((traversal) => traversal.nodeAlias)
|
|
4522
|
+
]);
|
|
4523
|
+
for (const field2 of selectiveFields) {
|
|
4524
|
+
if (!nodeAliases.has(field2.alias)) {
|
|
4525
|
+
return void 0;
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
for (const orderSpec of orderBy) {
|
|
4529
|
+
if (!nodeAliases.has(orderSpec.field.alias)) {
|
|
4530
|
+
return void 0;
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
const orderReferenced = /* @__PURE__ */ new Set();
|
|
4534
|
+
for (const orderSpec of orderBy) {
|
|
4535
|
+
const matched = findSelectivePropsFieldForFieldRef(
|
|
4536
|
+
selectiveFields,
|
|
4537
|
+
orderSpec.field
|
|
4538
|
+
);
|
|
4539
|
+
if (matched !== void 0) {
|
|
4540
|
+
orderReferenced.add(matched);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
const hasDeferredColumn = selectiveFields.some(
|
|
4544
|
+
(field2) => !selectiveFieldIsIdentity(field2) && !orderReferenced.has(field2)
|
|
4545
|
+
);
|
|
4546
|
+
if (!hasDeferredColumn) {
|
|
4547
|
+
return void 0;
|
|
4548
|
+
}
|
|
4549
|
+
const prunedSelectiveFields = selectiveFields.filter(
|
|
4550
|
+
(field2) => field2.isSystemField || orderReferenced.has(field2)
|
|
4551
|
+
);
|
|
4552
|
+
return {
|
|
4553
|
+
leanAst: { ...ast, selectiveFields: prunedSelectiveFields },
|
|
4554
|
+
limit: ast.limit,
|
|
4555
|
+
offset: ast.offset
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
function buildLateMaterializedOuterFromClause(ast, graphId, ctx) {
|
|
4559
|
+
const topk = sql.raw(LATE_MAT_TOPK_CTE_ALIAS);
|
|
4560
|
+
const joins = lateMaterializedProjectedNodeAliases(ast).map((alias) => {
|
|
4561
|
+
const physical = sql.raw(lateMaterializedPhysicalAlias(alias));
|
|
4562
|
+
return sql`JOIN ${ctx.schema.nodesTable} ${physical} ON ${physical}.graph_id = ${graphId} AND ${physical}.kind = ${topk}.${sql.raw(`${alias}_kind`)} AND ${physical}.id = ${topk}.${sql.raw(`${alias}_id`)}`;
|
|
4563
|
+
});
|
|
4564
|
+
return joins.length === 0 ? sql`FROM ${topk}` : sql`FROM ${topk} ${sql.join(joins, sql` `)}`;
|
|
4565
|
+
}
|
|
4566
|
+
function compileLateMaterializedQuery(ast, graphId, ctx, logicalPlan, predicateIndex, temporalFilterPass, collapsedTraversalCteAlias, shouldCollapseSelectiveTraversalRowset) {
|
|
4567
|
+
const plan = resolveLateMaterializationPlan(ast);
|
|
4568
|
+
if (plan === void 0) {
|
|
4569
|
+
return void 0;
|
|
4570
|
+
}
|
|
4571
|
+
const { leanAst, limit, offset } = plan;
|
|
4572
|
+
const { dialect } = ctx;
|
|
4573
|
+
const leanRequiredColumns = collectRequiredColumnsByAlias2(leanAst, {
|
|
4574
|
+
includeProjection: false
|
|
4575
|
+
});
|
|
4576
|
+
const ctes = buildStandardStartAndTraversalCtes({
|
|
4577
|
+
ast: leanAst,
|
|
4578
|
+
carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
|
|
4579
|
+
ctx,
|
|
4580
|
+
graphId,
|
|
4581
|
+
predicateIndex,
|
|
4582
|
+
requiredColumnsByAlias: leanRequiredColumns,
|
|
4583
|
+
temporalFilterPass,
|
|
4584
|
+
traversalLimit: void 0
|
|
4585
|
+
});
|
|
4586
|
+
ctes.push(
|
|
4587
|
+
buildLateMaterializedTopKCte({
|
|
4588
|
+
ast: leanAst,
|
|
4589
|
+
dialect,
|
|
4590
|
+
fromClause: buildStandardFromClause({
|
|
4591
|
+
ast: leanAst,
|
|
4592
|
+
...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
|
|
4593
|
+
}),
|
|
4594
|
+
limit,
|
|
4595
|
+
offset,
|
|
4596
|
+
...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
|
|
4597
|
+
})
|
|
4598
|
+
);
|
|
4599
|
+
const orderBy = buildLateMaterializedOuterOrderBy(ast);
|
|
4600
|
+
return emitStandardQuerySql({
|
|
4601
|
+
ctes,
|
|
4602
|
+
fromClause: buildLateMaterializedOuterFromClause(ast, graphId, ctx),
|
|
4603
|
+
...orderBy === void 0 ? {} : { orderBy },
|
|
4604
|
+
logicalPlan: stripLimitOffsetFromPlan(logicalPlan),
|
|
4605
|
+
projection: buildLateMaterializedOuterProjection(ast, dialect)
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4321
4608
|
var STANDARD_QUERY_STRATEGY_HANDLERS = {
|
|
4322
4609
|
cte_project: compileStandardQueryWithCteStrategy
|
|
4323
4610
|
};
|
|
@@ -4417,38 +4704,30 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
|
|
|
4417
4704
|
if (fastPathSql) {
|
|
4418
4705
|
return fastPathSql;
|
|
4419
4706
|
}
|
|
4420
|
-
|
|
4421
|
-
const ctes = [
|
|
4422
|
-
buildStandardStartCte({
|
|
4707
|
+
const lateMaterializedSql = compileLateMaterializedQuery(
|
|
4423
4708
|
ast,
|
|
4424
|
-
ctx,
|
|
4425
4709
|
graphId,
|
|
4710
|
+
ctx,
|
|
4711
|
+
logicalPlan,
|
|
4426
4712
|
predicateIndex,
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
];
|
|
4431
|
-
for (let index = 0; index < ast.traversals.length; index++) {
|
|
4432
|
-
const materializeTraversalCte = shouldMaterializeTraversalCte(
|
|
4433
|
-
dialect,
|
|
4434
|
-
ast.traversals.length,
|
|
4435
|
-
index
|
|
4436
|
-
);
|
|
4437
|
-
ctes.push(
|
|
4438
|
-
buildStandardTraversalCte({
|
|
4439
|
-
ast,
|
|
4440
|
-
carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
|
|
4441
|
-
ctx,
|
|
4442
|
-
graphId,
|
|
4443
|
-
materializeCte: materializeTraversalCte,
|
|
4444
|
-
predicateIndex,
|
|
4445
|
-
requiredColumnsByAlias,
|
|
4446
|
-
temporalFilterPass,
|
|
4447
|
-
traversalIndex: index,
|
|
4448
|
-
traversalLimit: traversalCteLimit
|
|
4449
|
-
})
|
|
4713
|
+
temporalFilterPass,
|
|
4714
|
+
collapsedTraversalCteAlias,
|
|
4715
|
+
shouldCollapseSelectiveTraversalRowset
|
|
4450
4716
|
);
|
|
4717
|
+
if (lateMaterializedSql) {
|
|
4718
|
+
return lateMaterializedSql;
|
|
4719
|
+
}
|
|
4451
4720
|
}
|
|
4721
|
+
const ctes = buildStandardStartAndTraversalCtes({
|
|
4722
|
+
ast,
|
|
4723
|
+
carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
|
|
4724
|
+
ctx,
|
|
4725
|
+
graphId,
|
|
4726
|
+
predicateIndex,
|
|
4727
|
+
requiredColumnsByAlias,
|
|
4728
|
+
temporalFilterPass,
|
|
4729
|
+
traversalLimit: traversalCteLimit
|
|
4730
|
+
});
|
|
4452
4731
|
if (vectorPredicate) {
|
|
4453
4732
|
const nodeKinds = getNodeKindsForAlias(ast, vectorPredicate.field.alias);
|
|
4454
4733
|
ctes.push(
|
|
@@ -10512,6 +10791,16 @@ function resolveMaxHops(rawMaxHops, fallback, optionName) {
|
|
|
10512
10791
|
}
|
|
10513
10792
|
return value;
|
|
10514
10793
|
}
|
|
10794
|
+
function resolveMaxIterations(value, fallback, algorithm) {
|
|
10795
|
+
const maxIterations = value ?? fallback;
|
|
10796
|
+
if (!Number.isSafeInteger(maxIterations) || maxIterations < 1) {
|
|
10797
|
+
throw new ConfigurationError(
|
|
10798
|
+
`${algorithm} maxIterations must be a positive safe integer, got ${String(maxIterations)}.`,
|
|
10799
|
+
{ maxIterations }
|
|
10800
|
+
);
|
|
10801
|
+
}
|
|
10802
|
+
return maxIterations;
|
|
10803
|
+
}
|
|
10515
10804
|
function assertEdgeKinds(edges) {
|
|
10516
10805
|
if (edges.length === 0) {
|
|
10517
10806
|
throw new ConfigurationError(
|
|
@@ -10561,65 +10850,982 @@ function compileDirectionFilter(ctx, direction, nodeId, nodesTable) {
|
|
|
10561
10850
|
}
|
|
10562
10851
|
}
|
|
10563
10852
|
}
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
const
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10853
|
+
var DEFAULT_MAX_BIND_PARAMETERS = 999;
|
|
10854
|
+
var RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH = 12;
|
|
10855
|
+
var RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH = 4;
|
|
10856
|
+
var WORKING_TABLE_ANALYZE_MINIMUM_ROWS = 16;
|
|
10857
|
+
var WORKING_TABLE_ANALYZE_GROWTH_FACTOR = 4;
|
|
10858
|
+
var ITERATIVE_WORKING_MEMORY_PATTERN = /^(\d+)(kB|MB|GB)$/;
|
|
10859
|
+
var WORKING_MEMORY_KILOBYTES_PER_UNIT = {
|
|
10860
|
+
kB: 1,
|
|
10861
|
+
MB: 1024,
|
|
10862
|
+
GB: 1024 * 1024
|
|
10863
|
+
};
|
|
10864
|
+
var MIN_WORKING_MEMORY_KILOBYTES = 64;
|
|
10865
|
+
var MAX_WORKING_MEMORY_KILOBYTES = 2147483647;
|
|
10866
|
+
function resolveWorkingMemory(workingMemory) {
|
|
10867
|
+
if (workingMemory === void 0) return void 0;
|
|
10868
|
+
const match = ITERATIVE_WORKING_MEMORY_PATTERN.exec(workingMemory);
|
|
10869
|
+
if (match === null) {
|
|
10870
|
+
throw new ConfigurationError(
|
|
10871
|
+
`Iterative graph operation workingMemory must be digits followed by kB, MB, or GB (for example "64MB"), got ${JSON.stringify(workingMemory)}.`,
|
|
10872
|
+
{ workingMemory }
|
|
10873
|
+
);
|
|
10874
|
+
}
|
|
10875
|
+
const kilobytes = Number(match[1]) * (WORKING_MEMORY_KILOBYTES_PER_UNIT[match[2] ?? ""] ?? 0);
|
|
10876
|
+
if (kilobytes < MIN_WORKING_MEMORY_KILOBYTES || kilobytes > MAX_WORKING_MEMORY_KILOBYTES) {
|
|
10877
|
+
throw new ConfigurationError(
|
|
10878
|
+
`Iterative graph operation workingMemory must be between ${MIN_WORKING_MEMORY_KILOBYTES}kB and ${MAX_WORKING_MEMORY_KILOBYTES}kB, got ${JSON.stringify(workingMemory)}.`,
|
|
10879
|
+
{ workingMemory, kilobytes }
|
|
10880
|
+
);
|
|
10881
|
+
}
|
|
10882
|
+
return workingMemory;
|
|
10594
10883
|
}
|
|
10595
|
-
async function
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
maxHops,
|
|
10607
|
-
direction: options.direction ?? "out",
|
|
10608
|
-
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
10609
|
-
includePath: false,
|
|
10610
|
-
...resolveTemporalOptions(ctx, options),
|
|
10611
|
-
dialect: ctx.dialect,
|
|
10612
|
-
schema: ctx.schema,
|
|
10613
|
-
...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
|
|
10614
|
-
});
|
|
10615
|
-
const query = sql`${cte} SELECT 1 AS hit FROM reachable WHERE id = ${targetId} LIMIT 1`;
|
|
10616
|
-
const rows = await ctx.backend.execute(
|
|
10617
|
-
asCompiledRowsSql(query)
|
|
10618
|
-
);
|
|
10619
|
-
return rows.length > 0;
|
|
10884
|
+
async function withInlineIterativeGraphOperation(ctx, options, run) {
|
|
10885
|
+
if (ctx.backend.capabilities.transactions) {
|
|
10886
|
+
return ctx.backend.transaction(
|
|
10887
|
+
async (backend) => run(createOperation(ctx, options, backend)),
|
|
10888
|
+
{
|
|
10889
|
+
isolationLevel: "repeatable_read",
|
|
10890
|
+
accessMode: "read_only"
|
|
10891
|
+
}
|
|
10892
|
+
);
|
|
10893
|
+
}
|
|
10894
|
+
return run(createOperation(ctx, options, ctx.backend));
|
|
10620
10895
|
}
|
|
10621
|
-
async function
|
|
10622
|
-
|
|
10896
|
+
async function runIterativeGraphOperation(ctx, options, plan) {
|
|
10897
|
+
if (!ctx.backend.capabilities.transactions) {
|
|
10898
|
+
throw new ConfigurationError(
|
|
10899
|
+
"Temporary-table graph iteration requires a transactional backend.",
|
|
10900
|
+
{ dialect: ctx.backend.dialect }
|
|
10901
|
+
);
|
|
10902
|
+
}
|
|
10903
|
+
return ctx.backend.transaction(
|
|
10904
|
+
async (backend) => {
|
|
10905
|
+
const temporaryBackend = requireTemporaryStatements(backend);
|
|
10906
|
+
const runId = generateId();
|
|
10907
|
+
const workingTable = sql.identifier(
|
|
10908
|
+
`typegraph_iterative_${runId.replaceAll("-", "_")}`
|
|
10909
|
+
);
|
|
10910
|
+
const context = {
|
|
10911
|
+
operation: createOperation(ctx, options, temporaryBackend),
|
|
10912
|
+
backend: temporaryBackend,
|
|
10913
|
+
workingTable,
|
|
10914
|
+
graphId: ctx.graphId,
|
|
10915
|
+
runId,
|
|
10916
|
+
executeTemporary: async (query) => {
|
|
10917
|
+
await temporaryBackend.executeTemporaryStatement(
|
|
10918
|
+
asCompiledTemporaryStatementSql(query)
|
|
10919
|
+
);
|
|
10920
|
+
}
|
|
10921
|
+
};
|
|
10922
|
+
if (context.operation.workingMemory !== void 0) {
|
|
10923
|
+
const workingMemoryStatement = ctx.dialect.setTransactionWorkingMemory(
|
|
10924
|
+
context.operation.workingMemory
|
|
10925
|
+
);
|
|
10926
|
+
if (workingMemoryStatement !== void 0) {
|
|
10927
|
+
await context.executeTemporary(workingMemoryStatement);
|
|
10928
|
+
}
|
|
10929
|
+
}
|
|
10930
|
+
await context.executeTemporary(plan.createWorkingTable(context));
|
|
10931
|
+
let operationError;
|
|
10932
|
+
try {
|
|
10933
|
+
let state = await plan.initialize(context);
|
|
10934
|
+
let analyzedRowCount = await refreshWorkingTableStatistics(
|
|
10935
|
+
context,
|
|
10936
|
+
state.workingTableSize
|
|
10937
|
+
);
|
|
10938
|
+
for (let iteration = 1; iteration <= plan.maxIterations && !plan.hasConverged(state); iteration++) {
|
|
10939
|
+
state = await plan.runRound(context, state, iteration);
|
|
10940
|
+
if (iteration < plan.maxIterations && !plan.hasConverged(state)) {
|
|
10941
|
+
analyzedRowCount = await refreshWorkingTableStatistics(
|
|
10942
|
+
context,
|
|
10943
|
+
state.workingTableSize,
|
|
10944
|
+
analyzedRowCount
|
|
10945
|
+
);
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
if (!plan.hasConverged(state)) {
|
|
10949
|
+
throw new GraphAlgorithmConvergenceError(
|
|
10950
|
+
plan.algorithm,
|
|
10951
|
+
plan.maxIterations
|
|
10952
|
+
);
|
|
10953
|
+
}
|
|
10954
|
+
return await plan.extractResult(context, state);
|
|
10955
|
+
} catch (error) {
|
|
10956
|
+
operationError = error;
|
|
10957
|
+
throw error;
|
|
10958
|
+
} finally {
|
|
10959
|
+
await dropWorkingTable(context, operationError);
|
|
10960
|
+
}
|
|
10961
|
+
},
|
|
10962
|
+
{
|
|
10963
|
+
isolationLevel: "repeatable_read",
|
|
10964
|
+
accessMode: "read_only",
|
|
10965
|
+
temporaryWrites: INTERNAL_TEMPORARY_WRITES
|
|
10966
|
+
}
|
|
10967
|
+
);
|
|
10968
|
+
}
|
|
10969
|
+
function shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount) {
|
|
10970
|
+
if (workingTableSize < WORKING_TABLE_ANALYZE_MINIMUM_ROWS) return false;
|
|
10971
|
+
if (analyzedRowCount === void 0) return true;
|
|
10972
|
+
return workingTableSize >= analyzedRowCount * WORKING_TABLE_ANALYZE_GROWTH_FACTOR;
|
|
10973
|
+
}
|
|
10974
|
+
async function refreshWorkingTableStatistics(context, workingTableSize, analyzedRowCount) {
|
|
10975
|
+
if (!shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount)) {
|
|
10976
|
+
return analyzedRowCount;
|
|
10977
|
+
}
|
|
10978
|
+
const statement = context.operation.ctx.dialect.analyzeTemporaryTable(
|
|
10979
|
+
context.workingTable
|
|
10980
|
+
);
|
|
10981
|
+
if (statement === void 0) return analyzedRowCount;
|
|
10982
|
+
await context.executeTemporary(statement);
|
|
10983
|
+
return workingTableSize;
|
|
10984
|
+
}
|
|
10985
|
+
async function dropWorkingTable(context, operationError) {
|
|
10986
|
+
try {
|
|
10987
|
+
await context.executeTemporary(
|
|
10988
|
+
sql`DROP TABLE IF EXISTS ${context.workingTable}`
|
|
10989
|
+
);
|
|
10990
|
+
} catch (cleanupError) {
|
|
10991
|
+
if (operationError === void 0) throw cleanupError;
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
async function fetchVisibleWorkingNodes(operation, nodeIds) {
|
|
10995
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
10996
|
+
for (const chunk2 of chunkValues(nodeIds, operation.maxWorkingSetSize)) {
|
|
10997
|
+
const query = sql`SELECT n.id, n.kind FROM ${operation.schema.nodesTable} n WHERE n.graph_id = ${operation.ctx.graphId} AND n.id IN (${sqlValueList(chunk2)}) AND ${operation.nodeTemporalFilter}`;
|
|
10998
|
+
const rows = await operation.backend.execute(
|
|
10999
|
+
asCompiledRowsSql(query)
|
|
11000
|
+
);
|
|
11001
|
+
for (const row of rows) nodes.set(nodeIdentityKey(row), row);
|
|
11002
|
+
}
|
|
11003
|
+
return [...nodes.values()].toSorted(
|
|
11004
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
11005
|
+
);
|
|
11006
|
+
}
|
|
11007
|
+
async function reduceExpandedWorkingSet(operation, workingSet, direction, reduce) {
|
|
11008
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
11009
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
11010
|
+
for (const chunk2 of chunkValues(workingSet, operation.maxWorkingSetSize)) {
|
|
11011
|
+
const rows = await operation.backend.execute(
|
|
11012
|
+
asCompiledRowsSql(
|
|
11013
|
+
compileExpansionQuery(operation, chunk2, direction, edgeKinds)
|
|
11014
|
+
)
|
|
11015
|
+
);
|
|
11016
|
+
for (const row of rows) {
|
|
11017
|
+
if (operation.weightExpression !== void 0 && !isPresent(row.weight)) {
|
|
11018
|
+
throw new CompilerInvariantError(
|
|
11019
|
+
"Weighted graph expansion produced a NULL weight. Either the plan skipped its weight audit, or edge data changed concurrently during a run on a backend without snapshot isolation.",
|
|
11020
|
+
{ source: row.source_id, target: row.target_id }
|
|
11021
|
+
);
|
|
11022
|
+
}
|
|
11023
|
+
const expansion = {
|
|
11024
|
+
source: { id: row.source_id, kind: row.source_kind },
|
|
11025
|
+
target: { id: row.target_id, kind: row.target_kind },
|
|
11026
|
+
...isPresent(row.weight) ? { weight: Number(row.weight) } : {}
|
|
11027
|
+
};
|
|
11028
|
+
const targetKey = nodeIdentityKey(expansion.target);
|
|
11029
|
+
const selected = reduce(reduced.get(targetKey), expansion);
|
|
11030
|
+
if (selected !== void 0) reduced.set(targetKey, selected);
|
|
11031
|
+
}
|
|
11032
|
+
}
|
|
11033
|
+
}
|
|
11034
|
+
return reduced;
|
|
11035
|
+
}
|
|
11036
|
+
function nodeIdentityKey(node) {
|
|
11037
|
+
return `${node.kind}\0${node.id}`;
|
|
11038
|
+
}
|
|
11039
|
+
function nodeIdentityKeyFromRow(id, kind) {
|
|
11040
|
+
if (typeof id !== "string" || typeof kind !== "string") return void 0;
|
|
11041
|
+
return nodeIdentityKey({ id, kind });
|
|
11042
|
+
}
|
|
11043
|
+
function compareNodeIdentity(left, right) {
|
|
11044
|
+
return compareStrings(left.id, right.id) || compareStrings(left.kind, right.kind);
|
|
11045
|
+
}
|
|
11046
|
+
function compileExpansionQuery(operation, workingSet, direction, edgeKinds) {
|
|
11047
|
+
const workingValues = sql.join(
|
|
11048
|
+
workingSet.map((node) => sql`(${node.id}, ${node.kind})`),
|
|
11049
|
+
sql`, `
|
|
11050
|
+
);
|
|
11051
|
+
const workingCte = sql`WITH working_set(node_id, node_kind) AS (VALUES ${workingValues})`;
|
|
11052
|
+
switch (direction) {
|
|
11053
|
+
case "out": {
|
|
11054
|
+
return sql`${workingCte} ${compileDirectionalExpansion(operation, sql`working_set`, sql`TRUE`, edgeKinds, "from_id", "from_kind", "to_id", "to_kind")}`;
|
|
11055
|
+
}
|
|
11056
|
+
case "in": {
|
|
11057
|
+
return sql`${workingCte} ${compileDirectionalExpansion(operation, sql`working_set`, sql`TRUE`, edgeKinds, "to_id", "to_kind", "from_id", "from_kind")}`;
|
|
11058
|
+
}
|
|
11059
|
+
case "both": {
|
|
11060
|
+
const outgoing = compileDirectionalExpansion(
|
|
11061
|
+
operation,
|
|
11062
|
+
sql`working_set`,
|
|
11063
|
+
sql`TRUE`,
|
|
11064
|
+
edgeKinds,
|
|
11065
|
+
"from_id",
|
|
11066
|
+
"from_kind",
|
|
11067
|
+
"to_id",
|
|
11068
|
+
"to_kind"
|
|
11069
|
+
);
|
|
11070
|
+
const incoming = compileDirectionalExpansion(
|
|
11071
|
+
operation,
|
|
11072
|
+
sql`working_set`,
|
|
11073
|
+
sql`TRUE`,
|
|
11074
|
+
edgeKinds,
|
|
11075
|
+
"to_id",
|
|
11076
|
+
"to_kind",
|
|
11077
|
+
"from_id",
|
|
11078
|
+
"from_kind"
|
|
11079
|
+
);
|
|
11080
|
+
return sql`${workingCte} ${outgoing} UNION ALL ${incoming}`;
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
function compileWorkingTableExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
|
|
11085
|
+
switch (direction) {
|
|
11086
|
+
case "out": {
|
|
11087
|
+
return compileDirectionalExpansion(
|
|
11088
|
+
operation,
|
|
11089
|
+
workingTable,
|
|
11090
|
+
sourceFilter,
|
|
11091
|
+
edgeKinds,
|
|
11092
|
+
"from_id",
|
|
11093
|
+
"from_kind",
|
|
11094
|
+
"to_id",
|
|
11095
|
+
"to_kind"
|
|
11096
|
+
);
|
|
11097
|
+
}
|
|
11098
|
+
case "in": {
|
|
11099
|
+
return compileDirectionalExpansion(
|
|
11100
|
+
operation,
|
|
11101
|
+
workingTable,
|
|
11102
|
+
sourceFilter,
|
|
11103
|
+
edgeKinds,
|
|
11104
|
+
"to_id",
|
|
11105
|
+
"to_kind",
|
|
11106
|
+
"from_id",
|
|
11107
|
+
"from_kind"
|
|
11108
|
+
);
|
|
11109
|
+
}
|
|
11110
|
+
case "both": {
|
|
11111
|
+
const outgoing = compileDirectionalExpansion(
|
|
11112
|
+
operation,
|
|
11113
|
+
workingTable,
|
|
11114
|
+
sourceFilter,
|
|
11115
|
+
edgeKinds,
|
|
11116
|
+
"from_id",
|
|
11117
|
+
"from_kind",
|
|
11118
|
+
"to_id",
|
|
11119
|
+
"to_kind"
|
|
11120
|
+
);
|
|
11121
|
+
const incoming = compileDirectionalExpansion(
|
|
11122
|
+
operation,
|
|
11123
|
+
workingTable,
|
|
11124
|
+
sourceFilter,
|
|
11125
|
+
edgeKinds,
|
|
11126
|
+
"to_id",
|
|
11127
|
+
"to_kind",
|
|
11128
|
+
"from_id",
|
|
11129
|
+
"from_kind"
|
|
11130
|
+
);
|
|
11131
|
+
return sql`${outgoing} UNION ALL ${incoming}`;
|
|
11132
|
+
}
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
function compileWorkingTableEdgeExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
|
|
11136
|
+
switch (direction) {
|
|
11137
|
+
case "out": {
|
|
11138
|
+
return compileDirectionalEdgeExpansion(
|
|
11139
|
+
operation,
|
|
11140
|
+
workingTable,
|
|
11141
|
+
sourceFilter,
|
|
11142
|
+
edgeKinds,
|
|
11143
|
+
"from_id",
|
|
11144
|
+
"from_kind",
|
|
11145
|
+
"to_id",
|
|
11146
|
+
"to_kind"
|
|
11147
|
+
);
|
|
11148
|
+
}
|
|
11149
|
+
case "in": {
|
|
11150
|
+
return compileDirectionalEdgeExpansion(
|
|
11151
|
+
operation,
|
|
11152
|
+
workingTable,
|
|
11153
|
+
sourceFilter,
|
|
11154
|
+
edgeKinds,
|
|
11155
|
+
"to_id",
|
|
11156
|
+
"to_kind",
|
|
11157
|
+
"from_id",
|
|
11158
|
+
"from_kind"
|
|
11159
|
+
);
|
|
11160
|
+
}
|
|
11161
|
+
case "both": {
|
|
11162
|
+
const outgoing = compileDirectionalEdgeExpansion(
|
|
11163
|
+
operation,
|
|
11164
|
+
workingTable,
|
|
11165
|
+
sourceFilter,
|
|
11166
|
+
edgeKinds,
|
|
11167
|
+
"from_id",
|
|
11168
|
+
"from_kind",
|
|
11169
|
+
"to_id",
|
|
11170
|
+
"to_kind"
|
|
11171
|
+
);
|
|
11172
|
+
const incoming = compileDirectionalEdgeExpansion(
|
|
11173
|
+
operation,
|
|
11174
|
+
workingTable,
|
|
11175
|
+
sourceFilter,
|
|
11176
|
+
edgeKinds,
|
|
11177
|
+
"to_id",
|
|
11178
|
+
"to_kind",
|
|
11179
|
+
"from_id",
|
|
11180
|
+
"from_kind"
|
|
11181
|
+
);
|
|
11182
|
+
return sql`${outgoing} UNION ALL ${incoming}`;
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
function compileDirectionalExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
|
|
11187
|
+
const edgeExpansion = compileDirectionalEdgeExpansion(
|
|
11188
|
+
operation,
|
|
11189
|
+
workingRelation,
|
|
11190
|
+
sourceFilter,
|
|
11191
|
+
edgeKinds,
|
|
11192
|
+
joinField,
|
|
11193
|
+
joinKindField,
|
|
11194
|
+
targetField,
|
|
11195
|
+
targetKindField
|
|
11196
|
+
);
|
|
11197
|
+
const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, expanded.weight`;
|
|
11198
|
+
return sql`SELECT expanded.source_id, expanded.source_kind, n.id AS target_id, n.kind AS target_kind${weightColumn} FROM (${edgeExpansion}) expanded JOIN ${operation.schema.nodesTable} n ON n.graph_id = ${operation.ctx.graphId} AND n.id = expanded.target_id AND n.kind = expanded.target_kind WHERE ${operation.nodeTemporalFilter}`;
|
|
11199
|
+
}
|
|
11200
|
+
function compileDirectionalEdgeExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
|
|
11201
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
|
|
11202
|
+
const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, ${operation.weightExpression} AS weight`;
|
|
11203
|
+
return sql`SELECT w.node_id AS source_id, w.node_kind AS source_kind, e.${sql.raw(targetField)} AS target_id, e.${sql.raw(targetKindField)} AS target_kind${weightColumn} FROM ${workingRelation} w JOIN ${operation.schema.edgesTable} e ON e.${sql.raw(joinField)} = w.node_id AND e.${sql.raw(joinKindField)} = w.node_kind AND e.graph_id = ${operation.ctx.graphId} WHERE ${sourceFilter} AND ${edgeKindFilter} AND ${operation.edgeTemporalFilter}`;
|
|
11204
|
+
}
|
|
11205
|
+
function chunkValues(values, chunkSize) {
|
|
11206
|
+
const chunks = [];
|
|
11207
|
+
for (let offset = 0; offset < values.length; offset += chunkSize) {
|
|
11208
|
+
chunks.push(values.slice(offset, offset + chunkSize));
|
|
11209
|
+
}
|
|
11210
|
+
return chunks;
|
|
11211
|
+
}
|
|
11212
|
+
function createOperation(ctx, options, backend) {
|
|
11213
|
+
const temporal = resolveTemporalOptions(ctx, options);
|
|
11214
|
+
const currentTimestamp = currentReadInstant();
|
|
11215
|
+
const nodeTemporalFilter = compileTemporalFilter({
|
|
11216
|
+
mode: temporal.temporalMode,
|
|
11217
|
+
...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
|
|
11218
|
+
...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
|
|
11219
|
+
tableAlias: "n",
|
|
11220
|
+
currentTimestamp
|
|
11221
|
+
});
|
|
11222
|
+
const edgeTemporalFilter = compileTemporalFilter({
|
|
11223
|
+
mode: temporal.temporalMode,
|
|
11224
|
+
...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
|
|
11225
|
+
...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
|
|
11226
|
+
tableAlias: "e",
|
|
11227
|
+
currentTimestamp
|
|
11228
|
+
});
|
|
11229
|
+
const weightExpression = options.weightProperty === void 0 ? void 0 : compileWeightExpression(
|
|
11230
|
+
ctx.dialect,
|
|
11231
|
+
options.weightProperty,
|
|
11232
|
+
options.defaultWeight
|
|
11233
|
+
);
|
|
11234
|
+
const direction = options.direction ?? "out";
|
|
11235
|
+
const branchCount = direction === "both" ? 2 : 1;
|
|
11236
|
+
const parameterLimit = backend.capabilities.maxBindParameters ?? DEFAULT_MAX_BIND_PARAMETERS;
|
|
11237
|
+
const reservedPerBranch = RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH + (weightExpression === void 0 ? 0 : RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH);
|
|
11238
|
+
const fixedParameters = branchCount * reservedPerBranch;
|
|
11239
|
+
const sharedBudget = parameterLimit - fixedParameters;
|
|
11240
|
+
const maxEdgeKindsPerQuery = Math.floor(sharedBudget / (branchCount + 2));
|
|
11241
|
+
if (maxEdgeKindsPerQuery < 1) {
|
|
11242
|
+
throw new ConfigurationError(
|
|
11243
|
+
"An iterative graph operation cannot fit its fixed filters within the backend bind-parameter limit.",
|
|
11244
|
+
{ parameterLimit, direction }
|
|
11245
|
+
);
|
|
11246
|
+
}
|
|
11247
|
+
const edgeKindChunks = chunkValues(
|
|
11248
|
+
[...new Set(options.edges)].toSorted(
|
|
11249
|
+
(left, right) => compareStrings(left, right)
|
|
11250
|
+
),
|
|
11251
|
+
maxEdgeKindsPerQuery
|
|
11252
|
+
);
|
|
11253
|
+
const largestEdgeKindChunk = Math.max(
|
|
11254
|
+
...edgeKindChunks.map((chunk2) => chunk2.length)
|
|
11255
|
+
);
|
|
11256
|
+
const maxWorkingSetSize = Math.floor(
|
|
11257
|
+
(parameterLimit - branchCount * (largestEdgeKindChunk + reservedPerBranch)) / 2
|
|
11258
|
+
);
|
|
11259
|
+
if (maxWorkingSetSize < 1) {
|
|
11260
|
+
throw new ConfigurationError(
|
|
11261
|
+
"An iterative graph operation cannot fit one working-set node within the backend bind-parameter limit.",
|
|
11262
|
+
{ parameterLimit, direction }
|
|
11263
|
+
);
|
|
11264
|
+
}
|
|
11265
|
+
return {
|
|
11266
|
+
backend,
|
|
11267
|
+
ctx,
|
|
11268
|
+
edgeKindChunks,
|
|
11269
|
+
direction,
|
|
11270
|
+
maxWorkingSetSize,
|
|
11271
|
+
nodeTemporalFilter,
|
|
11272
|
+
edgeTemporalFilter,
|
|
11273
|
+
schema: resolveReadSchema(ctx, options),
|
|
11274
|
+
workingMemory: resolveWorkingMemory(options.workingMemory),
|
|
11275
|
+
weightExpression
|
|
11276
|
+
};
|
|
11277
|
+
}
|
|
11278
|
+
function compileWeightExpression(dialect, weightProperty, defaultWeight) {
|
|
11279
|
+
const extracted = dialect.jsonExtractDouble(
|
|
11280
|
+
sql.raw("e.props"),
|
|
11281
|
+
jsonPointer([weightProperty])
|
|
11282
|
+
);
|
|
11283
|
+
return defaultWeight === void 0 ? extracted : sql`COALESCE(${extracted}, ${defaultWeight})`;
|
|
11284
|
+
}
|
|
11285
|
+
function supportsTemporaryIteration(ctx) {
|
|
11286
|
+
return ctx.backend.capabilities.transactions && ctx.backend.capabilities.returning !== false && ctx.backend.executeTemporaryStatement !== void 0;
|
|
11287
|
+
}
|
|
11288
|
+
function requireTemporaryStatements(backend) {
|
|
11289
|
+
if (backend.executeTemporaryStatement === void 0) {
|
|
11290
|
+
throw new ConfigurationError(
|
|
11291
|
+
"Iterative graph operations require temporary-statement support on the transaction backend.",
|
|
11292
|
+
{ dialect: backend.dialect },
|
|
11293
|
+
{
|
|
11294
|
+
suggestion: "Use a built-in SQLite/PostgreSQL backend or implement executeTemporaryStatement on the custom backend."
|
|
11295
|
+
}
|
|
11296
|
+
);
|
|
11297
|
+
}
|
|
11298
|
+
return backend;
|
|
11299
|
+
}
|
|
11300
|
+
|
|
11301
|
+
// src/store/algorithms/breadth-first.ts
|
|
11302
|
+
async function findReachableNodes(ctx, sourceId, maxHops, options) {
|
|
11303
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
11304
|
+
return findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options);
|
|
11305
|
+
}
|
|
11306
|
+
return findReachableNodesInline(ctx, sourceId, maxHops, options);
|
|
11307
|
+
}
|
|
11308
|
+
async function findShortestPath(ctx, sourceId, targetId, maxHops, options) {
|
|
11309
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
11310
|
+
return findShortestPathInWorkingTable(
|
|
11311
|
+
ctx,
|
|
11312
|
+
sourceId,
|
|
11313
|
+
targetId,
|
|
11314
|
+
maxHops,
|
|
11315
|
+
options
|
|
11316
|
+
);
|
|
11317
|
+
}
|
|
11318
|
+
return findShortestPathInline(ctx, sourceId, targetId, maxHops, options);
|
|
11319
|
+
}
|
|
11320
|
+
async function findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options) {
|
|
11321
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
11322
|
+
algorithm: "reachable",
|
|
11323
|
+
maxIterations: maxHops,
|
|
11324
|
+
createWorkingTable,
|
|
11325
|
+
async initialize(context) {
|
|
11326
|
+
const seeded = await seedWorkingSide(context, sourceId, "forward");
|
|
11327
|
+
return {
|
|
11328
|
+
depth: 0,
|
|
11329
|
+
frontierCount: seeded.length,
|
|
11330
|
+
workingTableSize: seeded.length
|
|
11331
|
+
};
|
|
11332
|
+
},
|
|
11333
|
+
async runRound(context, state) {
|
|
11334
|
+
const nextDepth = state.depth + 1;
|
|
11335
|
+
const frontierCount = await expandReachableWorkingTableRound(
|
|
11336
|
+
context,
|
|
11337
|
+
"forward",
|
|
11338
|
+
state.depth,
|
|
11339
|
+
nextDepth,
|
|
11340
|
+
context.operation.direction
|
|
11341
|
+
);
|
|
11342
|
+
return {
|
|
11343
|
+
depth: nextDepth,
|
|
11344
|
+
frontierCount,
|
|
11345
|
+
workingTableSize: state.workingTableSize + frontierCount
|
|
11346
|
+
};
|
|
11347
|
+
},
|
|
11348
|
+
hasConverged(state) {
|
|
11349
|
+
return state.frontierCount === 0 || state.depth >= maxHops;
|
|
11350
|
+
},
|
|
11351
|
+
async extractResult(context) {
|
|
11352
|
+
const rows = await readWorkingRows(context, "forward");
|
|
11353
|
+
return rows.map((row) => ({
|
|
11354
|
+
id: row.node_id,
|
|
11355
|
+
kind: row.node_kind,
|
|
11356
|
+
depth: Number(row.depth)
|
|
11357
|
+
})).toSorted(
|
|
11358
|
+
(left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
|
|
11359
|
+
);
|
|
11360
|
+
}
|
|
11361
|
+
});
|
|
11362
|
+
}
|
|
11363
|
+
async function findShortestPathInWorkingTable(ctx, sourceId, targetId, maxHops, options) {
|
|
11364
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
11365
|
+
algorithm: "shortestPath",
|
|
11366
|
+
maxIterations: maxHops,
|
|
11367
|
+
createWorkingTable,
|
|
11368
|
+
async initialize(context) {
|
|
11369
|
+
const forwardSeeds = await seedWorkingSide(context, sourceId, "forward");
|
|
11370
|
+
const reverseSeeds = await seedWorkingSide(context, targetId, "reverse");
|
|
11371
|
+
return {
|
|
11372
|
+
forwardDepth: 0,
|
|
11373
|
+
reverseDepth: 0,
|
|
11374
|
+
forwardFrontierCount: forwardSeeds.length,
|
|
11375
|
+
reverseFrontierCount: reverseSeeds.length,
|
|
11376
|
+
meeting: findSeedMeeting(forwardSeeds, reverseSeeds),
|
|
11377
|
+
workingTableSize: forwardSeeds.length + reverseSeeds.length
|
|
11378
|
+
};
|
|
11379
|
+
},
|
|
11380
|
+
async runRound(context, state) {
|
|
11381
|
+
const expandForward = state.forwardFrontierCount <= state.reverseFrontierCount;
|
|
11382
|
+
const side = expandForward ? "forward" : "reverse";
|
|
11383
|
+
const currentDepth = expandForward ? state.forwardDepth : state.reverseDepth;
|
|
11384
|
+
const nextDepth = currentDepth + 1;
|
|
11385
|
+
const direction = expandForward ? context.operation.direction : reverseDirection(context.operation.direction);
|
|
11386
|
+
const round = await expandWorkingTableRound(
|
|
11387
|
+
context,
|
|
11388
|
+
side,
|
|
11389
|
+
currentDepth,
|
|
11390
|
+
nextDepth,
|
|
11391
|
+
direction,
|
|
11392
|
+
maxHops
|
|
11393
|
+
);
|
|
11394
|
+
return {
|
|
11395
|
+
forwardDepth: expandForward ? nextDepth : state.forwardDepth,
|
|
11396
|
+
reverseDepth: expandForward ? state.reverseDepth : nextDepth,
|
|
11397
|
+
forwardFrontierCount: expandForward ? round.insertedCount : state.forwardFrontierCount,
|
|
11398
|
+
reverseFrontierCount: expandForward ? state.reverseFrontierCount : round.insertedCount,
|
|
11399
|
+
meeting: round.meeting,
|
|
11400
|
+
workingTableSize: state.workingTableSize + round.insertedCount
|
|
11401
|
+
};
|
|
11402
|
+
},
|
|
11403
|
+
hasConverged(state) {
|
|
11404
|
+
return state.meeting !== void 0 || state.forwardFrontierCount === 0 || state.reverseFrontierCount === 0 || state.forwardDepth + state.reverseDepth >= maxHops;
|
|
11405
|
+
},
|
|
11406
|
+
async extractResult(context, state) {
|
|
11407
|
+
if (state.meeting === void 0) return;
|
|
11408
|
+
const rows = await readWorkingRows(context);
|
|
11409
|
+
return buildShortestPath(
|
|
11410
|
+
nodeIdentityKey(state.meeting),
|
|
11411
|
+
createVisitedMapFromRows(rows, "forward"),
|
|
11412
|
+
createVisitedMapFromRows(rows, "reverse")
|
|
11413
|
+
);
|
|
11414
|
+
}
|
|
11415
|
+
});
|
|
11416
|
+
}
|
|
11417
|
+
function createWorkingTable(context) {
|
|
11418
|
+
return sql`
|
|
11419
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
11420
|
+
graph_id TEXT NOT NULL,
|
|
11421
|
+
run_id TEXT NOT NULL,
|
|
11422
|
+
side TEXT NOT NULL,
|
|
11423
|
+
node_id TEXT NOT NULL,
|
|
11424
|
+
node_kind TEXT NOT NULL,
|
|
11425
|
+
depth INTEGER NOT NULL,
|
|
11426
|
+
predecessor_id TEXT,
|
|
11427
|
+
predecessor_kind TEXT,
|
|
11428
|
+
meeting_depth INTEGER,
|
|
11429
|
+
PRIMARY KEY (graph_id, run_id, side, node_kind, node_id)
|
|
11430
|
+
)
|
|
11431
|
+
`;
|
|
11432
|
+
}
|
|
11433
|
+
async function seedWorkingSide(context, nodeId, side) {
|
|
11434
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11435
|
+
return context.backend.execute(
|
|
11436
|
+
asCompiledRowsSql(sql`
|
|
11437
|
+
INSERT INTO ${workingTable}
|
|
11438
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11439
|
+
predecessor_id, predecessor_kind)
|
|
11440
|
+
SELECT ${graphId}, ${runId}, ${side}, n.id, n.kind, 0, NULL, NULL
|
|
11441
|
+
FROM ${operation.schema.nodesTable} n
|
|
11442
|
+
WHERE n.graph_id = ${graphId}
|
|
11443
|
+
AND n.id = ${nodeId}
|
|
11444
|
+
AND ${operation.nodeTemporalFilter}
|
|
11445
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11446
|
+
RETURNING node_id, node_kind
|
|
11447
|
+
`)
|
|
11448
|
+
);
|
|
11449
|
+
}
|
|
11450
|
+
function findSeedMeeting(forwardSeeds, reverseSeeds) {
|
|
11451
|
+
const reverseKeys = new Set(
|
|
11452
|
+
reverseSeeds.map(
|
|
11453
|
+
(row) => nodeIdentityKey({ id: row.node_id, kind: row.node_kind })
|
|
11454
|
+
)
|
|
11455
|
+
);
|
|
11456
|
+
let meeting;
|
|
11457
|
+
for (const row of forwardSeeds) {
|
|
11458
|
+
const candidate = { id: row.node_id, kind: row.node_kind, depth: 0 };
|
|
11459
|
+
if (!reverseKeys.has(nodeIdentityKey(candidate))) continue;
|
|
11460
|
+
if (meeting === void 0 || compareNodeIdentity(candidate, meeting) < 0) {
|
|
11461
|
+
meeting = candidate;
|
|
11462
|
+
}
|
|
11463
|
+
}
|
|
11464
|
+
return meeting;
|
|
11465
|
+
}
|
|
11466
|
+
async function expandWorkingTableRound(context, side, currentDepth, nextDepth, direction, maxHops) {
|
|
11467
|
+
const oppositeSide = side === "forward" ? "reverse" : "forward";
|
|
11468
|
+
let insertedCount = 0;
|
|
11469
|
+
let meeting;
|
|
11470
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11471
|
+
const sourceFilter = sql`
|
|
11472
|
+
w.graph_id = ${context.graphId}
|
|
11473
|
+
AND w.run_id = ${context.runId}
|
|
11474
|
+
AND w.side = ${side}
|
|
11475
|
+
AND w.depth = ${currentDepth}
|
|
11476
|
+
`;
|
|
11477
|
+
const expansion = compileWorkingTableExpansion(
|
|
11478
|
+
context.operation,
|
|
11479
|
+
context.workingTable,
|
|
11480
|
+
sourceFilter,
|
|
11481
|
+
direction,
|
|
11482
|
+
edgeKinds
|
|
11483
|
+
);
|
|
11484
|
+
const rows = await context.backend.execute(
|
|
11485
|
+
asCompiledRowsSql(sql`
|
|
11486
|
+
INSERT INTO ${context.workingTable}
|
|
11487
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11488
|
+
predecessor_id, predecessor_kind, meeting_depth)
|
|
11489
|
+
SELECT
|
|
11490
|
+
${context.graphId}, ${context.runId}, ${side},
|
|
11491
|
+
ranked.target_id, ranked.target_kind, ${nextDepth},
|
|
11492
|
+
ranked.source_id, ranked.source_kind,
|
|
11493
|
+
(
|
|
11494
|
+
SELECT opposite.depth
|
|
11495
|
+
FROM ${context.workingTable} opposite
|
|
11496
|
+
WHERE opposite.graph_id = ${context.graphId}
|
|
11497
|
+
AND opposite.run_id = ${context.runId}
|
|
11498
|
+
AND opposite.side = ${oppositeSide}
|
|
11499
|
+
AND opposite.node_id = ranked.target_id
|
|
11500
|
+
AND opposite.node_kind = ranked.target_kind
|
|
11501
|
+
)
|
|
11502
|
+
FROM (
|
|
11503
|
+
SELECT expanded.*,
|
|
11504
|
+
ROW_NUMBER() OVER (
|
|
11505
|
+
PARTITION BY expanded.target_kind, expanded.target_id
|
|
11506
|
+
ORDER BY expanded.source_id, expanded.source_kind
|
|
11507
|
+
) AS candidate_rank
|
|
11508
|
+
FROM (${expansion}) expanded
|
|
11509
|
+
) ranked
|
|
11510
|
+
WHERE ranked.candidate_rank = 1
|
|
11511
|
+
AND NOT EXISTS (
|
|
11512
|
+
SELECT 1
|
|
11513
|
+
FROM ${context.workingTable} visited
|
|
11514
|
+
WHERE visited.graph_id = ${context.graphId}
|
|
11515
|
+
AND visited.run_id = ${context.runId}
|
|
11516
|
+
AND visited.side = ${side}
|
|
11517
|
+
AND visited.node_id = ranked.target_id
|
|
11518
|
+
AND visited.node_kind = ranked.target_kind
|
|
11519
|
+
)
|
|
11520
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11521
|
+
RETURNING node_id, node_kind, meeting_depth
|
|
11522
|
+
`)
|
|
11523
|
+
);
|
|
11524
|
+
insertedCount += rows.length;
|
|
11525
|
+
meeting = selectRoundMeeting(meeting, rows, nextDepth, maxHops);
|
|
11526
|
+
}
|
|
11527
|
+
return { insertedCount, meeting };
|
|
11528
|
+
}
|
|
11529
|
+
function selectRoundMeeting(currentMeeting, rows, nextDepth, maxHops) {
|
|
11530
|
+
let meeting = currentMeeting;
|
|
11531
|
+
for (const row of rows) {
|
|
11532
|
+
if (row.meeting_depth === null || row.meeting_depth === void 0) {
|
|
11533
|
+
continue;
|
|
11534
|
+
}
|
|
11535
|
+
const totalDepth = nextDepth + Number(row.meeting_depth);
|
|
11536
|
+
if (totalDepth > maxHops) continue;
|
|
11537
|
+
const candidate = {
|
|
11538
|
+
id: row.node_id,
|
|
11539
|
+
kind: row.node_kind,
|
|
11540
|
+
depth: totalDepth
|
|
11541
|
+
};
|
|
11542
|
+
if (meeting === void 0 || candidate.depth < meeting.depth || candidate.depth === meeting.depth && compareNodeIdentity(candidate, meeting) < 0) {
|
|
11543
|
+
meeting = candidate;
|
|
11544
|
+
}
|
|
11545
|
+
}
|
|
11546
|
+
return meeting;
|
|
11547
|
+
}
|
|
11548
|
+
async function expandReachableWorkingTableRound(context, side, currentDepth, nextDepth, direction) {
|
|
11549
|
+
let insertedCount = 0;
|
|
11550
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11551
|
+
const sourceFilter = sql`
|
|
11552
|
+
w.graph_id = ${context.graphId}
|
|
11553
|
+
AND w.run_id = ${context.runId}
|
|
11554
|
+
AND w.side = ${side}
|
|
11555
|
+
AND w.depth = ${currentDepth}
|
|
11556
|
+
`;
|
|
11557
|
+
const edgeExpansion = compileWorkingTableEdgeExpansion(
|
|
11558
|
+
context.operation,
|
|
11559
|
+
context.workingTable,
|
|
11560
|
+
sourceFilter,
|
|
11561
|
+
direction,
|
|
11562
|
+
edgeKinds
|
|
11563
|
+
);
|
|
11564
|
+
const rows = await context.backend.execute(
|
|
11565
|
+
asCompiledRowsSql(sql`
|
|
11566
|
+
INSERT INTO ${context.workingTable}
|
|
11567
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11568
|
+
predecessor_id, predecessor_kind)
|
|
11569
|
+
SELECT
|
|
11570
|
+
${context.graphId}, ${context.runId}, ${side},
|
|
11571
|
+
candidates.target_id, candidates.target_kind, ${nextDepth},
|
|
11572
|
+
NULL, NULL
|
|
11573
|
+
FROM (
|
|
11574
|
+
SELECT DISTINCT expanded.target_id, expanded.target_kind
|
|
11575
|
+
FROM (${edgeExpansion}) expanded
|
|
11576
|
+
) candidates
|
|
11577
|
+
JOIN ${context.operation.schema.nodesTable} n
|
|
11578
|
+
ON n.graph_id = ${context.graphId}
|
|
11579
|
+
AND n.id = candidates.target_id
|
|
11580
|
+
AND n.kind = candidates.target_kind
|
|
11581
|
+
WHERE ${context.operation.nodeTemporalFilter}
|
|
11582
|
+
AND NOT EXISTS (
|
|
11583
|
+
SELECT 1
|
|
11584
|
+
FROM ${context.workingTable} visited
|
|
11585
|
+
WHERE visited.graph_id = ${context.graphId}
|
|
11586
|
+
AND visited.run_id = ${context.runId}
|
|
11587
|
+
AND visited.side = ${side}
|
|
11588
|
+
AND visited.node_id = candidates.target_id
|
|
11589
|
+
AND visited.node_kind = candidates.target_kind
|
|
11590
|
+
)
|
|
11591
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11592
|
+
RETURNING node_id
|
|
11593
|
+
`)
|
|
11594
|
+
);
|
|
11595
|
+
insertedCount += rows.length;
|
|
11596
|
+
}
|
|
11597
|
+
return insertedCount;
|
|
11598
|
+
}
|
|
11599
|
+
async function readWorkingRows(context, side) {
|
|
11600
|
+
const sideFilter = side === void 0 ? sql`TRUE` : sql`side = ${side}`;
|
|
11601
|
+
return context.backend.execute(
|
|
11602
|
+
asCompiledRowsSql(sql`
|
|
11603
|
+
SELECT side, node_id, node_kind, depth,
|
|
11604
|
+
predecessor_id, predecessor_kind
|
|
11605
|
+
FROM ${context.workingTable}
|
|
11606
|
+
WHERE graph_id = ${context.graphId}
|
|
11607
|
+
AND run_id = ${context.runId}
|
|
11608
|
+
AND ${sideFilter}
|
|
11609
|
+
ORDER BY side, depth, node_id, node_kind
|
|
11610
|
+
`)
|
|
11611
|
+
);
|
|
11612
|
+
}
|
|
11613
|
+
async function findReachableNodesInline(ctx, sourceId, maxHops, options) {
|
|
11614
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
11615
|
+
const sources = await fetchVisibleWorkingNodes(operation, [sourceId]);
|
|
11616
|
+
if (sources.length === 0) return [];
|
|
11617
|
+
const reached = new Map(
|
|
11618
|
+
sources.map((source) => [
|
|
11619
|
+
nodeIdentityKey(source),
|
|
11620
|
+
{ id: source.id, kind: source.kind, depth: 0 }
|
|
11621
|
+
])
|
|
11622
|
+
);
|
|
11623
|
+
let workingSet = sources;
|
|
11624
|
+
for (let depth = 1; depth <= maxHops && workingSet.length > 0; depth++) {
|
|
11625
|
+
const candidates = await expandInlineWorkingSet(
|
|
11626
|
+
operation,
|
|
11627
|
+
workingSet,
|
|
11628
|
+
operation.direction,
|
|
11629
|
+
reached
|
|
11630
|
+
);
|
|
11631
|
+
const nextWorkingSet = sortCandidates(candidates);
|
|
11632
|
+
for (const candidate of nextWorkingSet) {
|
|
11633
|
+
reached.set(nodeIdentityKey(candidate), {
|
|
11634
|
+
id: candidate.id,
|
|
11635
|
+
kind: candidate.kind,
|
|
11636
|
+
depth
|
|
11637
|
+
});
|
|
11638
|
+
}
|
|
11639
|
+
workingSet = nextWorkingSet;
|
|
11640
|
+
}
|
|
11641
|
+
return [...reached.values()].toSorted(
|
|
11642
|
+
(left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
|
|
11643
|
+
);
|
|
11644
|
+
});
|
|
11645
|
+
}
|
|
11646
|
+
async function findShortestPathInline(ctx, sourceId, targetId, maxHops, options) {
|
|
11647
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
11648
|
+
const visibleNodes = await fetchVisibleWorkingNodes(operation, [
|
|
11649
|
+
sourceId,
|
|
11650
|
+
targetId
|
|
11651
|
+
]);
|
|
11652
|
+
const sources = visibleNodes.filter((node) => node.id === sourceId);
|
|
11653
|
+
const targets = visibleNodes.filter((node) => node.id === targetId);
|
|
11654
|
+
if (sources.length === 0 || targets.length === 0) return;
|
|
11655
|
+
if (sourceId === targetId) return { nodes: [sources[0]], depth: 0 };
|
|
11656
|
+
const forwardVisited = createVisitedMap(sources);
|
|
11657
|
+
const reverseVisited = createVisitedMap(targets);
|
|
11658
|
+
let forwardWorkingSet = sources;
|
|
11659
|
+
let reverseWorkingSet = targets;
|
|
11660
|
+
let forwardDepth = 0;
|
|
11661
|
+
let reverseDepth = 0;
|
|
11662
|
+
while (forwardWorkingSet.length > 0 && reverseWorkingSet.length > 0 && forwardDepth + reverseDepth < maxHops) {
|
|
11663
|
+
const expandForward = forwardWorkingSet.length <= reverseWorkingSet.length;
|
|
11664
|
+
const activeVisited = expandForward ? forwardVisited : reverseVisited;
|
|
11665
|
+
const oppositeVisited = expandForward ? reverseVisited : forwardVisited;
|
|
11666
|
+
const currentWorkingSet = expandForward ? forwardWorkingSet : reverseWorkingSet;
|
|
11667
|
+
const direction = expandForward ? operation.direction : reverseDirection(operation.direction);
|
|
11668
|
+
const candidates = await expandInlineWorkingSet(
|
|
11669
|
+
operation,
|
|
11670
|
+
currentWorkingSet,
|
|
11671
|
+
direction,
|
|
11672
|
+
activeVisited
|
|
11673
|
+
);
|
|
11674
|
+
const nextDepth = (expandForward ? forwardDepth : reverseDepth) + 1;
|
|
11675
|
+
const nextWorkingSet = sortCandidates(candidates);
|
|
11676
|
+
for (const candidate of nextWorkingSet) {
|
|
11677
|
+
activeVisited.set(nodeIdentityKey(candidate), {
|
|
11678
|
+
id: candidate.id,
|
|
11679
|
+
kind: candidate.kind,
|
|
11680
|
+
depth: nextDepth,
|
|
11681
|
+
parentKey: nodeIdentityKey({
|
|
11682
|
+
id: candidate.parentId,
|
|
11683
|
+
kind: candidate.parentKind
|
|
11684
|
+
})
|
|
11685
|
+
});
|
|
11686
|
+
}
|
|
11687
|
+
const meetingKey = findMeetingNodeKey(
|
|
11688
|
+
nextWorkingSet,
|
|
11689
|
+
activeVisited,
|
|
11690
|
+
oppositeVisited
|
|
11691
|
+
);
|
|
11692
|
+
if (meetingKey !== void 0) {
|
|
11693
|
+
return buildShortestPath(meetingKey, forwardVisited, reverseVisited);
|
|
11694
|
+
}
|
|
11695
|
+
if (expandForward) {
|
|
11696
|
+
forwardWorkingSet = nextWorkingSet;
|
|
11697
|
+
forwardDepth = nextDepth;
|
|
11698
|
+
} else {
|
|
11699
|
+
reverseWorkingSet = nextWorkingSet;
|
|
11700
|
+
reverseDepth = nextDepth;
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
return;
|
|
11704
|
+
});
|
|
11705
|
+
}
|
|
11706
|
+
async function expandInlineWorkingSet(operation, workingSet, direction, visited) {
|
|
11707
|
+
return reduceExpandedWorkingSet(
|
|
11708
|
+
operation,
|
|
11709
|
+
workingSet,
|
|
11710
|
+
direction,
|
|
11711
|
+
(existing, expansion) => selectPredecessor(existing, expansion, visited)
|
|
11712
|
+
);
|
|
11713
|
+
}
|
|
11714
|
+
function selectPredecessor(existing, expansion, visited) {
|
|
11715
|
+
if (visited.has(nodeIdentityKey(expansion.target))) return existing;
|
|
11716
|
+
if (existing !== void 0 && compareNodeIdentity(expansion.source, {
|
|
11717
|
+
id: existing.parentId,
|
|
11718
|
+
kind: existing.parentKind
|
|
11719
|
+
}) >= 0) {
|
|
11720
|
+
return existing;
|
|
11721
|
+
}
|
|
11722
|
+
return {
|
|
11723
|
+
id: expansion.target.id,
|
|
11724
|
+
kind: expansion.target.kind,
|
|
11725
|
+
parentId: expansion.source.id,
|
|
11726
|
+
parentKind: expansion.source.kind
|
|
11727
|
+
};
|
|
11728
|
+
}
|
|
11729
|
+
function sortCandidates(candidates) {
|
|
11730
|
+
return [...candidates.values()].toSorted(
|
|
11731
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
11732
|
+
);
|
|
11733
|
+
}
|
|
11734
|
+
function findMeetingNodeKey(candidates, activeVisited, oppositeVisited) {
|
|
11735
|
+
let best;
|
|
11736
|
+
for (const candidate of candidates) {
|
|
11737
|
+
const key = nodeIdentityKey(candidate);
|
|
11738
|
+
const active = activeVisited.get(key);
|
|
11739
|
+
const opposite = oppositeVisited.get(key);
|
|
11740
|
+
if (active === void 0 || opposite === void 0) continue;
|
|
11741
|
+
const meeting = { key, depth: active.depth + opposite.depth };
|
|
11742
|
+
if (best === void 0 || meeting.depth < best.depth || meeting.depth === best.depth && compareStrings(meeting.key, best.key) < 0) {
|
|
11743
|
+
best = meeting;
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
return best?.key;
|
|
11747
|
+
}
|
|
11748
|
+
function buildShortestPath(meetingKey, forwardVisited, reverseVisited) {
|
|
11749
|
+
const forwardKeys = [];
|
|
11750
|
+
let cursor = meetingKey;
|
|
11751
|
+
while (cursor !== void 0) {
|
|
11752
|
+
forwardKeys.push(cursor);
|
|
11753
|
+
cursor = forwardVisited.get(cursor)?.parentKey;
|
|
11754
|
+
}
|
|
11755
|
+
forwardKeys.reverse();
|
|
11756
|
+
const pathKeys = [...forwardKeys];
|
|
11757
|
+
cursor = reverseVisited.get(meetingKey)?.parentKey;
|
|
11758
|
+
while (cursor !== void 0) {
|
|
11759
|
+
pathKeys.push(cursor);
|
|
11760
|
+
cursor = reverseVisited.get(cursor)?.parentKey;
|
|
11761
|
+
}
|
|
11762
|
+
const nodes = pathKeys.map((key) => {
|
|
11763
|
+
const node = forwardVisited.get(key) ?? reverseVisited.get(key);
|
|
11764
|
+
return { id: node?.id ?? "", kind: node?.kind ?? "" };
|
|
11765
|
+
});
|
|
11766
|
+
return { nodes, depth: nodes.length - 1 };
|
|
11767
|
+
}
|
|
11768
|
+
function createVisitedMap(nodes) {
|
|
11769
|
+
return new Map(
|
|
11770
|
+
nodes.map((node) => [
|
|
11771
|
+
nodeIdentityKey(node),
|
|
11772
|
+
{ id: node.id, kind: node.kind, depth: 0, parentKey: void 0 }
|
|
11773
|
+
])
|
|
11774
|
+
);
|
|
11775
|
+
}
|
|
11776
|
+
function createVisitedMapFromRows(rows, side) {
|
|
11777
|
+
return new Map(
|
|
11778
|
+
rows.filter((row) => row.side === side).map((row) => {
|
|
11779
|
+
const parentKey = nodeIdentityKeyFromRow(
|
|
11780
|
+
row.predecessor_id,
|
|
11781
|
+
row.predecessor_kind
|
|
11782
|
+
);
|
|
11783
|
+
const node = {
|
|
11784
|
+
id: row.node_id,
|
|
11785
|
+
kind: row.node_kind,
|
|
11786
|
+
depth: Number(row.depth),
|
|
11787
|
+
parentKey
|
|
11788
|
+
};
|
|
11789
|
+
return [nodeIdentityKey(node), node];
|
|
11790
|
+
})
|
|
11791
|
+
);
|
|
11792
|
+
}
|
|
11793
|
+
function reverseDirection(direction) {
|
|
11794
|
+
switch (direction) {
|
|
11795
|
+
case "out": {
|
|
11796
|
+
return "in";
|
|
11797
|
+
}
|
|
11798
|
+
case "in": {
|
|
11799
|
+
return "out";
|
|
11800
|
+
}
|
|
11801
|
+
case "both": {
|
|
11802
|
+
return "both";
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
}
|
|
11806
|
+
|
|
11807
|
+
// src/store/algorithms/reachable.ts
|
|
11808
|
+
async function executeReachable(ctx, sourceId, options) {
|
|
11809
|
+
assertEdgeKinds(options.edges);
|
|
11810
|
+
const maxHops = resolveMaxHops(
|
|
11811
|
+
options.maxHops,
|
|
11812
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11813
|
+
"maxHops"
|
|
11814
|
+
);
|
|
11815
|
+
const reached = await findReachableNodes(ctx, sourceId, maxHops, options);
|
|
11816
|
+
return options.excludeSource === true ? reached.filter((node) => node.id !== sourceId) : reached;
|
|
11817
|
+
}
|
|
11818
|
+
async function executeCanReach(ctx, sourceId, targetId, options) {
|
|
11819
|
+
assertEdgeKinds(options.edges);
|
|
11820
|
+
const maxHops = resolveMaxHops(
|
|
11821
|
+
options.maxHops,
|
|
11822
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11823
|
+
"maxHops"
|
|
11824
|
+
);
|
|
11825
|
+
return await findShortestPath(ctx, sourceId, targetId, maxHops, options) !== void 0;
|
|
11826
|
+
}
|
|
11827
|
+
async function executeNeighbors(ctx, sourceId, options) {
|
|
11828
|
+
const depth = resolveMaxHops(options.depth, DEFAULT_NEIGHBOR_DEPTH, "depth");
|
|
10623
11829
|
return executeReachable(ctx, sourceId, {
|
|
10624
11830
|
edges: options.edges,
|
|
10625
11831
|
maxHops: depth,
|
|
@@ -10628,70 +11834,715 @@ async function executeNeighbors(ctx, sourceId, options) {
|
|
|
10628
11834
|
...options.temporalMode !== void 0 && {
|
|
10629
11835
|
temporalMode: options.temporalMode
|
|
10630
11836
|
},
|
|
10631
|
-
...options.asOf !== void 0 && { asOf: options.asOf },
|
|
10632
|
-
...options.recordedAsOf !== void 0 && {
|
|
10633
|
-
recordedAsOf: options.recordedAsOf
|
|
11837
|
+
...options.asOf !== void 0 && { asOf: options.asOf },
|
|
11838
|
+
...options.recordedAsOf !== void 0 && {
|
|
11839
|
+
recordedAsOf: options.recordedAsOf
|
|
11840
|
+
},
|
|
11841
|
+
...options.workingMemory !== void 0 && {
|
|
11842
|
+
workingMemory: options.workingMemory
|
|
11843
|
+
},
|
|
11844
|
+
excludeSource: true
|
|
11845
|
+
});
|
|
11846
|
+
}
|
|
11847
|
+
|
|
11848
|
+
// src/store/algorithms/shortest-path.ts
|
|
11849
|
+
async function executeShortestPath(ctx, sourceId, targetId, options) {
|
|
11850
|
+
assertEdgeKinds(options.edges);
|
|
11851
|
+
const maxHops = resolveMaxHops(
|
|
11852
|
+
options.maxHops,
|
|
11853
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11854
|
+
"maxHops"
|
|
11855
|
+
);
|
|
11856
|
+
return findShortestPath(ctx, sourceId, targetId, maxHops, options);
|
|
11857
|
+
}
|
|
11858
|
+
var DEFAULT_WCC_MAX_ITERATIONS = 1e3;
|
|
11859
|
+
async function executeWeaklyConnectedComponents(ctx, options) {
|
|
11860
|
+
assertEdgeKinds(options.edges);
|
|
11861
|
+
assertWeaklyConnectedComponentsSupported(ctx);
|
|
11862
|
+
const maxIterations = resolveMaxIterations(
|
|
11863
|
+
options.maxIterations,
|
|
11864
|
+
DEFAULT_WCC_MAX_ITERATIONS,
|
|
11865
|
+
"weaklyConnectedComponents"
|
|
11866
|
+
);
|
|
11867
|
+
const nodeKinds = options.nodeKinds === void 0 ? void 0 : [...new Set(options.nodeKinds)].toSorted(
|
|
11868
|
+
(left, right) => compareCodePoints(left, right)
|
|
11869
|
+
);
|
|
11870
|
+
const traversalOptions = {
|
|
11871
|
+
edges: options.edges,
|
|
11872
|
+
direction: "both",
|
|
11873
|
+
...options.temporalMode === void 0 ? {} : { temporalMode: options.temporalMode },
|
|
11874
|
+
...options.asOf === void 0 ? {} : { asOf: options.asOf },
|
|
11875
|
+
...options.recordedAsOf === void 0 ? {} : { recordedAsOf: options.recordedAsOf },
|
|
11876
|
+
...options.workingMemory === void 0 ? {} : { workingMemory: options.workingMemory }
|
|
11877
|
+
};
|
|
11878
|
+
return runIterativeGraphOperation(ctx, traversalOptions, {
|
|
11879
|
+
algorithm: "weaklyConnectedComponents",
|
|
11880
|
+
maxIterations,
|
|
11881
|
+
createWorkingTable: createWorkingTable2,
|
|
11882
|
+
initialize: (context) => initializeWorkingTable(context, nodeKinds),
|
|
11883
|
+
runRound: runLabelPropagationRound,
|
|
11884
|
+
hasConverged(state) {
|
|
11885
|
+
return state.changedCount === 0;
|
|
11886
|
+
},
|
|
11887
|
+
extractResult: extractMemberships
|
|
11888
|
+
});
|
|
11889
|
+
}
|
|
11890
|
+
function assertWeaklyConnectedComponentsSupported(ctx) {
|
|
11891
|
+
if (ctx.backend.capabilities.graphAnalytics?.supported === true && ctx.backend.capabilities.windowFunctions) {
|
|
11892
|
+
return;
|
|
11893
|
+
}
|
|
11894
|
+
throw new UnsupportedBackendCapabilityError(
|
|
11895
|
+
"weaklyConnectedComponents",
|
|
11896
|
+
"graphAnalytics",
|
|
11897
|
+
{
|
|
11898
|
+
dialect: ctx.backend.dialect,
|
|
11899
|
+
supported: ctx.backend.capabilities.graphAnalytics?.supported === true,
|
|
11900
|
+
windowFunctions: ctx.backend.capabilities.windowFunctions
|
|
11901
|
+
},
|
|
11902
|
+
"Use a built-in transactional SQLite/PostgreSQL backend, or declare graphAnalytics support on a compatible custom backend."
|
|
11903
|
+
);
|
|
11904
|
+
}
|
|
11905
|
+
function createWorkingTable2(context) {
|
|
11906
|
+
return sql`
|
|
11907
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
11908
|
+
graph_id TEXT NOT NULL,
|
|
11909
|
+
run_id TEXT NOT NULL,
|
|
11910
|
+
node_id TEXT NOT NULL,
|
|
11911
|
+
node_kind TEXT NOT NULL,
|
|
11912
|
+
label_id TEXT NOT NULL,
|
|
11913
|
+
label_kind TEXT NOT NULL,
|
|
11914
|
+
next_label_id TEXT NOT NULL,
|
|
11915
|
+
next_label_kind TEXT NOT NULL,
|
|
11916
|
+
PRIMARY KEY (graph_id, run_id, node_kind, node_id)
|
|
11917
|
+
)
|
|
11918
|
+
`;
|
|
11919
|
+
}
|
|
11920
|
+
async function initializeWorkingTable(context, nodeKinds) {
|
|
11921
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11922
|
+
const nodeKindFilter = nodeKinds === void 0 ? sql`TRUE` : compileKindFilter(sql.raw("n.kind"), nodeKinds);
|
|
11923
|
+
await context.executeTemporary(sql`
|
|
11924
|
+
INSERT INTO ${workingTable}
|
|
11925
|
+
(graph_id, run_id, node_id, node_kind, label_id, label_kind,
|
|
11926
|
+
next_label_id, next_label_kind)
|
|
11927
|
+
SELECT
|
|
11928
|
+
${graphId}, ${runId}, n.id, n.kind, n.id, n.kind, n.id, n.kind
|
|
11929
|
+
FROM ${operation.schema.nodesTable} n
|
|
11930
|
+
WHERE n.graph_id = ${graphId}
|
|
11931
|
+
AND ${nodeKindFilter}
|
|
11932
|
+
AND ${operation.nodeTemporalFilter}
|
|
11933
|
+
`);
|
|
11934
|
+
const rows = await context.backend.execute(
|
|
11935
|
+
asCompiledRowsSql(sql`
|
|
11936
|
+
SELECT COUNT(*) AS count
|
|
11937
|
+
FROM ${workingTable}
|
|
11938
|
+
WHERE graph_id = ${graphId} AND run_id = ${runId}
|
|
11939
|
+
`)
|
|
11940
|
+
);
|
|
11941
|
+
const workingTableSize = Number(rows[0]?.count ?? 0);
|
|
11942
|
+
return { changedCount: workingTableSize, workingTableSize };
|
|
11943
|
+
}
|
|
11944
|
+
async function runLabelPropagationRound(context, state) {
|
|
11945
|
+
await resetNextLabels(context);
|
|
11946
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11947
|
+
await propagateChunkLabels(context, edgeKinds);
|
|
11948
|
+
}
|
|
11949
|
+
const changedRows = await applyNextLabels(context);
|
|
11950
|
+
return {
|
|
11951
|
+
changedCount: changedRows.length,
|
|
11952
|
+
workingTableSize: state.workingTableSize
|
|
11953
|
+
};
|
|
11954
|
+
}
|
|
11955
|
+
async function resetNextLabels(context) {
|
|
11956
|
+
await context.executeTemporary(sql`
|
|
11957
|
+
UPDATE ${context.workingTable}
|
|
11958
|
+
SET next_label_id = label_id, next_label_kind = label_kind
|
|
11959
|
+
WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
|
|
11960
|
+
`);
|
|
11961
|
+
}
|
|
11962
|
+
async function propagateChunkLabels(context, edgeKinds) {
|
|
11963
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11964
|
+
const expansion = compileWorkingTableEdgeExpansion(
|
|
11965
|
+
operation,
|
|
11966
|
+
workingTable,
|
|
11967
|
+
sql`w.graph_id = ${graphId} AND w.run_id = ${runId}`,
|
|
11968
|
+
"both",
|
|
11969
|
+
edgeKinds
|
|
11970
|
+
);
|
|
11971
|
+
const candidateId = operation.ctx.dialect.binaryText(sql`ranked.label_id`);
|
|
11972
|
+
const candidateKind = operation.ctx.dialect.binaryText(
|
|
11973
|
+
sql`ranked.label_kind`
|
|
11974
|
+
);
|
|
11975
|
+
const currentId = operation.ctx.dialect.binaryText(sql`target.next_label_id`);
|
|
11976
|
+
const currentKind = operation.ctx.dialect.binaryText(
|
|
11977
|
+
sql`target.next_label_kind`
|
|
11978
|
+
);
|
|
11979
|
+
const sourceId = operation.ctx.dialect.binaryText(sql`source.label_id`);
|
|
11980
|
+
const sourceKind = operation.ctx.dialect.binaryText(sql`source.label_kind`);
|
|
11981
|
+
const targetId = operation.ctx.dialect.binaryText(sql`source.target_id`);
|
|
11982
|
+
const targetKind = operation.ctx.dialect.binaryText(sql`source.target_kind`);
|
|
11983
|
+
await context.executeTemporary(sql`
|
|
11984
|
+
WITH candidates AS (
|
|
11985
|
+
SELECT
|
|
11986
|
+
expanded.target_id,
|
|
11987
|
+
expanded.target_kind,
|
|
11988
|
+
source.label_id,
|
|
11989
|
+
source.label_kind
|
|
11990
|
+
FROM (${expansion}) expanded
|
|
11991
|
+
JOIN ${workingTable} source
|
|
11992
|
+
ON source.graph_id = ${graphId}
|
|
11993
|
+
AND source.run_id = ${runId}
|
|
11994
|
+
AND source.node_id = expanded.source_id
|
|
11995
|
+
AND source.node_kind = expanded.source_kind
|
|
11996
|
+
JOIN ${workingTable} scoped_target
|
|
11997
|
+
ON scoped_target.graph_id = ${graphId}
|
|
11998
|
+
AND scoped_target.run_id = ${runId}
|
|
11999
|
+
AND scoped_target.node_id = expanded.target_id
|
|
12000
|
+
AND scoped_target.node_kind = expanded.target_kind
|
|
12001
|
+
), ranked AS (
|
|
12002
|
+
SELECT
|
|
12003
|
+
target_id,
|
|
12004
|
+
target_kind,
|
|
12005
|
+
label_id,
|
|
12006
|
+
label_kind,
|
|
12007
|
+
ROW_NUMBER() OVER (
|
|
12008
|
+
PARTITION BY ${targetKind}, ${targetId}
|
|
12009
|
+
ORDER BY ${sourceId}, ${sourceKind}
|
|
12010
|
+
) AS candidate_rank
|
|
12011
|
+
FROM candidates source
|
|
12012
|
+
)
|
|
12013
|
+
UPDATE ${workingTable} AS target
|
|
12014
|
+
SET next_label_id = ranked.label_id,
|
|
12015
|
+
next_label_kind = ranked.label_kind
|
|
12016
|
+
FROM ranked
|
|
12017
|
+
WHERE target.graph_id = ${graphId}
|
|
12018
|
+
AND target.run_id = ${runId}
|
|
12019
|
+
AND target.node_id = ranked.target_id
|
|
12020
|
+
AND target.node_kind = ranked.target_kind
|
|
12021
|
+
AND ranked.candidate_rank = 1
|
|
12022
|
+
AND (
|
|
12023
|
+
${candidateId} < ${currentId}
|
|
12024
|
+
OR (
|
|
12025
|
+
${candidateId} = ${currentId}
|
|
12026
|
+
AND ${candidateKind} < ${currentKind}
|
|
12027
|
+
)
|
|
12028
|
+
)
|
|
12029
|
+
`);
|
|
12030
|
+
}
|
|
12031
|
+
function applyNextLabels(context) {
|
|
12032
|
+
return context.backend.execute(
|
|
12033
|
+
asCompiledRowsSql(sql`
|
|
12034
|
+
UPDATE ${context.workingTable}
|
|
12035
|
+
SET label_id = next_label_id, label_kind = next_label_kind
|
|
12036
|
+
WHERE graph_id = ${context.graphId}
|
|
12037
|
+
AND run_id = ${context.runId}
|
|
12038
|
+
AND (
|
|
12039
|
+
label_id <> next_label_id OR label_kind <> next_label_kind
|
|
12040
|
+
)
|
|
12041
|
+
RETURNING node_id
|
|
12042
|
+
`)
|
|
12043
|
+
);
|
|
12044
|
+
}
|
|
12045
|
+
async function extractMemberships(context) {
|
|
12046
|
+
const { operation } = context;
|
|
12047
|
+
const componentId = operation.ctx.dialect.binaryText(sql`label_id`);
|
|
12048
|
+
const componentKind = operation.ctx.dialect.binaryText(sql`label_kind`);
|
|
12049
|
+
const nodeId = operation.ctx.dialect.binaryText(sql`node_id`);
|
|
12050
|
+
const nodeKind = operation.ctx.dialect.binaryText(sql`node_kind`);
|
|
12051
|
+
const rows = await context.backend.execute(
|
|
12052
|
+
asCompiledRowsSql(sql`
|
|
12053
|
+
SELECT
|
|
12054
|
+
node_id,
|
|
12055
|
+
node_kind,
|
|
12056
|
+
label_id AS component_id,
|
|
12057
|
+
label_kind AS component_kind,
|
|
12058
|
+
COUNT(*) OVER (
|
|
12059
|
+
PARTITION BY ${componentKind}, ${componentId}
|
|
12060
|
+
) AS component_size
|
|
12061
|
+
FROM ${context.workingTable}
|
|
12062
|
+
WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
|
|
12063
|
+
ORDER BY ${componentId}, ${componentKind}, ${nodeId}, ${nodeKind}
|
|
12064
|
+
`)
|
|
12065
|
+
);
|
|
12066
|
+
return rows.map((row) => ({
|
|
12067
|
+
id: row.node_id,
|
|
12068
|
+
kind: row.node_kind,
|
|
12069
|
+
componentId: row.component_id,
|
|
12070
|
+
componentKind: row.component_kind,
|
|
12071
|
+
size: Number(row.component_size)
|
|
12072
|
+
})).toSorted(compareMemberships);
|
|
12073
|
+
}
|
|
12074
|
+
function compareMemberships(left, right) {
|
|
12075
|
+
return compareCodePoints(left.componentId, right.componentId) || compareCodePoints(left.componentKind, right.componentKind) || compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
|
|
12076
|
+
}
|
|
12077
|
+
var DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS = 1e3;
|
|
12078
|
+
var WEIGHTED_ALGORITHM_NAME = "weightedShortestPath";
|
|
12079
|
+
var MAX_EDGE_WEIGHT = Number.MAX_VALUE / 2 ** 64;
|
|
12080
|
+
var MIN_EDGE_WEIGHT = Number.MIN_VALUE;
|
|
12081
|
+
async function executeWeightedShortestPath(ctx, sourceId, targetId, options) {
|
|
12082
|
+
assertEdgeKinds(options.edges);
|
|
12083
|
+
assertWeightOptions(options.weightProperty, options.defaultWeight);
|
|
12084
|
+
const maxIterations = resolveMaxIterations(
|
|
12085
|
+
options.maxIterations,
|
|
12086
|
+
DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS,
|
|
12087
|
+
WEIGHTED_ALGORITHM_NAME
|
|
12088
|
+
);
|
|
12089
|
+
const traversalOptions = options;
|
|
12090
|
+
if (sourceId === targetId) {
|
|
12091
|
+
return findWeightedSelfPath(ctx, sourceId, traversalOptions);
|
|
12092
|
+
}
|
|
12093
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
12094
|
+
return findWeightedShortestPathInWorkingTable(
|
|
12095
|
+
ctx,
|
|
12096
|
+
sourceId,
|
|
12097
|
+
targetId,
|
|
12098
|
+
maxIterations,
|
|
12099
|
+
traversalOptions
|
|
12100
|
+
);
|
|
12101
|
+
}
|
|
12102
|
+
return findWeightedShortestPathInline(
|
|
12103
|
+
ctx,
|
|
12104
|
+
sourceId,
|
|
12105
|
+
targetId,
|
|
12106
|
+
maxIterations,
|
|
12107
|
+
traversalOptions
|
|
12108
|
+
);
|
|
12109
|
+
}
|
|
12110
|
+
function assertWeightOptions(weightProperty, defaultWeight) {
|
|
12111
|
+
if (typeof weightProperty !== "string" || weightProperty.length === 0) {
|
|
12112
|
+
throw new ConfigurationError(
|
|
12113
|
+
`${WEIGHTED_ALGORITHM_NAME} weightProperty must be a non-empty string.`,
|
|
12114
|
+
{ weightProperty }
|
|
12115
|
+
);
|
|
12116
|
+
}
|
|
12117
|
+
if (defaultWeight !== void 0 && (!Number.isFinite(defaultWeight) || defaultWeight < 0 || defaultWeight > MAX_EDGE_WEIGHT)) {
|
|
12118
|
+
throw new ConfigurationError(
|
|
12119
|
+
`${WEIGHTED_ALGORITHM_NAME} defaultWeight must be a non-negative number no greater than ${MAX_EDGE_WEIGHT}, got ${String(defaultWeight)}.`,
|
|
12120
|
+
{ defaultWeight, maximum: MAX_EDGE_WEIGHT }
|
|
12121
|
+
);
|
|
12122
|
+
}
|
|
12123
|
+
}
|
|
12124
|
+
async function assertValidEdgeWeights(operation, weightProperty, defaultWeight) {
|
|
12125
|
+
const { dialect } = operation.ctx;
|
|
12126
|
+
const pointer = jsonPointer([weightProperty]);
|
|
12127
|
+
const propsColumn = sql.raw("e.props");
|
|
12128
|
+
const isNumber = dialect.jsonPathIsNumber(propsColumn, pointer);
|
|
12129
|
+
const isMissing = dialect.jsonPathIsNull(propsColumn, pointer);
|
|
12130
|
+
const weightText = dialect.jsonExtractText(propsColumn, pointer);
|
|
12131
|
+
const weightNumber = sql`CASE WHEN ${isNumber} THEN ${dialect.jsonExtractNumber(propsColumn, pointer)} END`;
|
|
12132
|
+
const missingViolation = defaultWeight === void 0 ? sql`audited.is_missing = 1` : dialect.booleanLiteral(false);
|
|
12133
|
+
const negativeViolation = sql`audited.is_number = 1 AND audited.weight_number < 0`;
|
|
12134
|
+
const outOfRangeViolation = sql`audited.is_number = 1 AND (ABS(audited.weight_number) > CAST(${MAX_EDGE_WEIGHT} AS NUMERIC) OR (audited.weight_number <> 0 AND ABS(audited.weight_number) < CAST(${MIN_EDGE_WEIGHT} AS NUMERIC)))`;
|
|
12135
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
12136
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
|
|
12137
|
+
const rows = await operation.backend.execute(
|
|
12138
|
+
asCompiledRowsSql(sql`
|
|
12139
|
+
SELECT audited.edge_id, audited.edge_kind, audited.weight_text,
|
|
12140
|
+
audited.is_number, audited.is_missing
|
|
12141
|
+
FROM (
|
|
12142
|
+
SELECT e.id AS edge_id, e.kind AS edge_kind,
|
|
12143
|
+
${weightText} AS weight_text,
|
|
12144
|
+
${weightNumber} AS weight_number,
|
|
12145
|
+
CASE WHEN ${isNumber} THEN 1 ELSE 0 END AS is_number,
|
|
12146
|
+
CASE WHEN ${isMissing} THEN 1 ELSE 0 END AS is_missing
|
|
12147
|
+
FROM ${operation.schema.edgesTable} e
|
|
12148
|
+
WHERE e.graph_id = ${operation.ctx.graphId}
|
|
12149
|
+
AND ${edgeKindFilter}
|
|
12150
|
+
AND ${operation.edgeTemporalFilter}
|
|
12151
|
+
) audited
|
|
12152
|
+
WHERE (${missingViolation})
|
|
12153
|
+
OR (audited.is_missing = 0 AND audited.is_number = 0)
|
|
12154
|
+
OR (${negativeViolation})
|
|
12155
|
+
OR (${outOfRangeViolation})
|
|
12156
|
+
ORDER BY ${dialect.binaryText(sql`audited.edge_id`)}
|
|
12157
|
+
LIMIT 1
|
|
12158
|
+
`)
|
|
12159
|
+
);
|
|
12160
|
+
const row = rows[0];
|
|
12161
|
+
if (row !== void 0) {
|
|
12162
|
+
throw createInvalidEdgeWeightError(row, weightProperty);
|
|
12163
|
+
}
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
function createInvalidEdgeWeightError(row, weightProperty) {
|
|
12167
|
+
const reason = resolveInvalidWeightReason(row);
|
|
12168
|
+
const value = reason === "missing" || row.weight_text === null ? void 0 : formatWeightValue(row.weight_text);
|
|
12169
|
+
return new InvalidEdgeWeightError({
|
|
12170
|
+
edgeId: row.edge_id,
|
|
12171
|
+
edgeKind: row.edge_kind,
|
|
12172
|
+
property: weightProperty,
|
|
12173
|
+
reason,
|
|
12174
|
+
...value === void 0 ? {} : { value }
|
|
12175
|
+
});
|
|
12176
|
+
}
|
|
12177
|
+
function resolveInvalidWeightReason(row) {
|
|
12178
|
+
if (flagIsSet(row.is_missing)) return "missing";
|
|
12179
|
+
if (!flagIsSet(row.is_number)) return "non_numeric";
|
|
12180
|
+
return Number(row.weight_text) < 0 ? "negative" : "out_of_range";
|
|
12181
|
+
}
|
|
12182
|
+
function flagIsSet(value) {
|
|
12183
|
+
return Number(value) !== 0;
|
|
12184
|
+
}
|
|
12185
|
+
function formatWeightValue(value) {
|
|
12186
|
+
if (typeof value === "string") return value;
|
|
12187
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
12188
|
+
return String(value);
|
|
12189
|
+
}
|
|
12190
|
+
return JSON.stringify(value);
|
|
12191
|
+
}
|
|
12192
|
+
function compareNodeIdentityCodePoints(left, right) {
|
|
12193
|
+
return compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
|
|
12194
|
+
}
|
|
12195
|
+
async function findWeightedShortestPathInWorkingTable(ctx, sourceId, targetId, maxIterations, options) {
|
|
12196
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
12197
|
+
algorithm: WEIGHTED_ALGORITHM_NAME,
|
|
12198
|
+
maxIterations,
|
|
12199
|
+
createWorkingTable: createWorkingTable3,
|
|
12200
|
+
async initialize(context) {
|
|
12201
|
+
await assertValidEdgeWeights(
|
|
12202
|
+
context.operation,
|
|
12203
|
+
options.weightProperty,
|
|
12204
|
+
options.defaultWeight
|
|
12205
|
+
);
|
|
12206
|
+
if (!await hasVisibleNode(context.operation, targetId)) {
|
|
12207
|
+
return {
|
|
12208
|
+
frontierCount: 0,
|
|
12209
|
+
workingTableSize: 0,
|
|
12210
|
+
bestTargetDistance: void 0
|
|
12211
|
+
};
|
|
12212
|
+
}
|
|
12213
|
+
await context.executeTemporary(sql`
|
|
12214
|
+
CREATE INDEX ${frontierIndexIdentifier(context)}
|
|
12215
|
+
ON ${context.workingTable} (graph_id, run_id, improved_round)
|
|
12216
|
+
`);
|
|
12217
|
+
const seeded = await seedDistances(context, sourceId);
|
|
12218
|
+
return {
|
|
12219
|
+
frontierCount: seeded.length,
|
|
12220
|
+
workingTableSize: seeded.length,
|
|
12221
|
+
// Seeds carry the source id, never the target's — the self-path
|
|
12222
|
+
// case is short-circuited before this plan runs.
|
|
12223
|
+
bestTargetDistance: void 0
|
|
12224
|
+
};
|
|
12225
|
+
},
|
|
12226
|
+
async runRound(context, state, iteration) {
|
|
12227
|
+
const round = await relaxFrontierRound(
|
|
12228
|
+
context,
|
|
12229
|
+
targetId,
|
|
12230
|
+
iteration,
|
|
12231
|
+
state.bestTargetDistance
|
|
12232
|
+
);
|
|
12233
|
+
return {
|
|
12234
|
+
frontierCount: round.frontierCount,
|
|
12235
|
+
// Overcounts by re-improved existing rows (RETURNING includes
|
|
12236
|
+
// updates); acceptable because the size only paces the substrate's
|
|
12237
|
+
// ANALYZE refresh, where too-early merely refreshes stats sooner.
|
|
12238
|
+
workingTableSize: state.workingTableSize + round.frontierCount,
|
|
12239
|
+
bestTargetDistance: round.bestTargetDistance
|
|
12240
|
+
};
|
|
10634
12241
|
},
|
|
10635
|
-
|
|
12242
|
+
hasConverged(state) {
|
|
12243
|
+
return state.frontierCount === 0;
|
|
12244
|
+
},
|
|
12245
|
+
async extractResult(context) {
|
|
12246
|
+
return extractPathFromWorkingTable(context, targetId, maxIterations);
|
|
12247
|
+
}
|
|
10636
12248
|
});
|
|
10637
12249
|
}
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
options.maxHops,
|
|
10642
|
-
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
10643
|
-
"maxHops"
|
|
12250
|
+
function frontierIndexIdentifier(context) {
|
|
12251
|
+
return sql.identifier(
|
|
12252
|
+
`typegraph_iterative_${context.runId.replaceAll("-", "_")}_frontier`
|
|
10644
12253
|
);
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
12254
|
+
}
|
|
12255
|
+
async function hasVisibleNode(operation, nodeId) {
|
|
12256
|
+
const rows = await operation.backend.execute(
|
|
12257
|
+
asCompiledRowsSql(sql`
|
|
12258
|
+
SELECT n.id FROM ${operation.schema.nodesTable} n
|
|
12259
|
+
WHERE n.graph_id = ${operation.ctx.graphId}
|
|
12260
|
+
AND n.id = ${nodeId}
|
|
12261
|
+
AND ${operation.nodeTemporalFilter}
|
|
12262
|
+
LIMIT 1
|
|
12263
|
+
`)
|
|
12264
|
+
);
|
|
12265
|
+
return rows.length > 0;
|
|
12266
|
+
}
|
|
12267
|
+
function createWorkingTable3(context) {
|
|
12268
|
+
return sql`
|
|
12269
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
12270
|
+
graph_id TEXT NOT NULL,
|
|
12271
|
+
run_id TEXT NOT NULL,
|
|
12272
|
+
node_id TEXT NOT NULL,
|
|
12273
|
+
node_kind TEXT NOT NULL,
|
|
12274
|
+
distance DOUBLE PRECISION NOT NULL,
|
|
12275
|
+
hops INTEGER NOT NULL,
|
|
12276
|
+
predecessor_id TEXT,
|
|
12277
|
+
predecessor_kind TEXT,
|
|
12278
|
+
improved_round INTEGER NOT NULL,
|
|
12279
|
+
PRIMARY KEY (graph_id, run_id, node_kind, node_id)
|
|
12280
|
+
)
|
|
12281
|
+
`;
|
|
12282
|
+
}
|
|
12283
|
+
async function seedDistances(context, sourceId) {
|
|
12284
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12285
|
+
return context.backend.execute(
|
|
12286
|
+
asCompiledRowsSql(sql`
|
|
12287
|
+
INSERT INTO ${workingTable}
|
|
12288
|
+
(graph_id, run_id, node_id, node_kind, distance, hops,
|
|
12289
|
+
predecessor_id, predecessor_kind, improved_round)
|
|
12290
|
+
SELECT ${graphId}, ${runId}, n.id, n.kind, 0, 0, NULL, NULL, 0
|
|
12291
|
+
FROM ${operation.schema.nodesTable} n
|
|
12292
|
+
WHERE n.graph_id = ${graphId}
|
|
12293
|
+
AND n.id = ${sourceId}
|
|
12294
|
+
AND ${operation.nodeTemporalFilter}
|
|
12295
|
+
ON CONFLICT (graph_id, run_id, node_kind, node_id) DO NOTHING
|
|
12296
|
+
RETURNING node_id
|
|
12297
|
+
`)
|
|
12298
|
+
);
|
|
12299
|
+
}
|
|
12300
|
+
async function relaxFrontierRound(context, targetId, iteration, roundBound) {
|
|
12301
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12302
|
+
const { dialect } = operation.ctx;
|
|
12303
|
+
let frontierCount = 0;
|
|
12304
|
+
let bestTargetDistance = roundBound;
|
|
12305
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
12306
|
+
const sourceFilter = sql`
|
|
12307
|
+
w.graph_id = ${graphId}
|
|
12308
|
+
AND w.run_id = ${runId}
|
|
12309
|
+
AND w.improved_round = ${iteration - 1}
|
|
12310
|
+
`;
|
|
12311
|
+
const expansion = compileWorkingTableExpansion(
|
|
12312
|
+
operation,
|
|
12313
|
+
workingTable,
|
|
12314
|
+
sourceFilter,
|
|
12315
|
+
operation.direction,
|
|
12316
|
+
edgeKinds
|
|
12317
|
+
);
|
|
12318
|
+
const rows = await context.backend.execute(
|
|
12319
|
+
asCompiledRowsSql(sql`
|
|
12320
|
+
INSERT INTO ${workingTable}
|
|
12321
|
+
(graph_id, run_id, node_id, node_kind, distance, hops,
|
|
12322
|
+
predecessor_id, predecessor_kind, improved_round)
|
|
12323
|
+
SELECT
|
|
12324
|
+
${graphId}, ${runId}, ranked.target_id, ranked.target_kind,
|
|
12325
|
+
ranked.candidate_distance, ranked.candidate_hops,
|
|
12326
|
+
ranked.source_id, ranked.source_kind, ${iteration}
|
|
12327
|
+
FROM (
|
|
12328
|
+
SELECT candidates.*,
|
|
12329
|
+
ROW_NUMBER() OVER (
|
|
12330
|
+
PARTITION BY ${dialect.binaryText(sql`candidates.target_kind`)},
|
|
12331
|
+
${dialect.binaryText(sql`candidates.target_id`)}
|
|
12332
|
+
ORDER BY candidates.candidate_distance,
|
|
12333
|
+
${dialect.binaryText(sql`candidates.source_id`)},
|
|
12334
|
+
${dialect.binaryText(sql`candidates.source_kind`)}
|
|
12335
|
+
) AS candidate_rank
|
|
12336
|
+
FROM (
|
|
12337
|
+
SELECT expanded.source_id, expanded.source_kind,
|
|
12338
|
+
expanded.target_id, expanded.target_kind,
|
|
12339
|
+
frontier.distance + expanded.weight AS candidate_distance,
|
|
12340
|
+
frontier.hops + 1 AS candidate_hops
|
|
12341
|
+
FROM (${expansion}) expanded
|
|
12342
|
+
JOIN ${workingTable} frontier
|
|
12343
|
+
ON frontier.graph_id = ${graphId}
|
|
12344
|
+
AND frontier.run_id = ${runId}
|
|
12345
|
+
AND frontier.node_id = expanded.source_id
|
|
12346
|
+
AND frontier.node_kind = expanded.source_kind
|
|
12347
|
+
) candidates
|
|
12348
|
+
WHERE ${roundBound === void 0 ? sql`TRUE` : sql`candidates.candidate_distance <= ${roundBound}`}
|
|
12349
|
+
) ranked
|
|
12350
|
+
WHERE ranked.candidate_rank = 1
|
|
12351
|
+
ON CONFLICT (graph_id, run_id, node_kind, node_id) DO UPDATE SET
|
|
12352
|
+
distance = excluded.distance,
|
|
12353
|
+
hops = excluded.hops,
|
|
12354
|
+
predecessor_id = excluded.predecessor_id,
|
|
12355
|
+
predecessor_kind = excluded.predecessor_kind,
|
|
12356
|
+
improved_round = excluded.improved_round
|
|
12357
|
+
WHERE excluded.distance < ${workingTable}.distance
|
|
12358
|
+
RETURNING node_id, distance
|
|
12359
|
+
`)
|
|
12360
|
+
);
|
|
12361
|
+
frontierCount += rows.length;
|
|
12362
|
+
for (const row of rows) {
|
|
12363
|
+
if (row.node_id !== targetId) continue;
|
|
12364
|
+
const distance = Number(row.distance);
|
|
12365
|
+
if (bestTargetDistance === void 0 || distance < bestTargetDistance) {
|
|
12366
|
+
bestTargetDistance = distance;
|
|
12367
|
+
}
|
|
12368
|
+
}
|
|
10649
12369
|
}
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
12370
|
+
return { frontierCount, bestTargetDistance };
|
|
12371
|
+
}
|
|
12372
|
+
async function extractPathFromWorkingTable(context, targetId, maxIterations) {
|
|
12373
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12374
|
+
const { dialect } = operation.ctx;
|
|
12375
|
+
const rows = await context.backend.execute(
|
|
12376
|
+
asCompiledRowsSql(sql`
|
|
12377
|
+
WITH RECURSIVE chain AS (
|
|
12378
|
+
SELECT best.node_id, best.node_kind, best.distance, best.hops,
|
|
12379
|
+
best.predecessor_id, best.predecessor_kind, 0 AS position
|
|
12380
|
+
FROM (
|
|
12381
|
+
SELECT node_id, node_kind, distance, hops,
|
|
12382
|
+
predecessor_id, predecessor_kind
|
|
12383
|
+
FROM ${workingTable}
|
|
12384
|
+
WHERE graph_id = ${graphId}
|
|
12385
|
+
AND run_id = ${runId}
|
|
12386
|
+
AND node_id = ${targetId}
|
|
12387
|
+
ORDER BY distance ASC, ${dialect.binaryText(sql`node_kind`)}
|
|
12388
|
+
LIMIT 1
|
|
12389
|
+
) best
|
|
12390
|
+
UNION ALL
|
|
12391
|
+
SELECT w.node_id, w.node_kind, w.distance, w.hops,
|
|
12392
|
+
w.predecessor_id, w.predecessor_kind, c.position + 1
|
|
12393
|
+
FROM chain c
|
|
12394
|
+
JOIN ${workingTable} w
|
|
12395
|
+
ON w.graph_id = ${graphId}
|
|
12396
|
+
AND w.run_id = ${runId}
|
|
12397
|
+
AND w.node_id = c.predecessor_id
|
|
12398
|
+
AND w.node_kind = c.predecessor_kind
|
|
12399
|
+
WHERE c.position <= ${maxIterations}
|
|
12400
|
+
)
|
|
12401
|
+
SELECT node_id, node_kind, distance, hops
|
|
12402
|
+
FROM chain
|
|
12403
|
+
ORDER BY position DESC
|
|
12404
|
+
`)
|
|
10667
12405
|
);
|
|
10668
|
-
const
|
|
10669
|
-
if (
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
}));
|
|
10676
|
-
return { nodes: pathNodes, depth: Number(first.hit_depth) };
|
|
12406
|
+
const target = rows.at(-1);
|
|
12407
|
+
if (target === void 0) return void 0;
|
|
12408
|
+
return {
|
|
12409
|
+
nodes: rows.map((row) => ({ id: row.node_id, kind: row.node_kind })),
|
|
12410
|
+
depth: Number(target.hops),
|
|
12411
|
+
totalWeight: Number(target.distance)
|
|
12412
|
+
};
|
|
10677
12413
|
}
|
|
10678
|
-
function
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
12414
|
+
async function findWeightedSelfPath(ctx, nodeId, options) {
|
|
12415
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
12416
|
+
await assertValidEdgeWeights(
|
|
12417
|
+
operation,
|
|
12418
|
+
options.weightProperty,
|
|
12419
|
+
options.defaultWeight
|
|
12420
|
+
);
|
|
12421
|
+
const nodes = await fetchVisibleWorkingNodes(operation, [nodeId]);
|
|
12422
|
+
const node = nodes.toSorted(
|
|
12423
|
+
(left, right) => compareNodeIdentityCodePoints(left, right)
|
|
12424
|
+
)[0];
|
|
12425
|
+
if (node === void 0) return;
|
|
12426
|
+
return { nodes: [node], depth: 0, totalWeight: 0 };
|
|
12427
|
+
});
|
|
12428
|
+
}
|
|
12429
|
+
async function findWeightedShortestPathInline(ctx, sourceId, targetId, maxIterations, options) {
|
|
12430
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
12431
|
+
await assertValidEdgeWeights(
|
|
12432
|
+
operation,
|
|
12433
|
+
options.weightProperty,
|
|
12434
|
+
options.defaultWeight
|
|
12435
|
+
);
|
|
12436
|
+
const endpoints = await fetchVisibleWorkingNodes(operation, [
|
|
12437
|
+
sourceId,
|
|
12438
|
+
targetId
|
|
12439
|
+
]);
|
|
12440
|
+
const sources = endpoints.filter((node) => node.id === sourceId);
|
|
12441
|
+
if (sources.length === 0 || !endpoints.some((node) => node.id === targetId)) {
|
|
12442
|
+
return;
|
|
10683
12443
|
}
|
|
12444
|
+
const settled = new Map(
|
|
12445
|
+
sources.map((source) => [
|
|
12446
|
+
nodeIdentityKey(source),
|
|
12447
|
+
{
|
|
12448
|
+
id: source.id,
|
|
12449
|
+
kind: source.kind,
|
|
12450
|
+
distance: 0,
|
|
12451
|
+
hops: 0,
|
|
12452
|
+
parentKey: void 0
|
|
12453
|
+
}
|
|
12454
|
+
])
|
|
12455
|
+
);
|
|
12456
|
+
let bestTargetDistance;
|
|
12457
|
+
let frontier = sources;
|
|
12458
|
+
let rounds = 0;
|
|
12459
|
+
while (frontier.length > 0) {
|
|
12460
|
+
if (rounds >= maxIterations) {
|
|
12461
|
+
throw new GraphAlgorithmConvergenceError(
|
|
12462
|
+
WEIGHTED_ALGORITHM_NAME,
|
|
12463
|
+
maxIterations
|
|
12464
|
+
);
|
|
12465
|
+
}
|
|
12466
|
+
rounds++;
|
|
12467
|
+
const candidates = await reduceExpandedWorkingSet(
|
|
12468
|
+
operation,
|
|
12469
|
+
frontier,
|
|
12470
|
+
operation.direction,
|
|
12471
|
+
(existing, expansion) => reduceWeightedCandidate(existing, expansion, settled)
|
|
12472
|
+
);
|
|
12473
|
+
const roundBound = bestTargetDistance;
|
|
12474
|
+
const improved = [];
|
|
12475
|
+
for (const [candidateKey, candidate] of candidates) {
|
|
12476
|
+
const existing = settled.get(candidateKey);
|
|
12477
|
+
if (existing !== void 0 && existing.distance <= candidate.distance) {
|
|
12478
|
+
continue;
|
|
12479
|
+
}
|
|
12480
|
+
if (roundBound !== void 0 && candidate.distance > roundBound) {
|
|
12481
|
+
continue;
|
|
12482
|
+
}
|
|
12483
|
+
settled.set(candidateKey, {
|
|
12484
|
+
id: candidate.id,
|
|
12485
|
+
kind: candidate.kind,
|
|
12486
|
+
distance: candidate.distance,
|
|
12487
|
+
hops: candidate.hops,
|
|
12488
|
+
parentKey: nodeIdentityKey({
|
|
12489
|
+
id: candidate.parentId,
|
|
12490
|
+
kind: candidate.parentKind
|
|
12491
|
+
})
|
|
12492
|
+
});
|
|
12493
|
+
improved.push({ id: candidate.id, kind: candidate.kind });
|
|
12494
|
+
if (candidate.id === targetId && (bestTargetDistance === void 0 || candidate.distance < bestTargetDistance)) {
|
|
12495
|
+
bestTargetDistance = candidate.distance;
|
|
12496
|
+
}
|
|
12497
|
+
}
|
|
12498
|
+
frontier = improved.toSorted(
|
|
12499
|
+
(left, right) => compareNodeIdentityCodePoints(left, right)
|
|
12500
|
+
);
|
|
12501
|
+
}
|
|
12502
|
+
return buildWeightedPathResult(settled, targetId);
|
|
12503
|
+
});
|
|
12504
|
+
}
|
|
12505
|
+
function reduceWeightedCandidate(existing, expansion, settled) {
|
|
12506
|
+
const weight = expansion.weight;
|
|
12507
|
+
if (weight === void 0) return existing;
|
|
12508
|
+
const sourceEntry = settled.get(nodeIdentityKey(expansion.source));
|
|
12509
|
+
if (sourceEntry === void 0) return existing;
|
|
12510
|
+
const candidateDistance = sourceEntry.distance + weight;
|
|
12511
|
+
if (existing !== void 0 && (existing.distance < candidateDistance || existing.distance === candidateDistance && compareNodeIdentityCodePoints(
|
|
12512
|
+
{ id: existing.parentId, kind: existing.parentKind },
|
|
12513
|
+
expansion.source
|
|
12514
|
+
) <= 0)) {
|
|
12515
|
+
return existing;
|
|
10684
12516
|
}
|
|
10685
|
-
return
|
|
12517
|
+
return {
|
|
12518
|
+
id: expansion.target.id,
|
|
12519
|
+
kind: expansion.target.kind,
|
|
12520
|
+
distance: candidateDistance,
|
|
12521
|
+
hops: sourceEntry.hops + 1,
|
|
12522
|
+
parentId: expansion.source.id,
|
|
12523
|
+
parentKind: expansion.source.kind
|
|
12524
|
+
};
|
|
10686
12525
|
}
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
const
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
12526
|
+
function buildWeightedPathResult(visited, targetId) {
|
|
12527
|
+
let target;
|
|
12528
|
+
for (const node of visited.values()) {
|
|
12529
|
+
if (node.id !== targetId) continue;
|
|
12530
|
+
if (target === void 0 || node.distance < target.distance || node.distance === target.distance && compareNodeIdentityCodePoints(node, target) < 0) {
|
|
12531
|
+
target = node;
|
|
12532
|
+
}
|
|
12533
|
+
}
|
|
12534
|
+
if (target === void 0) return void 0;
|
|
12535
|
+
const reversedNodes = [];
|
|
12536
|
+
let cursor = target;
|
|
12537
|
+
while (cursor !== void 0) {
|
|
12538
|
+
reversedNodes.push({ id: cursor.id, kind: cursor.kind });
|
|
12539
|
+
cursor = cursor.parentKey === void 0 ? void 0 : visited.get(cursor.parentKey);
|
|
12540
|
+
}
|
|
12541
|
+
return {
|
|
12542
|
+
nodes: reversedNodes.toReversed(),
|
|
12543
|
+
depth: target.hops,
|
|
12544
|
+
totalWeight: target.distance
|
|
12545
|
+
};
|
|
10695
12546
|
}
|
|
10696
12547
|
|
|
10697
12548
|
// src/store/algorithms/index.ts
|
|
@@ -10704,7 +12555,7 @@ function assertRecordedAsOfInternalOnly(options, method, allowRecordedAsOf) {
|
|
|
10704
12555
|
code: "ALGORITHM_RECORDED_ASOF_INTERNAL_ONLY",
|
|
10705
12556
|
message: `recordedAsOf is only available through store.asOfRecorded(...).${method}(...).`,
|
|
10706
12557
|
context: { method },
|
|
10707
|
-
suggestion: "Use store.asOfRecorded(recordedAt)
|
|
12558
|
+
suggestion: "Use store.asOfRecorded(recordedAt) or a recorded StoreView instead of passing recordedAsOf directly."
|
|
10708
12559
|
});
|
|
10709
12560
|
}
|
|
10710
12561
|
function createGraphAlgorithms(params) {
|
|
@@ -10733,6 +12584,19 @@ function createGraphAlgorithms(params) {
|
|
|
10733
12584
|
options
|
|
10734
12585
|
);
|
|
10735
12586
|
},
|
|
12587
|
+
weightedShortestPath(from, to, options) {
|
|
12588
|
+
assertRecordedAsOfInternalOnly(
|
|
12589
|
+
options,
|
|
12590
|
+
"weightedShortestPath",
|
|
12591
|
+
allowRecordedAsOf
|
|
12592
|
+
);
|
|
12593
|
+
return executeWeightedShortestPath(
|
|
12594
|
+
ctx,
|
|
12595
|
+
resolveNodeId(from),
|
|
12596
|
+
resolveNodeId(to),
|
|
12597
|
+
options
|
|
12598
|
+
);
|
|
12599
|
+
},
|
|
10736
12600
|
reachable(from, options) {
|
|
10737
12601
|
assertRecordedAsOfInternalOnly(options, "reachable", allowRecordedAsOf);
|
|
10738
12602
|
return executeReachable(ctx, resolveNodeId(from), options);
|
|
@@ -10753,6 +12617,14 @@ function createGraphAlgorithms(params) {
|
|
|
10753
12617
|
degree(node, options) {
|
|
10754
12618
|
assertRecordedAsOfInternalOnly(options, "degree", allowRecordedAsOf);
|
|
10755
12619
|
return executeDegree(ctx, resolveNodeId(node), options ?? {});
|
|
12620
|
+
},
|
|
12621
|
+
weaklyConnectedComponents(options) {
|
|
12622
|
+
assertRecordedAsOfInternalOnly(
|
|
12623
|
+
options,
|
|
12624
|
+
"weaklyConnectedComponents",
|
|
12625
|
+
allowRecordedAsOf
|
|
12626
|
+
);
|
|
12627
|
+
return executeWeaklyConnectedComponents(ctx, options);
|
|
10756
12628
|
}
|
|
10757
12629
|
};
|
|
10758
12630
|
}
|
|
@@ -11614,6 +13486,33 @@ async function ensureFocusedStatusTable(backend, ensureFocused) {
|
|
|
11614
13486
|
}
|
|
11615
13487
|
await backend.bootstrapTables?.();
|
|
11616
13488
|
}
|
|
13489
|
+
async function runBucketedMaterialization(items, options, bucketKey, runOne) {
|
|
13490
|
+
if (options.stopOnError === true) {
|
|
13491
|
+
const results2 = [];
|
|
13492
|
+
for (const item of items) {
|
|
13493
|
+
const entry2 = await runOne(item);
|
|
13494
|
+
results2.push(entry2);
|
|
13495
|
+
if (entry2.status === "failed") break;
|
|
13496
|
+
}
|
|
13497
|
+
return results2;
|
|
13498
|
+
}
|
|
13499
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
13500
|
+
for (const [index, item] of items.entries()) {
|
|
13501
|
+
const key = bucketKey(item);
|
|
13502
|
+
const bucket = buckets.get(key);
|
|
13503
|
+
if (bucket === void 0) buckets.set(key, [[index, item]]);
|
|
13504
|
+
else bucket.push([index, item]);
|
|
13505
|
+
}
|
|
13506
|
+
const results = Array.from({ length: items.length });
|
|
13507
|
+
await Promise.all(
|
|
13508
|
+
[...buckets.values()].map(async (group) => {
|
|
13509
|
+
for (const [index, item] of group) {
|
|
13510
|
+
results[index] = await runOne(item);
|
|
13511
|
+
}
|
|
13512
|
+
})
|
|
13513
|
+
);
|
|
13514
|
+
return results;
|
|
13515
|
+
}
|
|
11617
13516
|
async function runMaterialization(items, options, runOne) {
|
|
11618
13517
|
if (options.stopOnError === true) {
|
|
11619
13518
|
const results = [];
|
|
@@ -11641,18 +13540,10 @@ function delay(ms) {
|
|
|
11641
13540
|
}
|
|
11642
13541
|
async function materializeIndexes(context, options = {}) {
|
|
11643
13542
|
const { graph, graphId, backend, schemaVersion } = context;
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
);
|
|
11649
|
-
}
|
|
11650
|
-
if (backend.getIndexMaterialization === void 0 || backend.recordIndexMaterialization === void 0) {
|
|
11651
|
-
throw new ConfigurationError(
|
|
11652
|
-
"store.materializeIndexes() requires a backend with index materialization status primitives. The bundled SQLite and Postgres backends provide them; a custom backend must implement `getIndexMaterialization` and `recordIndexMaterialization`.",
|
|
11653
|
-
{ code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
|
|
11654
|
-
);
|
|
11655
|
-
}
|
|
13543
|
+
assertBackendSupportsIndexMaterialization(
|
|
13544
|
+
backend,
|
|
13545
|
+
"store.materializeIndexes()"
|
|
13546
|
+
);
|
|
11656
13547
|
const declared = graph.indexes ?? [];
|
|
11657
13548
|
const kindFilter = options.kinds === void 0 ? void 0 : new Set(options.kinds);
|
|
11658
13549
|
if (kindFilter !== void 0) {
|
|
@@ -11699,65 +13590,42 @@ async function materializeIndexes(context, options = {}) {
|
|
|
11699
13590
|
backend,
|
|
11700
13591
|
relationalPhysicalNames
|
|
11701
13592
|
);
|
|
11702
|
-
const
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
graphId,
|
|
11715
|
-
schemaVersion,
|
|
11716
|
-
existingByStatusKey,
|
|
11717
|
-
invalidLeftovers
|
|
11718
|
-
);
|
|
11719
|
-
if (options.stopOnError === true) {
|
|
11720
|
-
const results2 = [];
|
|
11721
|
-
for (const declaration of candidates) {
|
|
11722
|
-
const entry2 = await materializeEntry(declaration);
|
|
11723
|
-
results2.push(entry2);
|
|
11724
|
-
if (entry2.status === "failed") break;
|
|
13593
|
+
const reservedSystemNames = resolveSystemIndexNames(backend.tableNames);
|
|
13594
|
+
const materializeEntry = (declaration) => {
|
|
13595
|
+
if (declaration.entity !== "vector" && reservedSystemNames.has(declaration.name)) {
|
|
13596
|
+
return Promise.resolve(
|
|
13597
|
+
entry(
|
|
13598
|
+
declaration,
|
|
13599
|
+
"failed",
|
|
13600
|
+
new Error(
|
|
13601
|
+
`Index name "${declaration.name}" collides with a TypeGraph system index. Rename the declaration \u2014 a same-named CREATE INDEX IF NOT EXISTS would silently no-op against the system index instead of creating the declared one.`
|
|
13602
|
+
)
|
|
13603
|
+
)
|
|
13604
|
+
);
|
|
11725
13605
|
}
|
|
11726
|
-
|
|
13606
|
+
return declaration.entity === "vector" ? materializeVectorIndex(
|
|
13607
|
+
declaration,
|
|
11727
13608
|
backend,
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
13609
|
+
graphId,
|
|
13610
|
+
schemaVersion,
|
|
13611
|
+
existingByStatusKey,
|
|
13612
|
+
invalidLeftovers
|
|
13613
|
+
) : materializeRelationalIndex(
|
|
13614
|
+
declaration,
|
|
13615
|
+
backend,
|
|
13616
|
+
dialect,
|
|
13617
|
+
ddlOptions,
|
|
13618
|
+
graphId,
|
|
13619
|
+
schemaVersion,
|
|
13620
|
+
existingByStatusKey,
|
|
13621
|
+
invalidLeftovers
|
|
11731
13622
|
);
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
if (list === void 0) buckets.set(key, [declaration]);
|
|
11739
|
-
else list.push(declaration);
|
|
11740
|
-
}
|
|
11741
|
-
const bucketResults = await Promise.all(
|
|
11742
|
-
[...buckets.values()].map(async (group) => {
|
|
11743
|
-
const out = [];
|
|
11744
|
-
for (const declaration of group) {
|
|
11745
|
-
out.push(await materializeEntry(declaration));
|
|
11746
|
-
}
|
|
11747
|
-
return out;
|
|
11748
|
-
})
|
|
11749
|
-
);
|
|
11750
|
-
const byDeclaration = /* @__PURE__ */ new Map();
|
|
11751
|
-
let bucketIndex = 0;
|
|
11752
|
-
for (const group of buckets.values()) {
|
|
11753
|
-
const entries = bucketResults[bucketIndex];
|
|
11754
|
-
for (const [declarationIndex, declaration] of group.entries()) {
|
|
11755
|
-
byDeclaration.set(declaration, entries[declarationIndex]);
|
|
11756
|
-
}
|
|
11757
|
-
bucketIndex += 1;
|
|
11758
|
-
}
|
|
11759
|
-
const results = candidates.map(
|
|
11760
|
-
(declaration) => byDeclaration.get(declaration)
|
|
13623
|
+
};
|
|
13624
|
+
const results = await runBucketedMaterialization(
|
|
13625
|
+
candidates,
|
|
13626
|
+
options,
|
|
13627
|
+
(declaration) => parallelBucketKey(declaration),
|
|
13628
|
+
(declaration) => materializeEntry(declaration)
|
|
11761
13629
|
);
|
|
11762
13630
|
await refreshStatisticsAfterCreation(
|
|
11763
13631
|
backend,
|
|
@@ -11814,7 +13682,7 @@ async function materializeRelationalIndex(declaration, backend, dialect, ddlOpti
|
|
|
11814
13682
|
await backend.executeDdl(ddl);
|
|
11815
13683
|
},
|
|
11816
13684
|
existingByStatusKey,
|
|
11817
|
-
invalidLeftovers
|
|
13685
|
+
physicalRebuildPreload: invalidLeftovers
|
|
11818
13686
|
});
|
|
11819
13687
|
}
|
|
11820
13688
|
async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey, invalidLeftovers) {
|
|
@@ -11872,7 +13740,7 @@ async function materializeVectorIndex(declaration, backend, graphId, schemaVersi
|
|
|
11872
13740
|
driftLabel: "Vector index",
|
|
11873
13741
|
run: () => backend.createVectorIndex(params),
|
|
11874
13742
|
existingByStatusKey,
|
|
11875
|
-
invalidLeftovers
|
|
13743
|
+
physicalRebuildPreload: invalidLeftovers
|
|
11876
13744
|
});
|
|
11877
13745
|
}
|
|
11878
13746
|
async function materializeOne(declaration, backend, graphId, schemaVersion, action) {
|
|
@@ -11889,7 +13757,8 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11889
13757
|
statusKey,
|
|
11890
13758
|
signature,
|
|
11891
13759
|
driftLabel,
|
|
11892
|
-
|
|
13760
|
+
needsPhysicalRebuild: (physicalIndexName) => action.physicalRebuildPreload.has(physicalIndexName),
|
|
13761
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11893
13762
|
}
|
|
11894
13763
|
);
|
|
11895
13764
|
if (settled !== void 0) return settled;
|
|
@@ -11898,7 +13767,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11898
13767
|
statusKey,
|
|
11899
13768
|
signature,
|
|
11900
13769
|
driftLabel,
|
|
11901
|
-
run
|
|
13770
|
+
run,
|
|
13771
|
+
...action.freshNeedsPhysicalRebuild === void 0 ? {} : { freshNeedsPhysicalRebuild: action.freshNeedsPhysicalRebuild },
|
|
13772
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11902
13773
|
});
|
|
11903
13774
|
}
|
|
11904
13775
|
try {
|
|
@@ -11935,9 +13806,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11935
13806
|
}
|
|
11936
13807
|
async function settleAgainstExisting(existing, declaration, backend, graphId, schemaVersion, action) {
|
|
11937
13808
|
if (existing?.materializedAt === void 0) return void 0;
|
|
11938
|
-
const { statusKey, signature, driftLabel,
|
|
13809
|
+
const { statusKey, signature, driftLabel, needsPhysicalRebuild } = action;
|
|
11939
13810
|
if (existing.signature === signature) {
|
|
11940
|
-
if (declaration.entity !== "vector" && hasIndexBuildClaimProtocol(backend) && await
|
|
13811
|
+
if (declaration.entity !== "vector" && (hasIndexBuildClaimProtocol(backend) || action.rebuildWithoutClaim === true) && await needsPhysicalRebuild(declaration.name)) {
|
|
11941
13812
|
return void 0;
|
|
11942
13813
|
}
|
|
11943
13814
|
return entry(declaration, "alreadyMaterialized");
|
|
@@ -12002,7 +13873,8 @@ async function materializeWithClaim(declaration, backend, graphId, schemaVersion
|
|
|
12002
13873
|
driftLabel,
|
|
12003
13874
|
// Post-claim the bulk preload is stale (we may have waited on
|
|
12004
13875
|
// another builder's claim), so re-check this index fresh.
|
|
12005
|
-
|
|
13876
|
+
needsPhysicalRebuild: action.freshNeedsPhysicalRebuild ?? ((physicalIndexName) => hasInvalidIndexLeftover(backend, physicalIndexName)),
|
|
13877
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
12006
13878
|
}
|
|
12007
13879
|
);
|
|
12008
13880
|
if (settled !== void 0) return settled;
|
|
@@ -12063,6 +13935,7 @@ async function hasInvalidIndexLeftover(backend, physicalIndexName) {
|
|
|
12063
13935
|
FROM pg_class c
|
|
12064
13936
|
JOIN pg_index i ON i.indexrelid = c.oid
|
|
12065
13937
|
WHERE c.relname = ${physicalIndexName}
|
|
13938
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
12066
13939
|
`)
|
|
12067
13940
|
);
|
|
12068
13941
|
return rows[0]?.invalid === true;
|
|
@@ -12071,17 +13944,64 @@ async function preloadInvalidIndexLeftovers(backend, physicalIndexNames) {
|
|
|
12071
13944
|
if (backend.dialect !== "postgres" || physicalIndexNames.length === 0) {
|
|
12072
13945
|
return /* @__PURE__ */ new Set();
|
|
12073
13946
|
}
|
|
13947
|
+
const { invalid } = await preloadPhysicalIndexStates(
|
|
13948
|
+
backend,
|
|
13949
|
+
physicalIndexNames
|
|
13950
|
+
);
|
|
13951
|
+
return invalid;
|
|
13952
|
+
}
|
|
13953
|
+
async function preloadExistingTables(backend, tableNames) {
|
|
13954
|
+
if (tableNames.length === 0) return /* @__PURE__ */ new Set();
|
|
13955
|
+
if (backend.dialect === "postgres") {
|
|
13956
|
+
const rows2 = await backend.execute(
|
|
13957
|
+
asCompiledRowsSql(sql`
|
|
13958
|
+
SELECT c.relname AS name
|
|
13959
|
+
FROM pg_class c
|
|
13960
|
+
WHERE c.relname IN (${sqlValueList(tableNames)})
|
|
13961
|
+
AND c.relkind IN ('r', 'p')
|
|
13962
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
13963
|
+
`)
|
|
13964
|
+
);
|
|
13965
|
+
return new Set(rows2.map((row) => row.name));
|
|
13966
|
+
}
|
|
12074
13967
|
const rows = await backend.execute(
|
|
12075
13968
|
asCompiledRowsSql(sql`
|
|
12076
|
-
SELECT
|
|
12077
|
-
FROM
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
AND NOT i.indisvalid
|
|
13969
|
+
SELECT name
|
|
13970
|
+
FROM sqlite_master
|
|
13971
|
+
WHERE type = 'table'
|
|
13972
|
+
AND name IN (${sqlValueList(tableNames)})
|
|
12081
13973
|
`)
|
|
12082
13974
|
);
|
|
12083
13975
|
return new Set(rows.map((row) => row.name));
|
|
12084
13976
|
}
|
|
13977
|
+
async function preloadPhysicalIndexStates(backend, physicalIndexNames) {
|
|
13978
|
+
const valid = /* @__PURE__ */ new Set();
|
|
13979
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
13980
|
+
if (physicalIndexNames.length === 0) return { valid, invalid };
|
|
13981
|
+
if (backend.dialect === "postgres") {
|
|
13982
|
+
const rows2 = await backend.execute(
|
|
13983
|
+
asCompiledRowsSql(sql`
|
|
13984
|
+
SELECT c.relname AS name, i.indisvalid AS valid
|
|
13985
|
+
FROM pg_class c
|
|
13986
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
13987
|
+
WHERE c.relname IN (${sqlValueList(physicalIndexNames)})
|
|
13988
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
13989
|
+
`)
|
|
13990
|
+
);
|
|
13991
|
+
for (const row of rows2) (row.valid ? valid : invalid).add(row.name);
|
|
13992
|
+
return { valid, invalid };
|
|
13993
|
+
}
|
|
13994
|
+
const rows = await backend.execute(
|
|
13995
|
+
asCompiledRowsSql(sql`
|
|
13996
|
+
SELECT name
|
|
13997
|
+
FROM sqlite_master
|
|
13998
|
+
WHERE type = 'index'
|
|
13999
|
+
AND name IN (${sqlValueList(physicalIndexNames)})
|
|
14000
|
+
`)
|
|
14001
|
+
);
|
|
14002
|
+
for (const row of rows) valid.add(row.name);
|
|
14003
|
+
return { valid, invalid };
|
|
14004
|
+
}
|
|
12085
14005
|
async function dropInvalidIndexLeftover(backend, physicalIndexName) {
|
|
12086
14006
|
if (backend.executeDdl === void 0) return;
|
|
12087
14007
|
if (!await hasInvalidIndexLeftover(backend, physicalIndexName)) return;
|
|
@@ -12140,6 +14060,121 @@ function buildAttempt(args) {
|
|
|
12140
14060
|
error: args.error?.message
|
|
12141
14061
|
};
|
|
12142
14062
|
}
|
|
14063
|
+
function backendSupportsIndexMaterialization(backend) {
|
|
14064
|
+
return backend.executeDdl !== void 0 && backend.getIndexMaterialization !== void 0 && backend.recordIndexMaterialization !== void 0;
|
|
14065
|
+
}
|
|
14066
|
+
function assertBackendSupportsIndexMaterialization(backend, surface) {
|
|
14067
|
+
if (backendSupportsIndexMaterialization(backend)) return;
|
|
14068
|
+
throw new ConfigurationError(
|
|
14069
|
+
`${surface} requires a backend with \`executeDdl\` and the index materialization status primitives (\`getIndexMaterialization\`, \`recordIndexMaterialization\`). The bundled SQLite and Postgres backends provide them; a custom backend must implement all three.`,
|
|
14070
|
+
{ code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
|
|
14071
|
+
);
|
|
14072
|
+
}
|
|
14073
|
+
async function materializeSystemIndexes(context, options = {}) {
|
|
14074
|
+
const { backend, graphId, schemaVersion } = context;
|
|
14075
|
+
assertBackendSupportsIndexMaterialization(
|
|
14076
|
+
backend,
|
|
14077
|
+
"store.materializeSystemIndexes()"
|
|
14078
|
+
);
|
|
14079
|
+
await ensureFocusedStatusTable(
|
|
14080
|
+
backend,
|
|
14081
|
+
backend.ensureIndexMaterializationsTable
|
|
14082
|
+
);
|
|
14083
|
+
const concurrent = backend.dialect === "postgres";
|
|
14084
|
+
const candidates = SYSTEM_INDEX_DECLARATIONS.map((declaration) => {
|
|
14085
|
+
const physicalTable = resolveSystemIndexTableName(
|
|
14086
|
+
declaration.table,
|
|
14087
|
+
backend.tableNames
|
|
14088
|
+
);
|
|
14089
|
+
return {
|
|
14090
|
+
declaration,
|
|
14091
|
+
physicalTable,
|
|
14092
|
+
name: systemIndexName(physicalTable, declaration.suffix)
|
|
14093
|
+
};
|
|
14094
|
+
});
|
|
14095
|
+
const statusKeys = candidates.map((candidate) => candidate.name);
|
|
14096
|
+
const targetTables = [
|
|
14097
|
+
...new Set(candidates.map((candidate) => candidate.physicalTable))
|
|
14098
|
+
];
|
|
14099
|
+
const [existingByStatusKey, physicalStates, existingTables] = await Promise.all([
|
|
14100
|
+
preloadMaterializations(backend, statusKeys),
|
|
14101
|
+
preloadPhysicalIndexStates(backend, statusKeys),
|
|
14102
|
+
preloadExistingTables(backend, targetTables)
|
|
14103
|
+
]);
|
|
14104
|
+
const { valid: physicallyPresent } = physicalStates;
|
|
14105
|
+
const physicalRebuildPreload = new Set(
|
|
14106
|
+
statusKeys.filter((name) => !physicallyPresent.has(name))
|
|
14107
|
+
);
|
|
14108
|
+
const materializeEntry = async (candidate) => {
|
|
14109
|
+
const identity = {
|
|
14110
|
+
name: candidate.name,
|
|
14111
|
+
entity: "system",
|
|
14112
|
+
kind: candidate.declaration.table
|
|
14113
|
+
};
|
|
14114
|
+
if (!existingTables.has(candidate.physicalTable)) {
|
|
14115
|
+
return {
|
|
14116
|
+
indexName: candidate.name,
|
|
14117
|
+
entity: "system",
|
|
14118
|
+
kind: candidate.declaration.table,
|
|
14119
|
+
status: "skipped",
|
|
14120
|
+
reason: `Relation "${candidate.physicalTable}" does not exist in this database`
|
|
14121
|
+
};
|
|
14122
|
+
}
|
|
14123
|
+
const existing = existingByStatusKey.get(candidate.name);
|
|
14124
|
+
if (existing !== void 0 && existing.entity !== "system") {
|
|
14125
|
+
return entry(
|
|
14126
|
+
identity,
|
|
14127
|
+
"failed",
|
|
14128
|
+
new Error(
|
|
14129
|
+
`System index name "${candidate.name}" is already recorded by a ${existing.entity} index declaration (graph "${existing.graphId}"). Rename the graph-declared index \u2014 "typegraph_"-prefixed names belong to TypeGraph's system indexes.`
|
|
14130
|
+
)
|
|
14131
|
+
);
|
|
14132
|
+
}
|
|
14133
|
+
if (existing === void 0 && physicallyPresent.has(candidate.name)) {
|
|
14134
|
+
return entry(identity, "alreadyMaterialized");
|
|
14135
|
+
}
|
|
14136
|
+
const signature = await computeSystemIndexSignature(
|
|
14137
|
+
backend.dialect,
|
|
14138
|
+
candidate.physicalTable,
|
|
14139
|
+
candidate.declaration
|
|
14140
|
+
);
|
|
14141
|
+
const ddl = generateSystemIndexDDL(
|
|
14142
|
+
candidate.declaration,
|
|
14143
|
+
candidate.physicalTable,
|
|
14144
|
+
{ concurrent }
|
|
14145
|
+
);
|
|
14146
|
+
return materializeOne(identity, backend, graphId, schemaVersion, {
|
|
14147
|
+
statusKey: candidate.name,
|
|
14148
|
+
signature,
|
|
14149
|
+
driftLabel: "System index",
|
|
14150
|
+
run: () => backend.executeDdl(ddl),
|
|
14151
|
+
existingByStatusKey,
|
|
14152
|
+
physicalRebuildPreload,
|
|
14153
|
+
freshNeedsPhysicalRebuild: async (physicalIndexName) => {
|
|
14154
|
+
const fresh = await preloadPhysicalIndexStates(backend, [
|
|
14155
|
+
physicalIndexName
|
|
14156
|
+
]);
|
|
14157
|
+
return !fresh.valid.has(physicalIndexName);
|
|
14158
|
+
},
|
|
14159
|
+
rebuildWithoutClaim: true
|
|
14160
|
+
});
|
|
14161
|
+
};
|
|
14162
|
+
const results = await runBucketedMaterialization(
|
|
14163
|
+
candidates,
|
|
14164
|
+
options,
|
|
14165
|
+
(candidate) => candidate.physicalTable,
|
|
14166
|
+
(candidate) => materializeEntry(candidate)
|
|
14167
|
+
);
|
|
14168
|
+
await refreshStatisticsAfterCreation(backend, results, options, concurrent);
|
|
14169
|
+
return { results };
|
|
14170
|
+
}
|
|
14171
|
+
async function computeSystemIndexSignature(dialect, targetTableName, declaration) {
|
|
14172
|
+
const json = JSON.stringify(
|
|
14173
|
+
{ dialect, targetTableName, declaration },
|
|
14174
|
+
sortedReplacer
|
|
14175
|
+
);
|
|
14176
|
+
return sha256Hex(json, 16);
|
|
14177
|
+
}
|
|
12143
14178
|
async function computeIndexSignature(dialect, targetTableName, declaration) {
|
|
12144
14179
|
const hashable = {
|
|
12145
14180
|
dialect,
|
|
@@ -12368,13 +14403,13 @@ function buildEmbeddingTableCleanup(ctx, row) {
|
|
|
12368
14403
|
const slots = await resolveRemovedKindEmbeddingSlots(ctx.backend, row);
|
|
12369
14404
|
await Promise.all(
|
|
12370
14405
|
slots.map(
|
|
12371
|
-
(slot) => dropVectorSlotStorage(vectorStrategy, executeDdl, slot)
|
|
14406
|
+
(slot) => dropVectorSlotStorage(ctx.backend, vectorStrategy, executeDdl, slot)
|
|
12372
14407
|
)
|
|
12373
14408
|
);
|
|
12374
14409
|
})();
|
|
12375
14410
|
return [cleanup];
|
|
12376
14411
|
}
|
|
12377
|
-
async function dropVectorSlotStorage(strategy, executeDdl, slot) {
|
|
14412
|
+
async function dropVectorSlotStorage(backend, strategy, executeDdl, slot) {
|
|
12378
14413
|
try {
|
|
12379
14414
|
for (const ddl of strategy.buildDropStorage(slot)) {
|
|
12380
14415
|
await executeDdl(ddl);
|
|
@@ -12382,6 +14417,7 @@ async function dropVectorSlotStorage(strategy, executeDdl, slot) {
|
|
|
12382
14417
|
} catch (error) {
|
|
12383
14418
|
if (!isMissingTableError(error)) throw error;
|
|
12384
14419
|
}
|
|
14420
|
+
await backend.deleteVectorSlotContribution?.(slot);
|
|
12385
14421
|
}
|
|
12386
14422
|
async function resolveRemovedKindEmbeddingSlots(backend, row) {
|
|
12387
14423
|
const priorVersion = row.schemaVersion - 1;
|
|
@@ -12457,7 +14493,12 @@ async function reclaimRemovedVectorFieldTables(context) {
|
|
|
12457
14493
|
const results = [];
|
|
12458
14494
|
for (const field2 of orphans) {
|
|
12459
14495
|
try {
|
|
12460
|
-
await dropVectorSlotStorage(
|
|
14496
|
+
await dropVectorSlotStorage(
|
|
14497
|
+
backend,
|
|
14498
|
+
vectorStrategy,
|
|
14499
|
+
executeDdl,
|
|
14500
|
+
field2.slot
|
|
14501
|
+
);
|
|
12461
14502
|
results.push({
|
|
12462
14503
|
kind: field2.kind,
|
|
12463
14504
|
fieldPath: field2.fieldPath,
|
|
@@ -13841,9 +15882,11 @@ var Store = class _Store {
|
|
|
13841
15882
|
* Tier 1 graph algorithms: shortest path, reachability, neighborhoods, and
|
|
13842
15883
|
* degree centrality.
|
|
13843
15884
|
*
|
|
13844
|
-
*
|
|
13845
|
-
*
|
|
13846
|
-
*
|
|
15885
|
+
* Traversal calls use the iterative graph-operation substrate: a set-based
|
|
15886
|
+
* breadth-first frontier backed by a temporary working table, or a chunked
|
|
15887
|
+
* inline relation when the backend cannot pin a transactional connection.
|
|
15888
|
+
* `degree` uses a single count query. The facade is built lazily on first
|
|
15889
|
+
* access and cached for the lifetime of the store.
|
|
13847
15890
|
*
|
|
13848
15891
|
* @example
|
|
13849
15892
|
* ```typescript
|
|
@@ -14607,10 +16650,10 @@ var Store = class _Store {
|
|
|
14607
16650
|
}) : backendOptions;
|
|
14608
16651
|
try {
|
|
14609
16652
|
const result = await this.#backend.transaction(
|
|
14610
|
-
async (txBackend,
|
|
16653
|
+
async (txBackend, sql19) => invoke(
|
|
14611
16654
|
this.#buildTransactionContext(
|
|
14612
16655
|
txBackend,
|
|
14613
|
-
|
|
16656
|
+
sql19,
|
|
14614
16657
|
runHooks,
|
|
14615
16658
|
receiptRecorder
|
|
14616
16659
|
)
|
|
@@ -14728,7 +16771,7 @@ var Store = class _Store {
|
|
|
14728
16771
|
* (#140); when history capture is enabled it is replaced with a fail-loud
|
|
14729
16772
|
* guard because raw graph writes would bypass recorded-time capture.
|
|
14730
16773
|
*/
|
|
14731
|
-
#buildTransactionContext(txBackend,
|
|
16774
|
+
#buildTransactionContext(txBackend, sql19, runHooks = this.#immediateHookRunner(), receiptRecorder) {
|
|
14732
16775
|
const txNodeOperations = {
|
|
14733
16776
|
...this.#buildNodeOperations(this.#createNodeOperationContext(runHooks)),
|
|
14734
16777
|
createQuery: () => this.#createQueryForBackend(txBackend),
|
|
@@ -14762,7 +16805,7 @@ var Store = class _Store {
|
|
|
14762
16805
|
));
|
|
14763
16806
|
}
|
|
14764
16807
|
const getNodeCollection = (kind) => this.#resolveDynamicNodeCollection(nodes, kind);
|
|
14765
|
-
const sqlAvailability = this.#captureEnabled ? "history" : this.#revisionTrackingEnabled ? "revisionTracking" :
|
|
16808
|
+
const sqlAvailability = this.#captureEnabled ? "history" : this.#revisionTrackingEnabled ? "revisionTracking" : sql19 === void 0 ? "unavailable" : "available";
|
|
14766
16809
|
const base = {
|
|
14767
16810
|
nodes,
|
|
14768
16811
|
edges,
|
|
@@ -14771,9 +16814,9 @@ var Store = class _Store {
|
|
|
14771
16814
|
getNodeCollection,
|
|
14772
16815
|
runNodeOperationHooks
|
|
14773
16816
|
};
|
|
14774
|
-
const withSql =
|
|
16817
|
+
const withSql = sql19 === void 0 ? base : {
|
|
14775
16818
|
...base,
|
|
14776
|
-
sql: this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() :
|
|
16819
|
+
sql: this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql19
|
|
14777
16820
|
};
|
|
14778
16821
|
return receiptRecorder === void 0 ? withSql : this.#attachMeasure(withSql);
|
|
14779
16822
|
}
|
|
@@ -14964,6 +17007,7 @@ var Store = class _Store {
|
|
|
14964
17007
|
merged,
|
|
14965
17008
|
activeRow.version
|
|
14966
17009
|
);
|
|
17010
|
+
await materializeVectorContributions(this.#backend, merged);
|
|
14967
17011
|
const evolved = this.#cloneWithGraph(
|
|
14968
17012
|
merged,
|
|
14969
17013
|
options?.ref,
|
|
@@ -15066,6 +17110,29 @@ var Store = class _Store {
|
|
|
15066
17110
|
options ?? {}
|
|
15067
17111
|
);
|
|
15068
17112
|
}
|
|
17113
|
+
/**
|
|
17114
|
+
* Materializes TypeGraph's own base-relation indexes
|
|
17115
|
+
* (`SYSTEM_INDEX_DECLARATIONS`) against the live database.
|
|
17116
|
+
*
|
|
17117
|
+
* Bootstrap DDL runs only on first boot, so a system index shipped in a
|
|
17118
|
+
* newer library version never reaches an already-initialized database on
|
|
17119
|
+
* its own. `createStoreWithSchema` runs this automatically; deployments
|
|
17120
|
+
* that boot with plain `createStore` / `createVerifiedStore` (zero-DDL by
|
|
17121
|
+
* contract) adopt new system indexes by calling this once under a role
|
|
17122
|
+
* that may run DDL. Same status-table, drift-signature, and Postgres
|
|
17123
|
+
* `CREATE INDEX CONCURRENTLY` claim semantics as `materializeIndexes`.
|
|
17124
|
+
*/
|
|
17125
|
+
async materializeSystemIndexes(options) {
|
|
17126
|
+
const { activeRow } = await this.#loadCaughtUp("materialize");
|
|
17127
|
+
return materializeSystemIndexes(
|
|
17128
|
+
{
|
|
17129
|
+
backend: this.#baseBackend,
|
|
17130
|
+
graphId: this.graphId,
|
|
17131
|
+
schemaVersion: activeRow.version
|
|
17132
|
+
},
|
|
17133
|
+
options ?? {}
|
|
17134
|
+
);
|
|
17135
|
+
}
|
|
15069
17136
|
/**
|
|
15070
17137
|
* Recreate a vector field's per-field storage at its current declared
|
|
15071
17138
|
* dimension, then optionally re-embed existing rows.
|
|
@@ -15122,10 +17189,14 @@ var Store = class _Store {
|
|
|
15122
17189
|
for (const ddl of strategy.buildDropStorage(slot)) {
|
|
15123
17190
|
await backend.executeDdl(ddl);
|
|
15124
17191
|
}
|
|
15125
|
-
|
|
15126
|
-
for (const
|
|
15127
|
-
|
|
17192
|
+
if (backend.ensureVectorSlotContribution === void 0) {
|
|
17193
|
+
for (const contribution of strategy.ownedTables(slot)) {
|
|
17194
|
+
for (const ddl of contribution.createDdl) {
|
|
17195
|
+
await backend.executeDdl(ddl);
|
|
17196
|
+
}
|
|
15128
17197
|
}
|
|
17198
|
+
} else {
|
|
17199
|
+
await backend.ensureVectorSlotContribution(slot, { force: true });
|
|
15129
17200
|
}
|
|
15130
17201
|
const declaredIndexMaterializes = declaration.indexType !== "none" && backend.capabilities.vector?.indexTypes.includes(
|
|
15131
17202
|
declaration.indexType
|
|
@@ -15607,6 +17678,20 @@ async function materializeRuntimeContributions(backend, graphId) {
|
|
|
15607
17678
|
}
|
|
15608
17679
|
await backend.ensureFulltextTable?.(graphId);
|
|
15609
17680
|
}
|
|
17681
|
+
async function materializeVectorContributions(backend, graph) {
|
|
17682
|
+
if (backend.capabilities.vector?.supported !== true) return;
|
|
17683
|
+
const slots = resolveGraphVectorSlots(graph);
|
|
17684
|
+
const ensureVectorSlotContributions = backend.ensureVectorSlotContributions;
|
|
17685
|
+
if (ensureVectorSlotContributions !== void 0) {
|
|
17686
|
+
await ensureVectorSlotContributions(slots, { onDrift: "skip" });
|
|
17687
|
+
return;
|
|
17688
|
+
}
|
|
17689
|
+
const ensureVectorSlotContribution = backend.ensureVectorSlotContribution;
|
|
17690
|
+
if (ensureVectorSlotContribution === void 0) return;
|
|
17691
|
+
for (const slot of slots) {
|
|
17692
|
+
await ensureVectorSlotContribution(slot, { onDrift: "skip" });
|
|
17693
|
+
}
|
|
17694
|
+
}
|
|
15610
17695
|
async function createStoreWithSchema(graph, backend, options) {
|
|
15611
17696
|
const {
|
|
15612
17697
|
graph: merged,
|
|
@@ -15618,6 +17703,10 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
15618
17703
|
preloaded: { activeRow, storedSchema }
|
|
15619
17704
|
});
|
|
15620
17705
|
await materializeRuntimeContributions(backend, merged.id);
|
|
17706
|
+
await materializeVectorContributions(backend, merged);
|
|
17707
|
+
if (options?.systemIndexes !== "skip") {
|
|
17708
|
+
await materializeSystemIndexesOnBoot(backend, merged.id, result);
|
|
17709
|
+
}
|
|
15621
17710
|
const store = new Store(
|
|
15622
17711
|
merged,
|
|
15623
17712
|
backend,
|
|
@@ -15626,6 +17715,30 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
15626
17715
|
);
|
|
15627
17716
|
return [store, result];
|
|
15628
17717
|
}
|
|
17718
|
+
async function materializeSystemIndexesOnBoot(backend, graphId, result) {
|
|
17719
|
+
if (result.status === "breaking") return;
|
|
17720
|
+
if (!backendSupportsIndexMaterialization(backend)) return;
|
|
17721
|
+
const schemaVersion = result.status === "migrated" ? result.toVersion : result.version;
|
|
17722
|
+
try {
|
|
17723
|
+
const { results } = await materializeSystemIndexes(
|
|
17724
|
+
{ backend: asRawBackend(backend), graphId, schemaVersion },
|
|
17725
|
+
{}
|
|
17726
|
+
);
|
|
17727
|
+
const failed = results.filter(
|
|
17728
|
+
(entryResult) => entryResult.status === "failed"
|
|
17729
|
+
);
|
|
17730
|
+
if (failed.length === 0) return;
|
|
17731
|
+
console.warn(
|
|
17732
|
+
`[typegraph] ${String(failed.length)} system index(es) failed to materialize at boot (${failed.map((entryResult) => entryResult.indexName).join(", ")}); the store is usable but the affected access paths fall back to scans. Retry via store.materializeSystemIndexes().`,
|
|
17733
|
+
failed[0]?.error
|
|
17734
|
+
);
|
|
17735
|
+
} catch (error) {
|
|
17736
|
+
console.warn(
|
|
17737
|
+
"[typegraph] system-index materialization failed at boot; the store is usable but new system indexes were not adopted. Retry via store.materializeSystemIndexes().",
|
|
17738
|
+
error
|
|
17739
|
+
);
|
|
17740
|
+
}
|
|
17741
|
+
}
|
|
15629
17742
|
async function createVerifiedStore(graph, backend, options) {
|
|
15630
17743
|
const {
|
|
15631
17744
|
graph: merged,
|
|
@@ -15642,5 +17755,5 @@ async function createVerifiedStore(graph, backend, options) {
|
|
|
15642
17755
|
}
|
|
15643
17756
|
|
|
15644
17757
|
export { ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, RecordedStoreView, StoreSearch, StoreView, UnionableQuery, avg, composeFragments, count, countDistinct, countDistinctEdges, countEdges, createFragment, createQueryBuilder, createStore, createStoreWithSchema, createVerifiedStore, defineSubgraphProject, field, having, havingEq, havingGt, havingGte, havingLt, havingLte, limitFragment, max, min, offsetFragment, orderByFragment, sum };
|
|
15645
|
-
//# sourceMappingURL=chunk-
|
|
15646
|
-
//# sourceMappingURL=chunk-
|
|
17758
|
+
//# sourceMappingURL=chunk-552KNYQJ.js.map
|
|
17759
|
+
//# sourceMappingURL=chunk-552KNYQJ.js.map
|