@nicia-ai/typegraph 0.36.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-7BQLPXK6.cjs → chunk-5AVRFNP4.cjs} +2955 -898
- package/dist/chunk-5AVRFNP4.cjs.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-JFMEONLG.cjs → chunk-EJYMTO3K.cjs} +332 -188
- package/dist/chunk-EJYMTO3K.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-5DIPR3L3.js → chunk-GA4TT3DH.js} +2338 -281
- package/dist/chunk-GA4TT3DH.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-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-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-BJVEU3ZL.js → chunk-UV3WP5WZ.js} +265 -121
- package/dist/chunk-UV3WP5WZ.js.map +1 -0
- 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(
|
|
@@ -10457,6 +10736,13 @@ function buildCompileTimeReferentIndex(graph, runtimeEdgeNames) {
|
|
|
10457
10736
|
// src/store/algorithms/context.ts
|
|
10458
10737
|
var DEFAULT_ALGORITHM_MAX_HOPS = 10;
|
|
10459
10738
|
var DEFAULT_NEIGHBOR_DEPTH = 1;
|
|
10739
|
+
function pickTemporalOptions(options) {
|
|
10740
|
+
return {
|
|
10741
|
+
...options.temporalMode === void 0 ? {} : { temporalMode: options.temporalMode },
|
|
10742
|
+
...options.asOf === void 0 ? {} : { asOf: options.asOf },
|
|
10743
|
+
...options.recordedAsOf === void 0 ? {} : { recordedAsOf: options.recordedAsOf }
|
|
10744
|
+
};
|
|
10745
|
+
}
|
|
10460
10746
|
function resolveTemporalOptions(ctx, options) {
|
|
10461
10747
|
const { valid } = resolveReadCoordinate(
|
|
10462
10748
|
options.temporalMode ?? ctx.defaultTemporalMode,
|
|
@@ -10512,6 +10798,16 @@ function resolveMaxHops(rawMaxHops, fallback, optionName) {
|
|
|
10512
10798
|
}
|
|
10513
10799
|
return value;
|
|
10514
10800
|
}
|
|
10801
|
+
function resolveMaxIterations(value, fallback, algorithm) {
|
|
10802
|
+
const maxIterations = value ?? fallback;
|
|
10803
|
+
if (!Number.isSafeInteger(maxIterations) || maxIterations < 1) {
|
|
10804
|
+
throw new ConfigurationError(
|
|
10805
|
+
`${algorithm} maxIterations must be a positive safe integer, got ${String(maxIterations)}.`,
|
|
10806
|
+
{ maxIterations }
|
|
10807
|
+
);
|
|
10808
|
+
}
|
|
10809
|
+
return maxIterations;
|
|
10810
|
+
}
|
|
10515
10811
|
function assertEdgeKinds(edges) {
|
|
10516
10812
|
if (edges.length === 0) {
|
|
10517
10813
|
throw new ConfigurationError(
|
|
@@ -10561,137 +10857,1636 @@ function compileDirectionFilter(ctx, direction, nodeId, nodesTable) {
|
|
|
10561
10857
|
}
|
|
10562
10858
|
}
|
|
10563
10859
|
}
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
options.maxHops,
|
|
10568
|
-
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
10569
|
-
"maxHops"
|
|
10570
|
-
);
|
|
10571
|
-
const cte = buildReachableCte({
|
|
10572
|
-
graphId: ctx.graphId,
|
|
10573
|
-
sourceId,
|
|
10574
|
-
edgeKinds: options.edges,
|
|
10575
|
-
maxHops,
|
|
10576
|
-
direction: options.direction ?? "out",
|
|
10577
|
-
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
10578
|
-
includePath: false,
|
|
10579
|
-
...resolveTemporalOptions(ctx, options),
|
|
10580
|
-
dialect: ctx.dialect,
|
|
10581
|
-
schema: ctx.schema,
|
|
10582
|
-
...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
|
|
10583
|
-
});
|
|
10584
|
-
const sourceFilter = options.excludeSource === true ? sql` WHERE id != ${sourceId}` : sql``;
|
|
10585
|
-
const query = sql`${cte} SELECT id, kind, MIN(depth) AS depth FROM reachable${sourceFilter} GROUP BY id, kind ORDER BY depth ASC, id ASC`;
|
|
10586
|
-
const rows = await ctx.backend.execute(
|
|
10587
|
-
asCompiledRowsSql(query)
|
|
10860
|
+
function frontierIndexIdentifier(context) {
|
|
10861
|
+
return sql.identifier(
|
|
10862
|
+
`typegraph_iterative_${context.runId.replaceAll("-", "_")}_frontier`
|
|
10588
10863
|
);
|
|
10589
|
-
return rows.map((row) => ({
|
|
10590
|
-
id: row.id,
|
|
10591
|
-
kind: row.kind,
|
|
10592
|
-
depth: Number(row.depth)
|
|
10593
|
-
}));
|
|
10594
10864
|
}
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
)
|
|
10619
|
-
|
|
10865
|
+
var DEFAULT_MAX_BIND_PARAMETERS = 999;
|
|
10866
|
+
var RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH = 12;
|
|
10867
|
+
var RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH = 4;
|
|
10868
|
+
var WORKING_TABLE_ANALYZE_MINIMUM_ROWS = 16;
|
|
10869
|
+
var WORKING_TABLE_ANALYZE_GROWTH_FACTOR = 4;
|
|
10870
|
+
var ITERATIVE_WORKING_MEMORY_PATTERN = /^(\d+)(kB|MB|GB)$/;
|
|
10871
|
+
var WORKING_MEMORY_KILOBYTES_PER_UNIT = {
|
|
10872
|
+
kB: 1,
|
|
10873
|
+
MB: 1024,
|
|
10874
|
+
GB: 1024 * 1024
|
|
10875
|
+
};
|
|
10876
|
+
var MIN_WORKING_MEMORY_KILOBYTES = 64;
|
|
10877
|
+
var MAX_WORKING_MEMORY_KILOBYTES = 2147483647;
|
|
10878
|
+
function resolveWorkingMemory(workingMemory) {
|
|
10879
|
+
if (workingMemory === void 0) return void 0;
|
|
10880
|
+
const match = ITERATIVE_WORKING_MEMORY_PATTERN.exec(workingMemory);
|
|
10881
|
+
if (match === null) {
|
|
10882
|
+
throw new ConfigurationError(
|
|
10883
|
+
`Iterative graph operation workingMemory must be digits followed by kB, MB, or GB (for example "64MB"), got ${JSON.stringify(workingMemory)}.`,
|
|
10884
|
+
{ workingMemory }
|
|
10885
|
+
);
|
|
10886
|
+
}
|
|
10887
|
+
const kilobytes = Number(match[1]) * (WORKING_MEMORY_KILOBYTES_PER_UNIT[match[2] ?? ""] ?? 0);
|
|
10888
|
+
if (kilobytes < MIN_WORKING_MEMORY_KILOBYTES || kilobytes > MAX_WORKING_MEMORY_KILOBYTES) {
|
|
10889
|
+
throw new ConfigurationError(
|
|
10890
|
+
`Iterative graph operation workingMemory must be between ${MIN_WORKING_MEMORY_KILOBYTES}kB and ${MAX_WORKING_MEMORY_KILOBYTES}kB, got ${JSON.stringify(workingMemory)}.`,
|
|
10891
|
+
{ workingMemory, kilobytes }
|
|
10892
|
+
);
|
|
10893
|
+
}
|
|
10894
|
+
return workingMemory;
|
|
10620
10895
|
}
|
|
10621
|
-
async function
|
|
10622
|
-
|
|
10623
|
-
|
|
10896
|
+
async function withInlineIterativeGraphOperation(ctx, options, run) {
|
|
10897
|
+
if (ctx.backend.capabilities.transactions) {
|
|
10898
|
+
return ctx.backend.transaction(
|
|
10899
|
+
async (backend) => run(createOperation(ctx, options, backend)),
|
|
10900
|
+
{
|
|
10901
|
+
isolationLevel: "repeatable_read",
|
|
10902
|
+
accessMode: "read_only"
|
|
10903
|
+
}
|
|
10904
|
+
);
|
|
10905
|
+
}
|
|
10906
|
+
return run(createOperation(ctx, options, ctx.backend));
|
|
10907
|
+
}
|
|
10908
|
+
async function runIterativeGraphOperation(ctx, options, plan) {
|
|
10909
|
+
if (!ctx.backend.capabilities.transactions) {
|
|
10910
|
+
throw new ConfigurationError(
|
|
10911
|
+
"Temporary-table graph iteration requires a transactional backend.",
|
|
10912
|
+
{ dialect: ctx.backend.dialect }
|
|
10913
|
+
);
|
|
10914
|
+
}
|
|
10915
|
+
return ctx.backend.transaction(
|
|
10916
|
+
async (backend) => {
|
|
10917
|
+
const temporaryBackend = requireTemporaryStatements(backend);
|
|
10918
|
+
const runId = generateId();
|
|
10919
|
+
const workingTable = sql.identifier(
|
|
10920
|
+
`typegraph_iterative_${runId.replaceAll("-", "_")}`
|
|
10921
|
+
);
|
|
10922
|
+
const context = {
|
|
10923
|
+
operation: createOperation(ctx, options, temporaryBackend),
|
|
10924
|
+
backend: temporaryBackend,
|
|
10925
|
+
workingTable,
|
|
10926
|
+
graphId: ctx.graphId,
|
|
10927
|
+
runId,
|
|
10928
|
+
executeTemporary: async (query) => {
|
|
10929
|
+
await temporaryBackend.executeTemporaryStatement(
|
|
10930
|
+
asCompiledTemporaryStatementSql(query)
|
|
10931
|
+
);
|
|
10932
|
+
}
|
|
10933
|
+
};
|
|
10934
|
+
if (context.operation.workingMemory !== void 0) {
|
|
10935
|
+
const workingMemoryStatement = ctx.dialect.setTransactionWorkingMemory(
|
|
10936
|
+
context.operation.workingMemory
|
|
10937
|
+
);
|
|
10938
|
+
if (workingMemoryStatement !== void 0) {
|
|
10939
|
+
await context.executeTemporary(workingMemoryStatement);
|
|
10940
|
+
}
|
|
10941
|
+
}
|
|
10942
|
+
await context.executeTemporary(plan.createWorkingTable(context));
|
|
10943
|
+
let operationError;
|
|
10944
|
+
try {
|
|
10945
|
+
let state = await plan.initialize(context);
|
|
10946
|
+
let analyzedRowCount = await refreshWorkingTableStatistics(
|
|
10947
|
+
context,
|
|
10948
|
+
state.workingTableSize
|
|
10949
|
+
);
|
|
10950
|
+
for (let iteration = 1; iteration <= plan.maxIterations && !plan.hasConverged(state); iteration++) {
|
|
10951
|
+
state = await plan.runRound(context, state, iteration);
|
|
10952
|
+
if (iteration < plan.maxIterations && !plan.hasConverged(state)) {
|
|
10953
|
+
analyzedRowCount = await refreshWorkingTableStatistics(
|
|
10954
|
+
context,
|
|
10955
|
+
state.workingTableSize,
|
|
10956
|
+
analyzedRowCount
|
|
10957
|
+
);
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
if (!plan.hasConverged(state)) {
|
|
10961
|
+
throw new GraphAlgorithmConvergenceError(
|
|
10962
|
+
plan.algorithm,
|
|
10963
|
+
plan.maxIterations
|
|
10964
|
+
);
|
|
10965
|
+
}
|
|
10966
|
+
return await plan.extractResult(context, state);
|
|
10967
|
+
} catch (error) {
|
|
10968
|
+
operationError = error;
|
|
10969
|
+
throw error;
|
|
10970
|
+
} finally {
|
|
10971
|
+
await dropWorkingTable(context, operationError);
|
|
10972
|
+
}
|
|
10973
|
+
},
|
|
10974
|
+
{
|
|
10975
|
+
isolationLevel: "repeatable_read",
|
|
10976
|
+
accessMode: "read_only",
|
|
10977
|
+
temporaryWrites: INTERNAL_TEMPORARY_WRITES
|
|
10978
|
+
}
|
|
10979
|
+
);
|
|
10980
|
+
}
|
|
10981
|
+
function shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount) {
|
|
10982
|
+
if (workingTableSize < WORKING_TABLE_ANALYZE_MINIMUM_ROWS) return false;
|
|
10983
|
+
if (analyzedRowCount === void 0) return true;
|
|
10984
|
+
return workingTableSize >= analyzedRowCount * WORKING_TABLE_ANALYZE_GROWTH_FACTOR;
|
|
10985
|
+
}
|
|
10986
|
+
async function refreshWorkingTableStatistics(context, workingTableSize, analyzedRowCount) {
|
|
10987
|
+
if (!shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount)) {
|
|
10988
|
+
return analyzedRowCount;
|
|
10989
|
+
}
|
|
10990
|
+
const statement = context.operation.ctx.dialect.analyzeTemporaryTable(
|
|
10991
|
+
context.workingTable
|
|
10992
|
+
);
|
|
10993
|
+
if (statement === void 0) return analyzedRowCount;
|
|
10994
|
+
await context.executeTemporary(statement);
|
|
10995
|
+
return workingTableSize;
|
|
10996
|
+
}
|
|
10997
|
+
async function dropWorkingTable(context, operationError) {
|
|
10998
|
+
try {
|
|
10999
|
+
await context.executeTemporary(
|
|
11000
|
+
sql`DROP TABLE IF EXISTS ${context.workingTable}`
|
|
11001
|
+
);
|
|
11002
|
+
} catch (cleanupError) {
|
|
11003
|
+
if (operationError === void 0) throw cleanupError;
|
|
11004
|
+
}
|
|
11005
|
+
}
|
|
11006
|
+
async function fetchVisibleWorkingNodes(operation, nodeIds) {
|
|
11007
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
11008
|
+
for (const chunk2 of chunkValues(nodeIds, operation.maxWorkingSetSize)) {
|
|
11009
|
+
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}`;
|
|
11010
|
+
const rows = await operation.backend.execute(
|
|
11011
|
+
asCompiledRowsSql(query)
|
|
11012
|
+
);
|
|
11013
|
+
for (const row of rows) nodes.set(nodeIdentityKey(row), row);
|
|
11014
|
+
}
|
|
11015
|
+
return [...nodes.values()].toSorted(
|
|
11016
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
11017
|
+
);
|
|
11018
|
+
}
|
|
11019
|
+
async function reduceExpandedWorkingSet(operation, workingSet, direction, reduce) {
|
|
11020
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
11021
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
11022
|
+
for (const chunk2 of chunkValues(workingSet, operation.maxWorkingSetSize)) {
|
|
11023
|
+
const rows = await operation.backend.execute(
|
|
11024
|
+
asCompiledRowsSql(
|
|
11025
|
+
compileExpansionQuery(operation, chunk2, direction, edgeKinds)
|
|
11026
|
+
)
|
|
11027
|
+
);
|
|
11028
|
+
for (const row of rows) {
|
|
11029
|
+
if (operation.weightExpression !== void 0 && !isPresent(row.weight)) {
|
|
11030
|
+
throw new CompilerInvariantError(
|
|
11031
|
+
"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.",
|
|
11032
|
+
{ source: row.source_id, target: row.target_id }
|
|
11033
|
+
);
|
|
11034
|
+
}
|
|
11035
|
+
const expansion = {
|
|
11036
|
+
source: { id: row.source_id, kind: row.source_kind },
|
|
11037
|
+
target: { id: row.target_id, kind: row.target_kind },
|
|
11038
|
+
...isPresent(row.weight) ? { weight: Number(row.weight) } : {}
|
|
11039
|
+
};
|
|
11040
|
+
const targetKey = nodeIdentityKey(expansion.target);
|
|
11041
|
+
const selected = reduce(reduced.get(targetKey), expansion);
|
|
11042
|
+
if (selected !== void 0) reduced.set(targetKey, selected);
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
}
|
|
11046
|
+
return reduced;
|
|
11047
|
+
}
|
|
11048
|
+
function nodeIdentityKey(node) {
|
|
11049
|
+
return `${node.kind}\0${node.id}`;
|
|
11050
|
+
}
|
|
11051
|
+
function nodeIdentityKeyFromRow(id, kind) {
|
|
11052
|
+
if (typeof id !== "string" || typeof kind !== "string") return void 0;
|
|
11053
|
+
return nodeIdentityKey({ id, kind });
|
|
11054
|
+
}
|
|
11055
|
+
function compareNodeIdentity(left, right) {
|
|
11056
|
+
return compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
|
|
11057
|
+
}
|
|
11058
|
+
var OUTGOING_DIRECTION_FIELDS = {
|
|
11059
|
+
joinField: "from_id",
|
|
11060
|
+
joinKindField: "from_kind",
|
|
11061
|
+
targetField: "to_id",
|
|
11062
|
+
targetKindField: "to_kind"
|
|
11063
|
+
};
|
|
11064
|
+
var INCOMING_DIRECTION_FIELDS = {
|
|
11065
|
+
joinField: "to_id",
|
|
11066
|
+
joinKindField: "to_kind",
|
|
11067
|
+
targetField: "from_id",
|
|
11068
|
+
targetKindField: "from_kind"
|
|
11069
|
+
};
|
|
11070
|
+
function fieldsForDirection(direction) {
|
|
11071
|
+
switch (direction) {
|
|
11072
|
+
case "out": {
|
|
11073
|
+
return [OUTGOING_DIRECTION_FIELDS];
|
|
11074
|
+
}
|
|
11075
|
+
case "in": {
|
|
11076
|
+
return [INCOMING_DIRECTION_FIELDS];
|
|
11077
|
+
}
|
|
11078
|
+
case "both": {
|
|
11079
|
+
return [OUTGOING_DIRECTION_FIELDS, INCOMING_DIRECTION_FIELDS];
|
|
11080
|
+
}
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
function compileDirectionUnion(direction, compileDirection) {
|
|
11084
|
+
return sql.join(
|
|
11085
|
+
fieldsForDirection(direction).map((fields) => compileDirection(fields)),
|
|
11086
|
+
sql` UNION ALL `
|
|
11087
|
+
);
|
|
11088
|
+
}
|
|
11089
|
+
function compileExpansionQuery(operation, workingSet, direction, edgeKinds) {
|
|
11090
|
+
const workingValues = sql.join(
|
|
11091
|
+
workingSet.map((node) => sql`(${node.id}, ${node.kind})`),
|
|
11092
|
+
sql`, `
|
|
11093
|
+
);
|
|
11094
|
+
const workingCte = sql`WITH working_set(node_id, node_kind) AS (VALUES ${workingValues})`;
|
|
11095
|
+
const expansion = compileDirectionUnion(
|
|
11096
|
+
direction,
|
|
11097
|
+
(fields) => compileDirectionalExpansion(
|
|
11098
|
+
operation,
|
|
11099
|
+
sql`working_set`,
|
|
11100
|
+
sql`TRUE`,
|
|
11101
|
+
edgeKinds,
|
|
11102
|
+
fields.joinField,
|
|
11103
|
+
fields.joinKindField,
|
|
11104
|
+
fields.targetField,
|
|
11105
|
+
fields.targetKindField
|
|
11106
|
+
)
|
|
11107
|
+
);
|
|
11108
|
+
return sql`${workingCte} ${expansion}`;
|
|
11109
|
+
}
|
|
11110
|
+
function compileWorkingTableExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
|
|
11111
|
+
return compileDirectionUnion(
|
|
11112
|
+
direction,
|
|
11113
|
+
(fields) => compileDirectionalExpansion(
|
|
11114
|
+
operation,
|
|
11115
|
+
workingTable,
|
|
11116
|
+
sourceFilter,
|
|
11117
|
+
edgeKinds,
|
|
11118
|
+
fields.joinField,
|
|
11119
|
+
fields.joinKindField,
|
|
11120
|
+
fields.targetField,
|
|
11121
|
+
fields.targetKindField
|
|
11122
|
+
)
|
|
11123
|
+
);
|
|
11124
|
+
}
|
|
11125
|
+
function compileWorkingTableEdgeExpansion(operation, workingTable, sourceFilter, direction, edgeKinds, sourceProjection) {
|
|
11126
|
+
return compileDirectionUnion(
|
|
11127
|
+
direction,
|
|
11128
|
+
(fields) => compileDirectionalEdgeExpansion(
|
|
11129
|
+
operation,
|
|
11130
|
+
workingTable,
|
|
11131
|
+
sourceFilter,
|
|
11132
|
+
edgeKinds,
|
|
11133
|
+
fields.joinField,
|
|
11134
|
+
fields.joinKindField,
|
|
11135
|
+
fields.targetField,
|
|
11136
|
+
fields.targetKindField,
|
|
11137
|
+
sourceProjection
|
|
11138
|
+
)
|
|
11139
|
+
);
|
|
11140
|
+
}
|
|
11141
|
+
function compileDirectionalExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
|
|
11142
|
+
const edgeExpansion = compileDirectionalEdgeExpansion(
|
|
11143
|
+
operation,
|
|
11144
|
+
workingRelation,
|
|
11145
|
+
sourceFilter,
|
|
11146
|
+
edgeKinds,
|
|
11147
|
+
joinField,
|
|
11148
|
+
joinKindField,
|
|
11149
|
+
targetField,
|
|
11150
|
+
targetKindField
|
|
11151
|
+
);
|
|
11152
|
+
const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, expanded.weight`;
|
|
11153
|
+
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}`;
|
|
11154
|
+
}
|
|
11155
|
+
function compileDirectionalEdgeExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField, sourceProjection) {
|
|
11156
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
|
|
11157
|
+
const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, ${operation.weightExpression} AS weight`;
|
|
11158
|
+
const sourceColumns = sourceProjection === void 0 ? sql`` : sql`, ${sourceProjection}`;
|
|
11159
|
+
return sql`SELECT w.node_id AS source_id, w.node_kind AS source_kind${sourceColumns}, 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}`;
|
|
11160
|
+
}
|
|
11161
|
+
function chunkValues(values, chunkSize) {
|
|
11162
|
+
const chunks = [];
|
|
11163
|
+
for (let offset = 0; offset < values.length; offset += chunkSize) {
|
|
11164
|
+
chunks.push(values.slice(offset, offset + chunkSize));
|
|
11165
|
+
}
|
|
11166
|
+
return chunks;
|
|
11167
|
+
}
|
|
11168
|
+
function createOperation(ctx, options, backend) {
|
|
11169
|
+
const temporal = resolveTemporalOptions(ctx, options);
|
|
11170
|
+
const currentTimestamp = currentReadInstant();
|
|
11171
|
+
const nodeTemporalFilter = compileTemporalFilter({
|
|
11172
|
+
mode: temporal.temporalMode,
|
|
11173
|
+
...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
|
|
11174
|
+
...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
|
|
11175
|
+
tableAlias: "n",
|
|
11176
|
+
currentTimestamp
|
|
11177
|
+
});
|
|
11178
|
+
const edgeTemporalFilter = compileTemporalFilter({
|
|
11179
|
+
mode: temporal.temporalMode,
|
|
11180
|
+
...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
|
|
11181
|
+
...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
|
|
11182
|
+
tableAlias: "e",
|
|
11183
|
+
currentTimestamp
|
|
11184
|
+
});
|
|
11185
|
+
const weightExpression = options.weightProperty === void 0 ? void 0 : compileWeightExpression(
|
|
11186
|
+
ctx.dialect,
|
|
11187
|
+
options.weightProperty,
|
|
11188
|
+
options.defaultWeight
|
|
11189
|
+
);
|
|
11190
|
+
const direction = options.direction ?? "out";
|
|
11191
|
+
const branchCount = direction === "both" ? 2 : 1;
|
|
11192
|
+
const parameterLimit = backend.capabilities.maxBindParameters ?? DEFAULT_MAX_BIND_PARAMETERS;
|
|
11193
|
+
const reservedPerBranch = RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH + (weightExpression === void 0 ? 0 : RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH);
|
|
11194
|
+
const fixedParameters = branchCount * reservedPerBranch;
|
|
11195
|
+
const sharedBudget = parameterLimit - fixedParameters;
|
|
11196
|
+
const maxEdgeKindsPerQuery = Math.floor(sharedBudget / (branchCount + 2));
|
|
11197
|
+
if (maxEdgeKindsPerQuery < 1) {
|
|
11198
|
+
throw new ConfigurationError(
|
|
11199
|
+
"An iterative graph operation cannot fit its fixed filters within the backend bind-parameter limit.",
|
|
11200
|
+
{ parameterLimit, direction }
|
|
11201
|
+
);
|
|
11202
|
+
}
|
|
11203
|
+
const edgeKindChunks = chunkValues(
|
|
11204
|
+
[...new Set(options.edges)].toSorted(
|
|
11205
|
+
(left, right) => compareStrings(left, right)
|
|
11206
|
+
),
|
|
11207
|
+
maxEdgeKindsPerQuery
|
|
11208
|
+
);
|
|
11209
|
+
const largestEdgeKindChunk = Math.max(
|
|
11210
|
+
...edgeKindChunks.map((chunk2) => chunk2.length)
|
|
11211
|
+
);
|
|
11212
|
+
const maxWorkingSetSize = Math.floor(
|
|
11213
|
+
(parameterLimit - branchCount * (largestEdgeKindChunk + reservedPerBranch)) / 2
|
|
11214
|
+
);
|
|
11215
|
+
if (maxWorkingSetSize < 1) {
|
|
11216
|
+
throw new ConfigurationError(
|
|
11217
|
+
"An iterative graph operation cannot fit one working-set node within the backend bind-parameter limit.",
|
|
11218
|
+
{ parameterLimit, direction }
|
|
11219
|
+
);
|
|
11220
|
+
}
|
|
11221
|
+
return {
|
|
11222
|
+
backend,
|
|
11223
|
+
ctx,
|
|
11224
|
+
edgeKindChunks,
|
|
11225
|
+
direction,
|
|
11226
|
+
maxWorkingSetSize,
|
|
11227
|
+
nodeTemporalFilter,
|
|
11228
|
+
edgeTemporalFilter,
|
|
11229
|
+
schema: resolveReadSchema(ctx, options),
|
|
11230
|
+
workingMemory: resolveWorkingMemory(options.workingMemory),
|
|
11231
|
+
weightExpression
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
function compileWeightExpression(dialect, weightProperty, defaultWeight) {
|
|
11235
|
+
const extracted = dialect.jsonExtractDouble(
|
|
11236
|
+
sql.raw("e.props"),
|
|
11237
|
+
jsonPointer([weightProperty])
|
|
11238
|
+
);
|
|
11239
|
+
return defaultWeight === void 0 ? extracted : sql`COALESCE(${extracted}, ${defaultWeight})`;
|
|
11240
|
+
}
|
|
11241
|
+
function supportsTemporaryIteration(ctx) {
|
|
11242
|
+
return ctx.backend.capabilities.transactions && ctx.backend.capabilities.graphAnalytics?.supported !== false && ctx.backend.capabilities.returning !== false && ctx.backend.executeTemporaryStatement !== void 0;
|
|
11243
|
+
}
|
|
11244
|
+
function requireTemporaryStatements(backend) {
|
|
11245
|
+
if (backend.executeTemporaryStatement === void 0) {
|
|
11246
|
+
throw new ConfigurationError(
|
|
11247
|
+
"Iterative graph operations require temporary-statement support on the transaction backend.",
|
|
11248
|
+
{ dialect: backend.dialect },
|
|
11249
|
+
{
|
|
11250
|
+
suggestion: "Use a built-in SQLite/PostgreSQL backend or implement executeTemporaryStatement on the custom backend."
|
|
11251
|
+
}
|
|
11252
|
+
);
|
|
11253
|
+
}
|
|
11254
|
+
return backend;
|
|
11255
|
+
}
|
|
11256
|
+
|
|
11257
|
+
// src/store/algorithms/breadth-first.ts
|
|
11258
|
+
async function findReachableNodes(ctx, sourceId, maxHops, options) {
|
|
11259
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
11260
|
+
return findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options);
|
|
11261
|
+
}
|
|
11262
|
+
return findReachableNodesInline(ctx, sourceId, maxHops, options);
|
|
11263
|
+
}
|
|
11264
|
+
async function findShortestPath(ctx, sourceId, targetId, maxHops, options) {
|
|
11265
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
11266
|
+
return findShortestPathInWorkingTable(
|
|
11267
|
+
ctx,
|
|
11268
|
+
sourceId,
|
|
11269
|
+
targetId,
|
|
11270
|
+
maxHops,
|
|
11271
|
+
options
|
|
11272
|
+
);
|
|
11273
|
+
}
|
|
11274
|
+
return findShortestPathInline(ctx, sourceId, targetId, maxHops, options);
|
|
11275
|
+
}
|
|
11276
|
+
async function findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options) {
|
|
11277
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
11278
|
+
algorithm: "reachable",
|
|
11279
|
+
maxIterations: maxHops,
|
|
11280
|
+
createWorkingTable,
|
|
11281
|
+
async initialize(context) {
|
|
11282
|
+
const seeded = await seedWorkingSide(context, sourceId, "forward");
|
|
11283
|
+
return {
|
|
11284
|
+
depth: 0,
|
|
11285
|
+
frontierCount: seeded.length,
|
|
11286
|
+
workingTableSize: seeded.length
|
|
11287
|
+
};
|
|
11288
|
+
},
|
|
11289
|
+
async runRound(context, state) {
|
|
11290
|
+
const nextDepth = state.depth + 1;
|
|
11291
|
+
const frontierCount = await expandReachableWorkingTableRound(
|
|
11292
|
+
context,
|
|
11293
|
+
"forward",
|
|
11294
|
+
state.depth,
|
|
11295
|
+
nextDepth,
|
|
11296
|
+
context.operation.direction
|
|
11297
|
+
);
|
|
11298
|
+
return {
|
|
11299
|
+
depth: nextDepth,
|
|
11300
|
+
frontierCount,
|
|
11301
|
+
workingTableSize: state.workingTableSize + frontierCount
|
|
11302
|
+
};
|
|
11303
|
+
},
|
|
11304
|
+
hasConverged(state) {
|
|
11305
|
+
return state.frontierCount === 0 || state.depth >= maxHops;
|
|
11306
|
+
},
|
|
11307
|
+
async extractResult(context) {
|
|
11308
|
+
const rows = await readWorkingRows(context, "forward");
|
|
11309
|
+
return rows.map((row) => ({
|
|
11310
|
+
id: row.node_id,
|
|
11311
|
+
kind: row.node_kind,
|
|
11312
|
+
depth: Number(row.depth)
|
|
11313
|
+
})).toSorted(
|
|
11314
|
+
(left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
|
|
11315
|
+
);
|
|
11316
|
+
}
|
|
11317
|
+
});
|
|
11318
|
+
}
|
|
11319
|
+
async function findShortestPathInWorkingTable(ctx, sourceId, targetId, maxHops, options) {
|
|
11320
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
11321
|
+
algorithm: "shortestPath",
|
|
11322
|
+
maxIterations: maxHops,
|
|
11323
|
+
createWorkingTable,
|
|
11324
|
+
async initialize(context) {
|
|
11325
|
+
const forwardSeeds = await seedWorkingSide(context, sourceId, "forward");
|
|
11326
|
+
const reverseSeeds = await seedWorkingSide(context, targetId, "reverse");
|
|
11327
|
+
return {
|
|
11328
|
+
forwardDepth: 0,
|
|
11329
|
+
reverseDepth: 0,
|
|
11330
|
+
forwardFrontierCount: forwardSeeds.length,
|
|
11331
|
+
reverseFrontierCount: reverseSeeds.length,
|
|
11332
|
+
meeting: findSeedMeeting(forwardSeeds, reverseSeeds),
|
|
11333
|
+
workingTableSize: forwardSeeds.length + reverseSeeds.length
|
|
11334
|
+
};
|
|
11335
|
+
},
|
|
11336
|
+
async runRound(context, state) {
|
|
11337
|
+
const expandForward = state.forwardFrontierCount <= state.reverseFrontierCount;
|
|
11338
|
+
const side = expandForward ? "forward" : "reverse";
|
|
11339
|
+
const currentDepth = expandForward ? state.forwardDepth : state.reverseDepth;
|
|
11340
|
+
const nextDepth = currentDepth + 1;
|
|
11341
|
+
const direction = expandForward ? context.operation.direction : reverseDirection(context.operation.direction);
|
|
11342
|
+
const round = await expandWorkingTableRound(
|
|
11343
|
+
context,
|
|
11344
|
+
side,
|
|
11345
|
+
currentDepth,
|
|
11346
|
+
nextDepth,
|
|
11347
|
+
direction,
|
|
11348
|
+
maxHops
|
|
11349
|
+
);
|
|
11350
|
+
return {
|
|
11351
|
+
forwardDepth: expandForward ? nextDepth : state.forwardDepth,
|
|
11352
|
+
reverseDepth: expandForward ? state.reverseDepth : nextDepth,
|
|
11353
|
+
forwardFrontierCount: expandForward ? round.insertedCount : state.forwardFrontierCount,
|
|
11354
|
+
reverseFrontierCount: expandForward ? state.reverseFrontierCount : round.insertedCount,
|
|
11355
|
+
meeting: round.meeting,
|
|
11356
|
+
workingTableSize: state.workingTableSize + round.insertedCount
|
|
11357
|
+
};
|
|
11358
|
+
},
|
|
11359
|
+
hasConverged(state) {
|
|
11360
|
+
return state.meeting !== void 0 || state.forwardFrontierCount === 0 || state.reverseFrontierCount === 0 || state.forwardDepth + state.reverseDepth >= maxHops;
|
|
11361
|
+
},
|
|
11362
|
+
async extractResult(context, state) {
|
|
11363
|
+
if (state.meeting === void 0) return;
|
|
11364
|
+
const rows = await readWorkingRows(context);
|
|
11365
|
+
return buildShortestPath(
|
|
11366
|
+
nodeIdentityKey(state.meeting),
|
|
11367
|
+
createVisitedMapFromRows(rows, "forward"),
|
|
11368
|
+
createVisitedMapFromRows(rows, "reverse")
|
|
11369
|
+
);
|
|
11370
|
+
}
|
|
11371
|
+
});
|
|
11372
|
+
}
|
|
11373
|
+
function createWorkingTable(context) {
|
|
11374
|
+
return sql`
|
|
11375
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
11376
|
+
graph_id TEXT NOT NULL,
|
|
11377
|
+
run_id TEXT NOT NULL,
|
|
11378
|
+
side TEXT NOT NULL,
|
|
11379
|
+
node_id TEXT NOT NULL,
|
|
11380
|
+
node_kind TEXT NOT NULL,
|
|
11381
|
+
depth INTEGER NOT NULL,
|
|
11382
|
+
predecessor_id TEXT,
|
|
11383
|
+
predecessor_kind TEXT,
|
|
11384
|
+
meeting_depth INTEGER,
|
|
11385
|
+
PRIMARY KEY (graph_id, run_id, side, node_kind, node_id)
|
|
11386
|
+
)
|
|
11387
|
+
`;
|
|
11388
|
+
}
|
|
11389
|
+
async function seedWorkingSide(context, nodeId, side) {
|
|
11390
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11391
|
+
return context.backend.execute(
|
|
11392
|
+
asCompiledRowsSql(sql`
|
|
11393
|
+
INSERT INTO ${workingTable}
|
|
11394
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11395
|
+
predecessor_id, predecessor_kind)
|
|
11396
|
+
SELECT ${graphId}, ${runId}, ${side}, n.id, n.kind, 0, NULL, NULL
|
|
11397
|
+
FROM ${operation.schema.nodesTable} n
|
|
11398
|
+
WHERE n.graph_id = ${graphId}
|
|
11399
|
+
AND n.id = ${nodeId}
|
|
11400
|
+
AND ${operation.nodeTemporalFilter}
|
|
11401
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11402
|
+
RETURNING node_id, node_kind
|
|
11403
|
+
`)
|
|
11404
|
+
);
|
|
11405
|
+
}
|
|
11406
|
+
function findSeedMeeting(forwardSeeds, reverseSeeds) {
|
|
11407
|
+
const reverseKeys = new Set(
|
|
11408
|
+
reverseSeeds.map(
|
|
11409
|
+
(row) => nodeIdentityKey({ id: row.node_id, kind: row.node_kind })
|
|
11410
|
+
)
|
|
11411
|
+
);
|
|
11412
|
+
let meeting;
|
|
11413
|
+
for (const row of forwardSeeds) {
|
|
11414
|
+
const candidate = { id: row.node_id, kind: row.node_kind, depth: 0 };
|
|
11415
|
+
if (!reverseKeys.has(nodeIdentityKey(candidate))) continue;
|
|
11416
|
+
if (meeting === void 0 || compareNodeIdentity(candidate, meeting) < 0) {
|
|
11417
|
+
meeting = candidate;
|
|
11418
|
+
}
|
|
11419
|
+
}
|
|
11420
|
+
return meeting;
|
|
11421
|
+
}
|
|
11422
|
+
async function expandWorkingTableRound(context, side, currentDepth, nextDepth, direction, maxHops) {
|
|
11423
|
+
const oppositeSide = side === "forward" ? "reverse" : "forward";
|
|
11424
|
+
let insertedCount = 0;
|
|
11425
|
+
let meeting;
|
|
11426
|
+
const { dialect } = context.operation.ctx;
|
|
11427
|
+
const targetKind = dialect.binaryText(sql`expanded.target_kind`);
|
|
11428
|
+
const targetId = dialect.binaryText(sql`expanded.target_id`);
|
|
11429
|
+
const sourceId = dialect.binaryText(sql`expanded.source_id`);
|
|
11430
|
+
const sourceKind = dialect.binaryText(sql`expanded.source_kind`);
|
|
11431
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11432
|
+
const sourceFilter = sql`
|
|
11433
|
+
w.graph_id = ${context.graphId}
|
|
11434
|
+
AND w.run_id = ${context.runId}
|
|
11435
|
+
AND w.side = ${side}
|
|
11436
|
+
AND w.depth = ${currentDepth}
|
|
11437
|
+
`;
|
|
11438
|
+
const expansion = compileWorkingTableExpansion(
|
|
11439
|
+
context.operation,
|
|
11440
|
+
context.workingTable,
|
|
11441
|
+
sourceFilter,
|
|
11442
|
+
direction,
|
|
11443
|
+
edgeKinds
|
|
11444
|
+
);
|
|
11445
|
+
const rows = await context.backend.execute(
|
|
11446
|
+
asCompiledRowsSql(sql`
|
|
11447
|
+
INSERT INTO ${context.workingTable}
|
|
11448
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11449
|
+
predecessor_id, predecessor_kind, meeting_depth)
|
|
11450
|
+
SELECT
|
|
11451
|
+
${context.graphId}, ${context.runId}, ${side},
|
|
11452
|
+
ranked.target_id, ranked.target_kind, ${nextDepth},
|
|
11453
|
+
ranked.source_id, ranked.source_kind,
|
|
11454
|
+
(
|
|
11455
|
+
SELECT opposite.depth
|
|
11456
|
+
FROM ${context.workingTable} opposite
|
|
11457
|
+
WHERE opposite.graph_id = ${context.graphId}
|
|
11458
|
+
AND opposite.run_id = ${context.runId}
|
|
11459
|
+
AND opposite.side = ${oppositeSide}
|
|
11460
|
+
AND opposite.node_id = ranked.target_id
|
|
11461
|
+
AND opposite.node_kind = ranked.target_kind
|
|
11462
|
+
)
|
|
11463
|
+
FROM (
|
|
11464
|
+
SELECT expanded.*,
|
|
11465
|
+
ROW_NUMBER() OVER (
|
|
11466
|
+
PARTITION BY ${targetKind}, ${targetId}
|
|
11467
|
+
ORDER BY ${sourceId}, ${sourceKind}
|
|
11468
|
+
) AS candidate_rank
|
|
11469
|
+
FROM (${expansion}) expanded
|
|
11470
|
+
) ranked
|
|
11471
|
+
WHERE ranked.candidate_rank = 1
|
|
11472
|
+
AND NOT EXISTS (
|
|
11473
|
+
SELECT 1
|
|
11474
|
+
FROM ${context.workingTable} visited
|
|
11475
|
+
WHERE visited.graph_id = ${context.graphId}
|
|
11476
|
+
AND visited.run_id = ${context.runId}
|
|
11477
|
+
AND visited.side = ${side}
|
|
11478
|
+
AND visited.node_id = ranked.target_id
|
|
11479
|
+
AND visited.node_kind = ranked.target_kind
|
|
11480
|
+
)
|
|
11481
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11482
|
+
RETURNING node_id, node_kind, meeting_depth
|
|
11483
|
+
`)
|
|
11484
|
+
);
|
|
11485
|
+
insertedCount += rows.length;
|
|
11486
|
+
meeting = selectRoundMeeting(meeting, rows, nextDepth, maxHops);
|
|
11487
|
+
}
|
|
11488
|
+
return { insertedCount, meeting };
|
|
11489
|
+
}
|
|
11490
|
+
function selectRoundMeeting(currentMeeting, rows, nextDepth, maxHops) {
|
|
11491
|
+
let meeting = currentMeeting;
|
|
11492
|
+
for (const row of rows) {
|
|
11493
|
+
if (row.meeting_depth === null || row.meeting_depth === void 0) {
|
|
11494
|
+
continue;
|
|
11495
|
+
}
|
|
11496
|
+
const totalDepth = nextDepth + Number(row.meeting_depth);
|
|
11497
|
+
if (totalDepth > maxHops) continue;
|
|
11498
|
+
const candidate = {
|
|
11499
|
+
id: row.node_id,
|
|
11500
|
+
kind: row.node_kind,
|
|
11501
|
+
depth: totalDepth
|
|
11502
|
+
};
|
|
11503
|
+
if (meeting === void 0 || candidate.depth < meeting.depth || candidate.depth === meeting.depth && compareNodeIdentity(candidate, meeting) < 0) {
|
|
11504
|
+
meeting = candidate;
|
|
11505
|
+
}
|
|
11506
|
+
}
|
|
11507
|
+
return meeting;
|
|
11508
|
+
}
|
|
11509
|
+
async function expandReachableWorkingTableRound(context, side, currentDepth, nextDepth, direction) {
|
|
11510
|
+
let insertedCount = 0;
|
|
11511
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11512
|
+
const sourceFilter = sql`
|
|
11513
|
+
w.graph_id = ${context.graphId}
|
|
11514
|
+
AND w.run_id = ${context.runId}
|
|
11515
|
+
AND w.side = ${side}
|
|
11516
|
+
AND w.depth = ${currentDepth}
|
|
11517
|
+
`;
|
|
11518
|
+
const edgeExpansion = compileWorkingTableEdgeExpansion(
|
|
11519
|
+
context.operation,
|
|
11520
|
+
context.workingTable,
|
|
11521
|
+
sourceFilter,
|
|
11522
|
+
direction,
|
|
11523
|
+
edgeKinds
|
|
11524
|
+
);
|
|
11525
|
+
const rows = await context.backend.execute(
|
|
11526
|
+
asCompiledRowsSql(sql`
|
|
11527
|
+
INSERT INTO ${context.workingTable}
|
|
11528
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11529
|
+
predecessor_id, predecessor_kind)
|
|
11530
|
+
SELECT
|
|
11531
|
+
${context.graphId}, ${context.runId}, ${side},
|
|
11532
|
+
candidates.target_id, candidates.target_kind, ${nextDepth},
|
|
11533
|
+
NULL, NULL
|
|
11534
|
+
FROM (
|
|
11535
|
+
SELECT DISTINCT expanded.target_id, expanded.target_kind
|
|
11536
|
+
FROM (${edgeExpansion}) expanded
|
|
11537
|
+
) candidates
|
|
11538
|
+
JOIN ${context.operation.schema.nodesTable} n
|
|
11539
|
+
ON n.graph_id = ${context.graphId}
|
|
11540
|
+
AND n.id = candidates.target_id
|
|
11541
|
+
AND n.kind = candidates.target_kind
|
|
11542
|
+
WHERE ${context.operation.nodeTemporalFilter}
|
|
11543
|
+
AND NOT EXISTS (
|
|
11544
|
+
SELECT 1
|
|
11545
|
+
FROM ${context.workingTable} visited
|
|
11546
|
+
WHERE visited.graph_id = ${context.graphId}
|
|
11547
|
+
AND visited.run_id = ${context.runId}
|
|
11548
|
+
AND visited.side = ${side}
|
|
11549
|
+
AND visited.node_id = candidates.target_id
|
|
11550
|
+
AND visited.node_kind = candidates.target_kind
|
|
11551
|
+
)
|
|
11552
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11553
|
+
RETURNING node_id
|
|
11554
|
+
`)
|
|
11555
|
+
);
|
|
11556
|
+
insertedCount += rows.length;
|
|
11557
|
+
}
|
|
11558
|
+
return insertedCount;
|
|
11559
|
+
}
|
|
11560
|
+
async function readWorkingRows(context, side) {
|
|
11561
|
+
const sideFilter = side === void 0 ? sql`TRUE` : sql`side = ${side}`;
|
|
11562
|
+
return context.backend.execute(
|
|
11563
|
+
asCompiledRowsSql(sql`
|
|
11564
|
+
SELECT side, node_id, node_kind, depth,
|
|
11565
|
+
predecessor_id, predecessor_kind
|
|
11566
|
+
FROM ${context.workingTable}
|
|
11567
|
+
WHERE graph_id = ${context.graphId}
|
|
11568
|
+
AND run_id = ${context.runId}
|
|
11569
|
+
AND ${sideFilter}
|
|
11570
|
+
`)
|
|
11571
|
+
);
|
|
11572
|
+
}
|
|
11573
|
+
async function findReachableNodesInline(ctx, sourceId, maxHops, options) {
|
|
11574
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
11575
|
+
const sources = await fetchVisibleWorkingNodes(operation, [sourceId]);
|
|
11576
|
+
if (sources.length === 0) return [];
|
|
11577
|
+
const reached = new Map(
|
|
11578
|
+
sources.map((source) => [
|
|
11579
|
+
nodeIdentityKey(source),
|
|
11580
|
+
{ id: source.id, kind: source.kind, depth: 0 }
|
|
11581
|
+
])
|
|
11582
|
+
);
|
|
11583
|
+
let workingSet = sources;
|
|
11584
|
+
for (let depth = 1; depth <= maxHops && workingSet.length > 0; depth++) {
|
|
11585
|
+
const candidates = await expandInlineWorkingSet(
|
|
11586
|
+
operation,
|
|
11587
|
+
workingSet,
|
|
11588
|
+
operation.direction,
|
|
11589
|
+
reached
|
|
11590
|
+
);
|
|
11591
|
+
const nextWorkingSet = sortCandidates(candidates);
|
|
11592
|
+
for (const candidate of nextWorkingSet) {
|
|
11593
|
+
reached.set(nodeIdentityKey(candidate), {
|
|
11594
|
+
id: candidate.id,
|
|
11595
|
+
kind: candidate.kind,
|
|
11596
|
+
depth
|
|
11597
|
+
});
|
|
11598
|
+
}
|
|
11599
|
+
workingSet = nextWorkingSet;
|
|
11600
|
+
}
|
|
11601
|
+
return [...reached.values()].toSorted(
|
|
11602
|
+
(left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
|
|
11603
|
+
);
|
|
11604
|
+
});
|
|
11605
|
+
}
|
|
11606
|
+
async function findShortestPathInline(ctx, sourceId, targetId, maxHops, options) {
|
|
11607
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
11608
|
+
const visibleNodes = await fetchVisibleWorkingNodes(operation, [
|
|
11609
|
+
sourceId,
|
|
11610
|
+
targetId
|
|
11611
|
+
]);
|
|
11612
|
+
const sources = visibleNodes.filter((node) => node.id === sourceId);
|
|
11613
|
+
const targets = visibleNodes.filter((node) => node.id === targetId);
|
|
11614
|
+
if (sources.length === 0 || targets.length === 0) return;
|
|
11615
|
+
if (sourceId === targetId) return { nodes: [sources[0]], depth: 0 };
|
|
11616
|
+
const forwardVisited = createVisitedMap(sources);
|
|
11617
|
+
const reverseVisited = createVisitedMap(targets);
|
|
11618
|
+
let forwardWorkingSet = sources;
|
|
11619
|
+
let reverseWorkingSet = targets;
|
|
11620
|
+
let forwardDepth = 0;
|
|
11621
|
+
let reverseDepth = 0;
|
|
11622
|
+
while (forwardWorkingSet.length > 0 && reverseWorkingSet.length > 0 && forwardDepth + reverseDepth < maxHops) {
|
|
11623
|
+
const expandForward = forwardWorkingSet.length <= reverseWorkingSet.length;
|
|
11624
|
+
const activeVisited = expandForward ? forwardVisited : reverseVisited;
|
|
11625
|
+
const oppositeVisited = expandForward ? reverseVisited : forwardVisited;
|
|
11626
|
+
const currentWorkingSet = expandForward ? forwardWorkingSet : reverseWorkingSet;
|
|
11627
|
+
const direction = expandForward ? operation.direction : reverseDirection(operation.direction);
|
|
11628
|
+
const candidates = await expandInlineWorkingSet(
|
|
11629
|
+
operation,
|
|
11630
|
+
currentWorkingSet,
|
|
11631
|
+
direction,
|
|
11632
|
+
activeVisited
|
|
11633
|
+
);
|
|
11634
|
+
const nextDepth = (expandForward ? forwardDepth : reverseDepth) + 1;
|
|
11635
|
+
const nextWorkingSet = sortCandidates(candidates);
|
|
11636
|
+
for (const candidate of nextWorkingSet) {
|
|
11637
|
+
activeVisited.set(nodeIdentityKey(candidate), {
|
|
11638
|
+
id: candidate.id,
|
|
11639
|
+
kind: candidate.kind,
|
|
11640
|
+
depth: nextDepth,
|
|
11641
|
+
parentKey: nodeIdentityKey({
|
|
11642
|
+
id: candidate.parentId,
|
|
11643
|
+
kind: candidate.parentKind
|
|
11644
|
+
})
|
|
11645
|
+
});
|
|
11646
|
+
}
|
|
11647
|
+
const meetingKey = findMeetingNodeKey(
|
|
11648
|
+
nextWorkingSet,
|
|
11649
|
+
activeVisited,
|
|
11650
|
+
oppositeVisited
|
|
11651
|
+
);
|
|
11652
|
+
if (meetingKey !== void 0) {
|
|
11653
|
+
return buildShortestPath(meetingKey, forwardVisited, reverseVisited);
|
|
11654
|
+
}
|
|
11655
|
+
if (expandForward) {
|
|
11656
|
+
forwardWorkingSet = nextWorkingSet;
|
|
11657
|
+
forwardDepth = nextDepth;
|
|
11658
|
+
} else {
|
|
11659
|
+
reverseWorkingSet = nextWorkingSet;
|
|
11660
|
+
reverseDepth = nextDepth;
|
|
11661
|
+
}
|
|
11662
|
+
}
|
|
11663
|
+
return;
|
|
11664
|
+
});
|
|
11665
|
+
}
|
|
11666
|
+
async function expandInlineWorkingSet(operation, workingSet, direction, visited) {
|
|
11667
|
+
return reduceExpandedWorkingSet(
|
|
11668
|
+
operation,
|
|
11669
|
+
workingSet,
|
|
11670
|
+
direction,
|
|
11671
|
+
(existing, expansion) => selectPredecessor(existing, expansion, visited)
|
|
11672
|
+
);
|
|
11673
|
+
}
|
|
11674
|
+
function selectPredecessor(existing, expansion, visited) {
|
|
11675
|
+
if (visited.has(nodeIdentityKey(expansion.target))) return existing;
|
|
11676
|
+
if (existing !== void 0 && compareNodeIdentity(expansion.source, {
|
|
11677
|
+
id: existing.parentId,
|
|
11678
|
+
kind: existing.parentKind
|
|
11679
|
+
}) >= 0) {
|
|
11680
|
+
return existing;
|
|
11681
|
+
}
|
|
11682
|
+
return {
|
|
11683
|
+
id: expansion.target.id,
|
|
11684
|
+
kind: expansion.target.kind,
|
|
11685
|
+
parentId: expansion.source.id,
|
|
11686
|
+
parentKind: expansion.source.kind
|
|
11687
|
+
};
|
|
11688
|
+
}
|
|
11689
|
+
function sortCandidates(candidates) {
|
|
11690
|
+
return [...candidates.values()].toSorted(
|
|
11691
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
11692
|
+
);
|
|
11693
|
+
}
|
|
11694
|
+
function findMeetingNodeKey(candidates, activeVisited, oppositeVisited) {
|
|
11695
|
+
let best;
|
|
11696
|
+
for (const candidate of candidates) {
|
|
11697
|
+
const key = nodeIdentityKey(candidate);
|
|
11698
|
+
const active = activeVisited.get(key);
|
|
11699
|
+
const opposite = oppositeVisited.get(key);
|
|
11700
|
+
if (active === void 0 || opposite === void 0) continue;
|
|
11701
|
+
const meeting = {
|
|
11702
|
+
key,
|
|
11703
|
+
node: { id: candidate.id, kind: candidate.kind },
|
|
11704
|
+
depth: active.depth + opposite.depth
|
|
11705
|
+
};
|
|
11706
|
+
if (best === void 0 || meeting.depth < best.depth || meeting.depth === best.depth && compareNodeIdentity(meeting.node, best.node) < 0) {
|
|
11707
|
+
best = meeting;
|
|
11708
|
+
}
|
|
11709
|
+
}
|
|
11710
|
+
return best?.key;
|
|
11711
|
+
}
|
|
11712
|
+
function buildShortestPath(meetingKey, forwardVisited, reverseVisited) {
|
|
11713
|
+
const forwardKeys = [];
|
|
11714
|
+
let cursor = meetingKey;
|
|
11715
|
+
while (cursor !== void 0) {
|
|
11716
|
+
forwardKeys.push(cursor);
|
|
11717
|
+
cursor = forwardVisited.get(cursor)?.parentKey;
|
|
11718
|
+
}
|
|
11719
|
+
forwardKeys.reverse();
|
|
11720
|
+
const pathKeys = [...forwardKeys];
|
|
11721
|
+
cursor = reverseVisited.get(meetingKey)?.parentKey;
|
|
11722
|
+
while (cursor !== void 0) {
|
|
11723
|
+
pathKeys.push(cursor);
|
|
11724
|
+
cursor = reverseVisited.get(cursor)?.parentKey;
|
|
11725
|
+
}
|
|
11726
|
+
const nodes = pathKeys.map((key) => {
|
|
11727
|
+
const node = forwardVisited.get(key) ?? reverseVisited.get(key);
|
|
11728
|
+
return { id: node?.id ?? "", kind: node?.kind ?? "" };
|
|
11729
|
+
});
|
|
11730
|
+
return { nodes, depth: nodes.length - 1 };
|
|
11731
|
+
}
|
|
11732
|
+
function createVisitedMap(nodes) {
|
|
11733
|
+
return new Map(
|
|
11734
|
+
nodes.map((node) => [
|
|
11735
|
+
nodeIdentityKey(node),
|
|
11736
|
+
{ id: node.id, kind: node.kind, depth: 0, parentKey: void 0 }
|
|
11737
|
+
])
|
|
11738
|
+
);
|
|
11739
|
+
}
|
|
11740
|
+
function createVisitedMapFromRows(rows, side) {
|
|
11741
|
+
return new Map(
|
|
11742
|
+
rows.filter((row) => row.side === side).map((row) => {
|
|
11743
|
+
const parentKey = nodeIdentityKeyFromRow(
|
|
11744
|
+
row.predecessor_id,
|
|
11745
|
+
row.predecessor_kind
|
|
11746
|
+
);
|
|
11747
|
+
const node = {
|
|
11748
|
+
id: row.node_id,
|
|
11749
|
+
kind: row.node_kind,
|
|
11750
|
+
depth: Number(row.depth),
|
|
11751
|
+
parentKey
|
|
11752
|
+
};
|
|
11753
|
+
return [nodeIdentityKey(node), node];
|
|
11754
|
+
})
|
|
11755
|
+
);
|
|
11756
|
+
}
|
|
11757
|
+
function reverseDirection(direction) {
|
|
11758
|
+
switch (direction) {
|
|
11759
|
+
case "out": {
|
|
11760
|
+
return "in";
|
|
11761
|
+
}
|
|
11762
|
+
case "in": {
|
|
11763
|
+
return "out";
|
|
11764
|
+
}
|
|
11765
|
+
case "both": {
|
|
11766
|
+
return "both";
|
|
11767
|
+
}
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
|
|
11771
|
+
// src/store/algorithms/reachable.ts
|
|
11772
|
+
async function executeReachable(ctx, sourceId, options) {
|
|
11773
|
+
assertEdgeKinds(options.edges);
|
|
11774
|
+
const maxHops = resolveMaxHops(
|
|
11775
|
+
options.maxHops,
|
|
11776
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11777
|
+
"maxHops"
|
|
11778
|
+
);
|
|
11779
|
+
const reached = await findReachableNodes(ctx, sourceId, maxHops, options);
|
|
11780
|
+
return options.excludeSource === true ? reached.filter((node) => node.id !== sourceId) : reached;
|
|
11781
|
+
}
|
|
11782
|
+
async function executeCanReach(ctx, sourceId, targetId, options) {
|
|
11783
|
+
assertEdgeKinds(options.edges);
|
|
11784
|
+
const maxHops = resolveMaxHops(
|
|
11785
|
+
options.maxHops,
|
|
11786
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11787
|
+
"maxHops"
|
|
11788
|
+
);
|
|
11789
|
+
return await findShortestPath(ctx, sourceId, targetId, maxHops, options) !== void 0;
|
|
11790
|
+
}
|
|
11791
|
+
async function executeNeighbors(ctx, sourceId, options) {
|
|
11792
|
+
const depth = resolveMaxHops(options.depth, DEFAULT_NEIGHBOR_DEPTH, "depth");
|
|
11793
|
+
return executeReachable(ctx, sourceId, {
|
|
10624
11794
|
edges: options.edges,
|
|
10625
11795
|
maxHops: depth,
|
|
10626
11796
|
direction: options.direction ?? "out",
|
|
10627
11797
|
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
10628
|
-
...options
|
|
10629
|
-
|
|
11798
|
+
...pickTemporalOptions(options),
|
|
11799
|
+
...options.workingMemory !== void 0 && {
|
|
11800
|
+
workingMemory: options.workingMemory
|
|
11801
|
+
},
|
|
11802
|
+
excludeSource: true
|
|
11803
|
+
});
|
|
11804
|
+
}
|
|
11805
|
+
|
|
11806
|
+
// src/store/algorithms/shortest-path.ts
|
|
11807
|
+
async function executeShortestPath(ctx, sourceId, targetId, options) {
|
|
11808
|
+
assertEdgeKinds(options.edges);
|
|
11809
|
+
const maxHops = resolveMaxHops(
|
|
11810
|
+
options.maxHops,
|
|
11811
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11812
|
+
"maxHops"
|
|
11813
|
+
);
|
|
11814
|
+
return findShortestPath(ctx, sourceId, targetId, maxHops, options);
|
|
11815
|
+
}
|
|
11816
|
+
var DEFAULT_WCC_MAX_ITERATIONS = 1e3;
|
|
11817
|
+
async function executeWeaklyConnectedComponents(ctx, options) {
|
|
11818
|
+
assertEdgeKinds(options.edges);
|
|
11819
|
+
assertWeaklyConnectedComponentsSupported(ctx);
|
|
11820
|
+
const maxIterations = resolveMaxIterations(
|
|
11821
|
+
options.maxIterations,
|
|
11822
|
+
DEFAULT_WCC_MAX_ITERATIONS,
|
|
11823
|
+
"weaklyConnectedComponents"
|
|
11824
|
+
);
|
|
11825
|
+
const nodeKinds = options.nodeKinds === void 0 ? void 0 : [...new Set(options.nodeKinds)].toSorted(
|
|
11826
|
+
(left, right) => compareCodePoints(left, right)
|
|
11827
|
+
);
|
|
11828
|
+
const traversalOptions = {
|
|
11829
|
+
edges: options.edges,
|
|
11830
|
+
direction: "both",
|
|
11831
|
+
...pickTemporalOptions(options),
|
|
11832
|
+
...options.workingMemory === void 0 ? {} : { workingMemory: options.workingMemory }
|
|
11833
|
+
};
|
|
11834
|
+
return runIterativeGraphOperation(ctx, traversalOptions, {
|
|
11835
|
+
algorithm: "weaklyConnectedComponents",
|
|
11836
|
+
maxIterations,
|
|
11837
|
+
createWorkingTable: createWorkingTable2,
|
|
11838
|
+
initialize: (context) => initializeWorkingTable(context, nodeKinds),
|
|
11839
|
+
runRound: runLabelPropagationRound,
|
|
11840
|
+
hasConverged(state) {
|
|
11841
|
+
return state.changedCount === 0;
|
|
11842
|
+
},
|
|
11843
|
+
extractResult: extractMemberships
|
|
11844
|
+
});
|
|
11845
|
+
}
|
|
11846
|
+
function assertWeaklyConnectedComponentsSupported(ctx) {
|
|
11847
|
+
if (ctx.backend.capabilities.graphAnalytics?.supported === true && ctx.backend.capabilities.windowFunctions) {
|
|
11848
|
+
return;
|
|
11849
|
+
}
|
|
11850
|
+
throw new UnsupportedBackendCapabilityError(
|
|
11851
|
+
"weaklyConnectedComponents",
|
|
11852
|
+
"graphAnalytics",
|
|
11853
|
+
{
|
|
11854
|
+
dialect: ctx.backend.dialect,
|
|
11855
|
+
supported: ctx.backend.capabilities.graphAnalytics?.supported === true,
|
|
11856
|
+
windowFunctions: ctx.backend.capabilities.windowFunctions
|
|
11857
|
+
},
|
|
11858
|
+
"Use a built-in transactional SQLite/PostgreSQL backend, or declare graphAnalytics support on a compatible custom backend."
|
|
11859
|
+
);
|
|
11860
|
+
}
|
|
11861
|
+
function createWorkingTable2(context) {
|
|
11862
|
+
return sql`
|
|
11863
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
11864
|
+
graph_id TEXT NOT NULL,
|
|
11865
|
+
run_id TEXT NOT NULL,
|
|
11866
|
+
node_id TEXT NOT NULL,
|
|
11867
|
+
node_kind TEXT NOT NULL,
|
|
11868
|
+
label_id TEXT NOT NULL,
|
|
11869
|
+
label_kind TEXT NOT NULL,
|
|
11870
|
+
next_label_id TEXT NOT NULL,
|
|
11871
|
+
next_label_kind TEXT NOT NULL,
|
|
11872
|
+
improved_round INTEGER NOT NULL,
|
|
11873
|
+
PRIMARY KEY (graph_id, run_id, node_kind, node_id)
|
|
11874
|
+
)
|
|
11875
|
+
`;
|
|
11876
|
+
}
|
|
11877
|
+
async function initializeWorkingTable(context, nodeKinds) {
|
|
11878
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11879
|
+
const nodeKindFilter = nodeKinds === void 0 ? sql`TRUE` : compileKindFilter(sql.raw("n.kind"), nodeKinds);
|
|
11880
|
+
await context.executeTemporary(sql`
|
|
11881
|
+
INSERT INTO ${workingTable}
|
|
11882
|
+
(graph_id, run_id, node_id, node_kind, label_id, label_kind,
|
|
11883
|
+
next_label_id, next_label_kind, improved_round)
|
|
11884
|
+
SELECT
|
|
11885
|
+
${graphId}, ${runId}, n.id, n.kind, n.id, n.kind, n.id, n.kind, 0
|
|
11886
|
+
FROM ${operation.schema.nodesTable} n
|
|
11887
|
+
WHERE n.graph_id = ${graphId}
|
|
11888
|
+
AND ${nodeKindFilter}
|
|
11889
|
+
AND ${operation.nodeTemporalFilter}
|
|
11890
|
+
`);
|
|
11891
|
+
await context.executeTemporary(sql`
|
|
11892
|
+
CREATE INDEX ${frontierIndexIdentifier(context)}
|
|
11893
|
+
ON ${workingTable} (graph_id, run_id, improved_round)
|
|
11894
|
+
`);
|
|
11895
|
+
const rows = await context.backend.execute(
|
|
11896
|
+
asCompiledRowsSql(sql`
|
|
11897
|
+
SELECT COUNT(*) AS count
|
|
11898
|
+
FROM ${workingTable}
|
|
11899
|
+
WHERE graph_id = ${graphId} AND run_id = ${runId}
|
|
11900
|
+
`)
|
|
11901
|
+
);
|
|
11902
|
+
const workingTableSize = Number(rows[0]?.count ?? 0);
|
|
11903
|
+
return { changedCount: workingTableSize, workingTableSize };
|
|
11904
|
+
}
|
|
11905
|
+
async function runLabelPropagationRound(context, state, iteration) {
|
|
11906
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11907
|
+
await propagateChunkLabels(context, edgeKinds, iteration);
|
|
11908
|
+
}
|
|
11909
|
+
const changedRows = await applyNextLabels(context, iteration);
|
|
11910
|
+
return {
|
|
11911
|
+
changedCount: changedRows.length,
|
|
11912
|
+
workingTableSize: state.workingTableSize
|
|
11913
|
+
};
|
|
11914
|
+
}
|
|
11915
|
+
async function propagateChunkLabels(context, edgeKinds, iteration) {
|
|
11916
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11917
|
+
const expansion = compileWorkingTableEdgeExpansion(
|
|
11918
|
+
operation,
|
|
11919
|
+
workingTable,
|
|
11920
|
+
sql`
|
|
11921
|
+
w.graph_id = ${graphId}
|
|
11922
|
+
AND w.run_id = ${runId}
|
|
11923
|
+
AND w.improved_round = ${iteration - 1}
|
|
11924
|
+
`,
|
|
11925
|
+
"both",
|
|
11926
|
+
edgeKinds,
|
|
11927
|
+
sql`w.label_id AS source_label_id, w.label_kind AS source_label_kind`
|
|
11928
|
+
);
|
|
11929
|
+
const candidateId = operation.ctx.dialect.binaryText(sql`ranked.label_id`);
|
|
11930
|
+
const candidateKind = operation.ctx.dialect.binaryText(
|
|
11931
|
+
sql`ranked.label_kind`
|
|
11932
|
+
);
|
|
11933
|
+
const currentId = operation.ctx.dialect.binaryText(sql`target.next_label_id`);
|
|
11934
|
+
const currentKind = operation.ctx.dialect.binaryText(
|
|
11935
|
+
sql`target.next_label_kind`
|
|
11936
|
+
);
|
|
11937
|
+
const sourceId = operation.ctx.dialect.binaryText(sql`source.label_id`);
|
|
11938
|
+
const sourceKind = operation.ctx.dialect.binaryText(sql`source.label_kind`);
|
|
11939
|
+
const targetId = operation.ctx.dialect.binaryText(sql`source.target_id`);
|
|
11940
|
+
const targetKind = operation.ctx.dialect.binaryText(sql`source.target_kind`);
|
|
11941
|
+
await context.executeTemporary(sql`
|
|
11942
|
+
WITH candidates AS (
|
|
11943
|
+
SELECT
|
|
11944
|
+
expanded.target_id,
|
|
11945
|
+
expanded.target_kind,
|
|
11946
|
+
expanded.source_label_id AS label_id,
|
|
11947
|
+
expanded.source_label_kind AS label_kind
|
|
11948
|
+
FROM (${expansion}) expanded
|
|
11949
|
+
JOIN ${workingTable} scoped_target
|
|
11950
|
+
ON scoped_target.graph_id = ${graphId}
|
|
11951
|
+
AND scoped_target.run_id = ${runId}
|
|
11952
|
+
AND scoped_target.node_id = expanded.target_id
|
|
11953
|
+
AND scoped_target.node_kind = expanded.target_kind
|
|
11954
|
+
), ranked AS (
|
|
11955
|
+
SELECT
|
|
11956
|
+
target_id,
|
|
11957
|
+
target_kind,
|
|
11958
|
+
label_id,
|
|
11959
|
+
label_kind,
|
|
11960
|
+
ROW_NUMBER() OVER (
|
|
11961
|
+
PARTITION BY ${targetKind}, ${targetId}
|
|
11962
|
+
ORDER BY ${sourceId}, ${sourceKind}
|
|
11963
|
+
) AS candidate_rank
|
|
11964
|
+
FROM candidates source
|
|
11965
|
+
)
|
|
11966
|
+
UPDATE ${workingTable} AS target
|
|
11967
|
+
SET next_label_id = ranked.label_id,
|
|
11968
|
+
next_label_kind = ranked.label_kind
|
|
11969
|
+
FROM ranked
|
|
11970
|
+
WHERE target.graph_id = ${graphId}
|
|
11971
|
+
AND target.run_id = ${runId}
|
|
11972
|
+
AND target.node_id = ranked.target_id
|
|
11973
|
+
AND target.node_kind = ranked.target_kind
|
|
11974
|
+
AND ranked.candidate_rank = 1
|
|
11975
|
+
AND (
|
|
11976
|
+
${candidateId} < ${currentId}
|
|
11977
|
+
OR (
|
|
11978
|
+
${candidateId} = ${currentId}
|
|
11979
|
+
AND ${candidateKind} < ${currentKind}
|
|
11980
|
+
)
|
|
11981
|
+
)
|
|
11982
|
+
`);
|
|
11983
|
+
}
|
|
11984
|
+
function applyNextLabels(context, iteration) {
|
|
11985
|
+
return context.backend.execute(
|
|
11986
|
+
asCompiledRowsSql(sql`
|
|
11987
|
+
UPDATE ${context.workingTable}
|
|
11988
|
+
SET label_id = next_label_id,
|
|
11989
|
+
label_kind = next_label_kind,
|
|
11990
|
+
improved_round = ${iteration}
|
|
11991
|
+
WHERE graph_id = ${context.graphId}
|
|
11992
|
+
AND run_id = ${context.runId}
|
|
11993
|
+
AND (
|
|
11994
|
+
label_id <> next_label_id OR label_kind <> next_label_kind
|
|
11995
|
+
)
|
|
11996
|
+
RETURNING node_id
|
|
11997
|
+
`)
|
|
11998
|
+
);
|
|
11999
|
+
}
|
|
12000
|
+
async function extractMemberships(context) {
|
|
12001
|
+
const { operation } = context;
|
|
12002
|
+
const componentId = operation.ctx.dialect.binaryText(sql`label_id`);
|
|
12003
|
+
const componentKind = operation.ctx.dialect.binaryText(sql`label_kind`);
|
|
12004
|
+
const nodeId = operation.ctx.dialect.binaryText(sql`node_id`);
|
|
12005
|
+
const nodeKind = operation.ctx.dialect.binaryText(sql`node_kind`);
|
|
12006
|
+
const rows = await context.backend.execute(
|
|
12007
|
+
asCompiledRowsSql(sql`
|
|
12008
|
+
SELECT
|
|
12009
|
+
node_id,
|
|
12010
|
+
node_kind,
|
|
12011
|
+
label_id AS component_id,
|
|
12012
|
+
label_kind AS component_kind,
|
|
12013
|
+
COUNT(*) OVER (
|
|
12014
|
+
PARTITION BY ${componentKind}, ${componentId}
|
|
12015
|
+
) AS component_size
|
|
12016
|
+
FROM ${context.workingTable}
|
|
12017
|
+
WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
|
|
12018
|
+
ORDER BY ${componentId}, ${componentKind}, ${nodeId}, ${nodeKind}
|
|
12019
|
+
`)
|
|
12020
|
+
);
|
|
12021
|
+
return rows.map((row) => ({
|
|
12022
|
+
id: row.node_id,
|
|
12023
|
+
kind: row.node_kind,
|
|
12024
|
+
componentId: row.component_id,
|
|
12025
|
+
componentKind: row.component_kind,
|
|
12026
|
+
size: Number(row.component_size)
|
|
12027
|
+
}));
|
|
12028
|
+
}
|
|
12029
|
+
var DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS = 1e3;
|
|
12030
|
+
var WEIGHTED_ALGORITHM_NAME = "weightedShortestPath";
|
|
12031
|
+
var MAX_EDGE_WEIGHT = Number.MAX_VALUE / 2 ** 64;
|
|
12032
|
+
var MIN_EDGE_WEIGHT = Number.MIN_VALUE;
|
|
12033
|
+
async function executeWeightedShortestPath(ctx, sourceId, targetId, options) {
|
|
12034
|
+
assertEdgeKinds(options.edges);
|
|
12035
|
+
assertWeightOptions(options.weightProperty, options.defaultWeight);
|
|
12036
|
+
const maxIterations = resolveMaxIterations(
|
|
12037
|
+
options.maxIterations,
|
|
12038
|
+
DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS,
|
|
12039
|
+
WEIGHTED_ALGORITHM_NAME
|
|
12040
|
+
);
|
|
12041
|
+
const traversalOptions = options;
|
|
12042
|
+
if (sourceId === targetId) {
|
|
12043
|
+
return findWeightedSelfPath(ctx, sourceId, traversalOptions);
|
|
12044
|
+
}
|
|
12045
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
12046
|
+
return findWeightedShortestPathInWorkingTable(
|
|
12047
|
+
ctx,
|
|
12048
|
+
sourceId,
|
|
12049
|
+
targetId,
|
|
12050
|
+
maxIterations,
|
|
12051
|
+
traversalOptions
|
|
12052
|
+
);
|
|
12053
|
+
}
|
|
12054
|
+
return findWeightedShortestPathInline(
|
|
12055
|
+
ctx,
|
|
12056
|
+
sourceId,
|
|
12057
|
+
targetId,
|
|
12058
|
+
maxIterations,
|
|
12059
|
+
traversalOptions
|
|
12060
|
+
);
|
|
12061
|
+
}
|
|
12062
|
+
function assertWeightOptions(weightProperty, defaultWeight) {
|
|
12063
|
+
if (typeof weightProperty !== "string" || weightProperty.length === 0) {
|
|
12064
|
+
throw new ConfigurationError(
|
|
12065
|
+
`${WEIGHTED_ALGORITHM_NAME} weightProperty must be a non-empty string.`,
|
|
12066
|
+
{ weightProperty }
|
|
12067
|
+
);
|
|
12068
|
+
}
|
|
12069
|
+
if (defaultWeight !== void 0 && (!Number.isFinite(defaultWeight) || defaultWeight < 0 || defaultWeight > MAX_EDGE_WEIGHT)) {
|
|
12070
|
+
throw new ConfigurationError(
|
|
12071
|
+
`${WEIGHTED_ALGORITHM_NAME} defaultWeight must be a non-negative number no greater than ${MAX_EDGE_WEIGHT}, got ${String(defaultWeight)}.`,
|
|
12072
|
+
{ defaultWeight, maximum: MAX_EDGE_WEIGHT }
|
|
12073
|
+
);
|
|
12074
|
+
}
|
|
12075
|
+
}
|
|
12076
|
+
async function assertValidEdgeWeights(operation, weightProperty, defaultWeight) {
|
|
12077
|
+
const { dialect } = operation.ctx;
|
|
12078
|
+
const pointer = jsonPointer([weightProperty]);
|
|
12079
|
+
const propsColumn = sql.raw("e.props");
|
|
12080
|
+
const isNumber = dialect.jsonPathIsNumber(propsColumn, pointer);
|
|
12081
|
+
const isMissing = dialect.jsonPathIsNull(propsColumn, pointer);
|
|
12082
|
+
const weightText = dialect.jsonExtractText(propsColumn, pointer);
|
|
12083
|
+
const weightNumber = sql`CASE WHEN ${isNumber} THEN ${dialect.jsonExtractNumber(propsColumn, pointer)} END`;
|
|
12084
|
+
const missingViolation = defaultWeight === void 0 ? sql`audited.is_missing = 1` : dialect.booleanLiteral(false);
|
|
12085
|
+
const negativeViolation = sql`audited.is_number = 1 AND audited.weight_number < 0`;
|
|
12086
|
+
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)))`;
|
|
12087
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
12088
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
|
|
12089
|
+
const rows = await operation.backend.execute(
|
|
12090
|
+
asCompiledRowsSql(sql`
|
|
12091
|
+
SELECT audited.edge_id, audited.edge_kind, audited.weight_text,
|
|
12092
|
+
audited.is_number, audited.is_missing
|
|
12093
|
+
FROM (
|
|
12094
|
+
SELECT e.id AS edge_id, e.kind AS edge_kind,
|
|
12095
|
+
${weightText} AS weight_text,
|
|
12096
|
+
${weightNumber} AS weight_number,
|
|
12097
|
+
CASE WHEN ${isNumber} THEN 1 ELSE 0 END AS is_number,
|
|
12098
|
+
CASE WHEN ${isMissing} THEN 1 ELSE 0 END AS is_missing
|
|
12099
|
+
FROM ${operation.schema.edgesTable} e
|
|
12100
|
+
WHERE e.graph_id = ${operation.ctx.graphId}
|
|
12101
|
+
AND ${edgeKindFilter}
|
|
12102
|
+
AND ${operation.edgeTemporalFilter}
|
|
12103
|
+
) audited
|
|
12104
|
+
WHERE (${missingViolation})
|
|
12105
|
+
OR (audited.is_missing = 0 AND audited.is_number = 0)
|
|
12106
|
+
OR (${negativeViolation})
|
|
12107
|
+
OR (${outOfRangeViolation})
|
|
12108
|
+
ORDER BY ${dialect.binaryText(sql`audited.edge_id`)}
|
|
12109
|
+
LIMIT 1
|
|
12110
|
+
`)
|
|
12111
|
+
);
|
|
12112
|
+
const row = rows[0];
|
|
12113
|
+
if (row !== void 0) {
|
|
12114
|
+
throw createInvalidEdgeWeightError(row, weightProperty);
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
12118
|
+
function createInvalidEdgeWeightError(row, weightProperty) {
|
|
12119
|
+
const reason = resolveInvalidWeightReason(row);
|
|
12120
|
+
const value = reason === "missing" || row.weight_text === null ? void 0 : formatWeightValue(row.weight_text);
|
|
12121
|
+
return new InvalidEdgeWeightError({
|
|
12122
|
+
edgeId: row.edge_id,
|
|
12123
|
+
edgeKind: row.edge_kind,
|
|
12124
|
+
property: weightProperty,
|
|
12125
|
+
reason,
|
|
12126
|
+
...value === void 0 ? {} : { value }
|
|
12127
|
+
});
|
|
12128
|
+
}
|
|
12129
|
+
function resolveInvalidWeightReason(row) {
|
|
12130
|
+
if (flagIsSet(row.is_missing)) return "missing";
|
|
12131
|
+
if (!flagIsSet(row.is_number)) return "non_numeric";
|
|
12132
|
+
return Number(row.weight_text) < 0 ? "negative" : "out_of_range";
|
|
12133
|
+
}
|
|
12134
|
+
function flagIsSet(value) {
|
|
12135
|
+
return Number(value) !== 0;
|
|
12136
|
+
}
|
|
12137
|
+
function formatWeightValue(value) {
|
|
12138
|
+
if (typeof value === "string") return value;
|
|
12139
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
12140
|
+
return String(value);
|
|
12141
|
+
}
|
|
12142
|
+
return JSON.stringify(value);
|
|
12143
|
+
}
|
|
12144
|
+
async function findWeightedShortestPathInWorkingTable(ctx, sourceId, targetId, maxIterations, options) {
|
|
12145
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
12146
|
+
algorithm: WEIGHTED_ALGORITHM_NAME,
|
|
12147
|
+
maxIterations,
|
|
12148
|
+
createWorkingTable: createWorkingTable3,
|
|
12149
|
+
async initialize(context) {
|
|
12150
|
+
await assertValidEdgeWeights(
|
|
12151
|
+
context.operation,
|
|
12152
|
+
options.weightProperty,
|
|
12153
|
+
options.defaultWeight
|
|
12154
|
+
);
|
|
12155
|
+
if (!await hasVisibleNode(context.operation, targetId)) {
|
|
12156
|
+
return {
|
|
12157
|
+
frontierCount: 0,
|
|
12158
|
+
workingTableSize: 0,
|
|
12159
|
+
bestTargetDistance: void 0
|
|
12160
|
+
};
|
|
12161
|
+
}
|
|
12162
|
+
await context.executeTemporary(sql`
|
|
12163
|
+
CREATE INDEX ${frontierIndexIdentifier(context)}
|
|
12164
|
+
ON ${context.workingTable} (graph_id, run_id, improved_round)
|
|
12165
|
+
`);
|
|
12166
|
+
const seeded = await seedDistances(context, sourceId);
|
|
12167
|
+
return {
|
|
12168
|
+
frontierCount: seeded.length,
|
|
12169
|
+
workingTableSize: seeded.length,
|
|
12170
|
+
// Seeds carry the source id, never the target's — the self-path
|
|
12171
|
+
// case is short-circuited before this plan runs.
|
|
12172
|
+
bestTargetDistance: void 0
|
|
12173
|
+
};
|
|
12174
|
+
},
|
|
12175
|
+
async runRound(context, state, iteration) {
|
|
12176
|
+
const round = await relaxFrontierRound(
|
|
12177
|
+
context,
|
|
12178
|
+
targetId,
|
|
12179
|
+
iteration,
|
|
12180
|
+
state.bestTargetDistance
|
|
12181
|
+
);
|
|
12182
|
+
return {
|
|
12183
|
+
frontierCount: round.frontierCount,
|
|
12184
|
+
// Overcounts by re-improved existing rows (RETURNING includes
|
|
12185
|
+
// updates); acceptable because the size only paces the substrate's
|
|
12186
|
+
// ANALYZE refresh, where too-early merely refreshes stats sooner.
|
|
12187
|
+
workingTableSize: state.workingTableSize + round.frontierCount,
|
|
12188
|
+
bestTargetDistance: round.bestTargetDistance
|
|
12189
|
+
};
|
|
10630
12190
|
},
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
recordedAsOf: options.recordedAsOf
|
|
12191
|
+
hasConverged(state) {
|
|
12192
|
+
return state.frontierCount === 0;
|
|
10634
12193
|
},
|
|
10635
|
-
|
|
12194
|
+
async extractResult(context) {
|
|
12195
|
+
return extractPathFromWorkingTable(context, targetId, maxIterations);
|
|
12196
|
+
}
|
|
10636
12197
|
});
|
|
10637
12198
|
}
|
|
10638
|
-
async function
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
12199
|
+
async function hasVisibleNode(operation, nodeId) {
|
|
12200
|
+
const rows = await operation.backend.execute(
|
|
12201
|
+
asCompiledRowsSql(sql`
|
|
12202
|
+
SELECT n.id FROM ${operation.schema.nodesTable} n
|
|
12203
|
+
WHERE n.graph_id = ${operation.ctx.graphId}
|
|
12204
|
+
AND n.id = ${nodeId}
|
|
12205
|
+
AND ${operation.nodeTemporalFilter}
|
|
12206
|
+
LIMIT 1
|
|
12207
|
+
`)
|
|
10644
12208
|
);
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
12209
|
+
return rows.length > 0;
|
|
12210
|
+
}
|
|
12211
|
+
function createWorkingTable3(context) {
|
|
12212
|
+
return sql`
|
|
12213
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
12214
|
+
graph_id TEXT NOT NULL,
|
|
12215
|
+
run_id TEXT NOT NULL,
|
|
12216
|
+
node_id TEXT NOT NULL,
|
|
12217
|
+
node_kind TEXT NOT NULL,
|
|
12218
|
+
distance DOUBLE PRECISION NOT NULL,
|
|
12219
|
+
hops INTEGER NOT NULL,
|
|
12220
|
+
predecessor_id TEXT,
|
|
12221
|
+
predecessor_kind TEXT,
|
|
12222
|
+
improved_round INTEGER NOT NULL,
|
|
12223
|
+
PRIMARY KEY (graph_id, run_id, node_kind, node_id)
|
|
12224
|
+
)
|
|
12225
|
+
`;
|
|
12226
|
+
}
|
|
12227
|
+
async function seedDistances(context, sourceId) {
|
|
12228
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12229
|
+
return context.backend.execute(
|
|
12230
|
+
asCompiledRowsSql(sql`
|
|
12231
|
+
INSERT INTO ${workingTable}
|
|
12232
|
+
(graph_id, run_id, node_id, node_kind, distance, hops,
|
|
12233
|
+
predecessor_id, predecessor_kind, improved_round)
|
|
12234
|
+
SELECT ${graphId}, ${runId}, n.id, n.kind, 0, 0, NULL, NULL, 0
|
|
12235
|
+
FROM ${operation.schema.nodesTable} n
|
|
12236
|
+
WHERE n.graph_id = ${graphId}
|
|
12237
|
+
AND n.id = ${sourceId}
|
|
12238
|
+
AND ${operation.nodeTemporalFilter}
|
|
12239
|
+
ON CONFLICT (graph_id, run_id, node_kind, node_id) DO NOTHING
|
|
12240
|
+
RETURNING node_id
|
|
12241
|
+
`)
|
|
12242
|
+
);
|
|
12243
|
+
}
|
|
12244
|
+
async function relaxFrontierRound(context, targetId, iteration, roundBound) {
|
|
12245
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12246
|
+
const { dialect } = operation.ctx;
|
|
12247
|
+
let frontierCount = 0;
|
|
12248
|
+
let bestTargetDistance = roundBound;
|
|
12249
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
12250
|
+
const sourceFilter = sql`
|
|
12251
|
+
w.graph_id = ${graphId}
|
|
12252
|
+
AND w.run_id = ${runId}
|
|
12253
|
+
AND w.improved_round = ${iteration - 1}
|
|
12254
|
+
`;
|
|
12255
|
+
const expansion = compileWorkingTableExpansion(
|
|
12256
|
+
operation,
|
|
12257
|
+
workingTable,
|
|
12258
|
+
sourceFilter,
|
|
12259
|
+
operation.direction,
|
|
12260
|
+
edgeKinds
|
|
12261
|
+
);
|
|
12262
|
+
const rows = await context.backend.execute(
|
|
12263
|
+
asCompiledRowsSql(sql`
|
|
12264
|
+
INSERT INTO ${workingTable}
|
|
12265
|
+
(graph_id, run_id, node_id, node_kind, distance, hops,
|
|
12266
|
+
predecessor_id, predecessor_kind, improved_round)
|
|
12267
|
+
SELECT
|
|
12268
|
+
${graphId}, ${runId}, ranked.target_id, ranked.target_kind,
|
|
12269
|
+
ranked.candidate_distance, ranked.candidate_hops,
|
|
12270
|
+
ranked.source_id, ranked.source_kind, ${iteration}
|
|
12271
|
+
FROM (
|
|
12272
|
+
SELECT candidates.*,
|
|
12273
|
+
ROW_NUMBER() OVER (
|
|
12274
|
+
PARTITION BY ${dialect.binaryText(sql`candidates.target_kind`)},
|
|
12275
|
+
${dialect.binaryText(sql`candidates.target_id`)}
|
|
12276
|
+
ORDER BY candidates.candidate_distance,
|
|
12277
|
+
${dialect.binaryText(sql`candidates.source_id`)},
|
|
12278
|
+
${dialect.binaryText(sql`candidates.source_kind`)}
|
|
12279
|
+
) AS candidate_rank
|
|
12280
|
+
FROM (
|
|
12281
|
+
SELECT expanded.source_id, expanded.source_kind,
|
|
12282
|
+
expanded.target_id, expanded.target_kind,
|
|
12283
|
+
frontier.distance + expanded.weight AS candidate_distance,
|
|
12284
|
+
frontier.hops + 1 AS candidate_hops
|
|
12285
|
+
FROM (${expansion}) expanded
|
|
12286
|
+
JOIN ${workingTable} frontier
|
|
12287
|
+
ON frontier.graph_id = ${graphId}
|
|
12288
|
+
AND frontier.run_id = ${runId}
|
|
12289
|
+
AND frontier.node_id = expanded.source_id
|
|
12290
|
+
AND frontier.node_kind = expanded.source_kind
|
|
12291
|
+
) candidates
|
|
12292
|
+
WHERE ${roundBound === void 0 ? sql`TRUE` : sql`candidates.candidate_distance <= ${roundBound}`}
|
|
12293
|
+
) ranked
|
|
12294
|
+
WHERE ranked.candidate_rank = 1
|
|
12295
|
+
ON CONFLICT (graph_id, run_id, node_kind, node_id) DO UPDATE SET
|
|
12296
|
+
distance = excluded.distance,
|
|
12297
|
+
hops = excluded.hops,
|
|
12298
|
+
predecessor_id = excluded.predecessor_id,
|
|
12299
|
+
predecessor_kind = excluded.predecessor_kind,
|
|
12300
|
+
improved_round = excluded.improved_round
|
|
12301
|
+
WHERE excluded.distance < ${workingTable}.distance
|
|
12302
|
+
RETURNING node_id, distance
|
|
12303
|
+
`)
|
|
12304
|
+
);
|
|
12305
|
+
frontierCount += rows.length;
|
|
12306
|
+
for (const row of rows) {
|
|
12307
|
+
if (row.node_id !== targetId) continue;
|
|
12308
|
+
const distance = Number(row.distance);
|
|
12309
|
+
if (bestTargetDistance === void 0 || distance < bestTargetDistance) {
|
|
12310
|
+
bestTargetDistance = distance;
|
|
12311
|
+
}
|
|
12312
|
+
}
|
|
10649
12313
|
}
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
12314
|
+
return { frontierCount, bestTargetDistance };
|
|
12315
|
+
}
|
|
12316
|
+
async function extractPathFromWorkingTable(context, targetId, maxIterations) {
|
|
12317
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12318
|
+
const { dialect } = operation.ctx;
|
|
12319
|
+
const rows = await context.backend.execute(
|
|
12320
|
+
asCompiledRowsSql(sql`
|
|
12321
|
+
WITH RECURSIVE chain AS (
|
|
12322
|
+
SELECT best.node_id, best.node_kind, best.distance, best.hops,
|
|
12323
|
+
best.predecessor_id, best.predecessor_kind, 0 AS position
|
|
12324
|
+
FROM (
|
|
12325
|
+
SELECT node_id, node_kind, distance, hops,
|
|
12326
|
+
predecessor_id, predecessor_kind
|
|
12327
|
+
FROM ${workingTable}
|
|
12328
|
+
WHERE graph_id = ${graphId}
|
|
12329
|
+
AND run_id = ${runId}
|
|
12330
|
+
AND node_id = ${targetId}
|
|
12331
|
+
ORDER BY distance ASC, ${dialect.binaryText(sql`node_kind`)}
|
|
12332
|
+
LIMIT 1
|
|
12333
|
+
) best
|
|
12334
|
+
UNION ALL
|
|
12335
|
+
SELECT w.node_id, w.node_kind, w.distance, w.hops,
|
|
12336
|
+
w.predecessor_id, w.predecessor_kind, c.position + 1
|
|
12337
|
+
FROM chain c
|
|
12338
|
+
JOIN ${workingTable} w
|
|
12339
|
+
ON w.graph_id = ${graphId}
|
|
12340
|
+
AND w.run_id = ${runId}
|
|
12341
|
+
AND w.node_id = c.predecessor_id
|
|
12342
|
+
AND w.node_kind = c.predecessor_kind
|
|
12343
|
+
WHERE c.position <= ${maxIterations}
|
|
12344
|
+
)
|
|
12345
|
+
SELECT node_id, node_kind, distance, hops
|
|
12346
|
+
FROM chain
|
|
12347
|
+
ORDER BY position DESC
|
|
12348
|
+
`)
|
|
10667
12349
|
);
|
|
10668
|
-
const
|
|
10669
|
-
if (
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
}));
|
|
10676
|
-
return { nodes: pathNodes, depth: Number(first.hit_depth) };
|
|
12350
|
+
const target = rows.at(-1);
|
|
12351
|
+
if (target === void 0) return void 0;
|
|
12352
|
+
return {
|
|
12353
|
+
nodes: rows.map((row) => ({ id: row.node_id, kind: row.node_kind })),
|
|
12354
|
+
depth: Number(target.hops),
|
|
12355
|
+
totalWeight: Number(target.distance)
|
|
12356
|
+
};
|
|
10677
12357
|
}
|
|
10678
|
-
function
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
12358
|
+
async function findWeightedSelfPath(ctx, nodeId, options) {
|
|
12359
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
12360
|
+
await assertValidEdgeWeights(
|
|
12361
|
+
operation,
|
|
12362
|
+
options.weightProperty,
|
|
12363
|
+
options.defaultWeight
|
|
12364
|
+
);
|
|
12365
|
+
const nodes = await fetchVisibleWorkingNodes(operation, [nodeId]);
|
|
12366
|
+
const node = nodes.toSorted(
|
|
12367
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
12368
|
+
)[0];
|
|
12369
|
+
if (node === void 0) return;
|
|
12370
|
+
return { nodes: [node], depth: 0, totalWeight: 0 };
|
|
12371
|
+
});
|
|
12372
|
+
}
|
|
12373
|
+
async function findWeightedShortestPathInline(ctx, sourceId, targetId, maxIterations, options) {
|
|
12374
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
12375
|
+
await assertValidEdgeWeights(
|
|
12376
|
+
operation,
|
|
12377
|
+
options.weightProperty,
|
|
12378
|
+
options.defaultWeight
|
|
12379
|
+
);
|
|
12380
|
+
const endpoints = await fetchVisibleWorkingNodes(operation, [
|
|
12381
|
+
sourceId,
|
|
12382
|
+
targetId
|
|
12383
|
+
]);
|
|
12384
|
+
const sources = endpoints.filter((node) => node.id === sourceId);
|
|
12385
|
+
if (sources.length === 0 || !endpoints.some((node) => node.id === targetId)) {
|
|
12386
|
+
return;
|
|
10683
12387
|
}
|
|
12388
|
+
const settled = new Map(
|
|
12389
|
+
sources.map((source) => [
|
|
12390
|
+
nodeIdentityKey(source),
|
|
12391
|
+
{
|
|
12392
|
+
id: source.id,
|
|
12393
|
+
kind: source.kind,
|
|
12394
|
+
distance: 0,
|
|
12395
|
+
hops: 0,
|
|
12396
|
+
parentKey: void 0
|
|
12397
|
+
}
|
|
12398
|
+
])
|
|
12399
|
+
);
|
|
12400
|
+
let bestTargetDistance;
|
|
12401
|
+
let frontier = sources;
|
|
12402
|
+
let rounds = 0;
|
|
12403
|
+
while (frontier.length > 0) {
|
|
12404
|
+
if (rounds >= maxIterations) {
|
|
12405
|
+
throw new GraphAlgorithmConvergenceError(
|
|
12406
|
+
WEIGHTED_ALGORITHM_NAME,
|
|
12407
|
+
maxIterations
|
|
12408
|
+
);
|
|
12409
|
+
}
|
|
12410
|
+
rounds++;
|
|
12411
|
+
const candidates = await reduceExpandedWorkingSet(
|
|
12412
|
+
operation,
|
|
12413
|
+
frontier,
|
|
12414
|
+
operation.direction,
|
|
12415
|
+
(existing, expansion) => reduceWeightedCandidate(existing, expansion, settled)
|
|
12416
|
+
);
|
|
12417
|
+
const roundBound = bestTargetDistance;
|
|
12418
|
+
const improved = [];
|
|
12419
|
+
for (const [candidateKey, candidate] of candidates) {
|
|
12420
|
+
const existing = settled.get(candidateKey);
|
|
12421
|
+
if (existing !== void 0 && existing.distance <= candidate.distance) {
|
|
12422
|
+
continue;
|
|
12423
|
+
}
|
|
12424
|
+
if (roundBound !== void 0 && candidate.distance > roundBound) {
|
|
12425
|
+
continue;
|
|
12426
|
+
}
|
|
12427
|
+
settled.set(candidateKey, {
|
|
12428
|
+
id: candidate.id,
|
|
12429
|
+
kind: candidate.kind,
|
|
12430
|
+
distance: candidate.distance,
|
|
12431
|
+
hops: candidate.hops,
|
|
12432
|
+
parentKey: nodeIdentityKey({
|
|
12433
|
+
id: candidate.parentId,
|
|
12434
|
+
kind: candidate.parentKind
|
|
12435
|
+
})
|
|
12436
|
+
});
|
|
12437
|
+
improved.push({ id: candidate.id, kind: candidate.kind });
|
|
12438
|
+
if (candidate.id === targetId && (bestTargetDistance === void 0 || candidate.distance < bestTargetDistance)) {
|
|
12439
|
+
bestTargetDistance = candidate.distance;
|
|
12440
|
+
}
|
|
12441
|
+
}
|
|
12442
|
+
frontier = improved.toSorted(
|
|
12443
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
12444
|
+
);
|
|
12445
|
+
}
|
|
12446
|
+
return buildWeightedPathResult(settled, targetId);
|
|
12447
|
+
});
|
|
12448
|
+
}
|
|
12449
|
+
function reduceWeightedCandidate(existing, expansion, settled) {
|
|
12450
|
+
const weight = expansion.weight;
|
|
12451
|
+
if (weight === void 0) return existing;
|
|
12452
|
+
const sourceEntry = settled.get(nodeIdentityKey(expansion.source));
|
|
12453
|
+
if (sourceEntry === void 0) return existing;
|
|
12454
|
+
const candidateDistance = sourceEntry.distance + weight;
|
|
12455
|
+
if (existing !== void 0 && (existing.distance < candidateDistance || existing.distance === candidateDistance && compareNodeIdentity(
|
|
12456
|
+
{ id: existing.parentId, kind: existing.parentKind },
|
|
12457
|
+
expansion.source
|
|
12458
|
+
) <= 0)) {
|
|
12459
|
+
return existing;
|
|
10684
12460
|
}
|
|
10685
|
-
return
|
|
12461
|
+
return {
|
|
12462
|
+
id: expansion.target.id,
|
|
12463
|
+
kind: expansion.target.kind,
|
|
12464
|
+
distance: candidateDistance,
|
|
12465
|
+
hops: sourceEntry.hops + 1,
|
|
12466
|
+
parentId: expansion.source.id,
|
|
12467
|
+
parentKind: expansion.source.kind
|
|
12468
|
+
};
|
|
10686
12469
|
}
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
const
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
12470
|
+
function buildWeightedPathResult(visited, targetId) {
|
|
12471
|
+
let target;
|
|
12472
|
+
for (const node of visited.values()) {
|
|
12473
|
+
if (node.id !== targetId) continue;
|
|
12474
|
+
if (target === void 0 || node.distance < target.distance || node.distance === target.distance && compareNodeIdentity(node, target) < 0) {
|
|
12475
|
+
target = node;
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12478
|
+
if (target === void 0) return void 0;
|
|
12479
|
+
const reversedNodes = [];
|
|
12480
|
+
let cursor = target;
|
|
12481
|
+
while (cursor !== void 0) {
|
|
12482
|
+
reversedNodes.push({ id: cursor.id, kind: cursor.kind });
|
|
12483
|
+
cursor = cursor.parentKey === void 0 ? void 0 : visited.get(cursor.parentKey);
|
|
12484
|
+
}
|
|
12485
|
+
return {
|
|
12486
|
+
nodes: reversedNodes.toReversed(),
|
|
12487
|
+
depth: target.hops,
|
|
12488
|
+
totalWeight: target.distance
|
|
12489
|
+
};
|
|
10695
12490
|
}
|
|
10696
12491
|
|
|
10697
12492
|
// src/store/algorithms/index.ts
|
|
@@ -10704,7 +12499,7 @@ function assertRecordedAsOfInternalOnly(options, method, allowRecordedAsOf) {
|
|
|
10704
12499
|
code: "ALGORITHM_RECORDED_ASOF_INTERNAL_ONLY",
|
|
10705
12500
|
message: `recordedAsOf is only available through store.asOfRecorded(...).${method}(...).`,
|
|
10706
12501
|
context: { method },
|
|
10707
|
-
suggestion: "Use store.asOfRecorded(recordedAt)
|
|
12502
|
+
suggestion: "Use store.asOfRecorded(recordedAt) or a recorded StoreView instead of passing recordedAsOf directly."
|
|
10708
12503
|
});
|
|
10709
12504
|
}
|
|
10710
12505
|
function createGraphAlgorithms(params) {
|
|
@@ -10733,6 +12528,19 @@ function createGraphAlgorithms(params) {
|
|
|
10733
12528
|
options
|
|
10734
12529
|
);
|
|
10735
12530
|
},
|
|
12531
|
+
weightedShortestPath(from, to, options) {
|
|
12532
|
+
assertRecordedAsOfInternalOnly(
|
|
12533
|
+
options,
|
|
12534
|
+
"weightedShortestPath",
|
|
12535
|
+
allowRecordedAsOf
|
|
12536
|
+
);
|
|
12537
|
+
return executeWeightedShortestPath(
|
|
12538
|
+
ctx,
|
|
12539
|
+
resolveNodeId(from),
|
|
12540
|
+
resolveNodeId(to),
|
|
12541
|
+
options
|
|
12542
|
+
);
|
|
12543
|
+
},
|
|
10736
12544
|
reachable(from, options) {
|
|
10737
12545
|
assertRecordedAsOfInternalOnly(options, "reachable", allowRecordedAsOf);
|
|
10738
12546
|
return executeReachable(ctx, resolveNodeId(from), options);
|
|
@@ -10753,6 +12561,14 @@ function createGraphAlgorithms(params) {
|
|
|
10753
12561
|
degree(node, options) {
|
|
10754
12562
|
assertRecordedAsOfInternalOnly(options, "degree", allowRecordedAsOf);
|
|
10755
12563
|
return executeDegree(ctx, resolveNodeId(node), options ?? {});
|
|
12564
|
+
},
|
|
12565
|
+
weaklyConnectedComponents(options) {
|
|
12566
|
+
assertRecordedAsOfInternalOnly(
|
|
12567
|
+
options,
|
|
12568
|
+
"weaklyConnectedComponents",
|
|
12569
|
+
allowRecordedAsOf
|
|
12570
|
+
);
|
|
12571
|
+
return executeWeaklyConnectedComponents(ctx, options);
|
|
10756
12572
|
}
|
|
10757
12573
|
};
|
|
10758
12574
|
}
|
|
@@ -11614,6 +13430,33 @@ async function ensureFocusedStatusTable(backend, ensureFocused) {
|
|
|
11614
13430
|
}
|
|
11615
13431
|
await backend.bootstrapTables?.();
|
|
11616
13432
|
}
|
|
13433
|
+
async function runBucketedMaterialization(items, options, bucketKey, runOne) {
|
|
13434
|
+
if (options.stopOnError === true) {
|
|
13435
|
+
const results2 = [];
|
|
13436
|
+
for (const item of items) {
|
|
13437
|
+
const entry2 = await runOne(item);
|
|
13438
|
+
results2.push(entry2);
|
|
13439
|
+
if (entry2.status === "failed") break;
|
|
13440
|
+
}
|
|
13441
|
+
return results2;
|
|
13442
|
+
}
|
|
13443
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
13444
|
+
for (const [index, item] of items.entries()) {
|
|
13445
|
+
const key = bucketKey(item);
|
|
13446
|
+
const bucket = buckets.get(key);
|
|
13447
|
+
if (bucket === void 0) buckets.set(key, [[index, item]]);
|
|
13448
|
+
else bucket.push([index, item]);
|
|
13449
|
+
}
|
|
13450
|
+
const results = Array.from({ length: items.length });
|
|
13451
|
+
await Promise.all(
|
|
13452
|
+
[...buckets.values()].map(async (group) => {
|
|
13453
|
+
for (const [index, item] of group) {
|
|
13454
|
+
results[index] = await runOne(item);
|
|
13455
|
+
}
|
|
13456
|
+
})
|
|
13457
|
+
);
|
|
13458
|
+
return results;
|
|
13459
|
+
}
|
|
11617
13460
|
async function runMaterialization(items, options, runOne) {
|
|
11618
13461
|
if (options.stopOnError === true) {
|
|
11619
13462
|
const results = [];
|
|
@@ -11641,18 +13484,10 @@ function delay(ms) {
|
|
|
11641
13484
|
}
|
|
11642
13485
|
async function materializeIndexes(context, options = {}) {
|
|
11643
13486
|
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
|
-
}
|
|
13487
|
+
assertBackendSupportsIndexMaterialization(
|
|
13488
|
+
backend,
|
|
13489
|
+
"store.materializeIndexes()"
|
|
13490
|
+
);
|
|
11656
13491
|
const declared = graph.indexes ?? [];
|
|
11657
13492
|
const kindFilter = options.kinds === void 0 ? void 0 : new Set(options.kinds);
|
|
11658
13493
|
if (kindFilter !== void 0) {
|
|
@@ -11699,65 +13534,42 @@ async function materializeIndexes(context, options = {}) {
|
|
|
11699
13534
|
backend,
|
|
11700
13535
|
relationalPhysicalNames
|
|
11701
13536
|
);
|
|
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;
|
|
13537
|
+
const reservedSystemNames = resolveSystemIndexNames(backend.tableNames);
|
|
13538
|
+
const materializeEntry = (declaration) => {
|
|
13539
|
+
if (declaration.entity !== "vector" && reservedSystemNames.has(declaration.name)) {
|
|
13540
|
+
return Promise.resolve(
|
|
13541
|
+
entry(
|
|
13542
|
+
declaration,
|
|
13543
|
+
"failed",
|
|
13544
|
+
new Error(
|
|
13545
|
+
`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.`
|
|
13546
|
+
)
|
|
13547
|
+
)
|
|
13548
|
+
);
|
|
11725
13549
|
}
|
|
11726
|
-
|
|
13550
|
+
return declaration.entity === "vector" ? materializeVectorIndex(
|
|
13551
|
+
declaration,
|
|
11727
13552
|
backend,
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
13553
|
+
graphId,
|
|
13554
|
+
schemaVersion,
|
|
13555
|
+
existingByStatusKey,
|
|
13556
|
+
invalidLeftovers
|
|
13557
|
+
) : materializeRelationalIndex(
|
|
13558
|
+
declaration,
|
|
13559
|
+
backend,
|
|
13560
|
+
dialect,
|
|
13561
|
+
ddlOptions,
|
|
13562
|
+
graphId,
|
|
13563
|
+
schemaVersion,
|
|
13564
|
+
existingByStatusKey,
|
|
13565
|
+
invalidLeftovers
|
|
11731
13566
|
);
|
|
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)
|
|
13567
|
+
};
|
|
13568
|
+
const results = await runBucketedMaterialization(
|
|
13569
|
+
candidates,
|
|
13570
|
+
options,
|
|
13571
|
+
(declaration) => parallelBucketKey(declaration),
|
|
13572
|
+
(declaration) => materializeEntry(declaration)
|
|
11761
13573
|
);
|
|
11762
13574
|
await refreshStatisticsAfterCreation(
|
|
11763
13575
|
backend,
|
|
@@ -11814,7 +13626,7 @@ async function materializeRelationalIndex(declaration, backend, dialect, ddlOpti
|
|
|
11814
13626
|
await backend.executeDdl(ddl);
|
|
11815
13627
|
},
|
|
11816
13628
|
existingByStatusKey,
|
|
11817
|
-
invalidLeftovers
|
|
13629
|
+
physicalRebuildPreload: invalidLeftovers
|
|
11818
13630
|
});
|
|
11819
13631
|
}
|
|
11820
13632
|
async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey, invalidLeftovers) {
|
|
@@ -11872,7 +13684,7 @@ async function materializeVectorIndex(declaration, backend, graphId, schemaVersi
|
|
|
11872
13684
|
driftLabel: "Vector index",
|
|
11873
13685
|
run: () => backend.createVectorIndex(params),
|
|
11874
13686
|
existingByStatusKey,
|
|
11875
|
-
invalidLeftovers
|
|
13687
|
+
physicalRebuildPreload: invalidLeftovers
|
|
11876
13688
|
});
|
|
11877
13689
|
}
|
|
11878
13690
|
async function materializeOne(declaration, backend, graphId, schemaVersion, action) {
|
|
@@ -11889,7 +13701,8 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11889
13701
|
statusKey,
|
|
11890
13702
|
signature,
|
|
11891
13703
|
driftLabel,
|
|
11892
|
-
|
|
13704
|
+
needsPhysicalRebuild: (physicalIndexName) => action.physicalRebuildPreload.has(physicalIndexName),
|
|
13705
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11893
13706
|
}
|
|
11894
13707
|
);
|
|
11895
13708
|
if (settled !== void 0) return settled;
|
|
@@ -11898,7 +13711,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11898
13711
|
statusKey,
|
|
11899
13712
|
signature,
|
|
11900
13713
|
driftLabel,
|
|
11901
|
-
run
|
|
13714
|
+
run,
|
|
13715
|
+
...action.freshNeedsPhysicalRebuild === void 0 ? {} : { freshNeedsPhysicalRebuild: action.freshNeedsPhysicalRebuild },
|
|
13716
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11902
13717
|
});
|
|
11903
13718
|
}
|
|
11904
13719
|
try {
|
|
@@ -11935,9 +13750,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11935
13750
|
}
|
|
11936
13751
|
async function settleAgainstExisting(existing, declaration, backend, graphId, schemaVersion, action) {
|
|
11937
13752
|
if (existing?.materializedAt === void 0) return void 0;
|
|
11938
|
-
const { statusKey, signature, driftLabel,
|
|
13753
|
+
const { statusKey, signature, driftLabel, needsPhysicalRebuild } = action;
|
|
11939
13754
|
if (existing.signature === signature) {
|
|
11940
|
-
if (declaration.entity !== "vector" && hasIndexBuildClaimProtocol(backend) && await
|
|
13755
|
+
if (declaration.entity !== "vector" && (hasIndexBuildClaimProtocol(backend) || action.rebuildWithoutClaim === true) && await needsPhysicalRebuild(declaration.name)) {
|
|
11941
13756
|
return void 0;
|
|
11942
13757
|
}
|
|
11943
13758
|
return entry(declaration, "alreadyMaterialized");
|
|
@@ -12002,7 +13817,8 @@ async function materializeWithClaim(declaration, backend, graphId, schemaVersion
|
|
|
12002
13817
|
driftLabel,
|
|
12003
13818
|
// Post-claim the bulk preload is stale (we may have waited on
|
|
12004
13819
|
// another builder's claim), so re-check this index fresh.
|
|
12005
|
-
|
|
13820
|
+
needsPhysicalRebuild: action.freshNeedsPhysicalRebuild ?? ((physicalIndexName) => hasInvalidIndexLeftover(backend, physicalIndexName)),
|
|
13821
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
12006
13822
|
}
|
|
12007
13823
|
);
|
|
12008
13824
|
if (settled !== void 0) return settled;
|
|
@@ -12063,6 +13879,7 @@ async function hasInvalidIndexLeftover(backend, physicalIndexName) {
|
|
|
12063
13879
|
FROM pg_class c
|
|
12064
13880
|
JOIN pg_index i ON i.indexrelid = c.oid
|
|
12065
13881
|
WHERE c.relname = ${physicalIndexName}
|
|
13882
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
12066
13883
|
`)
|
|
12067
13884
|
);
|
|
12068
13885
|
return rows[0]?.invalid === true;
|
|
@@ -12071,17 +13888,64 @@ async function preloadInvalidIndexLeftovers(backend, physicalIndexNames) {
|
|
|
12071
13888
|
if (backend.dialect !== "postgres" || physicalIndexNames.length === 0) {
|
|
12072
13889
|
return /* @__PURE__ */ new Set();
|
|
12073
13890
|
}
|
|
13891
|
+
const { invalid } = await preloadPhysicalIndexStates(
|
|
13892
|
+
backend,
|
|
13893
|
+
physicalIndexNames
|
|
13894
|
+
);
|
|
13895
|
+
return invalid;
|
|
13896
|
+
}
|
|
13897
|
+
async function preloadExistingTables(backend, tableNames) {
|
|
13898
|
+
if (tableNames.length === 0) return /* @__PURE__ */ new Set();
|
|
13899
|
+
if (backend.dialect === "postgres") {
|
|
13900
|
+
const rows2 = await backend.execute(
|
|
13901
|
+
asCompiledRowsSql(sql`
|
|
13902
|
+
SELECT c.relname AS name
|
|
13903
|
+
FROM pg_class c
|
|
13904
|
+
WHERE c.relname IN (${sqlValueList(tableNames)})
|
|
13905
|
+
AND c.relkind IN ('r', 'p')
|
|
13906
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
13907
|
+
`)
|
|
13908
|
+
);
|
|
13909
|
+
return new Set(rows2.map((row) => row.name));
|
|
13910
|
+
}
|
|
12074
13911
|
const rows = await backend.execute(
|
|
12075
13912
|
asCompiledRowsSql(sql`
|
|
12076
|
-
SELECT
|
|
12077
|
-
FROM
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
AND NOT i.indisvalid
|
|
13913
|
+
SELECT name
|
|
13914
|
+
FROM sqlite_master
|
|
13915
|
+
WHERE type = 'table'
|
|
13916
|
+
AND name IN (${sqlValueList(tableNames)})
|
|
12081
13917
|
`)
|
|
12082
13918
|
);
|
|
12083
13919
|
return new Set(rows.map((row) => row.name));
|
|
12084
13920
|
}
|
|
13921
|
+
async function preloadPhysicalIndexStates(backend, physicalIndexNames) {
|
|
13922
|
+
const valid = /* @__PURE__ */ new Set();
|
|
13923
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
13924
|
+
if (physicalIndexNames.length === 0) return { valid, invalid };
|
|
13925
|
+
if (backend.dialect === "postgres") {
|
|
13926
|
+
const rows2 = await backend.execute(
|
|
13927
|
+
asCompiledRowsSql(sql`
|
|
13928
|
+
SELECT c.relname AS name, i.indisvalid AS valid
|
|
13929
|
+
FROM pg_class c
|
|
13930
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
13931
|
+
WHERE c.relname IN (${sqlValueList(physicalIndexNames)})
|
|
13932
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
13933
|
+
`)
|
|
13934
|
+
);
|
|
13935
|
+
for (const row of rows2) (row.valid ? valid : invalid).add(row.name);
|
|
13936
|
+
return { valid, invalid };
|
|
13937
|
+
}
|
|
13938
|
+
const rows = await backend.execute(
|
|
13939
|
+
asCompiledRowsSql(sql`
|
|
13940
|
+
SELECT name
|
|
13941
|
+
FROM sqlite_master
|
|
13942
|
+
WHERE type = 'index'
|
|
13943
|
+
AND name IN (${sqlValueList(physicalIndexNames)})
|
|
13944
|
+
`)
|
|
13945
|
+
);
|
|
13946
|
+
for (const row of rows) valid.add(row.name);
|
|
13947
|
+
return { valid, invalid };
|
|
13948
|
+
}
|
|
12085
13949
|
async function dropInvalidIndexLeftover(backend, physicalIndexName) {
|
|
12086
13950
|
if (backend.executeDdl === void 0) return;
|
|
12087
13951
|
if (!await hasInvalidIndexLeftover(backend, physicalIndexName)) return;
|
|
@@ -12140,6 +14004,121 @@ function buildAttempt(args) {
|
|
|
12140
14004
|
error: args.error?.message
|
|
12141
14005
|
};
|
|
12142
14006
|
}
|
|
14007
|
+
function backendSupportsIndexMaterialization(backend) {
|
|
14008
|
+
return backend.executeDdl !== void 0 && backend.getIndexMaterialization !== void 0 && backend.recordIndexMaterialization !== void 0;
|
|
14009
|
+
}
|
|
14010
|
+
function assertBackendSupportsIndexMaterialization(backend, surface) {
|
|
14011
|
+
if (backendSupportsIndexMaterialization(backend)) return;
|
|
14012
|
+
throw new ConfigurationError(
|
|
14013
|
+
`${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.`,
|
|
14014
|
+
{ code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
|
|
14015
|
+
);
|
|
14016
|
+
}
|
|
14017
|
+
async function materializeSystemIndexes(context, options = {}) {
|
|
14018
|
+
const { backend, graphId, schemaVersion } = context;
|
|
14019
|
+
assertBackendSupportsIndexMaterialization(
|
|
14020
|
+
backend,
|
|
14021
|
+
"store.materializeSystemIndexes()"
|
|
14022
|
+
);
|
|
14023
|
+
await ensureFocusedStatusTable(
|
|
14024
|
+
backend,
|
|
14025
|
+
backend.ensureIndexMaterializationsTable
|
|
14026
|
+
);
|
|
14027
|
+
const concurrent = backend.dialect === "postgres";
|
|
14028
|
+
const candidates = SYSTEM_INDEX_DECLARATIONS.map((declaration) => {
|
|
14029
|
+
const physicalTable = resolveSystemIndexTableName(
|
|
14030
|
+
declaration.table,
|
|
14031
|
+
backend.tableNames
|
|
14032
|
+
);
|
|
14033
|
+
return {
|
|
14034
|
+
declaration,
|
|
14035
|
+
physicalTable,
|
|
14036
|
+
name: systemIndexName(physicalTable, declaration.suffix)
|
|
14037
|
+
};
|
|
14038
|
+
});
|
|
14039
|
+
const statusKeys = candidates.map((candidate) => candidate.name);
|
|
14040
|
+
const targetTables = [
|
|
14041
|
+
...new Set(candidates.map((candidate) => candidate.physicalTable))
|
|
14042
|
+
];
|
|
14043
|
+
const [existingByStatusKey, physicalStates, existingTables] = await Promise.all([
|
|
14044
|
+
preloadMaterializations(backend, statusKeys),
|
|
14045
|
+
preloadPhysicalIndexStates(backend, statusKeys),
|
|
14046
|
+
preloadExistingTables(backend, targetTables)
|
|
14047
|
+
]);
|
|
14048
|
+
const { valid: physicallyPresent } = physicalStates;
|
|
14049
|
+
const physicalRebuildPreload = new Set(
|
|
14050
|
+
statusKeys.filter((name) => !physicallyPresent.has(name))
|
|
14051
|
+
);
|
|
14052
|
+
const materializeEntry = async (candidate) => {
|
|
14053
|
+
const identity = {
|
|
14054
|
+
name: candidate.name,
|
|
14055
|
+
entity: "system",
|
|
14056
|
+
kind: candidate.declaration.table
|
|
14057
|
+
};
|
|
14058
|
+
if (!existingTables.has(candidate.physicalTable)) {
|
|
14059
|
+
return {
|
|
14060
|
+
indexName: candidate.name,
|
|
14061
|
+
entity: "system",
|
|
14062
|
+
kind: candidate.declaration.table,
|
|
14063
|
+
status: "skipped",
|
|
14064
|
+
reason: `Relation "${candidate.physicalTable}" does not exist in this database`
|
|
14065
|
+
};
|
|
14066
|
+
}
|
|
14067
|
+
const existing = existingByStatusKey.get(candidate.name);
|
|
14068
|
+
if (existing !== void 0 && existing.entity !== "system") {
|
|
14069
|
+
return entry(
|
|
14070
|
+
identity,
|
|
14071
|
+
"failed",
|
|
14072
|
+
new Error(
|
|
14073
|
+
`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.`
|
|
14074
|
+
)
|
|
14075
|
+
);
|
|
14076
|
+
}
|
|
14077
|
+
if (existing === void 0 && physicallyPresent.has(candidate.name)) {
|
|
14078
|
+
return entry(identity, "alreadyMaterialized");
|
|
14079
|
+
}
|
|
14080
|
+
const signature = await computeSystemIndexSignature(
|
|
14081
|
+
backend.dialect,
|
|
14082
|
+
candidate.physicalTable,
|
|
14083
|
+
candidate.declaration
|
|
14084
|
+
);
|
|
14085
|
+
const ddl = generateSystemIndexDDL(
|
|
14086
|
+
candidate.declaration,
|
|
14087
|
+
candidate.physicalTable,
|
|
14088
|
+
{ concurrent }
|
|
14089
|
+
);
|
|
14090
|
+
return materializeOne(identity, backend, graphId, schemaVersion, {
|
|
14091
|
+
statusKey: candidate.name,
|
|
14092
|
+
signature,
|
|
14093
|
+
driftLabel: "System index",
|
|
14094
|
+
run: () => backend.executeDdl(ddl),
|
|
14095
|
+
existingByStatusKey,
|
|
14096
|
+
physicalRebuildPreload,
|
|
14097
|
+
freshNeedsPhysicalRebuild: async (physicalIndexName) => {
|
|
14098
|
+
const fresh = await preloadPhysicalIndexStates(backend, [
|
|
14099
|
+
physicalIndexName
|
|
14100
|
+
]);
|
|
14101
|
+
return !fresh.valid.has(physicalIndexName);
|
|
14102
|
+
},
|
|
14103
|
+
rebuildWithoutClaim: true
|
|
14104
|
+
});
|
|
14105
|
+
};
|
|
14106
|
+
const results = await runBucketedMaterialization(
|
|
14107
|
+
candidates,
|
|
14108
|
+
options,
|
|
14109
|
+
(candidate) => candidate.physicalTable,
|
|
14110
|
+
(candidate) => materializeEntry(candidate)
|
|
14111
|
+
);
|
|
14112
|
+
await refreshStatisticsAfterCreation(backend, results, options, concurrent);
|
|
14113
|
+
return { results };
|
|
14114
|
+
}
|
|
14115
|
+
async function computeSystemIndexSignature(dialect, targetTableName, declaration) {
|
|
14116
|
+
const json = JSON.stringify(
|
|
14117
|
+
{ dialect, targetTableName, declaration },
|
|
14118
|
+
sortedReplacer
|
|
14119
|
+
);
|
|
14120
|
+
return sha256Hex(json, 16);
|
|
14121
|
+
}
|
|
12143
14122
|
async function computeIndexSignature(dialect, targetTableName, declaration) {
|
|
12144
14123
|
const hashable = {
|
|
12145
14124
|
dialect,
|
|
@@ -12368,13 +14347,13 @@ function buildEmbeddingTableCleanup(ctx, row) {
|
|
|
12368
14347
|
const slots = await resolveRemovedKindEmbeddingSlots(ctx.backend, row);
|
|
12369
14348
|
await Promise.all(
|
|
12370
14349
|
slots.map(
|
|
12371
|
-
(slot) => dropVectorSlotStorage(vectorStrategy, executeDdl, slot)
|
|
14350
|
+
(slot) => dropVectorSlotStorage(ctx.backend, vectorStrategy, executeDdl, slot)
|
|
12372
14351
|
)
|
|
12373
14352
|
);
|
|
12374
14353
|
})();
|
|
12375
14354
|
return [cleanup];
|
|
12376
14355
|
}
|
|
12377
|
-
async function dropVectorSlotStorage(strategy, executeDdl, slot) {
|
|
14356
|
+
async function dropVectorSlotStorage(backend, strategy, executeDdl, slot) {
|
|
12378
14357
|
try {
|
|
12379
14358
|
for (const ddl of strategy.buildDropStorage(slot)) {
|
|
12380
14359
|
await executeDdl(ddl);
|
|
@@ -12382,6 +14361,7 @@ async function dropVectorSlotStorage(strategy, executeDdl, slot) {
|
|
|
12382
14361
|
} catch (error) {
|
|
12383
14362
|
if (!isMissingTableError(error)) throw error;
|
|
12384
14363
|
}
|
|
14364
|
+
await backend.deleteVectorSlotContribution?.(slot);
|
|
12385
14365
|
}
|
|
12386
14366
|
async function resolveRemovedKindEmbeddingSlots(backend, row) {
|
|
12387
14367
|
const priorVersion = row.schemaVersion - 1;
|
|
@@ -12457,7 +14437,12 @@ async function reclaimRemovedVectorFieldTables(context) {
|
|
|
12457
14437
|
const results = [];
|
|
12458
14438
|
for (const field2 of orphans) {
|
|
12459
14439
|
try {
|
|
12460
|
-
await dropVectorSlotStorage(
|
|
14440
|
+
await dropVectorSlotStorage(
|
|
14441
|
+
backend,
|
|
14442
|
+
vectorStrategy,
|
|
14443
|
+
executeDdl,
|
|
14444
|
+
field2.slot
|
|
14445
|
+
);
|
|
12461
14446
|
results.push({
|
|
12462
14447
|
kind: field2.kind,
|
|
12463
14448
|
fieldPath: field2.fieldPath,
|
|
@@ -13841,9 +15826,11 @@ var Store = class _Store {
|
|
|
13841
15826
|
* Tier 1 graph algorithms: shortest path, reachability, neighborhoods, and
|
|
13842
15827
|
* degree centrality.
|
|
13843
15828
|
*
|
|
13844
|
-
*
|
|
13845
|
-
*
|
|
13846
|
-
*
|
|
15829
|
+
* Traversal calls use the iterative graph-operation substrate: a set-based
|
|
15830
|
+
* breadth-first frontier backed by a temporary working table, or a chunked
|
|
15831
|
+
* inline relation when the backend cannot pin a transactional connection.
|
|
15832
|
+
* `degree` uses a single count query. The facade is built lazily on first
|
|
15833
|
+
* access and cached for the lifetime of the store.
|
|
13847
15834
|
*
|
|
13848
15835
|
* @example
|
|
13849
15836
|
* ```typescript
|
|
@@ -14607,10 +16594,10 @@ var Store = class _Store {
|
|
|
14607
16594
|
}) : backendOptions;
|
|
14608
16595
|
try {
|
|
14609
16596
|
const result = await this.#backend.transaction(
|
|
14610
|
-
async (txBackend,
|
|
16597
|
+
async (txBackend, sql19) => invoke(
|
|
14611
16598
|
this.#buildTransactionContext(
|
|
14612
16599
|
txBackend,
|
|
14613
|
-
|
|
16600
|
+
sql19,
|
|
14614
16601
|
runHooks,
|
|
14615
16602
|
receiptRecorder
|
|
14616
16603
|
)
|
|
@@ -14728,7 +16715,7 @@ var Store = class _Store {
|
|
|
14728
16715
|
* (#140); when history capture is enabled it is replaced with a fail-loud
|
|
14729
16716
|
* guard because raw graph writes would bypass recorded-time capture.
|
|
14730
16717
|
*/
|
|
14731
|
-
#buildTransactionContext(txBackend,
|
|
16718
|
+
#buildTransactionContext(txBackend, sql19, runHooks = this.#immediateHookRunner(), receiptRecorder) {
|
|
14732
16719
|
const txNodeOperations = {
|
|
14733
16720
|
...this.#buildNodeOperations(this.#createNodeOperationContext(runHooks)),
|
|
14734
16721
|
createQuery: () => this.#createQueryForBackend(txBackend),
|
|
@@ -14762,7 +16749,7 @@ var Store = class _Store {
|
|
|
14762
16749
|
));
|
|
14763
16750
|
}
|
|
14764
16751
|
const getNodeCollection = (kind) => this.#resolveDynamicNodeCollection(nodes, kind);
|
|
14765
|
-
const sqlAvailability = this.#captureEnabled ? "history" : this.#revisionTrackingEnabled ? "revisionTracking" :
|
|
16752
|
+
const sqlAvailability = this.#captureEnabled ? "history" : this.#revisionTrackingEnabled ? "revisionTracking" : sql19 === void 0 ? "unavailable" : "available";
|
|
14766
16753
|
const base = {
|
|
14767
16754
|
nodes,
|
|
14768
16755
|
edges,
|
|
@@ -14771,9 +16758,9 @@ var Store = class _Store {
|
|
|
14771
16758
|
getNodeCollection,
|
|
14772
16759
|
runNodeOperationHooks
|
|
14773
16760
|
};
|
|
14774
|
-
const withSql =
|
|
16761
|
+
const withSql = sql19 === void 0 ? base : {
|
|
14775
16762
|
...base,
|
|
14776
|
-
sql: this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() :
|
|
16763
|
+
sql: this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql19
|
|
14777
16764
|
};
|
|
14778
16765
|
return receiptRecorder === void 0 ? withSql : this.#attachMeasure(withSql);
|
|
14779
16766
|
}
|
|
@@ -14964,6 +16951,7 @@ var Store = class _Store {
|
|
|
14964
16951
|
merged,
|
|
14965
16952
|
activeRow.version
|
|
14966
16953
|
);
|
|
16954
|
+
await materializeVectorContributions(this.#backend, merged);
|
|
14967
16955
|
const evolved = this.#cloneWithGraph(
|
|
14968
16956
|
merged,
|
|
14969
16957
|
options?.ref,
|
|
@@ -15066,6 +17054,29 @@ var Store = class _Store {
|
|
|
15066
17054
|
options ?? {}
|
|
15067
17055
|
);
|
|
15068
17056
|
}
|
|
17057
|
+
/**
|
|
17058
|
+
* Materializes TypeGraph's own base-relation indexes
|
|
17059
|
+
* (`SYSTEM_INDEX_DECLARATIONS`) against the live database.
|
|
17060
|
+
*
|
|
17061
|
+
* Bootstrap DDL runs only on first boot, so a system index shipped in a
|
|
17062
|
+
* newer library version never reaches an already-initialized database on
|
|
17063
|
+
* its own. `createStoreWithSchema` runs this automatically; deployments
|
|
17064
|
+
* that boot with plain `createStore` / `createVerifiedStore` (zero-DDL by
|
|
17065
|
+
* contract) adopt new system indexes by calling this once under a role
|
|
17066
|
+
* that may run DDL. Same status-table, drift-signature, and Postgres
|
|
17067
|
+
* `CREATE INDEX CONCURRENTLY` claim semantics as `materializeIndexes`.
|
|
17068
|
+
*/
|
|
17069
|
+
async materializeSystemIndexes(options) {
|
|
17070
|
+
const { activeRow } = await this.#loadCaughtUp("materialize");
|
|
17071
|
+
return materializeSystemIndexes(
|
|
17072
|
+
{
|
|
17073
|
+
backend: this.#baseBackend,
|
|
17074
|
+
graphId: this.graphId,
|
|
17075
|
+
schemaVersion: activeRow.version
|
|
17076
|
+
},
|
|
17077
|
+
options ?? {}
|
|
17078
|
+
);
|
|
17079
|
+
}
|
|
15069
17080
|
/**
|
|
15070
17081
|
* Recreate a vector field's per-field storage at its current declared
|
|
15071
17082
|
* dimension, then optionally re-embed existing rows.
|
|
@@ -15122,10 +17133,14 @@ var Store = class _Store {
|
|
|
15122
17133
|
for (const ddl of strategy.buildDropStorage(slot)) {
|
|
15123
17134
|
await backend.executeDdl(ddl);
|
|
15124
17135
|
}
|
|
15125
|
-
|
|
15126
|
-
for (const
|
|
15127
|
-
|
|
17136
|
+
if (backend.ensureVectorSlotContribution === void 0) {
|
|
17137
|
+
for (const contribution of strategy.ownedTables(slot)) {
|
|
17138
|
+
for (const ddl of contribution.createDdl) {
|
|
17139
|
+
await backend.executeDdl(ddl);
|
|
17140
|
+
}
|
|
15128
17141
|
}
|
|
17142
|
+
} else {
|
|
17143
|
+
await backend.ensureVectorSlotContribution(slot, { force: true });
|
|
15129
17144
|
}
|
|
15130
17145
|
const declaredIndexMaterializes = declaration.indexType !== "none" && backend.capabilities.vector?.indexTypes.includes(
|
|
15131
17146
|
declaration.indexType
|
|
@@ -15607,6 +17622,20 @@ async function materializeRuntimeContributions(backend, graphId) {
|
|
|
15607
17622
|
}
|
|
15608
17623
|
await backend.ensureFulltextTable?.(graphId);
|
|
15609
17624
|
}
|
|
17625
|
+
async function materializeVectorContributions(backend, graph) {
|
|
17626
|
+
if (backend.capabilities.vector?.supported !== true) return;
|
|
17627
|
+
const slots = resolveGraphVectorSlots(graph);
|
|
17628
|
+
const ensureVectorSlotContributions = backend.ensureVectorSlotContributions;
|
|
17629
|
+
if (ensureVectorSlotContributions !== void 0) {
|
|
17630
|
+
await ensureVectorSlotContributions(slots, { onDrift: "skip" });
|
|
17631
|
+
return;
|
|
17632
|
+
}
|
|
17633
|
+
const ensureVectorSlotContribution = backend.ensureVectorSlotContribution;
|
|
17634
|
+
if (ensureVectorSlotContribution === void 0) return;
|
|
17635
|
+
for (const slot of slots) {
|
|
17636
|
+
await ensureVectorSlotContribution(slot, { onDrift: "skip" });
|
|
17637
|
+
}
|
|
17638
|
+
}
|
|
15610
17639
|
async function createStoreWithSchema(graph, backend, options) {
|
|
15611
17640
|
const {
|
|
15612
17641
|
graph: merged,
|
|
@@ -15618,6 +17647,10 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
15618
17647
|
preloaded: { activeRow, storedSchema }
|
|
15619
17648
|
});
|
|
15620
17649
|
await materializeRuntimeContributions(backend, merged.id);
|
|
17650
|
+
await materializeVectorContributions(backend, merged);
|
|
17651
|
+
if (options?.systemIndexes !== "skip") {
|
|
17652
|
+
await materializeSystemIndexesOnBoot(backend, merged.id, result);
|
|
17653
|
+
}
|
|
15621
17654
|
const store = new Store(
|
|
15622
17655
|
merged,
|
|
15623
17656
|
backend,
|
|
@@ -15626,6 +17659,30 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
15626
17659
|
);
|
|
15627
17660
|
return [store, result];
|
|
15628
17661
|
}
|
|
17662
|
+
async function materializeSystemIndexesOnBoot(backend, graphId, result) {
|
|
17663
|
+
if (result.status === "breaking") return;
|
|
17664
|
+
if (!backendSupportsIndexMaterialization(backend)) return;
|
|
17665
|
+
const schemaVersion = result.status === "migrated" ? result.toVersion : result.version;
|
|
17666
|
+
try {
|
|
17667
|
+
const { results } = await materializeSystemIndexes(
|
|
17668
|
+
{ backend: asRawBackend(backend), graphId, schemaVersion },
|
|
17669
|
+
{}
|
|
17670
|
+
);
|
|
17671
|
+
const failed = results.filter(
|
|
17672
|
+
(entryResult) => entryResult.status === "failed"
|
|
17673
|
+
);
|
|
17674
|
+
if (failed.length === 0) return;
|
|
17675
|
+
console.warn(
|
|
17676
|
+
`[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().`,
|
|
17677
|
+
failed[0]?.error
|
|
17678
|
+
);
|
|
17679
|
+
} catch (error) {
|
|
17680
|
+
console.warn(
|
|
17681
|
+
"[typegraph] system-index materialization failed at boot; the store is usable but new system indexes were not adopted. Retry via store.materializeSystemIndexes().",
|
|
17682
|
+
error
|
|
17683
|
+
);
|
|
17684
|
+
}
|
|
17685
|
+
}
|
|
15629
17686
|
async function createVerifiedStore(graph, backend, options) {
|
|
15630
17687
|
const {
|
|
15631
17688
|
graph: merged,
|
|
@@ -15642,5 +17699,5 @@ async function createVerifiedStore(graph, backend, options) {
|
|
|
15642
17699
|
}
|
|
15643
17700
|
|
|
15644
17701
|
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-
|
|
17702
|
+
//# sourceMappingURL=chunk-GA4TT3DH.js.map
|
|
17703
|
+
//# sourceMappingURL=chunk-GA4TT3DH.js.map
|