@nicia-ai/typegraph 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/postgres/index.cjs +24 -24
- package/dist/backend/postgres/index.d.cts +6 -6
- package/dist/backend/postgres/index.d.ts +6 -6
- package/dist/backend/postgres/index.js +11 -11
- package/dist/backend/postgres/pglite.cjs +16 -16
- package/dist/backend/postgres/pglite.d.cts +3 -3
- package/dist/backend/postgres/pglite.d.ts +3 -3
- package/dist/backend/postgres/pglite.js +11 -11
- package/dist/backend/sqlite/index.cjs +22 -21
- package/dist/backend/sqlite/index.d.cts +11 -9
- package/dist/backend/sqlite/index.d.ts +11 -9
- package/dist/backend/sqlite/index.js +10 -9
- package/dist/backend/sqlite/libsql.cjs +15 -14
- package/dist/backend/sqlite/libsql.cjs.map +1 -1
- package/dist/backend/sqlite/libsql.d.cts +3 -3
- package/dist/backend/sqlite/libsql.d.ts +3 -3
- package/dist/backend/sqlite/libsql.js +11 -10
- package/dist/backend/sqlite/libsql.js.map +1 -1
- package/dist/backend/sqlite/local.cjs +30 -23
- package/dist/backend/sqlite/local.cjs.map +1 -1
- package/dist/backend/sqlite/local.d.cts +3 -3
- package/dist/backend/sqlite/local.d.ts +3 -3
- package/dist/backend/sqlite/local.js +18 -11
- package/dist/backend/sqlite/local.js.map +1 -1
- package/dist/{chunk-TCOCYN2P.cjs → chunk-27SSDCEP.cjs} +14 -14
- package/dist/{chunk-TCOCYN2P.cjs.map → chunk-27SSDCEP.cjs.map} +1 -1
- package/dist/{chunk-5AQIKK56.cjs → chunk-2N6EOBTH.cjs} +16 -16
- package/dist/{chunk-5AQIKK56.cjs.map → chunk-2N6EOBTH.cjs.map} +1 -1
- package/dist/{chunk-FN35DLCN.cjs → chunk-3C2RYI6L.cjs} +13 -107
- package/dist/chunk-3C2RYI6L.cjs.map +1 -0
- package/dist/{chunk-4JEZZJ6S.cjs → chunk-3DS7Z4T4.cjs} +39 -39
- package/dist/{chunk-4JEZZJ6S.cjs.map → chunk-3DS7Z4T4.cjs.map} +1 -1
- package/dist/{chunk-BLZDPRXT.cjs → chunk-3GH67G4Z.cjs} +5 -5
- package/dist/{chunk-BLZDPRXT.cjs.map → chunk-3GH67G4Z.cjs.map} +1 -1
- package/dist/{chunk-K3PPLHY7.js → chunk-3RWZPJKV.js} +24 -9
- package/dist/chunk-3RWZPJKV.js.map +1 -0
- package/dist/{chunk-QWEUYILR.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-JBTOYDMN.js → chunk-44TA22QT.js} +58 -11
- package/dist/chunk-44TA22QT.js.map +1 -0
- package/dist/{chunk-UES55P7G.js → chunk-552KNYQJ.js} +2652 -409
- package/dist/chunk-552KNYQJ.js.map +1 -0
- package/dist/{chunk-VPYHY3LJ.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-MP2JQKSG.js → chunk-5PNMDJ7J.js} +108 -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-LGEALRC6.cjs → chunk-BVRDGYXQ.cjs} +113 -4
- package/dist/chunk-BVRDGYXQ.cjs.map +1 -0
- package/dist/{chunk-GSQ6NPH6.cjs → chunk-DEFQM7JU.cjs} +7 -7
- package/dist/{chunk-GSQ6NPH6.cjs.map → chunk-DEFQM7JU.cjs.map} +1 -1
- package/dist/{chunk-EM3CWPND.cjs → chunk-DIQ3AV5W.cjs} +574 -268
- package/dist/chunk-DIQ3AV5W.cjs.map +1 -0
- package/dist/{chunk-FPIZFYHE.js → chunk-EXU3RASM.js} +74 -15
- package/dist/chunk-EXU3RASM.js.map +1 -0
- package/dist/{chunk-H7YNSMA7.cjs → chunk-HAHNRASW.cjs} +20 -2
- package/dist/chunk-HAHNRASW.cjs.map +1 -0
- package/dist/{chunk-FL472AMI.cjs → chunk-IP3CGGPA.cjs} +8 -8
- package/dist/{chunk-FL472AMI.cjs.map → chunk-IP3CGGPA.cjs.map} +1 -1
- package/dist/{chunk-4TOD4LBZ.cjs → chunk-IPSVVTWT.cjs} +94 -35
- package/dist/chunk-IPSVVTWT.cjs.map +1 -0
- package/dist/{chunk-RGP33ZJ5.js → chunk-IXGYRP2P.js} +184 -92
- package/dist/chunk-IXGYRP2P.js.map +1 -0
- package/dist/{chunk-3TWF3SH4.js → chunk-J4IUSYFF.js} +251 -121
- package/dist/chunk-J4IUSYFF.js.map +1 -0
- package/dist/{chunk-O7UN7OXA.cjs → chunk-JBNOGRKW.cjs} +318 -188
- package/dist/chunk-JBNOGRKW.cjs.map +1 -0
- package/dist/{chunk-WDBCBWC4.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-QOTI55H7.js → chunk-L7HQ2OSI.js} +4 -4
- package/dist/{chunk-QOTI55H7.js.map → chunk-L7HQ2OSI.js.map} +1 -1
- package/dist/{chunk-55MZDDZV.js → chunk-M6VES7V6.js} +4 -4
- package/dist/{chunk-55MZDDZV.js.map → chunk-M6VES7V6.js.map} +1 -1
- package/dist/{chunk-J7WFGBJR.cjs → chunk-NBP2WTJW.cjs} +3249 -1006
- package/dist/chunk-NBP2WTJW.cjs.map +1 -0
- package/dist/{chunk-CK5BVQOW.cjs → chunk-NUIG3BCV.cjs} +100 -85
- package/dist/chunk-NUIG3BCV.cjs.map +1 -0
- package/dist/{chunk-EMF62TZG.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-GUR2FNB5.cjs.map → chunk-QSXTWU4U.cjs.map} +1 -1
- package/dist/{chunk-QNKSQTKQ.js → chunk-RCVLFTCT.js} +49 -19
- package/dist/chunk-RCVLFTCT.js.map +1 -0
- package/dist/{chunk-VSPCZAXF.js → chunk-RRSNR3TP.js} +3 -3
- package/dist/{chunk-VSPCZAXF.js.map → chunk-RRSNR3TP.js.map} +1 -1
- package/dist/{chunk-WJT7OU7E.cjs → chunk-TGOMW4MB.cjs} +129 -98
- package/dist/chunk-TGOMW4MB.cjs.map +1 -0
- package/dist/{chunk-KMI7LYAF.js → chunk-TKT2XHED.js} +8 -8
- package/dist/{chunk-KMI7LYAF.js.map → chunk-TKT2XHED.js.map} +1 -1
- package/dist/{chunk-RGPG7EOJ.js → chunk-TPF25IG6.js} +3 -3
- package/dist/{chunk-RGPG7EOJ.js.map → chunk-TPF25IG6.js.map} +1 -1
- package/dist/{chunk-5SEGFTZX.js → chunk-UGZUAYAR.js} +3 -3
- package/dist/{chunk-5SEGFTZX.js.map → chunk-UGZUAYAR.js.map} +1 -1
- package/dist/{chunk-VHMPG6UL.js → chunk-UXLSNRYL.js} +11 -105
- package/dist/chunk-UXLSNRYL.js.map +1 -0
- package/dist/{chunk-FRV5Q5IO.js → chunk-UYQMBBWJ.js} +3 -3
- package/dist/{chunk-FRV5Q5IO.js.map → chunk-UYQMBBWJ.js.map} +1 -1
- package/dist/{chunk-UUT7UN2N.cjs → chunk-VGDDG3XJ.cjs} +7 -7
- package/dist/{chunk-UUT7UN2N.cjs.map → chunk-VGDDG3XJ.cjs.map} +1 -1
- package/dist/{chunk-CZQ3FO2Q.js → chunk-VKGKDOKR.js} +56 -21
- package/dist/chunk-VKGKDOKR.js.map +1 -0
- package/dist/{chunk-CTTSDJLY.cjs → chunk-VL2V6PEN.cjs} +11 -11
- package/dist/{chunk-CTTSDJLY.cjs.map → chunk-VL2V6PEN.cjs.map} +1 -1
- package/dist/{chunk-XOX22WHL.cjs → chunk-WOJMKB3N.cjs} +125 -90
- 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-I7ZUZKFD.js → chunk-XGPUQ56G.js} +3 -3
- package/dist/{chunk-I7ZUZKFD.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.cts → extension-types-BM2ACfYO.d.cts} +6 -1
- package/dist/{extension-types-ixRpJVUp.d.ts → 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.cjs.map +1 -1
- 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/graph-merge/index.js.map +1 -1
- package/dist/{index-Bks_3HHK.d.cts → index-BPM59vsg.d.cts} +119 -7
- package/dist/{index-6CSUAq-x.d.ts → index-BeacbPoe.d.ts} +119 -7
- package/dist/index.cjs +230 -185
- 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 +208 -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 +191 -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-DQ5R6rzV.d.ts → postgres-IaBA4RgE.d.ts} +1 -1
- package/dist/{postgres-CfEqsRMp.d.cts → 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-CKbHFRnn.d.ts → store-view-ANL6mTph.d.ts} +457 -57
- package/dist/{store-view-BSpqvAxK.d.cts → store-view-X96Rd00T.d.cts} +457 -57
- 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-3TWF3SH4.js.map +0 -1
- package/dist/chunk-4TOD4LBZ.cjs.map +0 -1
- package/dist/chunk-6FR2MMS6.js.map +0 -1
- package/dist/chunk-CK5BVQOW.cjs.map +0 -1
- package/dist/chunk-CZQ3FO2Q.js.map +0 -1
- package/dist/chunk-D7ZEOS56.cjs.map +0 -1
- package/dist/chunk-EM3CWPND.cjs.map +0 -1
- package/dist/chunk-EMF62TZG.cjs.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-GUR2FNB5.cjs +0 -12
- package/dist/chunk-H7YNSMA7.cjs.map +0 -1
- package/dist/chunk-J7WFGBJR.cjs.map +0 -1
- package/dist/chunk-JBTOYDMN.js.map +0 -1
- package/dist/chunk-K3PPLHY7.js.map +0 -1
- package/dist/chunk-KBGQIPRQ.js.map +0 -1
- package/dist/chunk-LGEALRC6.cjs.map +0 -1
- package/dist/chunk-MP2JQKSG.js.map +0 -1
- package/dist/chunk-O7UN7OXA.cjs.map +0 -1
- package/dist/chunk-QNKSQTKQ.js.map +0 -1
- package/dist/chunk-QWEUYILR.js.map +0 -1
- package/dist/chunk-RGP33ZJ5.js.map +0 -1
- package/dist/chunk-S55OV6PW.cjs.map +0 -1
- package/dist/chunk-UES55P7G.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-VPYHY3LJ.cjs.map +0 -1
- package/dist/chunk-WDBCBWC4.js.map +0 -1
- package/dist/chunk-WJT7OU7E.cjs.map +0 -1
- package/dist/chunk-XOX22WHL.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, 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 {
|
|
5
|
-
import {
|
|
6
|
-
import { generateIndexDDL } from './chunk-
|
|
7
|
-
import {
|
|
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
|
+
import { canonicalEqual, sortedReplacer } from './chunk-UR7W5YQK.js';
|
|
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-I7ZUZKFD.js';
|
|
15
|
+
import { resolveReadCoordinate, withRecordedCoordinate, describeCoordinate, coordinateContext, DEFAULT_SQL_SCHEMA, requireSqlSchema, recordedReadSchemaFor, assertRevisionTrackableBackend, createSqlSchema, requireExternalRecordedReadSource, createRecordedReadBinding, createRecordedBackend, readRecordedClock, asRecordedInstant, ensureRevisionOrigin, withRecordedFlushObserver, recordedCaptureRequiresCallbackTransactionError, assertRecordedCaptureTransactionIsolation, createRecordedTransactionScope, lockRecordedGraphWrite, createHistoryUnsafeSqlRef, createRevisionTrackingUnsafeSqlRef, generateId, vectorSlotKey, computeFulltextContent, advanceRevisionClock, recordedReadSqlSchema, requireRecordedReadBinding, runInsertBatch, edgeInsertDispatch, runInsertBatchReturning, getEmbeddingFields, getNodeRowsByIds, getEdgeRowsByIds, validateEdgeEndpoints, recordedBindParamBudget, assertValidRecordedInstant, closeRecordedHardDeletedKind, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-VKGKDOKR.js';
|
|
16
|
+
import { isParameterRef, fieldRef, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, markAnnIndexScan, asCompiledSelectSql, isRawExecutable, runOptionallyInTransaction, baseField, objectField, arrayField, dateField, numberField, annIndexScanTypes, rowPropsToObject, asCompiledRowsSql, sqlValueList, createBackendOverlay, compileKindFilter, currentReadInstant, compileTemporalFilter, markForceCustomPlan, chunk, INTERNAL_TEMPORARY_WRITES, buildPredicateIndex, compileFieldValue, asCompiledTemporaryStatementSql, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getNodeKindsForAlias, getHybridTargetAlias, asCompiledStatementSql, compilePredicateClauses, isPresent } from './chunk-44TA22QT.js';
|
|
17
|
+
import { withRecordedRelationsPrecondition, nowIso, validateOptionalCanonicalIsoDate, isMissingTableError } from './chunk-XGPUQ56G.js';
|
|
18
18
|
import { DEFAULT_PAGINATION_LIMIT, DEFAULT_STREAM_BATCH_SIZE, SQLITE_PATH_DELIMITER, PG_ARRAY_START, PG_ARRAY_END, PG_PATH_ELEMENT_SEPARATOR, MAX_PG_IDENTIFIER_LENGTH } from './chunk-OHMP6N2J.js';
|
|
19
|
-
import { getSearchableFields, resolveEmbeddingFields, resolveDeclaredFulltextLanguage } from './chunk-
|
|
20
|
-
import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, EndpointNotFoundError } from './chunk-
|
|
19
|
+
import { getSearchableFields, resolveEmbeddingFields, resolveGraphVectorSlots, resolveDeclaredFulltextLanguage } from './chunk-PC2UBAUB.js';
|
|
20
|
+
import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, UnsupportedBackendCapabilityError, GraphAlgorithmConvergenceError, EndpointNotFoundError, InvalidEdgeWeightError } from './chunk-5PNMDJ7J.js';
|
|
21
21
|
import { jsonPointer, parseJsonPointer, resolveJsonPointer } from './chunk-G6Q5UM7U.js';
|
|
22
22
|
import { Placeholder, sql } from 'drizzle-orm';
|
|
23
23
|
|
|
@@ -394,7 +394,8 @@ function recordedSearch(coordinate) {
|
|
|
394
394
|
var CoordinatePinnedView = class {
|
|
395
395
|
store;
|
|
396
396
|
coordinate;
|
|
397
|
-
#
|
|
397
|
+
#algorithmFacade;
|
|
398
|
+
#internalAlgorithms;
|
|
398
399
|
constructor(store, coordinate) {
|
|
399
400
|
this.store = store;
|
|
400
401
|
this.coordinate = coordinate;
|
|
@@ -415,9 +416,24 @@ var CoordinatePinnedView = class {
|
|
|
415
416
|
query() {
|
|
416
417
|
return this.store.sealedQuery(this.coordinate);
|
|
417
418
|
}
|
|
418
|
-
|
|
419
|
-
this.#
|
|
420
|
-
|
|
419
|
+
internalAlgorithms() {
|
|
420
|
+
this.#internalAlgorithms ??= this.store.algorithmsAtCoordinate(
|
|
421
|
+
this.coordinate
|
|
422
|
+
);
|
|
423
|
+
return this.#internalAlgorithms;
|
|
424
|
+
}
|
|
425
|
+
/** Graph algorithms pinned to this view's immutable temporal coordinate. */
|
|
426
|
+
get algorithms() {
|
|
427
|
+
this.#algorithmFacade ??= Object.freeze({
|
|
428
|
+
shortestPath: (from, to, options) => this.shortestPath(from, to, options),
|
|
429
|
+
weightedShortestPath: (from, to, options) => this.weightedShortestPath(from, to, options),
|
|
430
|
+
reachable: (from, options) => this.reachable(from, options),
|
|
431
|
+
canReach: (from, to, options) => this.canReach(from, to, options),
|
|
432
|
+
neighbors: (node, options) => this.neighbors(node, options),
|
|
433
|
+
degree: (node, options) => this.degree(node, options),
|
|
434
|
+
weaklyConnectedComponents: (options) => this.weaklyConnectedComponents(options)
|
|
435
|
+
});
|
|
436
|
+
return this.#algorithmFacade;
|
|
421
437
|
}
|
|
422
438
|
/** Extracts a subgraph at this view's pinned coordinate. */
|
|
423
439
|
subgraph(rootId, options) {
|
|
@@ -429,35 +445,49 @@ var CoordinatePinnedView = class {
|
|
|
429
445
|
}
|
|
430
446
|
/** Shortest path between two nodes at this view's pinned coordinate. */
|
|
431
447
|
shortestPath(from, to, options) {
|
|
432
|
-
return this.
|
|
448
|
+
return this.internalAlgorithms().shortestPath(from, to, {
|
|
449
|
+
...options,
|
|
450
|
+
...withCoordinate(this.coordinate)
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/** Minimum-total-weight path at this view's pinned coordinate. */
|
|
454
|
+
weightedShortestPath(from, to, options) {
|
|
455
|
+
return this.internalAlgorithms().weightedShortestPath(from, to, {
|
|
433
456
|
...options,
|
|
434
457
|
...withCoordinate(this.coordinate)
|
|
435
458
|
});
|
|
436
459
|
}
|
|
437
460
|
/** Nodes reachable from `from` at this view's pinned coordinate. */
|
|
438
461
|
reachable(from, options) {
|
|
439
|
-
return this.
|
|
462
|
+
return this.internalAlgorithms().reachable(from, {
|
|
440
463
|
...options,
|
|
441
464
|
...withCoordinate(this.coordinate)
|
|
442
465
|
});
|
|
443
466
|
}
|
|
444
467
|
/** Whether `to` is reachable from `from` at this view's pinned coordinate. */
|
|
445
468
|
canReach(from, to, options) {
|
|
446
|
-
return this.
|
|
469
|
+
return this.internalAlgorithms().canReach(from, to, {
|
|
447
470
|
...options,
|
|
448
471
|
...withCoordinate(this.coordinate)
|
|
449
472
|
});
|
|
450
473
|
}
|
|
451
474
|
/** The k-hop neighborhood of `node` at this view's pinned coordinate. */
|
|
452
475
|
neighbors(node, options) {
|
|
453
|
-
return this.
|
|
476
|
+
return this.internalAlgorithms().neighbors(node, {
|
|
454
477
|
...options,
|
|
455
478
|
...withCoordinate(this.coordinate)
|
|
456
479
|
});
|
|
457
480
|
}
|
|
458
481
|
/** Counts active edges incident to `node` at this view's pinned coordinate. */
|
|
459
482
|
degree(node, options) {
|
|
460
|
-
return this.
|
|
483
|
+
return this.internalAlgorithms().degree(node, {
|
|
484
|
+
...options,
|
|
485
|
+
...withCoordinate(this.coordinate)
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/** Exact WCC memberships at this view's pinned coordinate. */
|
|
489
|
+
weaklyConnectedComponents(options) {
|
|
490
|
+
return this.internalAlgorithms().weaklyConnectedComponents({
|
|
461
491
|
...options,
|
|
462
492
|
...withCoordinate(this.coordinate)
|
|
463
493
|
});
|
|
@@ -1457,6 +1487,97 @@ function buildStandardOrderBy(input) {
|
|
|
1457
1487
|
}
|
|
1458
1488
|
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
1459
1489
|
}
|
|
1490
|
+
var LATE_MAT_TOPK_CTE_ALIAS = "cte_lm_topk";
|
|
1491
|
+
var LATE_MAT_SORT_KEY_PREFIX = "__lm_sk";
|
|
1492
|
+
var LATE_MAT_PHYSICAL_ALIAS_PREFIX = "lm_";
|
|
1493
|
+
function lateMaterializedPhysicalAlias(alias) {
|
|
1494
|
+
return `${LATE_MAT_PHYSICAL_ALIAS_PREFIX}${alias}`;
|
|
1495
|
+
}
|
|
1496
|
+
function lateMaterializedNodeAliases(ast) {
|
|
1497
|
+
return [
|
|
1498
|
+
ast.start.alias,
|
|
1499
|
+
...ast.traversals.map((traversal) => traversal.nodeAlias)
|
|
1500
|
+
];
|
|
1501
|
+
}
|
|
1502
|
+
function lateMaterializedProjectedNodeAliases(ast) {
|
|
1503
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1504
|
+
for (const field2 of ast.selectiveFields ?? []) {
|
|
1505
|
+
referenced.add(field2.alias);
|
|
1506
|
+
}
|
|
1507
|
+
return lateMaterializedNodeAliases(ast).filter(
|
|
1508
|
+
(alias) => referenced.has(alias)
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
function buildLateMaterializedTopKCte(input) {
|
|
1512
|
+
const { ast, collapsedTraversalCteAlias, dialect, fromClause } = input;
|
|
1513
|
+
const aliasToCte = buildAliasToCteMap(ast);
|
|
1514
|
+
const cteAliasFor = (alias) => collapsedTraversalCteAlias ?? aliasToCte.get(alias) ?? `${ALIAS_CTE_PREFIX}${alias}`;
|
|
1515
|
+
const columns = [];
|
|
1516
|
+
for (const alias of lateMaterializedNodeAliases(ast)) {
|
|
1517
|
+
const cteAlias = cteAliasFor(alias);
|
|
1518
|
+
columns.push(
|
|
1519
|
+
sql`${qualifyColumn(cteAlias, `${alias}_id`)} AS ${sql.raw(`${alias}_id`)}`,
|
|
1520
|
+
sql`${qualifyColumn(cteAlias, `${alias}_kind`)} AS ${sql.raw(`${alias}_kind`)}`
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
for (const [index, orderSpec] of (ast.orderBy ?? []).entries()) {
|
|
1524
|
+
const value = compileOrderFieldValue(
|
|
1525
|
+
ast,
|
|
1526
|
+
orderSpec.field,
|
|
1527
|
+
dialect,
|
|
1528
|
+
cteAliasFor(orderSpec.field.alias)
|
|
1529
|
+
);
|
|
1530
|
+
columns.push(
|
|
1531
|
+
sql`${value} AS ${sql.raw(`${LATE_MAT_SORT_KEY_PREFIX}${index}`)}`
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
const innerOrderBy = buildStandardOrderBy({
|
|
1535
|
+
ast,
|
|
1536
|
+
dialect,
|
|
1537
|
+
...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
|
|
1538
|
+
});
|
|
1539
|
+
const limitOffset = buildLimitOffsetClause({
|
|
1540
|
+
limit: input.limit,
|
|
1541
|
+
offset: input.offset
|
|
1542
|
+
});
|
|
1543
|
+
const parts = [sql`SELECT ${sql.join(columns, sql`, `)}`, fromClause];
|
|
1544
|
+
if (innerOrderBy !== void 0) parts.push(innerOrderBy);
|
|
1545
|
+
if (limitOffset !== void 0) parts.push(limitOffset);
|
|
1546
|
+
return sql`${sql.raw(LATE_MAT_TOPK_CTE_ALIAS)} AS (${sql.join(parts, sql` `)})`;
|
|
1547
|
+
}
|
|
1548
|
+
function buildLateMaterializedOuterProjection(ast, dialect) {
|
|
1549
|
+
const columns = (ast.selectiveFields ?? []).map((field2) => {
|
|
1550
|
+
const physicalAlias = lateMaterializedPhysicalAlias(field2.alias);
|
|
1551
|
+
if (field2.isSystemField) {
|
|
1552
|
+
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
1553
|
+
return sql`${compileColumnReference(physicalAlias, dbColumn)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1554
|
+
}
|
|
1555
|
+
const extracted = compileSelectivePropsExtraction(
|
|
1556
|
+
field2,
|
|
1557
|
+
compileColumnReference(physicalAlias, "props"),
|
|
1558
|
+
dialect
|
|
1559
|
+
);
|
|
1560
|
+
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1561
|
+
});
|
|
1562
|
+
return sql.join(columns, sql`, `);
|
|
1563
|
+
}
|
|
1564
|
+
function buildLateMaterializedOuterOrderBy(ast) {
|
|
1565
|
+
const orderBy = ast.orderBy ?? [];
|
|
1566
|
+
if (orderBy.length === 0) return void 0;
|
|
1567
|
+
const topk = sql.raw(LATE_MAT_TOPK_CTE_ALIAS);
|
|
1568
|
+
const parts = [];
|
|
1569
|
+
for (const [index, orderSpec] of orderBy.entries()) {
|
|
1570
|
+
const column = sql`${topk}.${sql.raw(`${LATE_MAT_SORT_KEY_PREFIX}${index}`)}`;
|
|
1571
|
+
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
1572
|
+
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
1573
|
+
const nullsDirection = sql.raw(nulls === "first" ? "DESC" : "ASC");
|
|
1574
|
+
parts.push(
|
|
1575
|
+
sql`(${column} IS NULL) ${nullsDirection}`,
|
|
1576
|
+
sql`${column} ${direction}`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
1580
|
+
}
|
|
1460
1581
|
function fieldRefKey(field2) {
|
|
1461
1582
|
const pointer = field2.jsonPointer ?? "";
|
|
1462
1583
|
return `${field2.alias}:${field2.path.join(".")}:${pointer}`;
|
|
@@ -3611,31 +3732,34 @@ function markPredicateFieldsAsRequired(requiredColumnsByAlias, expression) {
|
|
|
3611
3732
|
}
|
|
3612
3733
|
}
|
|
3613
3734
|
}
|
|
3614
|
-
function collectRequiredColumnsByAlias2(ast) {
|
|
3735
|
+
function collectRequiredColumnsByAlias2(ast, options) {
|
|
3736
|
+
const includeProjection = options?.includeProjection ?? true;
|
|
3615
3737
|
const requiredColumnsByAlias = /* @__PURE__ */ new Map();
|
|
3616
3738
|
addRequiredColumn(requiredColumnsByAlias, ast.start.alias, "id");
|
|
3617
3739
|
for (const traversal of ast.traversals) {
|
|
3618
3740
|
addRequiredColumn(requiredColumnsByAlias, traversal.nodeAlias, "id");
|
|
3619
3741
|
}
|
|
3620
3742
|
const hasSelectiveFields = (ast.selectiveFields?.length ?? 0) > 0;
|
|
3621
|
-
if (
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3743
|
+
if (includeProjection) {
|
|
3744
|
+
if (hasSelectiveFields) {
|
|
3745
|
+
for (const field2 of ast.selectiveFields ?? []) {
|
|
3746
|
+
if (field2.isSystemField) {
|
|
3747
|
+
addRequiredColumn(
|
|
3748
|
+
requiredColumnsByAlias,
|
|
3749
|
+
field2.alias,
|
|
3750
|
+
mapSelectiveSystemFieldToColumn(field2.field)
|
|
3751
|
+
);
|
|
3752
|
+
}
|
|
3629
3753
|
}
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3754
|
+
} else {
|
|
3755
|
+
for (const projectedField of ast.projection.fields) {
|
|
3756
|
+
const source = projectedField.source;
|
|
3757
|
+
if (source.__type === "field_ref") {
|
|
3758
|
+
markFieldRefAsRequired(requiredColumnsByAlias, source);
|
|
3759
|
+
} else {
|
|
3760
|
+
addRequiredColumn(requiredColumnsByAlias, source.field.alias, "id");
|
|
3761
|
+
markFieldRefAsRequired(requiredColumnsByAlias, source.field);
|
|
3762
|
+
}
|
|
3639
3763
|
}
|
|
3640
3764
|
}
|
|
3641
3765
|
}
|
|
@@ -4318,6 +4442,169 @@ function stripLimitOffsetFromPlanNode(node) {
|
|
|
4318
4442
|
}
|
|
4319
4443
|
return node;
|
|
4320
4444
|
}
|
|
4445
|
+
function buildStandardStartAndTraversalCtes(input) {
|
|
4446
|
+
const {
|
|
4447
|
+
ast,
|
|
4448
|
+
carryForwardPreviousColumns,
|
|
4449
|
+
ctx,
|
|
4450
|
+
graphId,
|
|
4451
|
+
predicateIndex,
|
|
4452
|
+
requiredColumnsByAlias,
|
|
4453
|
+
temporalFilterPass,
|
|
4454
|
+
traversalLimit
|
|
4455
|
+
} = input;
|
|
4456
|
+
const ctes = [
|
|
4457
|
+
buildStandardStartCte({
|
|
4458
|
+
ast,
|
|
4459
|
+
ctx,
|
|
4460
|
+
graphId,
|
|
4461
|
+
predicateIndex,
|
|
4462
|
+
requiredColumnsByAlias,
|
|
4463
|
+
temporalFilterPass
|
|
4464
|
+
})
|
|
4465
|
+
];
|
|
4466
|
+
for (let index = 0; index < ast.traversals.length; index++) {
|
|
4467
|
+
ctes.push(
|
|
4468
|
+
buildStandardTraversalCte({
|
|
4469
|
+
ast,
|
|
4470
|
+
carryForwardPreviousColumns,
|
|
4471
|
+
ctx,
|
|
4472
|
+
graphId,
|
|
4473
|
+
materializeCte: shouldMaterializeTraversalCte(
|
|
4474
|
+
ctx.dialect,
|
|
4475
|
+
ast.traversals.length,
|
|
4476
|
+
index
|
|
4477
|
+
),
|
|
4478
|
+
predicateIndex,
|
|
4479
|
+
requiredColumnsByAlias,
|
|
4480
|
+
temporalFilterPass,
|
|
4481
|
+
traversalIndex: index,
|
|
4482
|
+
traversalLimit
|
|
4483
|
+
})
|
|
4484
|
+
);
|
|
4485
|
+
}
|
|
4486
|
+
return ctes;
|
|
4487
|
+
}
|
|
4488
|
+
function selectiveFieldIsIdentity(field2) {
|
|
4489
|
+
if (!field2.isSystemField) {
|
|
4490
|
+
return false;
|
|
4491
|
+
}
|
|
4492
|
+
const column = mapSelectiveSystemFieldToColumn(field2.field);
|
|
4493
|
+
return column === "id" || column === "kind";
|
|
4494
|
+
}
|
|
4495
|
+
function resolveLateMaterializationPlan(ast) {
|
|
4496
|
+
if (ast.recordedAsOf !== void 0) {
|
|
4497
|
+
return void 0;
|
|
4498
|
+
}
|
|
4499
|
+
const selectiveFields = ast.selectiveFields;
|
|
4500
|
+
if (selectiveFields === void 0 || selectiveFields.length === 0) {
|
|
4501
|
+
return void 0;
|
|
4502
|
+
}
|
|
4503
|
+
const orderBy = ast.orderBy;
|
|
4504
|
+
if (orderBy === void 0 || orderBy.length === 0) {
|
|
4505
|
+
return void 0;
|
|
4506
|
+
}
|
|
4507
|
+
if (ast.limit === void 0 || ast.limit <= 0) {
|
|
4508
|
+
return void 0;
|
|
4509
|
+
}
|
|
4510
|
+
if (ast.groupBy !== void 0 || ast.having !== void 0) {
|
|
4511
|
+
return void 0;
|
|
4512
|
+
}
|
|
4513
|
+
if (ast.aggregateOrderBy !== void 0 && ast.aggregateOrderBy.length > 0) {
|
|
4514
|
+
return void 0;
|
|
4515
|
+
}
|
|
4516
|
+
if (ast.traversals.some((traversal) => traversal.optional)) {
|
|
4517
|
+
return void 0;
|
|
4518
|
+
}
|
|
4519
|
+
const nodeAliases = /* @__PURE__ */ new Set([
|
|
4520
|
+
ast.start.alias,
|
|
4521
|
+
...ast.traversals.map((traversal) => traversal.nodeAlias)
|
|
4522
|
+
]);
|
|
4523
|
+
for (const field2 of selectiveFields) {
|
|
4524
|
+
if (!nodeAliases.has(field2.alias)) {
|
|
4525
|
+
return void 0;
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
for (const orderSpec of orderBy) {
|
|
4529
|
+
if (!nodeAliases.has(orderSpec.field.alias)) {
|
|
4530
|
+
return void 0;
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
const orderReferenced = /* @__PURE__ */ new Set();
|
|
4534
|
+
for (const orderSpec of orderBy) {
|
|
4535
|
+
const matched = findSelectivePropsFieldForFieldRef(
|
|
4536
|
+
selectiveFields,
|
|
4537
|
+
orderSpec.field
|
|
4538
|
+
);
|
|
4539
|
+
if (matched !== void 0) {
|
|
4540
|
+
orderReferenced.add(matched);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
const hasDeferredColumn = selectiveFields.some(
|
|
4544
|
+
(field2) => !selectiveFieldIsIdentity(field2) && !orderReferenced.has(field2)
|
|
4545
|
+
);
|
|
4546
|
+
if (!hasDeferredColumn) {
|
|
4547
|
+
return void 0;
|
|
4548
|
+
}
|
|
4549
|
+
const prunedSelectiveFields = selectiveFields.filter(
|
|
4550
|
+
(field2) => field2.isSystemField || orderReferenced.has(field2)
|
|
4551
|
+
);
|
|
4552
|
+
return {
|
|
4553
|
+
leanAst: { ...ast, selectiveFields: prunedSelectiveFields },
|
|
4554
|
+
limit: ast.limit,
|
|
4555
|
+
offset: ast.offset
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
function buildLateMaterializedOuterFromClause(ast, graphId, ctx) {
|
|
4559
|
+
const topk = sql.raw(LATE_MAT_TOPK_CTE_ALIAS);
|
|
4560
|
+
const joins = lateMaterializedProjectedNodeAliases(ast).map((alias) => {
|
|
4561
|
+
const physical = sql.raw(lateMaterializedPhysicalAlias(alias));
|
|
4562
|
+
return sql`JOIN ${ctx.schema.nodesTable} ${physical} ON ${physical}.graph_id = ${graphId} AND ${physical}.kind = ${topk}.${sql.raw(`${alias}_kind`)} AND ${physical}.id = ${topk}.${sql.raw(`${alias}_id`)}`;
|
|
4563
|
+
});
|
|
4564
|
+
return joins.length === 0 ? sql`FROM ${topk}` : sql`FROM ${topk} ${sql.join(joins, sql` `)}`;
|
|
4565
|
+
}
|
|
4566
|
+
function compileLateMaterializedQuery(ast, graphId, ctx, logicalPlan, predicateIndex, temporalFilterPass, collapsedTraversalCteAlias, shouldCollapseSelectiveTraversalRowset) {
|
|
4567
|
+
const plan = resolveLateMaterializationPlan(ast);
|
|
4568
|
+
if (plan === void 0) {
|
|
4569
|
+
return void 0;
|
|
4570
|
+
}
|
|
4571
|
+
const { leanAst, limit, offset } = plan;
|
|
4572
|
+
const { dialect } = ctx;
|
|
4573
|
+
const leanRequiredColumns = collectRequiredColumnsByAlias2(leanAst, {
|
|
4574
|
+
includeProjection: false
|
|
4575
|
+
});
|
|
4576
|
+
const ctes = buildStandardStartAndTraversalCtes({
|
|
4577
|
+
ast: leanAst,
|
|
4578
|
+
carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
|
|
4579
|
+
ctx,
|
|
4580
|
+
graphId,
|
|
4581
|
+
predicateIndex,
|
|
4582
|
+
requiredColumnsByAlias: leanRequiredColumns,
|
|
4583
|
+
temporalFilterPass,
|
|
4584
|
+
traversalLimit: void 0
|
|
4585
|
+
});
|
|
4586
|
+
ctes.push(
|
|
4587
|
+
buildLateMaterializedTopKCte({
|
|
4588
|
+
ast: leanAst,
|
|
4589
|
+
dialect,
|
|
4590
|
+
fromClause: buildStandardFromClause({
|
|
4591
|
+
ast: leanAst,
|
|
4592
|
+
...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
|
|
4593
|
+
}),
|
|
4594
|
+
limit,
|
|
4595
|
+
offset,
|
|
4596
|
+
...collapsedTraversalCteAlias === void 0 ? {} : { collapsedTraversalCteAlias }
|
|
4597
|
+
})
|
|
4598
|
+
);
|
|
4599
|
+
const orderBy = buildLateMaterializedOuterOrderBy(ast);
|
|
4600
|
+
return emitStandardQuerySql({
|
|
4601
|
+
ctes,
|
|
4602
|
+
fromClause: buildLateMaterializedOuterFromClause(ast, graphId, ctx),
|
|
4603
|
+
...orderBy === void 0 ? {} : { orderBy },
|
|
4604
|
+
logicalPlan: stripLimitOffsetFromPlan(logicalPlan),
|
|
4605
|
+
projection: buildLateMaterializedOuterProjection(ast, dialect)
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4321
4608
|
var STANDARD_QUERY_STRATEGY_HANDLERS = {
|
|
4322
4609
|
cte_project: compileStandardQueryWithCteStrategy
|
|
4323
4610
|
};
|
|
@@ -4417,38 +4704,30 @@ function compileStandardQueryWithCteStrategy(ast, graphId, ctx) {
|
|
|
4417
4704
|
if (fastPathSql) {
|
|
4418
4705
|
return fastPathSql;
|
|
4419
4706
|
}
|
|
4420
|
-
|
|
4421
|
-
const ctes = [
|
|
4422
|
-
buildStandardStartCte({
|
|
4707
|
+
const lateMaterializedSql = compileLateMaterializedQuery(
|
|
4423
4708
|
ast,
|
|
4424
|
-
ctx,
|
|
4425
4709
|
graphId,
|
|
4710
|
+
ctx,
|
|
4711
|
+
logicalPlan,
|
|
4426
4712
|
predicateIndex,
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
];
|
|
4431
|
-
for (let index = 0; index < ast.traversals.length; index++) {
|
|
4432
|
-
const materializeTraversalCte = shouldMaterializeTraversalCte(
|
|
4433
|
-
dialect,
|
|
4434
|
-
ast.traversals.length,
|
|
4435
|
-
index
|
|
4436
|
-
);
|
|
4437
|
-
ctes.push(
|
|
4438
|
-
buildStandardTraversalCte({
|
|
4439
|
-
ast,
|
|
4440
|
-
carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
|
|
4441
|
-
ctx,
|
|
4442
|
-
graphId,
|
|
4443
|
-
materializeCte: materializeTraversalCte,
|
|
4444
|
-
predicateIndex,
|
|
4445
|
-
requiredColumnsByAlias,
|
|
4446
|
-
temporalFilterPass,
|
|
4447
|
-
traversalIndex: index,
|
|
4448
|
-
traversalLimit: traversalCteLimit
|
|
4449
|
-
})
|
|
4713
|
+
temporalFilterPass,
|
|
4714
|
+
collapsedTraversalCteAlias,
|
|
4715
|
+
shouldCollapseSelectiveTraversalRowset
|
|
4450
4716
|
);
|
|
4717
|
+
if (lateMaterializedSql) {
|
|
4718
|
+
return lateMaterializedSql;
|
|
4719
|
+
}
|
|
4451
4720
|
}
|
|
4721
|
+
const ctes = buildStandardStartAndTraversalCtes({
|
|
4722
|
+
ast,
|
|
4723
|
+
carryForwardPreviousColumns: shouldCollapseSelectiveTraversalRowset,
|
|
4724
|
+
ctx,
|
|
4725
|
+
graphId,
|
|
4726
|
+
predicateIndex,
|
|
4727
|
+
requiredColumnsByAlias,
|
|
4728
|
+
temporalFilterPass,
|
|
4729
|
+
traversalLimit: traversalCteLimit
|
|
4730
|
+
});
|
|
4452
4731
|
if (vectorPredicate) {
|
|
4453
4732
|
const nodeKinds = getNodeKindsForAlias(ast, vectorPredicate.field.alias);
|
|
4454
4733
|
ctes.push(
|
|
@@ -10512,6 +10791,16 @@ function resolveMaxHops(rawMaxHops, fallback, optionName) {
|
|
|
10512
10791
|
}
|
|
10513
10792
|
return value;
|
|
10514
10793
|
}
|
|
10794
|
+
function resolveMaxIterations(value, fallback, algorithm) {
|
|
10795
|
+
const maxIterations = value ?? fallback;
|
|
10796
|
+
if (!Number.isSafeInteger(maxIterations) || maxIterations < 1) {
|
|
10797
|
+
throw new ConfigurationError(
|
|
10798
|
+
`${algorithm} maxIterations must be a positive safe integer, got ${String(maxIterations)}.`,
|
|
10799
|
+
{ maxIterations }
|
|
10800
|
+
);
|
|
10801
|
+
}
|
|
10802
|
+
return maxIterations;
|
|
10803
|
+
}
|
|
10515
10804
|
function assertEdgeKinds(edges) {
|
|
10516
10805
|
if (edges.length === 0) {
|
|
10517
10806
|
throw new ConfigurationError(
|
|
@@ -10561,65 +10850,982 @@ function compileDirectionFilter(ctx, direction, nodeId, nodesTable) {
|
|
|
10561
10850
|
}
|
|
10562
10851
|
}
|
|
10563
10852
|
}
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
const
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10853
|
+
var DEFAULT_MAX_BIND_PARAMETERS = 999;
|
|
10854
|
+
var RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH = 12;
|
|
10855
|
+
var RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH = 4;
|
|
10856
|
+
var WORKING_TABLE_ANALYZE_MINIMUM_ROWS = 16;
|
|
10857
|
+
var WORKING_TABLE_ANALYZE_GROWTH_FACTOR = 4;
|
|
10858
|
+
var ITERATIVE_WORKING_MEMORY_PATTERN = /^(\d+)(kB|MB|GB)$/;
|
|
10859
|
+
var WORKING_MEMORY_KILOBYTES_PER_UNIT = {
|
|
10860
|
+
kB: 1,
|
|
10861
|
+
MB: 1024,
|
|
10862
|
+
GB: 1024 * 1024
|
|
10863
|
+
};
|
|
10864
|
+
var MIN_WORKING_MEMORY_KILOBYTES = 64;
|
|
10865
|
+
var MAX_WORKING_MEMORY_KILOBYTES = 2147483647;
|
|
10866
|
+
function resolveWorkingMemory(workingMemory) {
|
|
10867
|
+
if (workingMemory === void 0) return void 0;
|
|
10868
|
+
const match = ITERATIVE_WORKING_MEMORY_PATTERN.exec(workingMemory);
|
|
10869
|
+
if (match === null) {
|
|
10870
|
+
throw new ConfigurationError(
|
|
10871
|
+
`Iterative graph operation workingMemory must be digits followed by kB, MB, or GB (for example "64MB"), got ${JSON.stringify(workingMemory)}.`,
|
|
10872
|
+
{ workingMemory }
|
|
10873
|
+
);
|
|
10874
|
+
}
|
|
10875
|
+
const kilobytes = Number(match[1]) * (WORKING_MEMORY_KILOBYTES_PER_UNIT[match[2] ?? ""] ?? 0);
|
|
10876
|
+
if (kilobytes < MIN_WORKING_MEMORY_KILOBYTES || kilobytes > MAX_WORKING_MEMORY_KILOBYTES) {
|
|
10877
|
+
throw new ConfigurationError(
|
|
10878
|
+
`Iterative graph operation workingMemory must be between ${MIN_WORKING_MEMORY_KILOBYTES}kB and ${MAX_WORKING_MEMORY_KILOBYTES}kB, got ${JSON.stringify(workingMemory)}.`,
|
|
10879
|
+
{ workingMemory, kilobytes }
|
|
10880
|
+
);
|
|
10881
|
+
}
|
|
10882
|
+
return workingMemory;
|
|
10594
10883
|
}
|
|
10595
|
-
async function
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
maxHops,
|
|
10607
|
-
direction: options.direction ?? "out",
|
|
10608
|
-
cyclePolicy: options.cyclePolicy ?? "prevent",
|
|
10609
|
-
includePath: false,
|
|
10610
|
-
...resolveTemporalOptions(ctx, options),
|
|
10611
|
-
dialect: ctx.dialect,
|
|
10612
|
-
schema: ctx.schema,
|
|
10613
|
-
...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
|
|
10614
|
-
});
|
|
10615
|
-
const query = sql`${cte} SELECT 1 AS hit FROM reachable WHERE id = ${targetId} LIMIT 1`;
|
|
10616
|
-
const rows = await ctx.backend.execute(
|
|
10617
|
-
asCompiledRowsSql(query)
|
|
10618
|
-
);
|
|
10619
|
-
return rows.length > 0;
|
|
10884
|
+
async function withInlineIterativeGraphOperation(ctx, options, run) {
|
|
10885
|
+
if (ctx.backend.capabilities.transactions) {
|
|
10886
|
+
return ctx.backend.transaction(
|
|
10887
|
+
async (backend) => run(createOperation(ctx, options, backend)),
|
|
10888
|
+
{
|
|
10889
|
+
isolationLevel: "repeatable_read",
|
|
10890
|
+
accessMode: "read_only"
|
|
10891
|
+
}
|
|
10892
|
+
);
|
|
10893
|
+
}
|
|
10894
|
+
return run(createOperation(ctx, options, ctx.backend));
|
|
10620
10895
|
}
|
|
10621
|
-
async function
|
|
10622
|
-
|
|
10896
|
+
async function runIterativeGraphOperation(ctx, options, plan) {
|
|
10897
|
+
if (!ctx.backend.capabilities.transactions) {
|
|
10898
|
+
throw new ConfigurationError(
|
|
10899
|
+
"Temporary-table graph iteration requires a transactional backend.",
|
|
10900
|
+
{ dialect: ctx.backend.dialect }
|
|
10901
|
+
);
|
|
10902
|
+
}
|
|
10903
|
+
return ctx.backend.transaction(
|
|
10904
|
+
async (backend) => {
|
|
10905
|
+
const temporaryBackend = requireTemporaryStatements(backend);
|
|
10906
|
+
const runId = generateId();
|
|
10907
|
+
const workingTable = sql.identifier(
|
|
10908
|
+
`typegraph_iterative_${runId.replaceAll("-", "_")}`
|
|
10909
|
+
);
|
|
10910
|
+
const context = {
|
|
10911
|
+
operation: createOperation(ctx, options, temporaryBackend),
|
|
10912
|
+
backend: temporaryBackend,
|
|
10913
|
+
workingTable,
|
|
10914
|
+
graphId: ctx.graphId,
|
|
10915
|
+
runId,
|
|
10916
|
+
executeTemporary: async (query) => {
|
|
10917
|
+
await temporaryBackend.executeTemporaryStatement(
|
|
10918
|
+
asCompiledTemporaryStatementSql(query)
|
|
10919
|
+
);
|
|
10920
|
+
}
|
|
10921
|
+
};
|
|
10922
|
+
if (context.operation.workingMemory !== void 0) {
|
|
10923
|
+
const workingMemoryStatement = ctx.dialect.setTransactionWorkingMemory(
|
|
10924
|
+
context.operation.workingMemory
|
|
10925
|
+
);
|
|
10926
|
+
if (workingMemoryStatement !== void 0) {
|
|
10927
|
+
await context.executeTemporary(workingMemoryStatement);
|
|
10928
|
+
}
|
|
10929
|
+
}
|
|
10930
|
+
await context.executeTemporary(plan.createWorkingTable(context));
|
|
10931
|
+
let operationError;
|
|
10932
|
+
try {
|
|
10933
|
+
let state = await plan.initialize(context);
|
|
10934
|
+
let analyzedRowCount = await refreshWorkingTableStatistics(
|
|
10935
|
+
context,
|
|
10936
|
+
state.workingTableSize
|
|
10937
|
+
);
|
|
10938
|
+
for (let iteration = 1; iteration <= plan.maxIterations && !plan.hasConverged(state); iteration++) {
|
|
10939
|
+
state = await plan.runRound(context, state, iteration);
|
|
10940
|
+
if (iteration < plan.maxIterations && !plan.hasConverged(state)) {
|
|
10941
|
+
analyzedRowCount = await refreshWorkingTableStatistics(
|
|
10942
|
+
context,
|
|
10943
|
+
state.workingTableSize,
|
|
10944
|
+
analyzedRowCount
|
|
10945
|
+
);
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
if (!plan.hasConverged(state)) {
|
|
10949
|
+
throw new GraphAlgorithmConvergenceError(
|
|
10950
|
+
plan.algorithm,
|
|
10951
|
+
plan.maxIterations
|
|
10952
|
+
);
|
|
10953
|
+
}
|
|
10954
|
+
return await plan.extractResult(context, state);
|
|
10955
|
+
} catch (error) {
|
|
10956
|
+
operationError = error;
|
|
10957
|
+
throw error;
|
|
10958
|
+
} finally {
|
|
10959
|
+
await dropWorkingTable(context, operationError);
|
|
10960
|
+
}
|
|
10961
|
+
},
|
|
10962
|
+
{
|
|
10963
|
+
isolationLevel: "repeatable_read",
|
|
10964
|
+
accessMode: "read_only",
|
|
10965
|
+
temporaryWrites: INTERNAL_TEMPORARY_WRITES
|
|
10966
|
+
}
|
|
10967
|
+
);
|
|
10968
|
+
}
|
|
10969
|
+
function shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount) {
|
|
10970
|
+
if (workingTableSize < WORKING_TABLE_ANALYZE_MINIMUM_ROWS) return false;
|
|
10971
|
+
if (analyzedRowCount === void 0) return true;
|
|
10972
|
+
return workingTableSize >= analyzedRowCount * WORKING_TABLE_ANALYZE_GROWTH_FACTOR;
|
|
10973
|
+
}
|
|
10974
|
+
async function refreshWorkingTableStatistics(context, workingTableSize, analyzedRowCount) {
|
|
10975
|
+
if (!shouldRefreshWorkingTableStatistics(workingTableSize, analyzedRowCount)) {
|
|
10976
|
+
return analyzedRowCount;
|
|
10977
|
+
}
|
|
10978
|
+
const statement = context.operation.ctx.dialect.analyzeTemporaryTable(
|
|
10979
|
+
context.workingTable
|
|
10980
|
+
);
|
|
10981
|
+
if (statement === void 0) return analyzedRowCount;
|
|
10982
|
+
await context.executeTemporary(statement);
|
|
10983
|
+
return workingTableSize;
|
|
10984
|
+
}
|
|
10985
|
+
async function dropWorkingTable(context, operationError) {
|
|
10986
|
+
try {
|
|
10987
|
+
await context.executeTemporary(
|
|
10988
|
+
sql`DROP TABLE IF EXISTS ${context.workingTable}`
|
|
10989
|
+
);
|
|
10990
|
+
} catch (cleanupError) {
|
|
10991
|
+
if (operationError === void 0) throw cleanupError;
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
async function fetchVisibleWorkingNodes(operation, nodeIds) {
|
|
10995
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
10996
|
+
for (const chunk2 of chunkValues(nodeIds, operation.maxWorkingSetSize)) {
|
|
10997
|
+
const query = sql`SELECT n.id, n.kind FROM ${operation.schema.nodesTable} n WHERE n.graph_id = ${operation.ctx.graphId} AND n.id IN (${sqlValueList(chunk2)}) AND ${operation.nodeTemporalFilter}`;
|
|
10998
|
+
const rows = await operation.backend.execute(
|
|
10999
|
+
asCompiledRowsSql(query)
|
|
11000
|
+
);
|
|
11001
|
+
for (const row of rows) nodes.set(nodeIdentityKey(row), row);
|
|
11002
|
+
}
|
|
11003
|
+
return [...nodes.values()].toSorted(
|
|
11004
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
11005
|
+
);
|
|
11006
|
+
}
|
|
11007
|
+
async function reduceExpandedWorkingSet(operation, workingSet, direction, reduce) {
|
|
11008
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
11009
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
11010
|
+
for (const chunk2 of chunkValues(workingSet, operation.maxWorkingSetSize)) {
|
|
11011
|
+
const rows = await operation.backend.execute(
|
|
11012
|
+
asCompiledRowsSql(
|
|
11013
|
+
compileExpansionQuery(operation, chunk2, direction, edgeKinds)
|
|
11014
|
+
)
|
|
11015
|
+
);
|
|
11016
|
+
for (const row of rows) {
|
|
11017
|
+
if (operation.weightExpression !== void 0 && !isPresent(row.weight)) {
|
|
11018
|
+
throw new CompilerInvariantError(
|
|
11019
|
+
"Weighted graph expansion produced a NULL weight. Either the plan skipped its weight audit, or edge data changed concurrently during a run on a backend without snapshot isolation.",
|
|
11020
|
+
{ source: row.source_id, target: row.target_id }
|
|
11021
|
+
);
|
|
11022
|
+
}
|
|
11023
|
+
const expansion = {
|
|
11024
|
+
source: { id: row.source_id, kind: row.source_kind },
|
|
11025
|
+
target: { id: row.target_id, kind: row.target_kind },
|
|
11026
|
+
...isPresent(row.weight) ? { weight: Number(row.weight) } : {}
|
|
11027
|
+
};
|
|
11028
|
+
const targetKey = nodeIdentityKey(expansion.target);
|
|
11029
|
+
const selected = reduce(reduced.get(targetKey), expansion);
|
|
11030
|
+
if (selected !== void 0) reduced.set(targetKey, selected);
|
|
11031
|
+
}
|
|
11032
|
+
}
|
|
11033
|
+
}
|
|
11034
|
+
return reduced;
|
|
11035
|
+
}
|
|
11036
|
+
function nodeIdentityKey(node) {
|
|
11037
|
+
return `${node.kind}\0${node.id}`;
|
|
11038
|
+
}
|
|
11039
|
+
function nodeIdentityKeyFromRow(id, kind) {
|
|
11040
|
+
if (typeof id !== "string" || typeof kind !== "string") return void 0;
|
|
11041
|
+
return nodeIdentityKey({ id, kind });
|
|
11042
|
+
}
|
|
11043
|
+
function compareNodeIdentity(left, right) {
|
|
11044
|
+
return compareStrings(left.id, right.id) || compareStrings(left.kind, right.kind);
|
|
11045
|
+
}
|
|
11046
|
+
function compileExpansionQuery(operation, workingSet, direction, edgeKinds) {
|
|
11047
|
+
const workingValues = sql.join(
|
|
11048
|
+
workingSet.map((node) => sql`(${node.id}, ${node.kind})`),
|
|
11049
|
+
sql`, `
|
|
11050
|
+
);
|
|
11051
|
+
const workingCte = sql`WITH working_set(node_id, node_kind) AS (VALUES ${workingValues})`;
|
|
11052
|
+
switch (direction) {
|
|
11053
|
+
case "out": {
|
|
11054
|
+
return sql`${workingCte} ${compileDirectionalExpansion(operation, sql`working_set`, sql`TRUE`, edgeKinds, "from_id", "from_kind", "to_id", "to_kind")}`;
|
|
11055
|
+
}
|
|
11056
|
+
case "in": {
|
|
11057
|
+
return sql`${workingCte} ${compileDirectionalExpansion(operation, sql`working_set`, sql`TRUE`, edgeKinds, "to_id", "to_kind", "from_id", "from_kind")}`;
|
|
11058
|
+
}
|
|
11059
|
+
case "both": {
|
|
11060
|
+
const outgoing = compileDirectionalExpansion(
|
|
11061
|
+
operation,
|
|
11062
|
+
sql`working_set`,
|
|
11063
|
+
sql`TRUE`,
|
|
11064
|
+
edgeKinds,
|
|
11065
|
+
"from_id",
|
|
11066
|
+
"from_kind",
|
|
11067
|
+
"to_id",
|
|
11068
|
+
"to_kind"
|
|
11069
|
+
);
|
|
11070
|
+
const incoming = compileDirectionalExpansion(
|
|
11071
|
+
operation,
|
|
11072
|
+
sql`working_set`,
|
|
11073
|
+
sql`TRUE`,
|
|
11074
|
+
edgeKinds,
|
|
11075
|
+
"to_id",
|
|
11076
|
+
"to_kind",
|
|
11077
|
+
"from_id",
|
|
11078
|
+
"from_kind"
|
|
11079
|
+
);
|
|
11080
|
+
return sql`${workingCte} ${outgoing} UNION ALL ${incoming}`;
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
function compileWorkingTableExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
|
|
11085
|
+
switch (direction) {
|
|
11086
|
+
case "out": {
|
|
11087
|
+
return compileDirectionalExpansion(
|
|
11088
|
+
operation,
|
|
11089
|
+
workingTable,
|
|
11090
|
+
sourceFilter,
|
|
11091
|
+
edgeKinds,
|
|
11092
|
+
"from_id",
|
|
11093
|
+
"from_kind",
|
|
11094
|
+
"to_id",
|
|
11095
|
+
"to_kind"
|
|
11096
|
+
);
|
|
11097
|
+
}
|
|
11098
|
+
case "in": {
|
|
11099
|
+
return compileDirectionalExpansion(
|
|
11100
|
+
operation,
|
|
11101
|
+
workingTable,
|
|
11102
|
+
sourceFilter,
|
|
11103
|
+
edgeKinds,
|
|
11104
|
+
"to_id",
|
|
11105
|
+
"to_kind",
|
|
11106
|
+
"from_id",
|
|
11107
|
+
"from_kind"
|
|
11108
|
+
);
|
|
11109
|
+
}
|
|
11110
|
+
case "both": {
|
|
11111
|
+
const outgoing = compileDirectionalExpansion(
|
|
11112
|
+
operation,
|
|
11113
|
+
workingTable,
|
|
11114
|
+
sourceFilter,
|
|
11115
|
+
edgeKinds,
|
|
11116
|
+
"from_id",
|
|
11117
|
+
"from_kind",
|
|
11118
|
+
"to_id",
|
|
11119
|
+
"to_kind"
|
|
11120
|
+
);
|
|
11121
|
+
const incoming = compileDirectionalExpansion(
|
|
11122
|
+
operation,
|
|
11123
|
+
workingTable,
|
|
11124
|
+
sourceFilter,
|
|
11125
|
+
edgeKinds,
|
|
11126
|
+
"to_id",
|
|
11127
|
+
"to_kind",
|
|
11128
|
+
"from_id",
|
|
11129
|
+
"from_kind"
|
|
11130
|
+
);
|
|
11131
|
+
return sql`${outgoing} UNION ALL ${incoming}`;
|
|
11132
|
+
}
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
function compileWorkingTableEdgeExpansion(operation, workingTable, sourceFilter, direction, edgeKinds) {
|
|
11136
|
+
switch (direction) {
|
|
11137
|
+
case "out": {
|
|
11138
|
+
return compileDirectionalEdgeExpansion(
|
|
11139
|
+
operation,
|
|
11140
|
+
workingTable,
|
|
11141
|
+
sourceFilter,
|
|
11142
|
+
edgeKinds,
|
|
11143
|
+
"from_id",
|
|
11144
|
+
"from_kind",
|
|
11145
|
+
"to_id",
|
|
11146
|
+
"to_kind"
|
|
11147
|
+
);
|
|
11148
|
+
}
|
|
11149
|
+
case "in": {
|
|
11150
|
+
return compileDirectionalEdgeExpansion(
|
|
11151
|
+
operation,
|
|
11152
|
+
workingTable,
|
|
11153
|
+
sourceFilter,
|
|
11154
|
+
edgeKinds,
|
|
11155
|
+
"to_id",
|
|
11156
|
+
"to_kind",
|
|
11157
|
+
"from_id",
|
|
11158
|
+
"from_kind"
|
|
11159
|
+
);
|
|
11160
|
+
}
|
|
11161
|
+
case "both": {
|
|
11162
|
+
const outgoing = compileDirectionalEdgeExpansion(
|
|
11163
|
+
operation,
|
|
11164
|
+
workingTable,
|
|
11165
|
+
sourceFilter,
|
|
11166
|
+
edgeKinds,
|
|
11167
|
+
"from_id",
|
|
11168
|
+
"from_kind",
|
|
11169
|
+
"to_id",
|
|
11170
|
+
"to_kind"
|
|
11171
|
+
);
|
|
11172
|
+
const incoming = compileDirectionalEdgeExpansion(
|
|
11173
|
+
operation,
|
|
11174
|
+
workingTable,
|
|
11175
|
+
sourceFilter,
|
|
11176
|
+
edgeKinds,
|
|
11177
|
+
"to_id",
|
|
11178
|
+
"to_kind",
|
|
11179
|
+
"from_id",
|
|
11180
|
+
"from_kind"
|
|
11181
|
+
);
|
|
11182
|
+
return sql`${outgoing} UNION ALL ${incoming}`;
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
function compileDirectionalExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
|
|
11187
|
+
const edgeExpansion = compileDirectionalEdgeExpansion(
|
|
11188
|
+
operation,
|
|
11189
|
+
workingRelation,
|
|
11190
|
+
sourceFilter,
|
|
11191
|
+
edgeKinds,
|
|
11192
|
+
joinField,
|
|
11193
|
+
joinKindField,
|
|
11194
|
+
targetField,
|
|
11195
|
+
targetKindField
|
|
11196
|
+
);
|
|
11197
|
+
const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, expanded.weight`;
|
|
11198
|
+
return sql`SELECT expanded.source_id, expanded.source_kind, n.id AS target_id, n.kind AS target_kind${weightColumn} FROM (${edgeExpansion}) expanded JOIN ${operation.schema.nodesTable} n ON n.graph_id = ${operation.ctx.graphId} AND n.id = expanded.target_id AND n.kind = expanded.target_kind WHERE ${operation.nodeTemporalFilter}`;
|
|
11199
|
+
}
|
|
11200
|
+
function compileDirectionalEdgeExpansion(operation, workingRelation, sourceFilter, edgeKinds, joinField, joinKindField, targetField, targetKindField) {
|
|
11201
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
|
|
11202
|
+
const weightColumn = operation.weightExpression === void 0 ? sql`` : sql`, ${operation.weightExpression} AS weight`;
|
|
11203
|
+
return sql`SELECT w.node_id AS source_id, w.node_kind AS source_kind, e.${sql.raw(targetField)} AS target_id, e.${sql.raw(targetKindField)} AS target_kind${weightColumn} FROM ${workingRelation} w JOIN ${operation.schema.edgesTable} e ON e.${sql.raw(joinField)} = w.node_id AND e.${sql.raw(joinKindField)} = w.node_kind AND e.graph_id = ${operation.ctx.graphId} WHERE ${sourceFilter} AND ${edgeKindFilter} AND ${operation.edgeTemporalFilter}`;
|
|
11204
|
+
}
|
|
11205
|
+
function chunkValues(values, chunkSize) {
|
|
11206
|
+
const chunks = [];
|
|
11207
|
+
for (let offset = 0; offset < values.length; offset += chunkSize) {
|
|
11208
|
+
chunks.push(values.slice(offset, offset + chunkSize));
|
|
11209
|
+
}
|
|
11210
|
+
return chunks;
|
|
11211
|
+
}
|
|
11212
|
+
function createOperation(ctx, options, backend) {
|
|
11213
|
+
const temporal = resolveTemporalOptions(ctx, options);
|
|
11214
|
+
const currentTimestamp = currentReadInstant();
|
|
11215
|
+
const nodeTemporalFilter = compileTemporalFilter({
|
|
11216
|
+
mode: temporal.temporalMode,
|
|
11217
|
+
...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
|
|
11218
|
+
...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
|
|
11219
|
+
tableAlias: "n",
|
|
11220
|
+
currentTimestamp
|
|
11221
|
+
});
|
|
11222
|
+
const edgeTemporalFilter = compileTemporalFilter({
|
|
11223
|
+
mode: temporal.temporalMode,
|
|
11224
|
+
...temporal.asOf === void 0 ? {} : { asOf: temporal.asOf },
|
|
11225
|
+
...temporal.recordedAsOf === void 0 ? {} : { recordedAsOf: temporal.recordedAsOf },
|
|
11226
|
+
tableAlias: "e",
|
|
11227
|
+
currentTimestamp
|
|
11228
|
+
});
|
|
11229
|
+
const weightExpression = options.weightProperty === void 0 ? void 0 : compileWeightExpression(
|
|
11230
|
+
ctx.dialect,
|
|
11231
|
+
options.weightProperty,
|
|
11232
|
+
options.defaultWeight
|
|
11233
|
+
);
|
|
11234
|
+
const direction = options.direction ?? "out";
|
|
11235
|
+
const branchCount = direction === "both" ? 2 : 1;
|
|
11236
|
+
const parameterLimit = backend.capabilities.maxBindParameters ?? DEFAULT_MAX_BIND_PARAMETERS;
|
|
11237
|
+
const reservedPerBranch = RESERVED_TEMPORAL_BIND_PARAMETERS_PER_BRANCH + (weightExpression === void 0 ? 0 : RESERVED_WEIGHT_BIND_PARAMETERS_PER_BRANCH);
|
|
11238
|
+
const fixedParameters = branchCount * reservedPerBranch;
|
|
11239
|
+
const sharedBudget = parameterLimit - fixedParameters;
|
|
11240
|
+
const maxEdgeKindsPerQuery = Math.floor(sharedBudget / (branchCount + 2));
|
|
11241
|
+
if (maxEdgeKindsPerQuery < 1) {
|
|
11242
|
+
throw new ConfigurationError(
|
|
11243
|
+
"An iterative graph operation cannot fit its fixed filters within the backend bind-parameter limit.",
|
|
11244
|
+
{ parameterLimit, direction }
|
|
11245
|
+
);
|
|
11246
|
+
}
|
|
11247
|
+
const edgeKindChunks = chunkValues(
|
|
11248
|
+
[...new Set(options.edges)].toSorted(
|
|
11249
|
+
(left, right) => compareStrings(left, right)
|
|
11250
|
+
),
|
|
11251
|
+
maxEdgeKindsPerQuery
|
|
11252
|
+
);
|
|
11253
|
+
const largestEdgeKindChunk = Math.max(
|
|
11254
|
+
...edgeKindChunks.map((chunk2) => chunk2.length)
|
|
11255
|
+
);
|
|
11256
|
+
const maxWorkingSetSize = Math.floor(
|
|
11257
|
+
(parameterLimit - branchCount * (largestEdgeKindChunk + reservedPerBranch)) / 2
|
|
11258
|
+
);
|
|
11259
|
+
if (maxWorkingSetSize < 1) {
|
|
11260
|
+
throw new ConfigurationError(
|
|
11261
|
+
"An iterative graph operation cannot fit one working-set node within the backend bind-parameter limit.",
|
|
11262
|
+
{ parameterLimit, direction }
|
|
11263
|
+
);
|
|
11264
|
+
}
|
|
11265
|
+
return {
|
|
11266
|
+
backend,
|
|
11267
|
+
ctx,
|
|
11268
|
+
edgeKindChunks,
|
|
11269
|
+
direction,
|
|
11270
|
+
maxWorkingSetSize,
|
|
11271
|
+
nodeTemporalFilter,
|
|
11272
|
+
edgeTemporalFilter,
|
|
11273
|
+
schema: resolveReadSchema(ctx, options),
|
|
11274
|
+
workingMemory: resolveWorkingMemory(options.workingMemory),
|
|
11275
|
+
weightExpression
|
|
11276
|
+
};
|
|
11277
|
+
}
|
|
11278
|
+
function compileWeightExpression(dialect, weightProperty, defaultWeight) {
|
|
11279
|
+
const extracted = dialect.jsonExtractDouble(
|
|
11280
|
+
sql.raw("e.props"),
|
|
11281
|
+
jsonPointer([weightProperty])
|
|
11282
|
+
);
|
|
11283
|
+
return defaultWeight === void 0 ? extracted : sql`COALESCE(${extracted}, ${defaultWeight})`;
|
|
11284
|
+
}
|
|
11285
|
+
function supportsTemporaryIteration(ctx) {
|
|
11286
|
+
return ctx.backend.capabilities.transactions && ctx.backend.capabilities.returning !== false && ctx.backend.executeTemporaryStatement !== void 0;
|
|
11287
|
+
}
|
|
11288
|
+
function requireTemporaryStatements(backend) {
|
|
11289
|
+
if (backend.executeTemporaryStatement === void 0) {
|
|
11290
|
+
throw new ConfigurationError(
|
|
11291
|
+
"Iterative graph operations require temporary-statement support on the transaction backend.",
|
|
11292
|
+
{ dialect: backend.dialect },
|
|
11293
|
+
{
|
|
11294
|
+
suggestion: "Use a built-in SQLite/PostgreSQL backend or implement executeTemporaryStatement on the custom backend."
|
|
11295
|
+
}
|
|
11296
|
+
);
|
|
11297
|
+
}
|
|
11298
|
+
return backend;
|
|
11299
|
+
}
|
|
11300
|
+
|
|
11301
|
+
// src/store/algorithms/breadth-first.ts
|
|
11302
|
+
async function findReachableNodes(ctx, sourceId, maxHops, options) {
|
|
11303
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
11304
|
+
return findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options);
|
|
11305
|
+
}
|
|
11306
|
+
return findReachableNodesInline(ctx, sourceId, maxHops, options);
|
|
11307
|
+
}
|
|
11308
|
+
async function findShortestPath(ctx, sourceId, targetId, maxHops, options) {
|
|
11309
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
11310
|
+
return findShortestPathInWorkingTable(
|
|
11311
|
+
ctx,
|
|
11312
|
+
sourceId,
|
|
11313
|
+
targetId,
|
|
11314
|
+
maxHops,
|
|
11315
|
+
options
|
|
11316
|
+
);
|
|
11317
|
+
}
|
|
11318
|
+
return findShortestPathInline(ctx, sourceId, targetId, maxHops, options);
|
|
11319
|
+
}
|
|
11320
|
+
async function findReachableNodesInWorkingTable(ctx, sourceId, maxHops, options) {
|
|
11321
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
11322
|
+
algorithm: "reachable",
|
|
11323
|
+
maxIterations: maxHops,
|
|
11324
|
+
createWorkingTable,
|
|
11325
|
+
async initialize(context) {
|
|
11326
|
+
const seeded = await seedWorkingSide(context, sourceId, "forward");
|
|
11327
|
+
return {
|
|
11328
|
+
depth: 0,
|
|
11329
|
+
frontierCount: seeded.length,
|
|
11330
|
+
workingTableSize: seeded.length
|
|
11331
|
+
};
|
|
11332
|
+
},
|
|
11333
|
+
async runRound(context, state) {
|
|
11334
|
+
const nextDepth = state.depth + 1;
|
|
11335
|
+
const frontierCount = await expandReachableWorkingTableRound(
|
|
11336
|
+
context,
|
|
11337
|
+
"forward",
|
|
11338
|
+
state.depth,
|
|
11339
|
+
nextDepth,
|
|
11340
|
+
context.operation.direction
|
|
11341
|
+
);
|
|
11342
|
+
return {
|
|
11343
|
+
depth: nextDepth,
|
|
11344
|
+
frontierCount,
|
|
11345
|
+
workingTableSize: state.workingTableSize + frontierCount
|
|
11346
|
+
};
|
|
11347
|
+
},
|
|
11348
|
+
hasConverged(state) {
|
|
11349
|
+
return state.frontierCount === 0 || state.depth >= maxHops;
|
|
11350
|
+
},
|
|
11351
|
+
async extractResult(context) {
|
|
11352
|
+
const rows = await readWorkingRows(context, "forward");
|
|
11353
|
+
return rows.map((row) => ({
|
|
11354
|
+
id: row.node_id,
|
|
11355
|
+
kind: row.node_kind,
|
|
11356
|
+
depth: Number(row.depth)
|
|
11357
|
+
})).toSorted(
|
|
11358
|
+
(left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
|
|
11359
|
+
);
|
|
11360
|
+
}
|
|
11361
|
+
});
|
|
11362
|
+
}
|
|
11363
|
+
async function findShortestPathInWorkingTable(ctx, sourceId, targetId, maxHops, options) {
|
|
11364
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
11365
|
+
algorithm: "shortestPath",
|
|
11366
|
+
maxIterations: maxHops,
|
|
11367
|
+
createWorkingTable,
|
|
11368
|
+
async initialize(context) {
|
|
11369
|
+
const forwardSeeds = await seedWorkingSide(context, sourceId, "forward");
|
|
11370
|
+
const reverseSeeds = await seedWorkingSide(context, targetId, "reverse");
|
|
11371
|
+
return {
|
|
11372
|
+
forwardDepth: 0,
|
|
11373
|
+
reverseDepth: 0,
|
|
11374
|
+
forwardFrontierCount: forwardSeeds.length,
|
|
11375
|
+
reverseFrontierCount: reverseSeeds.length,
|
|
11376
|
+
meeting: findSeedMeeting(forwardSeeds, reverseSeeds),
|
|
11377
|
+
workingTableSize: forwardSeeds.length + reverseSeeds.length
|
|
11378
|
+
};
|
|
11379
|
+
},
|
|
11380
|
+
async runRound(context, state) {
|
|
11381
|
+
const expandForward = state.forwardFrontierCount <= state.reverseFrontierCount;
|
|
11382
|
+
const side = expandForward ? "forward" : "reverse";
|
|
11383
|
+
const currentDepth = expandForward ? state.forwardDepth : state.reverseDepth;
|
|
11384
|
+
const nextDepth = currentDepth + 1;
|
|
11385
|
+
const direction = expandForward ? context.operation.direction : reverseDirection(context.operation.direction);
|
|
11386
|
+
const round = await expandWorkingTableRound(
|
|
11387
|
+
context,
|
|
11388
|
+
side,
|
|
11389
|
+
currentDepth,
|
|
11390
|
+
nextDepth,
|
|
11391
|
+
direction,
|
|
11392
|
+
maxHops
|
|
11393
|
+
);
|
|
11394
|
+
return {
|
|
11395
|
+
forwardDepth: expandForward ? nextDepth : state.forwardDepth,
|
|
11396
|
+
reverseDepth: expandForward ? state.reverseDepth : nextDepth,
|
|
11397
|
+
forwardFrontierCount: expandForward ? round.insertedCount : state.forwardFrontierCount,
|
|
11398
|
+
reverseFrontierCount: expandForward ? state.reverseFrontierCount : round.insertedCount,
|
|
11399
|
+
meeting: round.meeting,
|
|
11400
|
+
workingTableSize: state.workingTableSize + round.insertedCount
|
|
11401
|
+
};
|
|
11402
|
+
},
|
|
11403
|
+
hasConverged(state) {
|
|
11404
|
+
return state.meeting !== void 0 || state.forwardFrontierCount === 0 || state.reverseFrontierCount === 0 || state.forwardDepth + state.reverseDepth >= maxHops;
|
|
11405
|
+
},
|
|
11406
|
+
async extractResult(context, state) {
|
|
11407
|
+
if (state.meeting === void 0) return;
|
|
11408
|
+
const rows = await readWorkingRows(context);
|
|
11409
|
+
return buildShortestPath(
|
|
11410
|
+
nodeIdentityKey(state.meeting),
|
|
11411
|
+
createVisitedMapFromRows(rows, "forward"),
|
|
11412
|
+
createVisitedMapFromRows(rows, "reverse")
|
|
11413
|
+
);
|
|
11414
|
+
}
|
|
11415
|
+
});
|
|
11416
|
+
}
|
|
11417
|
+
function createWorkingTable(context) {
|
|
11418
|
+
return sql`
|
|
11419
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
11420
|
+
graph_id TEXT NOT NULL,
|
|
11421
|
+
run_id TEXT NOT NULL,
|
|
11422
|
+
side TEXT NOT NULL,
|
|
11423
|
+
node_id TEXT NOT NULL,
|
|
11424
|
+
node_kind TEXT NOT NULL,
|
|
11425
|
+
depth INTEGER NOT NULL,
|
|
11426
|
+
predecessor_id TEXT,
|
|
11427
|
+
predecessor_kind TEXT,
|
|
11428
|
+
meeting_depth INTEGER,
|
|
11429
|
+
PRIMARY KEY (graph_id, run_id, side, node_kind, node_id)
|
|
11430
|
+
)
|
|
11431
|
+
`;
|
|
11432
|
+
}
|
|
11433
|
+
async function seedWorkingSide(context, nodeId, side) {
|
|
11434
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11435
|
+
return context.backend.execute(
|
|
11436
|
+
asCompiledRowsSql(sql`
|
|
11437
|
+
INSERT INTO ${workingTable}
|
|
11438
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11439
|
+
predecessor_id, predecessor_kind)
|
|
11440
|
+
SELECT ${graphId}, ${runId}, ${side}, n.id, n.kind, 0, NULL, NULL
|
|
11441
|
+
FROM ${operation.schema.nodesTable} n
|
|
11442
|
+
WHERE n.graph_id = ${graphId}
|
|
11443
|
+
AND n.id = ${nodeId}
|
|
11444
|
+
AND ${operation.nodeTemporalFilter}
|
|
11445
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11446
|
+
RETURNING node_id, node_kind
|
|
11447
|
+
`)
|
|
11448
|
+
);
|
|
11449
|
+
}
|
|
11450
|
+
function findSeedMeeting(forwardSeeds, reverseSeeds) {
|
|
11451
|
+
const reverseKeys = new Set(
|
|
11452
|
+
reverseSeeds.map(
|
|
11453
|
+
(row) => nodeIdentityKey({ id: row.node_id, kind: row.node_kind })
|
|
11454
|
+
)
|
|
11455
|
+
);
|
|
11456
|
+
let meeting;
|
|
11457
|
+
for (const row of forwardSeeds) {
|
|
11458
|
+
const candidate = { id: row.node_id, kind: row.node_kind, depth: 0 };
|
|
11459
|
+
if (!reverseKeys.has(nodeIdentityKey(candidate))) continue;
|
|
11460
|
+
if (meeting === void 0 || compareNodeIdentity(candidate, meeting) < 0) {
|
|
11461
|
+
meeting = candidate;
|
|
11462
|
+
}
|
|
11463
|
+
}
|
|
11464
|
+
return meeting;
|
|
11465
|
+
}
|
|
11466
|
+
async function expandWorkingTableRound(context, side, currentDepth, nextDepth, direction, maxHops) {
|
|
11467
|
+
const oppositeSide = side === "forward" ? "reverse" : "forward";
|
|
11468
|
+
let insertedCount = 0;
|
|
11469
|
+
let meeting;
|
|
11470
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11471
|
+
const sourceFilter = sql`
|
|
11472
|
+
w.graph_id = ${context.graphId}
|
|
11473
|
+
AND w.run_id = ${context.runId}
|
|
11474
|
+
AND w.side = ${side}
|
|
11475
|
+
AND w.depth = ${currentDepth}
|
|
11476
|
+
`;
|
|
11477
|
+
const expansion = compileWorkingTableExpansion(
|
|
11478
|
+
context.operation,
|
|
11479
|
+
context.workingTable,
|
|
11480
|
+
sourceFilter,
|
|
11481
|
+
direction,
|
|
11482
|
+
edgeKinds
|
|
11483
|
+
);
|
|
11484
|
+
const rows = await context.backend.execute(
|
|
11485
|
+
asCompiledRowsSql(sql`
|
|
11486
|
+
INSERT INTO ${context.workingTable}
|
|
11487
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11488
|
+
predecessor_id, predecessor_kind, meeting_depth)
|
|
11489
|
+
SELECT
|
|
11490
|
+
${context.graphId}, ${context.runId}, ${side},
|
|
11491
|
+
ranked.target_id, ranked.target_kind, ${nextDepth},
|
|
11492
|
+
ranked.source_id, ranked.source_kind,
|
|
11493
|
+
(
|
|
11494
|
+
SELECT opposite.depth
|
|
11495
|
+
FROM ${context.workingTable} opposite
|
|
11496
|
+
WHERE opposite.graph_id = ${context.graphId}
|
|
11497
|
+
AND opposite.run_id = ${context.runId}
|
|
11498
|
+
AND opposite.side = ${oppositeSide}
|
|
11499
|
+
AND opposite.node_id = ranked.target_id
|
|
11500
|
+
AND opposite.node_kind = ranked.target_kind
|
|
11501
|
+
)
|
|
11502
|
+
FROM (
|
|
11503
|
+
SELECT expanded.*,
|
|
11504
|
+
ROW_NUMBER() OVER (
|
|
11505
|
+
PARTITION BY expanded.target_kind, expanded.target_id
|
|
11506
|
+
ORDER BY expanded.source_id, expanded.source_kind
|
|
11507
|
+
) AS candidate_rank
|
|
11508
|
+
FROM (${expansion}) expanded
|
|
11509
|
+
) ranked
|
|
11510
|
+
WHERE ranked.candidate_rank = 1
|
|
11511
|
+
AND NOT EXISTS (
|
|
11512
|
+
SELECT 1
|
|
11513
|
+
FROM ${context.workingTable} visited
|
|
11514
|
+
WHERE visited.graph_id = ${context.graphId}
|
|
11515
|
+
AND visited.run_id = ${context.runId}
|
|
11516
|
+
AND visited.side = ${side}
|
|
11517
|
+
AND visited.node_id = ranked.target_id
|
|
11518
|
+
AND visited.node_kind = ranked.target_kind
|
|
11519
|
+
)
|
|
11520
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11521
|
+
RETURNING node_id, node_kind, meeting_depth
|
|
11522
|
+
`)
|
|
11523
|
+
);
|
|
11524
|
+
insertedCount += rows.length;
|
|
11525
|
+
meeting = selectRoundMeeting(meeting, rows, nextDepth, maxHops);
|
|
11526
|
+
}
|
|
11527
|
+
return { insertedCount, meeting };
|
|
11528
|
+
}
|
|
11529
|
+
function selectRoundMeeting(currentMeeting, rows, nextDepth, maxHops) {
|
|
11530
|
+
let meeting = currentMeeting;
|
|
11531
|
+
for (const row of rows) {
|
|
11532
|
+
if (row.meeting_depth === null || row.meeting_depth === void 0) {
|
|
11533
|
+
continue;
|
|
11534
|
+
}
|
|
11535
|
+
const totalDepth = nextDepth + Number(row.meeting_depth);
|
|
11536
|
+
if (totalDepth > maxHops) continue;
|
|
11537
|
+
const candidate = {
|
|
11538
|
+
id: row.node_id,
|
|
11539
|
+
kind: row.node_kind,
|
|
11540
|
+
depth: totalDepth
|
|
11541
|
+
};
|
|
11542
|
+
if (meeting === void 0 || candidate.depth < meeting.depth || candidate.depth === meeting.depth && compareNodeIdentity(candidate, meeting) < 0) {
|
|
11543
|
+
meeting = candidate;
|
|
11544
|
+
}
|
|
11545
|
+
}
|
|
11546
|
+
return meeting;
|
|
11547
|
+
}
|
|
11548
|
+
async function expandReachableWorkingTableRound(context, side, currentDepth, nextDepth, direction) {
|
|
11549
|
+
let insertedCount = 0;
|
|
11550
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11551
|
+
const sourceFilter = sql`
|
|
11552
|
+
w.graph_id = ${context.graphId}
|
|
11553
|
+
AND w.run_id = ${context.runId}
|
|
11554
|
+
AND w.side = ${side}
|
|
11555
|
+
AND w.depth = ${currentDepth}
|
|
11556
|
+
`;
|
|
11557
|
+
const edgeExpansion = compileWorkingTableEdgeExpansion(
|
|
11558
|
+
context.operation,
|
|
11559
|
+
context.workingTable,
|
|
11560
|
+
sourceFilter,
|
|
11561
|
+
direction,
|
|
11562
|
+
edgeKinds
|
|
11563
|
+
);
|
|
11564
|
+
const rows = await context.backend.execute(
|
|
11565
|
+
asCompiledRowsSql(sql`
|
|
11566
|
+
INSERT INTO ${context.workingTable}
|
|
11567
|
+
(graph_id, run_id, side, node_id, node_kind, depth,
|
|
11568
|
+
predecessor_id, predecessor_kind)
|
|
11569
|
+
SELECT
|
|
11570
|
+
${context.graphId}, ${context.runId}, ${side},
|
|
11571
|
+
candidates.target_id, candidates.target_kind, ${nextDepth},
|
|
11572
|
+
NULL, NULL
|
|
11573
|
+
FROM (
|
|
11574
|
+
SELECT DISTINCT expanded.target_id, expanded.target_kind
|
|
11575
|
+
FROM (${edgeExpansion}) expanded
|
|
11576
|
+
) candidates
|
|
11577
|
+
JOIN ${context.operation.schema.nodesTable} n
|
|
11578
|
+
ON n.graph_id = ${context.graphId}
|
|
11579
|
+
AND n.id = candidates.target_id
|
|
11580
|
+
AND n.kind = candidates.target_kind
|
|
11581
|
+
WHERE ${context.operation.nodeTemporalFilter}
|
|
11582
|
+
AND NOT EXISTS (
|
|
11583
|
+
SELECT 1
|
|
11584
|
+
FROM ${context.workingTable} visited
|
|
11585
|
+
WHERE visited.graph_id = ${context.graphId}
|
|
11586
|
+
AND visited.run_id = ${context.runId}
|
|
11587
|
+
AND visited.side = ${side}
|
|
11588
|
+
AND visited.node_id = candidates.target_id
|
|
11589
|
+
AND visited.node_kind = candidates.target_kind
|
|
11590
|
+
)
|
|
11591
|
+
ON CONFLICT (graph_id, run_id, side, node_kind, node_id) DO NOTHING
|
|
11592
|
+
RETURNING node_id
|
|
11593
|
+
`)
|
|
11594
|
+
);
|
|
11595
|
+
insertedCount += rows.length;
|
|
11596
|
+
}
|
|
11597
|
+
return insertedCount;
|
|
11598
|
+
}
|
|
11599
|
+
async function readWorkingRows(context, side) {
|
|
11600
|
+
const sideFilter = side === void 0 ? sql`TRUE` : sql`side = ${side}`;
|
|
11601
|
+
return context.backend.execute(
|
|
11602
|
+
asCompiledRowsSql(sql`
|
|
11603
|
+
SELECT side, node_id, node_kind, depth,
|
|
11604
|
+
predecessor_id, predecessor_kind
|
|
11605
|
+
FROM ${context.workingTable}
|
|
11606
|
+
WHERE graph_id = ${context.graphId}
|
|
11607
|
+
AND run_id = ${context.runId}
|
|
11608
|
+
AND ${sideFilter}
|
|
11609
|
+
ORDER BY side, depth, node_id, node_kind
|
|
11610
|
+
`)
|
|
11611
|
+
);
|
|
11612
|
+
}
|
|
11613
|
+
async function findReachableNodesInline(ctx, sourceId, maxHops, options) {
|
|
11614
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
11615
|
+
const sources = await fetchVisibleWorkingNodes(operation, [sourceId]);
|
|
11616
|
+
if (sources.length === 0) return [];
|
|
11617
|
+
const reached = new Map(
|
|
11618
|
+
sources.map((source) => [
|
|
11619
|
+
nodeIdentityKey(source),
|
|
11620
|
+
{ id: source.id, kind: source.kind, depth: 0 }
|
|
11621
|
+
])
|
|
11622
|
+
);
|
|
11623
|
+
let workingSet = sources;
|
|
11624
|
+
for (let depth = 1; depth <= maxHops && workingSet.length > 0; depth++) {
|
|
11625
|
+
const candidates = await expandInlineWorkingSet(
|
|
11626
|
+
operation,
|
|
11627
|
+
workingSet,
|
|
11628
|
+
operation.direction,
|
|
11629
|
+
reached
|
|
11630
|
+
);
|
|
11631
|
+
const nextWorkingSet = sortCandidates(candidates);
|
|
11632
|
+
for (const candidate of nextWorkingSet) {
|
|
11633
|
+
reached.set(nodeIdentityKey(candidate), {
|
|
11634
|
+
id: candidate.id,
|
|
11635
|
+
kind: candidate.kind,
|
|
11636
|
+
depth
|
|
11637
|
+
});
|
|
11638
|
+
}
|
|
11639
|
+
workingSet = nextWorkingSet;
|
|
11640
|
+
}
|
|
11641
|
+
return [...reached.values()].toSorted(
|
|
11642
|
+
(left, right) => left.depth - right.depth || compareNodeIdentity(left, right)
|
|
11643
|
+
);
|
|
11644
|
+
});
|
|
11645
|
+
}
|
|
11646
|
+
async function findShortestPathInline(ctx, sourceId, targetId, maxHops, options) {
|
|
11647
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
11648
|
+
const visibleNodes = await fetchVisibleWorkingNodes(operation, [
|
|
11649
|
+
sourceId,
|
|
11650
|
+
targetId
|
|
11651
|
+
]);
|
|
11652
|
+
const sources = visibleNodes.filter((node) => node.id === sourceId);
|
|
11653
|
+
const targets = visibleNodes.filter((node) => node.id === targetId);
|
|
11654
|
+
if (sources.length === 0 || targets.length === 0) return;
|
|
11655
|
+
if (sourceId === targetId) return { nodes: [sources[0]], depth: 0 };
|
|
11656
|
+
const forwardVisited = createVisitedMap(sources);
|
|
11657
|
+
const reverseVisited = createVisitedMap(targets);
|
|
11658
|
+
let forwardWorkingSet = sources;
|
|
11659
|
+
let reverseWorkingSet = targets;
|
|
11660
|
+
let forwardDepth = 0;
|
|
11661
|
+
let reverseDepth = 0;
|
|
11662
|
+
while (forwardWorkingSet.length > 0 && reverseWorkingSet.length > 0 && forwardDepth + reverseDepth < maxHops) {
|
|
11663
|
+
const expandForward = forwardWorkingSet.length <= reverseWorkingSet.length;
|
|
11664
|
+
const activeVisited = expandForward ? forwardVisited : reverseVisited;
|
|
11665
|
+
const oppositeVisited = expandForward ? reverseVisited : forwardVisited;
|
|
11666
|
+
const currentWorkingSet = expandForward ? forwardWorkingSet : reverseWorkingSet;
|
|
11667
|
+
const direction = expandForward ? operation.direction : reverseDirection(operation.direction);
|
|
11668
|
+
const candidates = await expandInlineWorkingSet(
|
|
11669
|
+
operation,
|
|
11670
|
+
currentWorkingSet,
|
|
11671
|
+
direction,
|
|
11672
|
+
activeVisited
|
|
11673
|
+
);
|
|
11674
|
+
const nextDepth = (expandForward ? forwardDepth : reverseDepth) + 1;
|
|
11675
|
+
const nextWorkingSet = sortCandidates(candidates);
|
|
11676
|
+
for (const candidate of nextWorkingSet) {
|
|
11677
|
+
activeVisited.set(nodeIdentityKey(candidate), {
|
|
11678
|
+
id: candidate.id,
|
|
11679
|
+
kind: candidate.kind,
|
|
11680
|
+
depth: nextDepth,
|
|
11681
|
+
parentKey: nodeIdentityKey({
|
|
11682
|
+
id: candidate.parentId,
|
|
11683
|
+
kind: candidate.parentKind
|
|
11684
|
+
})
|
|
11685
|
+
});
|
|
11686
|
+
}
|
|
11687
|
+
const meetingKey = findMeetingNodeKey(
|
|
11688
|
+
nextWorkingSet,
|
|
11689
|
+
activeVisited,
|
|
11690
|
+
oppositeVisited
|
|
11691
|
+
);
|
|
11692
|
+
if (meetingKey !== void 0) {
|
|
11693
|
+
return buildShortestPath(meetingKey, forwardVisited, reverseVisited);
|
|
11694
|
+
}
|
|
11695
|
+
if (expandForward) {
|
|
11696
|
+
forwardWorkingSet = nextWorkingSet;
|
|
11697
|
+
forwardDepth = nextDepth;
|
|
11698
|
+
} else {
|
|
11699
|
+
reverseWorkingSet = nextWorkingSet;
|
|
11700
|
+
reverseDepth = nextDepth;
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
return;
|
|
11704
|
+
});
|
|
11705
|
+
}
|
|
11706
|
+
async function expandInlineWorkingSet(operation, workingSet, direction, visited) {
|
|
11707
|
+
return reduceExpandedWorkingSet(
|
|
11708
|
+
operation,
|
|
11709
|
+
workingSet,
|
|
11710
|
+
direction,
|
|
11711
|
+
(existing, expansion) => selectPredecessor(existing, expansion, visited)
|
|
11712
|
+
);
|
|
11713
|
+
}
|
|
11714
|
+
function selectPredecessor(existing, expansion, visited) {
|
|
11715
|
+
if (visited.has(nodeIdentityKey(expansion.target))) return existing;
|
|
11716
|
+
if (existing !== void 0 && compareNodeIdentity(expansion.source, {
|
|
11717
|
+
id: existing.parentId,
|
|
11718
|
+
kind: existing.parentKind
|
|
11719
|
+
}) >= 0) {
|
|
11720
|
+
return existing;
|
|
11721
|
+
}
|
|
11722
|
+
return {
|
|
11723
|
+
id: expansion.target.id,
|
|
11724
|
+
kind: expansion.target.kind,
|
|
11725
|
+
parentId: expansion.source.id,
|
|
11726
|
+
parentKind: expansion.source.kind
|
|
11727
|
+
};
|
|
11728
|
+
}
|
|
11729
|
+
function sortCandidates(candidates) {
|
|
11730
|
+
return [...candidates.values()].toSorted(
|
|
11731
|
+
(left, right) => compareNodeIdentity(left, right)
|
|
11732
|
+
);
|
|
11733
|
+
}
|
|
11734
|
+
function findMeetingNodeKey(candidates, activeVisited, oppositeVisited) {
|
|
11735
|
+
let best;
|
|
11736
|
+
for (const candidate of candidates) {
|
|
11737
|
+
const key = nodeIdentityKey(candidate);
|
|
11738
|
+
const active = activeVisited.get(key);
|
|
11739
|
+
const opposite = oppositeVisited.get(key);
|
|
11740
|
+
if (active === void 0 || opposite === void 0) continue;
|
|
11741
|
+
const meeting = { key, depth: active.depth + opposite.depth };
|
|
11742
|
+
if (best === void 0 || meeting.depth < best.depth || meeting.depth === best.depth && compareStrings(meeting.key, best.key) < 0) {
|
|
11743
|
+
best = meeting;
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
return best?.key;
|
|
11747
|
+
}
|
|
11748
|
+
function buildShortestPath(meetingKey, forwardVisited, reverseVisited) {
|
|
11749
|
+
const forwardKeys = [];
|
|
11750
|
+
let cursor = meetingKey;
|
|
11751
|
+
while (cursor !== void 0) {
|
|
11752
|
+
forwardKeys.push(cursor);
|
|
11753
|
+
cursor = forwardVisited.get(cursor)?.parentKey;
|
|
11754
|
+
}
|
|
11755
|
+
forwardKeys.reverse();
|
|
11756
|
+
const pathKeys = [...forwardKeys];
|
|
11757
|
+
cursor = reverseVisited.get(meetingKey)?.parentKey;
|
|
11758
|
+
while (cursor !== void 0) {
|
|
11759
|
+
pathKeys.push(cursor);
|
|
11760
|
+
cursor = reverseVisited.get(cursor)?.parentKey;
|
|
11761
|
+
}
|
|
11762
|
+
const nodes = pathKeys.map((key) => {
|
|
11763
|
+
const node = forwardVisited.get(key) ?? reverseVisited.get(key);
|
|
11764
|
+
return { id: node?.id ?? "", kind: node?.kind ?? "" };
|
|
11765
|
+
});
|
|
11766
|
+
return { nodes, depth: nodes.length - 1 };
|
|
11767
|
+
}
|
|
11768
|
+
function createVisitedMap(nodes) {
|
|
11769
|
+
return new Map(
|
|
11770
|
+
nodes.map((node) => [
|
|
11771
|
+
nodeIdentityKey(node),
|
|
11772
|
+
{ id: node.id, kind: node.kind, depth: 0, parentKey: void 0 }
|
|
11773
|
+
])
|
|
11774
|
+
);
|
|
11775
|
+
}
|
|
11776
|
+
function createVisitedMapFromRows(rows, side) {
|
|
11777
|
+
return new Map(
|
|
11778
|
+
rows.filter((row) => row.side === side).map((row) => {
|
|
11779
|
+
const parentKey = nodeIdentityKeyFromRow(
|
|
11780
|
+
row.predecessor_id,
|
|
11781
|
+
row.predecessor_kind
|
|
11782
|
+
);
|
|
11783
|
+
const node = {
|
|
11784
|
+
id: row.node_id,
|
|
11785
|
+
kind: row.node_kind,
|
|
11786
|
+
depth: Number(row.depth),
|
|
11787
|
+
parentKey
|
|
11788
|
+
};
|
|
11789
|
+
return [nodeIdentityKey(node), node];
|
|
11790
|
+
})
|
|
11791
|
+
);
|
|
11792
|
+
}
|
|
11793
|
+
function reverseDirection(direction) {
|
|
11794
|
+
switch (direction) {
|
|
11795
|
+
case "out": {
|
|
11796
|
+
return "in";
|
|
11797
|
+
}
|
|
11798
|
+
case "in": {
|
|
11799
|
+
return "out";
|
|
11800
|
+
}
|
|
11801
|
+
case "both": {
|
|
11802
|
+
return "both";
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
}
|
|
11806
|
+
|
|
11807
|
+
// src/store/algorithms/reachable.ts
|
|
11808
|
+
async function executeReachable(ctx, sourceId, options) {
|
|
11809
|
+
assertEdgeKinds(options.edges);
|
|
11810
|
+
const maxHops = resolveMaxHops(
|
|
11811
|
+
options.maxHops,
|
|
11812
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11813
|
+
"maxHops"
|
|
11814
|
+
);
|
|
11815
|
+
const reached = await findReachableNodes(ctx, sourceId, maxHops, options);
|
|
11816
|
+
return options.excludeSource === true ? reached.filter((node) => node.id !== sourceId) : reached;
|
|
11817
|
+
}
|
|
11818
|
+
async function executeCanReach(ctx, sourceId, targetId, options) {
|
|
11819
|
+
assertEdgeKinds(options.edges);
|
|
11820
|
+
const maxHops = resolveMaxHops(
|
|
11821
|
+
options.maxHops,
|
|
11822
|
+
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
11823
|
+
"maxHops"
|
|
11824
|
+
);
|
|
11825
|
+
return await findShortestPath(ctx, sourceId, targetId, maxHops, options) !== void 0;
|
|
11826
|
+
}
|
|
11827
|
+
async function executeNeighbors(ctx, sourceId, options) {
|
|
11828
|
+
const depth = resolveMaxHops(options.depth, DEFAULT_NEIGHBOR_DEPTH, "depth");
|
|
10623
11829
|
return executeReachable(ctx, sourceId, {
|
|
10624
11830
|
edges: options.edges,
|
|
10625
11831
|
maxHops: depth,
|
|
@@ -10632,9 +11838,14 @@ async function executeNeighbors(ctx, sourceId, options) {
|
|
|
10632
11838
|
...options.recordedAsOf !== void 0 && {
|
|
10633
11839
|
recordedAsOf: options.recordedAsOf
|
|
10634
11840
|
},
|
|
11841
|
+
...options.workingMemory !== void 0 && {
|
|
11842
|
+
workingMemory: options.workingMemory
|
|
11843
|
+
},
|
|
10635
11844
|
excludeSource: true
|
|
10636
11845
|
});
|
|
10637
11846
|
}
|
|
11847
|
+
|
|
11848
|
+
// src/store/algorithms/shortest-path.ts
|
|
10638
11849
|
async function executeShortestPath(ctx, sourceId, targetId, options) {
|
|
10639
11850
|
assertEdgeKinds(options.edges);
|
|
10640
11851
|
const maxHops = resolveMaxHops(
|
|
@@ -10642,56 +11853,696 @@ async function executeShortestPath(ctx, sourceId, targetId, options) {
|
|
|
10642
11853
|
DEFAULT_ALGORITHM_MAX_HOPS,
|
|
10643
11854
|
"maxHops"
|
|
10644
11855
|
);
|
|
11856
|
+
return findShortestPath(ctx, sourceId, targetId, maxHops, options);
|
|
11857
|
+
}
|
|
11858
|
+
var DEFAULT_WCC_MAX_ITERATIONS = 1e3;
|
|
11859
|
+
async function executeWeaklyConnectedComponents(ctx, options) {
|
|
11860
|
+
assertEdgeKinds(options.edges);
|
|
11861
|
+
assertWeaklyConnectedComponentsSupported(ctx);
|
|
11862
|
+
const maxIterations = resolveMaxIterations(
|
|
11863
|
+
options.maxIterations,
|
|
11864
|
+
DEFAULT_WCC_MAX_ITERATIONS,
|
|
11865
|
+
"weaklyConnectedComponents"
|
|
11866
|
+
);
|
|
11867
|
+
const nodeKinds = options.nodeKinds === void 0 ? void 0 : [...new Set(options.nodeKinds)].toSorted(
|
|
11868
|
+
(left, right) => compareCodePoints(left, right)
|
|
11869
|
+
);
|
|
11870
|
+
const traversalOptions = {
|
|
11871
|
+
edges: options.edges,
|
|
11872
|
+
direction: "both",
|
|
11873
|
+
...options.temporalMode === void 0 ? {} : { temporalMode: options.temporalMode },
|
|
11874
|
+
...options.asOf === void 0 ? {} : { asOf: options.asOf },
|
|
11875
|
+
...options.recordedAsOf === void 0 ? {} : { recordedAsOf: options.recordedAsOf },
|
|
11876
|
+
...options.workingMemory === void 0 ? {} : { workingMemory: options.workingMemory }
|
|
11877
|
+
};
|
|
11878
|
+
return runIterativeGraphOperation(ctx, traversalOptions, {
|
|
11879
|
+
algorithm: "weaklyConnectedComponents",
|
|
11880
|
+
maxIterations,
|
|
11881
|
+
createWorkingTable: createWorkingTable2,
|
|
11882
|
+
initialize: (context) => initializeWorkingTable(context, nodeKinds),
|
|
11883
|
+
runRound: runLabelPropagationRound,
|
|
11884
|
+
hasConverged(state) {
|
|
11885
|
+
return state.changedCount === 0;
|
|
11886
|
+
},
|
|
11887
|
+
extractResult: extractMemberships
|
|
11888
|
+
});
|
|
11889
|
+
}
|
|
11890
|
+
function assertWeaklyConnectedComponentsSupported(ctx) {
|
|
11891
|
+
if (ctx.backend.capabilities.graphAnalytics?.supported === true && ctx.backend.capabilities.windowFunctions) {
|
|
11892
|
+
return;
|
|
11893
|
+
}
|
|
11894
|
+
throw new UnsupportedBackendCapabilityError(
|
|
11895
|
+
"weaklyConnectedComponents",
|
|
11896
|
+
"graphAnalytics",
|
|
11897
|
+
{
|
|
11898
|
+
dialect: ctx.backend.dialect,
|
|
11899
|
+
supported: ctx.backend.capabilities.graphAnalytics?.supported === true,
|
|
11900
|
+
windowFunctions: ctx.backend.capabilities.windowFunctions
|
|
11901
|
+
},
|
|
11902
|
+
"Use a built-in transactional SQLite/PostgreSQL backend, or declare graphAnalytics support on a compatible custom backend."
|
|
11903
|
+
);
|
|
11904
|
+
}
|
|
11905
|
+
function createWorkingTable2(context) {
|
|
11906
|
+
return sql`
|
|
11907
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
11908
|
+
graph_id TEXT NOT NULL,
|
|
11909
|
+
run_id TEXT NOT NULL,
|
|
11910
|
+
node_id TEXT NOT NULL,
|
|
11911
|
+
node_kind TEXT NOT NULL,
|
|
11912
|
+
label_id TEXT NOT NULL,
|
|
11913
|
+
label_kind TEXT NOT NULL,
|
|
11914
|
+
next_label_id TEXT NOT NULL,
|
|
11915
|
+
next_label_kind TEXT NOT NULL,
|
|
11916
|
+
PRIMARY KEY (graph_id, run_id, node_kind, node_id)
|
|
11917
|
+
)
|
|
11918
|
+
`;
|
|
11919
|
+
}
|
|
11920
|
+
async function initializeWorkingTable(context, nodeKinds) {
|
|
11921
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11922
|
+
const nodeKindFilter = nodeKinds === void 0 ? sql`TRUE` : compileKindFilter(sql.raw("n.kind"), nodeKinds);
|
|
11923
|
+
await context.executeTemporary(sql`
|
|
11924
|
+
INSERT INTO ${workingTable}
|
|
11925
|
+
(graph_id, run_id, node_id, node_kind, label_id, label_kind,
|
|
11926
|
+
next_label_id, next_label_kind)
|
|
11927
|
+
SELECT
|
|
11928
|
+
${graphId}, ${runId}, n.id, n.kind, n.id, n.kind, n.id, n.kind
|
|
11929
|
+
FROM ${operation.schema.nodesTable} n
|
|
11930
|
+
WHERE n.graph_id = ${graphId}
|
|
11931
|
+
AND ${nodeKindFilter}
|
|
11932
|
+
AND ${operation.nodeTemporalFilter}
|
|
11933
|
+
`);
|
|
11934
|
+
const rows = await context.backend.execute(
|
|
11935
|
+
asCompiledRowsSql(sql`
|
|
11936
|
+
SELECT COUNT(*) AS count
|
|
11937
|
+
FROM ${workingTable}
|
|
11938
|
+
WHERE graph_id = ${graphId} AND run_id = ${runId}
|
|
11939
|
+
`)
|
|
11940
|
+
);
|
|
11941
|
+
const workingTableSize = Number(rows[0]?.count ?? 0);
|
|
11942
|
+
return { changedCount: workingTableSize, workingTableSize };
|
|
11943
|
+
}
|
|
11944
|
+
async function runLabelPropagationRound(context, state) {
|
|
11945
|
+
await resetNextLabels(context);
|
|
11946
|
+
for (const edgeKinds of context.operation.edgeKindChunks) {
|
|
11947
|
+
await propagateChunkLabels(context, edgeKinds);
|
|
11948
|
+
}
|
|
11949
|
+
const changedRows = await applyNextLabels(context);
|
|
11950
|
+
return {
|
|
11951
|
+
changedCount: changedRows.length,
|
|
11952
|
+
workingTableSize: state.workingTableSize
|
|
11953
|
+
};
|
|
11954
|
+
}
|
|
11955
|
+
async function resetNextLabels(context) {
|
|
11956
|
+
await context.executeTemporary(sql`
|
|
11957
|
+
UPDATE ${context.workingTable}
|
|
11958
|
+
SET next_label_id = label_id, next_label_kind = label_kind
|
|
11959
|
+
WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
|
|
11960
|
+
`);
|
|
11961
|
+
}
|
|
11962
|
+
async function propagateChunkLabels(context, edgeKinds) {
|
|
11963
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
11964
|
+
const expansion = compileWorkingTableEdgeExpansion(
|
|
11965
|
+
operation,
|
|
11966
|
+
workingTable,
|
|
11967
|
+
sql`w.graph_id = ${graphId} AND w.run_id = ${runId}`,
|
|
11968
|
+
"both",
|
|
11969
|
+
edgeKinds
|
|
11970
|
+
);
|
|
11971
|
+
const candidateId = operation.ctx.dialect.binaryText(sql`ranked.label_id`);
|
|
11972
|
+
const candidateKind = operation.ctx.dialect.binaryText(
|
|
11973
|
+
sql`ranked.label_kind`
|
|
11974
|
+
);
|
|
11975
|
+
const currentId = operation.ctx.dialect.binaryText(sql`target.next_label_id`);
|
|
11976
|
+
const currentKind = operation.ctx.dialect.binaryText(
|
|
11977
|
+
sql`target.next_label_kind`
|
|
11978
|
+
);
|
|
11979
|
+
const sourceId = operation.ctx.dialect.binaryText(sql`source.label_id`);
|
|
11980
|
+
const sourceKind = operation.ctx.dialect.binaryText(sql`source.label_kind`);
|
|
11981
|
+
const targetId = operation.ctx.dialect.binaryText(sql`source.target_id`);
|
|
11982
|
+
const targetKind = operation.ctx.dialect.binaryText(sql`source.target_kind`);
|
|
11983
|
+
await context.executeTemporary(sql`
|
|
11984
|
+
WITH candidates AS (
|
|
11985
|
+
SELECT
|
|
11986
|
+
expanded.target_id,
|
|
11987
|
+
expanded.target_kind,
|
|
11988
|
+
source.label_id,
|
|
11989
|
+
source.label_kind
|
|
11990
|
+
FROM (${expansion}) expanded
|
|
11991
|
+
JOIN ${workingTable} source
|
|
11992
|
+
ON source.graph_id = ${graphId}
|
|
11993
|
+
AND source.run_id = ${runId}
|
|
11994
|
+
AND source.node_id = expanded.source_id
|
|
11995
|
+
AND source.node_kind = expanded.source_kind
|
|
11996
|
+
JOIN ${workingTable} scoped_target
|
|
11997
|
+
ON scoped_target.graph_id = ${graphId}
|
|
11998
|
+
AND scoped_target.run_id = ${runId}
|
|
11999
|
+
AND scoped_target.node_id = expanded.target_id
|
|
12000
|
+
AND scoped_target.node_kind = expanded.target_kind
|
|
12001
|
+
), ranked AS (
|
|
12002
|
+
SELECT
|
|
12003
|
+
target_id,
|
|
12004
|
+
target_kind,
|
|
12005
|
+
label_id,
|
|
12006
|
+
label_kind,
|
|
12007
|
+
ROW_NUMBER() OVER (
|
|
12008
|
+
PARTITION BY ${targetKind}, ${targetId}
|
|
12009
|
+
ORDER BY ${sourceId}, ${sourceKind}
|
|
12010
|
+
) AS candidate_rank
|
|
12011
|
+
FROM candidates source
|
|
12012
|
+
)
|
|
12013
|
+
UPDATE ${workingTable} AS target
|
|
12014
|
+
SET next_label_id = ranked.label_id,
|
|
12015
|
+
next_label_kind = ranked.label_kind
|
|
12016
|
+
FROM ranked
|
|
12017
|
+
WHERE target.graph_id = ${graphId}
|
|
12018
|
+
AND target.run_id = ${runId}
|
|
12019
|
+
AND target.node_id = ranked.target_id
|
|
12020
|
+
AND target.node_kind = ranked.target_kind
|
|
12021
|
+
AND ranked.candidate_rank = 1
|
|
12022
|
+
AND (
|
|
12023
|
+
${candidateId} < ${currentId}
|
|
12024
|
+
OR (
|
|
12025
|
+
${candidateId} = ${currentId}
|
|
12026
|
+
AND ${candidateKind} < ${currentKind}
|
|
12027
|
+
)
|
|
12028
|
+
)
|
|
12029
|
+
`);
|
|
12030
|
+
}
|
|
12031
|
+
function applyNextLabels(context) {
|
|
12032
|
+
return context.backend.execute(
|
|
12033
|
+
asCompiledRowsSql(sql`
|
|
12034
|
+
UPDATE ${context.workingTable}
|
|
12035
|
+
SET label_id = next_label_id, label_kind = next_label_kind
|
|
12036
|
+
WHERE graph_id = ${context.graphId}
|
|
12037
|
+
AND run_id = ${context.runId}
|
|
12038
|
+
AND (
|
|
12039
|
+
label_id <> next_label_id OR label_kind <> next_label_kind
|
|
12040
|
+
)
|
|
12041
|
+
RETURNING node_id
|
|
12042
|
+
`)
|
|
12043
|
+
);
|
|
12044
|
+
}
|
|
12045
|
+
async function extractMemberships(context) {
|
|
12046
|
+
const { operation } = context;
|
|
12047
|
+
const componentId = operation.ctx.dialect.binaryText(sql`label_id`);
|
|
12048
|
+
const componentKind = operation.ctx.dialect.binaryText(sql`label_kind`);
|
|
12049
|
+
const nodeId = operation.ctx.dialect.binaryText(sql`node_id`);
|
|
12050
|
+
const nodeKind = operation.ctx.dialect.binaryText(sql`node_kind`);
|
|
12051
|
+
const rows = await context.backend.execute(
|
|
12052
|
+
asCompiledRowsSql(sql`
|
|
12053
|
+
SELECT
|
|
12054
|
+
node_id,
|
|
12055
|
+
node_kind,
|
|
12056
|
+
label_id AS component_id,
|
|
12057
|
+
label_kind AS component_kind,
|
|
12058
|
+
COUNT(*) OVER (
|
|
12059
|
+
PARTITION BY ${componentKind}, ${componentId}
|
|
12060
|
+
) AS component_size
|
|
12061
|
+
FROM ${context.workingTable}
|
|
12062
|
+
WHERE graph_id = ${context.graphId} AND run_id = ${context.runId}
|
|
12063
|
+
ORDER BY ${componentId}, ${componentKind}, ${nodeId}, ${nodeKind}
|
|
12064
|
+
`)
|
|
12065
|
+
);
|
|
12066
|
+
return rows.map((row) => ({
|
|
12067
|
+
id: row.node_id,
|
|
12068
|
+
kind: row.node_kind,
|
|
12069
|
+
componentId: row.component_id,
|
|
12070
|
+
componentKind: row.component_kind,
|
|
12071
|
+
size: Number(row.component_size)
|
|
12072
|
+
})).toSorted(compareMemberships);
|
|
12073
|
+
}
|
|
12074
|
+
function compareMemberships(left, right) {
|
|
12075
|
+
return compareCodePoints(left.componentId, right.componentId) || compareCodePoints(left.componentKind, right.componentKind) || compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
|
|
12076
|
+
}
|
|
12077
|
+
var DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS = 1e3;
|
|
12078
|
+
var WEIGHTED_ALGORITHM_NAME = "weightedShortestPath";
|
|
12079
|
+
var MAX_EDGE_WEIGHT = Number.MAX_VALUE / 2 ** 64;
|
|
12080
|
+
var MIN_EDGE_WEIGHT = Number.MIN_VALUE;
|
|
12081
|
+
async function executeWeightedShortestPath(ctx, sourceId, targetId, options) {
|
|
12082
|
+
assertEdgeKinds(options.edges);
|
|
12083
|
+
assertWeightOptions(options.weightProperty, options.defaultWeight);
|
|
12084
|
+
const maxIterations = resolveMaxIterations(
|
|
12085
|
+
options.maxIterations,
|
|
12086
|
+
DEFAULT_WEIGHTED_SHORTEST_PATH_MAX_ITERATIONS,
|
|
12087
|
+
WEIGHTED_ALGORITHM_NAME
|
|
12088
|
+
);
|
|
12089
|
+
const traversalOptions = options;
|
|
10645
12090
|
if (sourceId === targetId) {
|
|
10646
|
-
|
|
10647
|
-
if (source === void 0) return void 0;
|
|
10648
|
-
return { nodes: [source], depth: 0 };
|
|
12091
|
+
return findWeightedSelfPath(ctx, sourceId, traversalOptions);
|
|
10649
12092
|
}
|
|
10650
|
-
|
|
10651
|
-
|
|
12093
|
+
if (supportsTemporaryIteration(ctx)) {
|
|
12094
|
+
return findWeightedShortestPathInWorkingTable(
|
|
12095
|
+
ctx,
|
|
12096
|
+
sourceId,
|
|
12097
|
+
targetId,
|
|
12098
|
+
maxIterations,
|
|
12099
|
+
traversalOptions
|
|
12100
|
+
);
|
|
12101
|
+
}
|
|
12102
|
+
return findWeightedShortestPathInline(
|
|
12103
|
+
ctx,
|
|
10652
12104
|
sourceId,
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
12105
|
+
targetId,
|
|
12106
|
+
maxIterations,
|
|
12107
|
+
traversalOptions
|
|
12108
|
+
);
|
|
12109
|
+
}
|
|
12110
|
+
function assertWeightOptions(weightProperty, defaultWeight) {
|
|
12111
|
+
if (typeof weightProperty !== "string" || weightProperty.length === 0) {
|
|
12112
|
+
throw new ConfigurationError(
|
|
12113
|
+
`${WEIGHTED_ALGORITHM_NAME} weightProperty must be a non-empty string.`,
|
|
12114
|
+
{ weightProperty }
|
|
12115
|
+
);
|
|
12116
|
+
}
|
|
12117
|
+
if (defaultWeight !== void 0 && (!Number.isFinite(defaultWeight) || defaultWeight < 0 || defaultWeight > MAX_EDGE_WEIGHT)) {
|
|
12118
|
+
throw new ConfigurationError(
|
|
12119
|
+
`${WEIGHTED_ALGORITHM_NAME} defaultWeight must be a non-negative number no greater than ${MAX_EDGE_WEIGHT}, got ${String(defaultWeight)}.`,
|
|
12120
|
+
{ defaultWeight, maximum: MAX_EDGE_WEIGHT }
|
|
12121
|
+
);
|
|
12122
|
+
}
|
|
12123
|
+
}
|
|
12124
|
+
async function assertValidEdgeWeights(operation, weightProperty, defaultWeight) {
|
|
12125
|
+
const { dialect } = operation.ctx;
|
|
12126
|
+
const pointer = jsonPointer([weightProperty]);
|
|
12127
|
+
const propsColumn = sql.raw("e.props");
|
|
12128
|
+
const isNumber = dialect.jsonPathIsNumber(propsColumn, pointer);
|
|
12129
|
+
const isMissing = dialect.jsonPathIsNull(propsColumn, pointer);
|
|
12130
|
+
const weightText = dialect.jsonExtractText(propsColumn, pointer);
|
|
12131
|
+
const weightNumber = sql`CASE WHEN ${isNumber} THEN ${dialect.jsonExtractNumber(propsColumn, pointer)} END`;
|
|
12132
|
+
const missingViolation = defaultWeight === void 0 ? sql`audited.is_missing = 1` : dialect.booleanLiteral(false);
|
|
12133
|
+
const negativeViolation = sql`audited.is_number = 1 AND audited.weight_number < 0`;
|
|
12134
|
+
const outOfRangeViolation = sql`audited.is_number = 1 AND (ABS(audited.weight_number) > CAST(${MAX_EDGE_WEIGHT} AS NUMERIC) OR (audited.weight_number <> 0 AND ABS(audited.weight_number) < CAST(${MIN_EDGE_WEIGHT} AS NUMERIC)))`;
|
|
12135
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
12136
|
+
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), edgeKinds);
|
|
12137
|
+
const rows = await operation.backend.execute(
|
|
12138
|
+
asCompiledRowsSql(sql`
|
|
12139
|
+
SELECT audited.edge_id, audited.edge_kind, audited.weight_text,
|
|
12140
|
+
audited.is_number, audited.is_missing
|
|
12141
|
+
FROM (
|
|
12142
|
+
SELECT e.id AS edge_id, e.kind AS edge_kind,
|
|
12143
|
+
${weightText} AS weight_text,
|
|
12144
|
+
${weightNumber} AS weight_number,
|
|
12145
|
+
CASE WHEN ${isNumber} THEN 1 ELSE 0 END AS is_number,
|
|
12146
|
+
CASE WHEN ${isMissing} THEN 1 ELSE 0 END AS is_missing
|
|
12147
|
+
FROM ${operation.schema.edgesTable} e
|
|
12148
|
+
WHERE e.graph_id = ${operation.ctx.graphId}
|
|
12149
|
+
AND ${edgeKindFilter}
|
|
12150
|
+
AND ${operation.edgeTemporalFilter}
|
|
12151
|
+
) audited
|
|
12152
|
+
WHERE (${missingViolation})
|
|
12153
|
+
OR (audited.is_missing = 0 AND audited.is_number = 0)
|
|
12154
|
+
OR (${negativeViolation})
|
|
12155
|
+
OR (${outOfRangeViolation})
|
|
12156
|
+
ORDER BY ${dialect.binaryText(sql`audited.edge_id`)}
|
|
12157
|
+
LIMIT 1
|
|
12158
|
+
`)
|
|
12159
|
+
);
|
|
12160
|
+
const row = rows[0];
|
|
12161
|
+
if (row !== void 0) {
|
|
12162
|
+
throw createInvalidEdgeWeightError(row, weightProperty);
|
|
12163
|
+
}
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
function createInvalidEdgeWeightError(row, weightProperty) {
|
|
12167
|
+
const reason = resolveInvalidWeightReason(row);
|
|
12168
|
+
const value = reason === "missing" || row.weight_text === null ? void 0 : formatWeightValue(row.weight_text);
|
|
12169
|
+
return new InvalidEdgeWeightError({
|
|
12170
|
+
edgeId: row.edge_id,
|
|
12171
|
+
edgeKind: row.edge_kind,
|
|
12172
|
+
property: weightProperty,
|
|
12173
|
+
reason,
|
|
12174
|
+
...value === void 0 ? {} : { value }
|
|
10662
12175
|
});
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
12176
|
+
}
|
|
12177
|
+
function resolveInvalidWeightReason(row) {
|
|
12178
|
+
if (flagIsSet(row.is_missing)) return "missing";
|
|
12179
|
+
if (!flagIsSet(row.is_number)) return "non_numeric";
|
|
12180
|
+
return Number(row.weight_text) < 0 ? "negative" : "out_of_range";
|
|
12181
|
+
}
|
|
12182
|
+
function flagIsSet(value) {
|
|
12183
|
+
return Number(value) !== 0;
|
|
12184
|
+
}
|
|
12185
|
+
function formatWeightValue(value) {
|
|
12186
|
+
if (typeof value === "string") return value;
|
|
12187
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
12188
|
+
return String(value);
|
|
12189
|
+
}
|
|
12190
|
+
return JSON.stringify(value);
|
|
12191
|
+
}
|
|
12192
|
+
function compareNodeIdentityCodePoints(left, right) {
|
|
12193
|
+
return compareCodePoints(left.id, right.id) || compareCodePoints(left.kind, right.kind);
|
|
12194
|
+
}
|
|
12195
|
+
async function findWeightedShortestPathInWorkingTable(ctx, sourceId, targetId, maxIterations, options) {
|
|
12196
|
+
return runIterativeGraphOperation(ctx, options, {
|
|
12197
|
+
algorithm: WEIGHTED_ALGORITHM_NAME,
|
|
12198
|
+
maxIterations,
|
|
12199
|
+
createWorkingTable: createWorkingTable3,
|
|
12200
|
+
async initialize(context) {
|
|
12201
|
+
await assertValidEdgeWeights(
|
|
12202
|
+
context.operation,
|
|
12203
|
+
options.weightProperty,
|
|
12204
|
+
options.defaultWeight
|
|
12205
|
+
);
|
|
12206
|
+
if (!await hasVisibleNode(context.operation, targetId)) {
|
|
12207
|
+
return {
|
|
12208
|
+
frontierCount: 0,
|
|
12209
|
+
workingTableSize: 0,
|
|
12210
|
+
bestTargetDistance: void 0
|
|
12211
|
+
};
|
|
12212
|
+
}
|
|
12213
|
+
await context.executeTemporary(sql`
|
|
12214
|
+
CREATE INDEX ${frontierIndexIdentifier(context)}
|
|
12215
|
+
ON ${context.workingTable} (graph_id, run_id, improved_round)
|
|
12216
|
+
`);
|
|
12217
|
+
const seeded = await seedDistances(context, sourceId);
|
|
12218
|
+
return {
|
|
12219
|
+
frontierCount: seeded.length,
|
|
12220
|
+
workingTableSize: seeded.length,
|
|
12221
|
+
// Seeds carry the source id, never the target's — the self-path
|
|
12222
|
+
// case is short-circuited before this plan runs.
|
|
12223
|
+
bestTargetDistance: void 0
|
|
12224
|
+
};
|
|
12225
|
+
},
|
|
12226
|
+
async runRound(context, state, iteration) {
|
|
12227
|
+
const round = await relaxFrontierRound(
|
|
12228
|
+
context,
|
|
12229
|
+
targetId,
|
|
12230
|
+
iteration,
|
|
12231
|
+
state.bestTargetDistance
|
|
12232
|
+
);
|
|
12233
|
+
return {
|
|
12234
|
+
frontierCount: round.frontierCount,
|
|
12235
|
+
// Overcounts by re-improved existing rows (RETURNING includes
|
|
12236
|
+
// updates); acceptable because the size only paces the substrate's
|
|
12237
|
+
// ANALYZE refresh, where too-early merely refreshes stats sooner.
|
|
12238
|
+
workingTableSize: state.workingTableSize + round.frontierCount,
|
|
12239
|
+
bestTargetDistance: round.bestTargetDistance
|
|
12240
|
+
};
|
|
12241
|
+
},
|
|
12242
|
+
hasConverged(state) {
|
|
12243
|
+
return state.frontierCount === 0;
|
|
12244
|
+
},
|
|
12245
|
+
async extractResult(context) {
|
|
12246
|
+
return extractPathFromWorkingTable(context, targetId, maxIterations);
|
|
12247
|
+
}
|
|
12248
|
+
});
|
|
12249
|
+
}
|
|
12250
|
+
function frontierIndexIdentifier(context) {
|
|
12251
|
+
return sql.identifier(
|
|
12252
|
+
`typegraph_iterative_${context.runId.replaceAll("-", "_")}_frontier`
|
|
10667
12253
|
);
|
|
10668
|
-
const first = rows[0];
|
|
10669
|
-
if (first === void 0) return void 0;
|
|
10670
|
-
const pathIds = normalizePath(first.hit_path);
|
|
10671
|
-
const kindById = buildKindIndex(rows);
|
|
10672
|
-
const pathNodes = pathIds.map((id) => ({
|
|
10673
|
-
id,
|
|
10674
|
-
kind: kindById.get(id) ?? ""
|
|
10675
|
-
}));
|
|
10676
|
-
return { nodes: pathNodes, depth: Number(first.hit_depth) };
|
|
10677
12254
|
}
|
|
10678
|
-
function
|
|
10679
|
-
const
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
12255
|
+
async function hasVisibleNode(operation, nodeId) {
|
|
12256
|
+
const rows = await operation.backend.execute(
|
|
12257
|
+
asCompiledRowsSql(sql`
|
|
12258
|
+
SELECT n.id FROM ${operation.schema.nodesTable} n
|
|
12259
|
+
WHERE n.graph_id = ${operation.ctx.graphId}
|
|
12260
|
+
AND n.id = ${nodeId}
|
|
12261
|
+
AND ${operation.nodeTemporalFilter}
|
|
12262
|
+
LIMIT 1
|
|
12263
|
+
`)
|
|
12264
|
+
);
|
|
12265
|
+
return rows.length > 0;
|
|
12266
|
+
}
|
|
12267
|
+
function createWorkingTable3(context) {
|
|
12268
|
+
return sql`
|
|
12269
|
+
CREATE TEMP TABLE ${context.workingTable} (
|
|
12270
|
+
graph_id TEXT NOT NULL,
|
|
12271
|
+
run_id TEXT NOT NULL,
|
|
12272
|
+
node_id TEXT NOT NULL,
|
|
12273
|
+
node_kind TEXT NOT NULL,
|
|
12274
|
+
distance DOUBLE PRECISION NOT NULL,
|
|
12275
|
+
hops INTEGER NOT NULL,
|
|
12276
|
+
predecessor_id TEXT,
|
|
12277
|
+
predecessor_kind TEXT,
|
|
12278
|
+
improved_round INTEGER NOT NULL,
|
|
12279
|
+
PRIMARY KEY (graph_id, run_id, node_kind, node_id)
|
|
12280
|
+
)
|
|
12281
|
+
`;
|
|
12282
|
+
}
|
|
12283
|
+
async function seedDistances(context, sourceId) {
|
|
12284
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12285
|
+
return context.backend.execute(
|
|
12286
|
+
asCompiledRowsSql(sql`
|
|
12287
|
+
INSERT INTO ${workingTable}
|
|
12288
|
+
(graph_id, run_id, node_id, node_kind, distance, hops,
|
|
12289
|
+
predecessor_id, predecessor_kind, improved_round)
|
|
12290
|
+
SELECT ${graphId}, ${runId}, n.id, n.kind, 0, 0, NULL, NULL, 0
|
|
12291
|
+
FROM ${operation.schema.nodesTable} n
|
|
12292
|
+
WHERE n.graph_id = ${graphId}
|
|
12293
|
+
AND n.id = ${sourceId}
|
|
12294
|
+
AND ${operation.nodeTemporalFilter}
|
|
12295
|
+
ON CONFLICT (graph_id, run_id, node_kind, node_id) DO NOTHING
|
|
12296
|
+
RETURNING node_id
|
|
12297
|
+
`)
|
|
12298
|
+
);
|
|
12299
|
+
}
|
|
12300
|
+
async function relaxFrontierRound(context, targetId, iteration, roundBound) {
|
|
12301
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12302
|
+
const { dialect } = operation.ctx;
|
|
12303
|
+
let frontierCount = 0;
|
|
12304
|
+
let bestTargetDistance = roundBound;
|
|
12305
|
+
for (const edgeKinds of operation.edgeKindChunks) {
|
|
12306
|
+
const sourceFilter = sql`
|
|
12307
|
+
w.graph_id = ${graphId}
|
|
12308
|
+
AND w.run_id = ${runId}
|
|
12309
|
+
AND w.improved_round = ${iteration - 1}
|
|
12310
|
+
`;
|
|
12311
|
+
const expansion = compileWorkingTableExpansion(
|
|
12312
|
+
operation,
|
|
12313
|
+
workingTable,
|
|
12314
|
+
sourceFilter,
|
|
12315
|
+
operation.direction,
|
|
12316
|
+
edgeKinds
|
|
12317
|
+
);
|
|
12318
|
+
const rows = await context.backend.execute(
|
|
12319
|
+
asCompiledRowsSql(sql`
|
|
12320
|
+
INSERT INTO ${workingTable}
|
|
12321
|
+
(graph_id, run_id, node_id, node_kind, distance, hops,
|
|
12322
|
+
predecessor_id, predecessor_kind, improved_round)
|
|
12323
|
+
SELECT
|
|
12324
|
+
${graphId}, ${runId}, ranked.target_id, ranked.target_kind,
|
|
12325
|
+
ranked.candidate_distance, ranked.candidate_hops,
|
|
12326
|
+
ranked.source_id, ranked.source_kind, ${iteration}
|
|
12327
|
+
FROM (
|
|
12328
|
+
SELECT candidates.*,
|
|
12329
|
+
ROW_NUMBER() OVER (
|
|
12330
|
+
PARTITION BY ${dialect.binaryText(sql`candidates.target_kind`)},
|
|
12331
|
+
${dialect.binaryText(sql`candidates.target_id`)}
|
|
12332
|
+
ORDER BY candidates.candidate_distance,
|
|
12333
|
+
${dialect.binaryText(sql`candidates.source_id`)},
|
|
12334
|
+
${dialect.binaryText(sql`candidates.source_kind`)}
|
|
12335
|
+
) AS candidate_rank
|
|
12336
|
+
FROM (
|
|
12337
|
+
SELECT expanded.source_id, expanded.source_kind,
|
|
12338
|
+
expanded.target_id, expanded.target_kind,
|
|
12339
|
+
frontier.distance + expanded.weight AS candidate_distance,
|
|
12340
|
+
frontier.hops + 1 AS candidate_hops
|
|
12341
|
+
FROM (${expansion}) expanded
|
|
12342
|
+
JOIN ${workingTable} frontier
|
|
12343
|
+
ON frontier.graph_id = ${graphId}
|
|
12344
|
+
AND frontier.run_id = ${runId}
|
|
12345
|
+
AND frontier.node_id = expanded.source_id
|
|
12346
|
+
AND frontier.node_kind = expanded.source_kind
|
|
12347
|
+
) candidates
|
|
12348
|
+
WHERE ${roundBound === void 0 ? sql`TRUE` : sql`candidates.candidate_distance <= ${roundBound}`}
|
|
12349
|
+
) ranked
|
|
12350
|
+
WHERE ranked.candidate_rank = 1
|
|
12351
|
+
ON CONFLICT (graph_id, run_id, node_kind, node_id) DO UPDATE SET
|
|
12352
|
+
distance = excluded.distance,
|
|
12353
|
+
hops = excluded.hops,
|
|
12354
|
+
predecessor_id = excluded.predecessor_id,
|
|
12355
|
+
predecessor_kind = excluded.predecessor_kind,
|
|
12356
|
+
improved_round = excluded.improved_round
|
|
12357
|
+
WHERE excluded.distance < ${workingTable}.distance
|
|
12358
|
+
RETURNING node_id, distance
|
|
12359
|
+
`)
|
|
12360
|
+
);
|
|
12361
|
+
frontierCount += rows.length;
|
|
12362
|
+
for (const row of rows) {
|
|
12363
|
+
if (row.node_id !== targetId) continue;
|
|
12364
|
+
const distance = Number(row.distance);
|
|
12365
|
+
if (bestTargetDistance === void 0 || distance < bestTargetDistance) {
|
|
12366
|
+
bestTargetDistance = distance;
|
|
12367
|
+
}
|
|
10683
12368
|
}
|
|
10684
12369
|
}
|
|
10685
|
-
return
|
|
12370
|
+
return { frontierCount, bestTargetDistance };
|
|
10686
12371
|
}
|
|
10687
|
-
async function
|
|
10688
|
-
const
|
|
10689
|
-
const
|
|
10690
|
-
const
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
12372
|
+
async function extractPathFromWorkingTable(context, targetId, maxIterations) {
|
|
12373
|
+
const { operation, workingTable, graphId, runId } = context;
|
|
12374
|
+
const { dialect } = operation.ctx;
|
|
12375
|
+
const rows = await context.backend.execute(
|
|
12376
|
+
asCompiledRowsSql(sql`
|
|
12377
|
+
WITH RECURSIVE chain AS (
|
|
12378
|
+
SELECT best.node_id, best.node_kind, best.distance, best.hops,
|
|
12379
|
+
best.predecessor_id, best.predecessor_kind, 0 AS position
|
|
12380
|
+
FROM (
|
|
12381
|
+
SELECT node_id, node_kind, distance, hops,
|
|
12382
|
+
predecessor_id, predecessor_kind
|
|
12383
|
+
FROM ${workingTable}
|
|
12384
|
+
WHERE graph_id = ${graphId}
|
|
12385
|
+
AND run_id = ${runId}
|
|
12386
|
+
AND node_id = ${targetId}
|
|
12387
|
+
ORDER BY distance ASC, ${dialect.binaryText(sql`node_kind`)}
|
|
12388
|
+
LIMIT 1
|
|
12389
|
+
) best
|
|
12390
|
+
UNION ALL
|
|
12391
|
+
SELECT w.node_id, w.node_kind, w.distance, w.hops,
|
|
12392
|
+
w.predecessor_id, w.predecessor_kind, c.position + 1
|
|
12393
|
+
FROM chain c
|
|
12394
|
+
JOIN ${workingTable} w
|
|
12395
|
+
ON w.graph_id = ${graphId}
|
|
12396
|
+
AND w.run_id = ${runId}
|
|
12397
|
+
AND w.node_id = c.predecessor_id
|
|
12398
|
+
AND w.node_kind = c.predecessor_kind
|
|
12399
|
+
WHERE c.position <= ${maxIterations}
|
|
12400
|
+
)
|
|
12401
|
+
SELECT node_id, node_kind, distance, hops
|
|
12402
|
+
FROM chain
|
|
12403
|
+
ORDER BY position DESC
|
|
12404
|
+
`)
|
|
12405
|
+
);
|
|
12406
|
+
const target = rows.at(-1);
|
|
12407
|
+
if (target === void 0) return void 0;
|
|
12408
|
+
return {
|
|
12409
|
+
nodes: rows.map((row) => ({ id: row.node_id, kind: row.node_kind })),
|
|
12410
|
+
depth: Number(target.hops),
|
|
12411
|
+
totalWeight: Number(target.distance)
|
|
12412
|
+
};
|
|
12413
|
+
}
|
|
12414
|
+
async function findWeightedSelfPath(ctx, nodeId, options) {
|
|
12415
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
12416
|
+
await assertValidEdgeWeights(
|
|
12417
|
+
operation,
|
|
12418
|
+
options.weightProperty,
|
|
12419
|
+
options.defaultWeight
|
|
12420
|
+
);
|
|
12421
|
+
const nodes = await fetchVisibleWorkingNodes(operation, [nodeId]);
|
|
12422
|
+
const node = nodes.toSorted(
|
|
12423
|
+
(left, right) => compareNodeIdentityCodePoints(left, right)
|
|
12424
|
+
)[0];
|
|
12425
|
+
if (node === void 0) return;
|
|
12426
|
+
return { nodes: [node], depth: 0, totalWeight: 0 };
|
|
12427
|
+
});
|
|
12428
|
+
}
|
|
12429
|
+
async function findWeightedShortestPathInline(ctx, sourceId, targetId, maxIterations, options) {
|
|
12430
|
+
return withInlineIterativeGraphOperation(ctx, options, async (operation) => {
|
|
12431
|
+
await assertValidEdgeWeights(
|
|
12432
|
+
operation,
|
|
12433
|
+
options.weightProperty,
|
|
12434
|
+
options.defaultWeight
|
|
12435
|
+
);
|
|
12436
|
+
const endpoints = await fetchVisibleWorkingNodes(operation, [
|
|
12437
|
+
sourceId,
|
|
12438
|
+
targetId
|
|
12439
|
+
]);
|
|
12440
|
+
const sources = endpoints.filter((node) => node.id === sourceId);
|
|
12441
|
+
if (sources.length === 0 || !endpoints.some((node) => node.id === targetId)) {
|
|
12442
|
+
return;
|
|
12443
|
+
}
|
|
12444
|
+
const settled = new Map(
|
|
12445
|
+
sources.map((source) => [
|
|
12446
|
+
nodeIdentityKey(source),
|
|
12447
|
+
{
|
|
12448
|
+
id: source.id,
|
|
12449
|
+
kind: source.kind,
|
|
12450
|
+
distance: 0,
|
|
12451
|
+
hops: 0,
|
|
12452
|
+
parentKey: void 0
|
|
12453
|
+
}
|
|
12454
|
+
])
|
|
12455
|
+
);
|
|
12456
|
+
let bestTargetDistance;
|
|
12457
|
+
let frontier = sources;
|
|
12458
|
+
let rounds = 0;
|
|
12459
|
+
while (frontier.length > 0) {
|
|
12460
|
+
if (rounds >= maxIterations) {
|
|
12461
|
+
throw new GraphAlgorithmConvergenceError(
|
|
12462
|
+
WEIGHTED_ALGORITHM_NAME,
|
|
12463
|
+
maxIterations
|
|
12464
|
+
);
|
|
12465
|
+
}
|
|
12466
|
+
rounds++;
|
|
12467
|
+
const candidates = await reduceExpandedWorkingSet(
|
|
12468
|
+
operation,
|
|
12469
|
+
frontier,
|
|
12470
|
+
operation.direction,
|
|
12471
|
+
(existing, expansion) => reduceWeightedCandidate(existing, expansion, settled)
|
|
12472
|
+
);
|
|
12473
|
+
const roundBound = bestTargetDistance;
|
|
12474
|
+
const improved = [];
|
|
12475
|
+
for (const [candidateKey, candidate] of candidates) {
|
|
12476
|
+
const existing = settled.get(candidateKey);
|
|
12477
|
+
if (existing !== void 0 && existing.distance <= candidate.distance) {
|
|
12478
|
+
continue;
|
|
12479
|
+
}
|
|
12480
|
+
if (roundBound !== void 0 && candidate.distance > roundBound) {
|
|
12481
|
+
continue;
|
|
12482
|
+
}
|
|
12483
|
+
settled.set(candidateKey, {
|
|
12484
|
+
id: candidate.id,
|
|
12485
|
+
kind: candidate.kind,
|
|
12486
|
+
distance: candidate.distance,
|
|
12487
|
+
hops: candidate.hops,
|
|
12488
|
+
parentKey: nodeIdentityKey({
|
|
12489
|
+
id: candidate.parentId,
|
|
12490
|
+
kind: candidate.parentKind
|
|
12491
|
+
})
|
|
12492
|
+
});
|
|
12493
|
+
improved.push({ id: candidate.id, kind: candidate.kind });
|
|
12494
|
+
if (candidate.id === targetId && (bestTargetDistance === void 0 || candidate.distance < bestTargetDistance)) {
|
|
12495
|
+
bestTargetDistance = candidate.distance;
|
|
12496
|
+
}
|
|
12497
|
+
}
|
|
12498
|
+
frontier = improved.toSorted(
|
|
12499
|
+
(left, right) => compareNodeIdentityCodePoints(left, right)
|
|
12500
|
+
);
|
|
12501
|
+
}
|
|
12502
|
+
return buildWeightedPathResult(settled, targetId);
|
|
12503
|
+
});
|
|
12504
|
+
}
|
|
12505
|
+
function reduceWeightedCandidate(existing, expansion, settled) {
|
|
12506
|
+
const weight = expansion.weight;
|
|
12507
|
+
if (weight === void 0) return existing;
|
|
12508
|
+
const sourceEntry = settled.get(nodeIdentityKey(expansion.source));
|
|
12509
|
+
if (sourceEntry === void 0) return existing;
|
|
12510
|
+
const candidateDistance = sourceEntry.distance + weight;
|
|
12511
|
+
if (existing !== void 0 && (existing.distance < candidateDistance || existing.distance === candidateDistance && compareNodeIdentityCodePoints(
|
|
12512
|
+
{ id: existing.parentId, kind: existing.parentKind },
|
|
12513
|
+
expansion.source
|
|
12514
|
+
) <= 0)) {
|
|
12515
|
+
return existing;
|
|
12516
|
+
}
|
|
12517
|
+
return {
|
|
12518
|
+
id: expansion.target.id,
|
|
12519
|
+
kind: expansion.target.kind,
|
|
12520
|
+
distance: candidateDistance,
|
|
12521
|
+
hops: sourceEntry.hops + 1,
|
|
12522
|
+
parentId: expansion.source.id,
|
|
12523
|
+
parentKind: expansion.source.kind
|
|
12524
|
+
};
|
|
12525
|
+
}
|
|
12526
|
+
function buildWeightedPathResult(visited, targetId) {
|
|
12527
|
+
let target;
|
|
12528
|
+
for (const node of visited.values()) {
|
|
12529
|
+
if (node.id !== targetId) continue;
|
|
12530
|
+
if (target === void 0 || node.distance < target.distance || node.distance === target.distance && compareNodeIdentityCodePoints(node, target) < 0) {
|
|
12531
|
+
target = node;
|
|
12532
|
+
}
|
|
12533
|
+
}
|
|
12534
|
+
if (target === void 0) return void 0;
|
|
12535
|
+
const reversedNodes = [];
|
|
12536
|
+
let cursor = target;
|
|
12537
|
+
while (cursor !== void 0) {
|
|
12538
|
+
reversedNodes.push({ id: cursor.id, kind: cursor.kind });
|
|
12539
|
+
cursor = cursor.parentKey === void 0 ? void 0 : visited.get(cursor.parentKey);
|
|
12540
|
+
}
|
|
12541
|
+
return {
|
|
12542
|
+
nodes: reversedNodes.toReversed(),
|
|
12543
|
+
depth: target.hops,
|
|
12544
|
+
totalWeight: target.distance
|
|
12545
|
+
};
|
|
10695
12546
|
}
|
|
10696
12547
|
|
|
10697
12548
|
// src/store/algorithms/index.ts
|
|
@@ -10704,7 +12555,7 @@ function assertRecordedAsOfInternalOnly(options, method, allowRecordedAsOf) {
|
|
|
10704
12555
|
code: "ALGORITHM_RECORDED_ASOF_INTERNAL_ONLY",
|
|
10705
12556
|
message: `recordedAsOf is only available through store.asOfRecorded(...).${method}(...).`,
|
|
10706
12557
|
context: { method },
|
|
10707
|
-
suggestion: "Use store.asOfRecorded(recordedAt)
|
|
12558
|
+
suggestion: "Use store.asOfRecorded(recordedAt) or a recorded StoreView instead of passing recordedAsOf directly."
|
|
10708
12559
|
});
|
|
10709
12560
|
}
|
|
10710
12561
|
function createGraphAlgorithms(params) {
|
|
@@ -10733,6 +12584,19 @@ function createGraphAlgorithms(params) {
|
|
|
10733
12584
|
options
|
|
10734
12585
|
);
|
|
10735
12586
|
},
|
|
12587
|
+
weightedShortestPath(from, to, options) {
|
|
12588
|
+
assertRecordedAsOfInternalOnly(
|
|
12589
|
+
options,
|
|
12590
|
+
"weightedShortestPath",
|
|
12591
|
+
allowRecordedAsOf
|
|
12592
|
+
);
|
|
12593
|
+
return executeWeightedShortestPath(
|
|
12594
|
+
ctx,
|
|
12595
|
+
resolveNodeId(from),
|
|
12596
|
+
resolveNodeId(to),
|
|
12597
|
+
options
|
|
12598
|
+
);
|
|
12599
|
+
},
|
|
10736
12600
|
reachable(from, options) {
|
|
10737
12601
|
assertRecordedAsOfInternalOnly(options, "reachable", allowRecordedAsOf);
|
|
10738
12602
|
return executeReachable(ctx, resolveNodeId(from), options);
|
|
@@ -10753,6 +12617,14 @@ function createGraphAlgorithms(params) {
|
|
|
10753
12617
|
degree(node, options) {
|
|
10754
12618
|
assertRecordedAsOfInternalOnly(options, "degree", allowRecordedAsOf);
|
|
10755
12619
|
return executeDegree(ctx, resolveNodeId(node), options ?? {});
|
|
12620
|
+
},
|
|
12621
|
+
weaklyConnectedComponents(options) {
|
|
12622
|
+
assertRecordedAsOfInternalOnly(
|
|
12623
|
+
options,
|
|
12624
|
+
"weaklyConnectedComponents",
|
|
12625
|
+
allowRecordedAsOf
|
|
12626
|
+
);
|
|
12627
|
+
return executeWeaklyConnectedComponents(ctx, options);
|
|
10756
12628
|
}
|
|
10757
12629
|
};
|
|
10758
12630
|
}
|
|
@@ -10955,32 +12827,70 @@ function createEdgeCollection(config) {
|
|
|
10955
12827
|
const upsertAll = async (target) => {
|
|
10956
12828
|
const ids = items.map((item) => item.id);
|
|
10957
12829
|
const existingMap = await getEdgeRowsByIds(target, graphId, ids);
|
|
12830
|
+
const results2 = Array.from({ length: items.length });
|
|
10958
12831
|
const toCreate = [];
|
|
10959
12832
|
const toUpdate = [];
|
|
12833
|
+
const pending = /* @__PURE__ */ new Map();
|
|
12834
|
+
const deferred = [];
|
|
10960
12835
|
let itemIndex = 0;
|
|
10961
12836
|
for (const item of items) {
|
|
10962
|
-
const
|
|
10963
|
-
|
|
10964
|
-
|
|
12837
|
+
const pendingEntry = pending.get(item.id);
|
|
12838
|
+
const original = existingMap.get(item.id);
|
|
12839
|
+
const inputProps = item.props ?? {};
|
|
12840
|
+
if (pendingEntry === void 0 && original === void 0) {
|
|
12841
|
+
toCreate.push({
|
|
10965
12842
|
index: itemIndex,
|
|
10966
|
-
input:
|
|
10967
|
-
|
|
12843
|
+
input: buildCreateEdgeInput(
|
|
12844
|
+
kind,
|
|
12845
|
+
item.from,
|
|
12846
|
+
item.to,
|
|
12847
|
+
inputProps,
|
|
12848
|
+
item
|
|
12849
|
+
)
|
|
10968
12850
|
});
|
|
12851
|
+
itemIndex++;
|
|
12852
|
+
continue;
|
|
12853
|
+
}
|
|
12854
|
+
const deletedAt = pendingEntry === void 0 ? original.deleted_at : void 0;
|
|
12855
|
+
let dirty;
|
|
12856
|
+
if (config.upsertDirtyCheck !== void 0) {
|
|
12857
|
+
const currentProps = pendingEntry?.props ?? rowPropsToObject(original.props);
|
|
12858
|
+
try {
|
|
12859
|
+
dirty = config.upsertDirtyCheck(
|
|
12860
|
+
original?.kind ?? kind,
|
|
12861
|
+
item.id,
|
|
12862
|
+
currentProps,
|
|
12863
|
+
inputProps
|
|
12864
|
+
);
|
|
12865
|
+
} catch {
|
|
12866
|
+
dirty = void 0;
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
const coalesce = dirty?.unchanged === true && deletedAt === void 0 && item.validFrom === void 0 && item.validTo === void 0;
|
|
12870
|
+
if (coalesce) {
|
|
12871
|
+
if (pendingEntry === void 0) {
|
|
12872
|
+
results2[itemIndex] = narrowEdge(rowToEdge2(original));
|
|
12873
|
+
} else {
|
|
12874
|
+
deferred.push({
|
|
12875
|
+
index: itemIndex,
|
|
12876
|
+
sourceIndex: pendingEntry.sourceIndex
|
|
12877
|
+
});
|
|
12878
|
+
}
|
|
10969
12879
|
} else {
|
|
10970
|
-
|
|
12880
|
+
toUpdate.push({
|
|
10971
12881
|
index: itemIndex,
|
|
10972
|
-
input:
|
|
10973
|
-
|
|
10974
|
-
item.from,
|
|
10975
|
-
item.to,
|
|
10976
|
-
item.props ?? {},
|
|
10977
|
-
item
|
|
10978
|
-
)
|
|
12882
|
+
input: buildUpdateEdgeInput(item.id, inputProps, item),
|
|
12883
|
+
clearDeleted: deletedAt !== void 0
|
|
10979
12884
|
});
|
|
12885
|
+
if (dirty !== void 0) {
|
|
12886
|
+
pending.set(item.id, {
|
|
12887
|
+
props: dirty.validatedProps,
|
|
12888
|
+
sourceIndex: itemIndex
|
|
12889
|
+
});
|
|
12890
|
+
}
|
|
10980
12891
|
}
|
|
10981
12892
|
itemIndex++;
|
|
10982
12893
|
}
|
|
10983
|
-
const results2 = Array.from({ length: items.length });
|
|
10984
12894
|
if (toCreate.length > 0) {
|
|
10985
12895
|
const createInputs = toCreate.map((entry2) => entry2.input);
|
|
10986
12896
|
const created = await executeEdgeCreateBatch2(createInputs, target);
|
|
@@ -10994,10 +12904,13 @@ function createEdgeCollection(config) {
|
|
|
10994
12904
|
});
|
|
10995
12905
|
results2[entry2.index] = narrowEdge(result);
|
|
10996
12906
|
}
|
|
10997
|
-
|
|
12907
|
+
for (const { index, sourceIndex } of deferred) {
|
|
12908
|
+
results2[index] = results2[sourceIndex];
|
|
12909
|
+
}
|
|
12910
|
+
return { results: results2, mutations: toCreate.length + toUpdate.length };
|
|
10998
12911
|
};
|
|
10999
|
-
const results = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
|
|
11000
|
-
await config.maybeRefreshStatisticsAfterBulk?.(
|
|
12912
|
+
const { results, mutations } = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
|
|
12913
|
+
await config.maybeRefreshStatisticsAfterBulk?.(mutations);
|
|
11001
12914
|
return results;
|
|
11002
12915
|
},
|
|
11003
12916
|
async bulkInsert(items) {
|
|
@@ -11091,6 +13004,18 @@ function createEdgeCollection(config) {
|
|
|
11091
13004
|
};
|
|
11092
13005
|
}
|
|
11093
13006
|
|
|
13007
|
+
// src/store/collections/coalesce.ts
|
|
13008
|
+
function shouldCoalesceUpsert(existing, options, runDirtyCheck) {
|
|
13009
|
+
if (runDirtyCheck === void 0 || existing.deleted_at !== void 0 || options?.validFrom !== void 0 || options?.validTo !== void 0) {
|
|
13010
|
+
return false;
|
|
13011
|
+
}
|
|
13012
|
+
try {
|
|
13013
|
+
return runDirtyCheck().unchanged;
|
|
13014
|
+
} catch {
|
|
13015
|
+
return false;
|
|
13016
|
+
}
|
|
13017
|
+
}
|
|
13018
|
+
|
|
11094
13019
|
// src/store/collections/node-collection.ts
|
|
11095
13020
|
function narrowNode(node) {
|
|
11096
13021
|
return node;
|
|
@@ -11218,11 +13143,19 @@ function createNodeCollection(config) {
|
|
|
11218
13143
|
async upsertByIdFromRecord(id, data, options) {
|
|
11219
13144
|
const existing = await backend.getNode(graphId, kind, id);
|
|
11220
13145
|
if (existing) {
|
|
11221
|
-
const
|
|
13146
|
+
const runDirtyCheck = config.upsertDirtyCheck && (() => config.upsertDirtyCheck(
|
|
13147
|
+
kind,
|
|
13148
|
+
id,
|
|
13149
|
+
rowPropsToObject(existing.props),
|
|
13150
|
+
data
|
|
13151
|
+
));
|
|
13152
|
+
if (shouldCoalesceUpsert(existing, options, runDirtyCheck)) {
|
|
13153
|
+
return narrowNode(rowToNode2(existing));
|
|
13154
|
+
}
|
|
11222
13155
|
const result2 = await executeNodeUpdate2(
|
|
11223
13156
|
buildUpdateInput(kind, id, data, options),
|
|
11224
13157
|
backend,
|
|
11225
|
-
{ clearDeleted }
|
|
13158
|
+
{ clearDeleted: existing.deleted_at !== void 0 }
|
|
11226
13159
|
);
|
|
11227
13160
|
return narrowNode(result2);
|
|
11228
13161
|
}
|
|
@@ -11263,26 +13196,63 @@ function createNodeCollection(config) {
|
|
|
11263
13196
|
existingMap.set(row.id, row);
|
|
11264
13197
|
}
|
|
11265
13198
|
}
|
|
13199
|
+
const results2 = Array.from({ length: items.length });
|
|
11266
13200
|
const toCreate = [];
|
|
11267
13201
|
const toUpdate = [];
|
|
13202
|
+
const pending = /* @__PURE__ */ new Map();
|
|
13203
|
+
const deferred = [];
|
|
11268
13204
|
let itemIndex = 0;
|
|
11269
13205
|
for (const item of items) {
|
|
11270
|
-
const
|
|
11271
|
-
|
|
11272
|
-
|
|
13206
|
+
const pendingEntry = pending.get(item.id);
|
|
13207
|
+
const original = existingMap.get(item.id);
|
|
13208
|
+
if (pendingEntry === void 0 && original === void 0) {
|
|
13209
|
+
toCreate.push({
|
|
11273
13210
|
index: itemIndex,
|
|
11274
|
-
input:
|
|
11275
|
-
clearDeleted: existing.deleted_at !== void 0
|
|
13211
|
+
input: buildCreateInput(kind, item.props, item)
|
|
11276
13212
|
});
|
|
13213
|
+
itemIndex++;
|
|
13214
|
+
continue;
|
|
13215
|
+
}
|
|
13216
|
+
const deletedAt = pendingEntry === void 0 ? original.deleted_at : void 0;
|
|
13217
|
+
let dirty;
|
|
13218
|
+
if (config.upsertDirtyCheck !== void 0) {
|
|
13219
|
+
const currentProps = pendingEntry?.props ?? rowPropsToObject(original.props);
|
|
13220
|
+
try {
|
|
13221
|
+
dirty = config.upsertDirtyCheck(
|
|
13222
|
+
kind,
|
|
13223
|
+
item.id,
|
|
13224
|
+
currentProps,
|
|
13225
|
+
item.props
|
|
13226
|
+
);
|
|
13227
|
+
} catch {
|
|
13228
|
+
dirty = void 0;
|
|
13229
|
+
}
|
|
13230
|
+
}
|
|
13231
|
+
const coalesce = dirty?.unchanged === true && deletedAt === void 0 && item.validFrom === void 0 && item.validTo === void 0;
|
|
13232
|
+
if (coalesce) {
|
|
13233
|
+
if (pendingEntry === void 0) {
|
|
13234
|
+
results2[itemIndex] = narrowNode(rowToNode2(original));
|
|
13235
|
+
} else {
|
|
13236
|
+
deferred.push({
|
|
13237
|
+
index: itemIndex,
|
|
13238
|
+
sourceIndex: pendingEntry.sourceIndex
|
|
13239
|
+
});
|
|
13240
|
+
}
|
|
11277
13241
|
} else {
|
|
11278
|
-
|
|
13242
|
+
toUpdate.push({
|
|
11279
13243
|
index: itemIndex,
|
|
11280
|
-
input:
|
|
13244
|
+
input: buildUpdateInput(kind, item.id, item.props, item),
|
|
13245
|
+
clearDeleted: deletedAt !== void 0
|
|
11281
13246
|
});
|
|
13247
|
+
if (dirty !== void 0) {
|
|
13248
|
+
pending.set(item.id, {
|
|
13249
|
+
props: dirty.validatedProps,
|
|
13250
|
+
sourceIndex: itemIndex
|
|
13251
|
+
});
|
|
13252
|
+
}
|
|
11282
13253
|
}
|
|
11283
13254
|
itemIndex++;
|
|
11284
13255
|
}
|
|
11285
|
-
const results2 = Array.from({ length: items.length });
|
|
11286
13256
|
if (toCreate.length > 0) {
|
|
11287
13257
|
const createInputs = toCreate.map((entry2) => entry2.input);
|
|
11288
13258
|
const created = await executeNodeCreateBatch2(createInputs, target);
|
|
@@ -11296,10 +13266,13 @@ function createNodeCollection(config) {
|
|
|
11296
13266
|
});
|
|
11297
13267
|
results2[entry2.index] = narrowNode(result);
|
|
11298
13268
|
}
|
|
11299
|
-
|
|
13269
|
+
for (const { index, sourceIndex } of deferred) {
|
|
13270
|
+
results2[index] = results2[sourceIndex];
|
|
13271
|
+
}
|
|
13272
|
+
return { results: results2, mutations: toCreate.length + toUpdate.length };
|
|
11300
13273
|
};
|
|
11301
|
-
const results = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
|
|
11302
|
-
await config.maybeRefreshStatisticsAfterBulk?.(
|
|
13274
|
+
const { results, mutations } = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
|
|
13275
|
+
await config.maybeRefreshStatisticsAfterBulk?.(mutations);
|
|
11303
13276
|
return results;
|
|
11304
13277
|
},
|
|
11305
13278
|
async bulkInsert(items) {
|
|
@@ -11513,6 +13486,33 @@ async function ensureFocusedStatusTable(backend, ensureFocused) {
|
|
|
11513
13486
|
}
|
|
11514
13487
|
await backend.bootstrapTables?.();
|
|
11515
13488
|
}
|
|
13489
|
+
async function runBucketedMaterialization(items, options, bucketKey, runOne) {
|
|
13490
|
+
if (options.stopOnError === true) {
|
|
13491
|
+
const results2 = [];
|
|
13492
|
+
for (const item of items) {
|
|
13493
|
+
const entry2 = await runOne(item);
|
|
13494
|
+
results2.push(entry2);
|
|
13495
|
+
if (entry2.status === "failed") break;
|
|
13496
|
+
}
|
|
13497
|
+
return results2;
|
|
13498
|
+
}
|
|
13499
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
13500
|
+
for (const [index, item] of items.entries()) {
|
|
13501
|
+
const key = bucketKey(item);
|
|
13502
|
+
const bucket = buckets.get(key);
|
|
13503
|
+
if (bucket === void 0) buckets.set(key, [[index, item]]);
|
|
13504
|
+
else bucket.push([index, item]);
|
|
13505
|
+
}
|
|
13506
|
+
const results = Array.from({ length: items.length });
|
|
13507
|
+
await Promise.all(
|
|
13508
|
+
[...buckets.values()].map(async (group) => {
|
|
13509
|
+
for (const [index, item] of group) {
|
|
13510
|
+
results[index] = await runOne(item);
|
|
13511
|
+
}
|
|
13512
|
+
})
|
|
13513
|
+
);
|
|
13514
|
+
return results;
|
|
13515
|
+
}
|
|
11516
13516
|
async function runMaterialization(items, options, runOne) {
|
|
11517
13517
|
if (options.stopOnError === true) {
|
|
11518
13518
|
const results = [];
|
|
@@ -11540,18 +13540,10 @@ function delay(ms) {
|
|
|
11540
13540
|
}
|
|
11541
13541
|
async function materializeIndexes(context, options = {}) {
|
|
11542
13542
|
const { graph, graphId, backend, schemaVersion } = context;
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
);
|
|
11548
|
-
}
|
|
11549
|
-
if (backend.getIndexMaterialization === void 0 || backend.recordIndexMaterialization === void 0) {
|
|
11550
|
-
throw new ConfigurationError(
|
|
11551
|
-
"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`.",
|
|
11552
|
-
{ code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
|
|
11553
|
-
);
|
|
11554
|
-
}
|
|
13543
|
+
assertBackendSupportsIndexMaterialization(
|
|
13544
|
+
backend,
|
|
13545
|
+
"store.materializeIndexes()"
|
|
13546
|
+
);
|
|
11555
13547
|
const declared = graph.indexes ?? [];
|
|
11556
13548
|
const kindFilter = options.kinds === void 0 ? void 0 : new Set(options.kinds);
|
|
11557
13549
|
if (kindFilter !== void 0) {
|
|
@@ -11598,65 +13590,42 @@ async function materializeIndexes(context, options = {}) {
|
|
|
11598
13590
|
backend,
|
|
11599
13591
|
relationalPhysicalNames
|
|
11600
13592
|
);
|
|
11601
|
-
const
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
graphId,
|
|
11614
|
-
schemaVersion,
|
|
11615
|
-
existingByStatusKey,
|
|
11616
|
-
invalidLeftovers
|
|
11617
|
-
);
|
|
11618
|
-
if (options.stopOnError === true) {
|
|
11619
|
-
const results2 = [];
|
|
11620
|
-
for (const declaration of candidates) {
|
|
11621
|
-
const entry2 = await materializeEntry(declaration);
|
|
11622
|
-
results2.push(entry2);
|
|
11623
|
-
if (entry2.status === "failed") break;
|
|
13593
|
+
const reservedSystemNames = resolveSystemIndexNames(backend.tableNames);
|
|
13594
|
+
const materializeEntry = (declaration) => {
|
|
13595
|
+
if (declaration.entity !== "vector" && reservedSystemNames.has(declaration.name)) {
|
|
13596
|
+
return Promise.resolve(
|
|
13597
|
+
entry(
|
|
13598
|
+
declaration,
|
|
13599
|
+
"failed",
|
|
13600
|
+
new Error(
|
|
13601
|
+
`Index name "${declaration.name}" collides with a TypeGraph system index. Rename the declaration \u2014 a same-named CREATE INDEX IF NOT EXISTS would silently no-op against the system index instead of creating the declared one.`
|
|
13602
|
+
)
|
|
13603
|
+
)
|
|
13604
|
+
);
|
|
11624
13605
|
}
|
|
11625
|
-
|
|
13606
|
+
return declaration.entity === "vector" ? materializeVectorIndex(
|
|
13607
|
+
declaration,
|
|
11626
13608
|
backend,
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
13609
|
+
graphId,
|
|
13610
|
+
schemaVersion,
|
|
13611
|
+
existingByStatusKey,
|
|
13612
|
+
invalidLeftovers
|
|
13613
|
+
) : materializeRelationalIndex(
|
|
13614
|
+
declaration,
|
|
13615
|
+
backend,
|
|
13616
|
+
dialect,
|
|
13617
|
+
ddlOptions,
|
|
13618
|
+
graphId,
|
|
13619
|
+
schemaVersion,
|
|
13620
|
+
existingByStatusKey,
|
|
13621
|
+
invalidLeftovers
|
|
11630
13622
|
);
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
if (list === void 0) buckets.set(key, [declaration]);
|
|
11638
|
-
else list.push(declaration);
|
|
11639
|
-
}
|
|
11640
|
-
const bucketResults = await Promise.all(
|
|
11641
|
-
[...buckets.values()].map(async (group) => {
|
|
11642
|
-
const out = [];
|
|
11643
|
-
for (const declaration of group) {
|
|
11644
|
-
out.push(await materializeEntry(declaration));
|
|
11645
|
-
}
|
|
11646
|
-
return out;
|
|
11647
|
-
})
|
|
11648
|
-
);
|
|
11649
|
-
const byDeclaration = /* @__PURE__ */ new Map();
|
|
11650
|
-
let bucketIndex = 0;
|
|
11651
|
-
for (const group of buckets.values()) {
|
|
11652
|
-
const entries = bucketResults[bucketIndex];
|
|
11653
|
-
for (const [declarationIndex, declaration] of group.entries()) {
|
|
11654
|
-
byDeclaration.set(declaration, entries[declarationIndex]);
|
|
11655
|
-
}
|
|
11656
|
-
bucketIndex += 1;
|
|
11657
|
-
}
|
|
11658
|
-
const results = candidates.map(
|
|
11659
|
-
(declaration) => byDeclaration.get(declaration)
|
|
13623
|
+
};
|
|
13624
|
+
const results = await runBucketedMaterialization(
|
|
13625
|
+
candidates,
|
|
13626
|
+
options,
|
|
13627
|
+
(declaration) => parallelBucketKey(declaration),
|
|
13628
|
+
(declaration) => materializeEntry(declaration)
|
|
11660
13629
|
);
|
|
11661
13630
|
await refreshStatisticsAfterCreation(
|
|
11662
13631
|
backend,
|
|
@@ -11713,7 +13682,7 @@ async function materializeRelationalIndex(declaration, backend, dialect, ddlOpti
|
|
|
11713
13682
|
await backend.executeDdl(ddl);
|
|
11714
13683
|
},
|
|
11715
13684
|
existingByStatusKey,
|
|
11716
|
-
invalidLeftovers
|
|
13685
|
+
physicalRebuildPreload: invalidLeftovers
|
|
11717
13686
|
});
|
|
11718
13687
|
}
|
|
11719
13688
|
async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey, invalidLeftovers) {
|
|
@@ -11771,7 +13740,7 @@ async function materializeVectorIndex(declaration, backend, graphId, schemaVersi
|
|
|
11771
13740
|
driftLabel: "Vector index",
|
|
11772
13741
|
run: () => backend.createVectorIndex(params),
|
|
11773
13742
|
existingByStatusKey,
|
|
11774
|
-
invalidLeftovers
|
|
13743
|
+
physicalRebuildPreload: invalidLeftovers
|
|
11775
13744
|
});
|
|
11776
13745
|
}
|
|
11777
13746
|
async function materializeOne(declaration, backend, graphId, schemaVersion, action) {
|
|
@@ -11788,7 +13757,8 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11788
13757
|
statusKey,
|
|
11789
13758
|
signature,
|
|
11790
13759
|
driftLabel,
|
|
11791
|
-
|
|
13760
|
+
needsPhysicalRebuild: (physicalIndexName) => action.physicalRebuildPreload.has(physicalIndexName),
|
|
13761
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11792
13762
|
}
|
|
11793
13763
|
);
|
|
11794
13764
|
if (settled !== void 0) return settled;
|
|
@@ -11797,7 +13767,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11797
13767
|
statusKey,
|
|
11798
13768
|
signature,
|
|
11799
13769
|
driftLabel,
|
|
11800
|
-
run
|
|
13770
|
+
run,
|
|
13771
|
+
...action.freshNeedsPhysicalRebuild === void 0 ? {} : { freshNeedsPhysicalRebuild: action.freshNeedsPhysicalRebuild },
|
|
13772
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11801
13773
|
});
|
|
11802
13774
|
}
|
|
11803
13775
|
try {
|
|
@@ -11834,9 +13806,9 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11834
13806
|
}
|
|
11835
13807
|
async function settleAgainstExisting(existing, declaration, backend, graphId, schemaVersion, action) {
|
|
11836
13808
|
if (existing?.materializedAt === void 0) return void 0;
|
|
11837
|
-
const { statusKey, signature, driftLabel,
|
|
13809
|
+
const { statusKey, signature, driftLabel, needsPhysicalRebuild } = action;
|
|
11838
13810
|
if (existing.signature === signature) {
|
|
11839
|
-
if (declaration.entity !== "vector" && hasIndexBuildClaimProtocol(backend) && await
|
|
13811
|
+
if (declaration.entity !== "vector" && (hasIndexBuildClaimProtocol(backend) || action.rebuildWithoutClaim === true) && await needsPhysicalRebuild(declaration.name)) {
|
|
11840
13812
|
return void 0;
|
|
11841
13813
|
}
|
|
11842
13814
|
return entry(declaration, "alreadyMaterialized");
|
|
@@ -11901,7 +13873,8 @@ async function materializeWithClaim(declaration, backend, graphId, schemaVersion
|
|
|
11901
13873
|
driftLabel,
|
|
11902
13874
|
// Post-claim the bulk preload is stale (we may have waited on
|
|
11903
13875
|
// another builder's claim), so re-check this index fresh.
|
|
11904
|
-
|
|
13876
|
+
needsPhysicalRebuild: action.freshNeedsPhysicalRebuild ?? ((physicalIndexName) => hasInvalidIndexLeftover(backend, physicalIndexName)),
|
|
13877
|
+
...action.rebuildWithoutClaim === void 0 ? {} : { rebuildWithoutClaim: action.rebuildWithoutClaim }
|
|
11905
13878
|
}
|
|
11906
13879
|
);
|
|
11907
13880
|
if (settled !== void 0) return settled;
|
|
@@ -11962,6 +13935,7 @@ async function hasInvalidIndexLeftover(backend, physicalIndexName) {
|
|
|
11962
13935
|
FROM pg_class c
|
|
11963
13936
|
JOIN pg_index i ON i.indexrelid = c.oid
|
|
11964
13937
|
WHERE c.relname = ${physicalIndexName}
|
|
13938
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
11965
13939
|
`)
|
|
11966
13940
|
);
|
|
11967
13941
|
return rows[0]?.invalid === true;
|
|
@@ -11970,17 +13944,64 @@ async function preloadInvalidIndexLeftovers(backend, physicalIndexNames) {
|
|
|
11970
13944
|
if (backend.dialect !== "postgres" || physicalIndexNames.length === 0) {
|
|
11971
13945
|
return /* @__PURE__ */ new Set();
|
|
11972
13946
|
}
|
|
13947
|
+
const { invalid } = await preloadPhysicalIndexStates(
|
|
13948
|
+
backend,
|
|
13949
|
+
physicalIndexNames
|
|
13950
|
+
);
|
|
13951
|
+
return invalid;
|
|
13952
|
+
}
|
|
13953
|
+
async function preloadExistingTables(backend, tableNames) {
|
|
13954
|
+
if (tableNames.length === 0) return /* @__PURE__ */ new Set();
|
|
13955
|
+
if (backend.dialect === "postgres") {
|
|
13956
|
+
const rows2 = await backend.execute(
|
|
13957
|
+
asCompiledRowsSql(sql`
|
|
13958
|
+
SELECT c.relname AS name
|
|
13959
|
+
FROM pg_class c
|
|
13960
|
+
WHERE c.relname IN (${sqlValueList(tableNames)})
|
|
13961
|
+
AND c.relkind IN ('r', 'p')
|
|
13962
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
13963
|
+
`)
|
|
13964
|
+
);
|
|
13965
|
+
return new Set(rows2.map((row) => row.name));
|
|
13966
|
+
}
|
|
11973
13967
|
const rows = await backend.execute(
|
|
11974
13968
|
asCompiledRowsSql(sql`
|
|
11975
|
-
SELECT
|
|
11976
|
-
FROM
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
AND NOT i.indisvalid
|
|
13969
|
+
SELECT name
|
|
13970
|
+
FROM sqlite_master
|
|
13971
|
+
WHERE type = 'table'
|
|
13972
|
+
AND name IN (${sqlValueList(tableNames)})
|
|
11980
13973
|
`)
|
|
11981
13974
|
);
|
|
11982
13975
|
return new Set(rows.map((row) => row.name));
|
|
11983
13976
|
}
|
|
13977
|
+
async function preloadPhysicalIndexStates(backend, physicalIndexNames) {
|
|
13978
|
+
const valid = /* @__PURE__ */ new Set();
|
|
13979
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
13980
|
+
if (physicalIndexNames.length === 0) return { valid, invalid };
|
|
13981
|
+
if (backend.dialect === "postgres") {
|
|
13982
|
+
const rows2 = await backend.execute(
|
|
13983
|
+
asCompiledRowsSql(sql`
|
|
13984
|
+
SELECT c.relname AS name, i.indisvalid AS valid
|
|
13985
|
+
FROM pg_class c
|
|
13986
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
13987
|
+
WHERE c.relname IN (${sqlValueList(physicalIndexNames)})
|
|
13988
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
13989
|
+
`)
|
|
13990
|
+
);
|
|
13991
|
+
for (const row of rows2) (row.valid ? valid : invalid).add(row.name);
|
|
13992
|
+
return { valid, invalid };
|
|
13993
|
+
}
|
|
13994
|
+
const rows = await backend.execute(
|
|
13995
|
+
asCompiledRowsSql(sql`
|
|
13996
|
+
SELECT name
|
|
13997
|
+
FROM sqlite_master
|
|
13998
|
+
WHERE type = 'index'
|
|
13999
|
+
AND name IN (${sqlValueList(physicalIndexNames)})
|
|
14000
|
+
`)
|
|
14001
|
+
);
|
|
14002
|
+
for (const row of rows) valid.add(row.name);
|
|
14003
|
+
return { valid, invalid };
|
|
14004
|
+
}
|
|
11984
14005
|
async function dropInvalidIndexLeftover(backend, physicalIndexName) {
|
|
11985
14006
|
if (backend.executeDdl === void 0) return;
|
|
11986
14007
|
if (!await hasInvalidIndexLeftover(backend, physicalIndexName)) return;
|
|
@@ -12039,6 +14060,121 @@ function buildAttempt(args) {
|
|
|
12039
14060
|
error: args.error?.message
|
|
12040
14061
|
};
|
|
12041
14062
|
}
|
|
14063
|
+
function backendSupportsIndexMaterialization(backend) {
|
|
14064
|
+
return backend.executeDdl !== void 0 && backend.getIndexMaterialization !== void 0 && backend.recordIndexMaterialization !== void 0;
|
|
14065
|
+
}
|
|
14066
|
+
function assertBackendSupportsIndexMaterialization(backend, surface) {
|
|
14067
|
+
if (backendSupportsIndexMaterialization(backend)) return;
|
|
14068
|
+
throw new ConfigurationError(
|
|
14069
|
+
`${surface} requires a backend with \`executeDdl\` and the index materialization status primitives (\`getIndexMaterialization\`, \`recordIndexMaterialization\`). The bundled SQLite and Postgres backends provide them; a custom backend must implement all three.`,
|
|
14070
|
+
{ code: "MATERIALIZE_BACKEND_UNSUPPORTED" }
|
|
14071
|
+
);
|
|
14072
|
+
}
|
|
14073
|
+
async function materializeSystemIndexes(context, options = {}) {
|
|
14074
|
+
const { backend, graphId, schemaVersion } = context;
|
|
14075
|
+
assertBackendSupportsIndexMaterialization(
|
|
14076
|
+
backend,
|
|
14077
|
+
"store.materializeSystemIndexes()"
|
|
14078
|
+
);
|
|
14079
|
+
await ensureFocusedStatusTable(
|
|
14080
|
+
backend,
|
|
14081
|
+
backend.ensureIndexMaterializationsTable
|
|
14082
|
+
);
|
|
14083
|
+
const concurrent = backend.dialect === "postgres";
|
|
14084
|
+
const candidates = SYSTEM_INDEX_DECLARATIONS.map((declaration) => {
|
|
14085
|
+
const physicalTable = resolveSystemIndexTableName(
|
|
14086
|
+
declaration.table,
|
|
14087
|
+
backend.tableNames
|
|
14088
|
+
);
|
|
14089
|
+
return {
|
|
14090
|
+
declaration,
|
|
14091
|
+
physicalTable,
|
|
14092
|
+
name: systemIndexName(physicalTable, declaration.suffix)
|
|
14093
|
+
};
|
|
14094
|
+
});
|
|
14095
|
+
const statusKeys = candidates.map((candidate) => candidate.name);
|
|
14096
|
+
const targetTables = [
|
|
14097
|
+
...new Set(candidates.map((candidate) => candidate.physicalTable))
|
|
14098
|
+
];
|
|
14099
|
+
const [existingByStatusKey, physicalStates, existingTables] = await Promise.all([
|
|
14100
|
+
preloadMaterializations(backend, statusKeys),
|
|
14101
|
+
preloadPhysicalIndexStates(backend, statusKeys),
|
|
14102
|
+
preloadExistingTables(backend, targetTables)
|
|
14103
|
+
]);
|
|
14104
|
+
const { valid: physicallyPresent } = physicalStates;
|
|
14105
|
+
const physicalRebuildPreload = new Set(
|
|
14106
|
+
statusKeys.filter((name) => !physicallyPresent.has(name))
|
|
14107
|
+
);
|
|
14108
|
+
const materializeEntry = async (candidate) => {
|
|
14109
|
+
const identity = {
|
|
14110
|
+
name: candidate.name,
|
|
14111
|
+
entity: "system",
|
|
14112
|
+
kind: candidate.declaration.table
|
|
14113
|
+
};
|
|
14114
|
+
if (!existingTables.has(candidate.physicalTable)) {
|
|
14115
|
+
return {
|
|
14116
|
+
indexName: candidate.name,
|
|
14117
|
+
entity: "system",
|
|
14118
|
+
kind: candidate.declaration.table,
|
|
14119
|
+
status: "skipped",
|
|
14120
|
+
reason: `Relation "${candidate.physicalTable}" does not exist in this database`
|
|
14121
|
+
};
|
|
14122
|
+
}
|
|
14123
|
+
const existing = existingByStatusKey.get(candidate.name);
|
|
14124
|
+
if (existing !== void 0 && existing.entity !== "system") {
|
|
14125
|
+
return entry(
|
|
14126
|
+
identity,
|
|
14127
|
+
"failed",
|
|
14128
|
+
new Error(
|
|
14129
|
+
`System index name "${candidate.name}" is already recorded by a ${existing.entity} index declaration (graph "${existing.graphId}"). Rename the graph-declared index \u2014 "typegraph_"-prefixed names belong to TypeGraph's system indexes.`
|
|
14130
|
+
)
|
|
14131
|
+
);
|
|
14132
|
+
}
|
|
14133
|
+
if (existing === void 0 && physicallyPresent.has(candidate.name)) {
|
|
14134
|
+
return entry(identity, "alreadyMaterialized");
|
|
14135
|
+
}
|
|
14136
|
+
const signature = await computeSystemIndexSignature(
|
|
14137
|
+
backend.dialect,
|
|
14138
|
+
candidate.physicalTable,
|
|
14139
|
+
candidate.declaration
|
|
14140
|
+
);
|
|
14141
|
+
const ddl = generateSystemIndexDDL(
|
|
14142
|
+
candidate.declaration,
|
|
14143
|
+
candidate.physicalTable,
|
|
14144
|
+
{ concurrent }
|
|
14145
|
+
);
|
|
14146
|
+
return materializeOne(identity, backend, graphId, schemaVersion, {
|
|
14147
|
+
statusKey: candidate.name,
|
|
14148
|
+
signature,
|
|
14149
|
+
driftLabel: "System index",
|
|
14150
|
+
run: () => backend.executeDdl(ddl),
|
|
14151
|
+
existingByStatusKey,
|
|
14152
|
+
physicalRebuildPreload,
|
|
14153
|
+
freshNeedsPhysicalRebuild: async (physicalIndexName) => {
|
|
14154
|
+
const fresh = await preloadPhysicalIndexStates(backend, [
|
|
14155
|
+
physicalIndexName
|
|
14156
|
+
]);
|
|
14157
|
+
return !fresh.valid.has(physicalIndexName);
|
|
14158
|
+
},
|
|
14159
|
+
rebuildWithoutClaim: true
|
|
14160
|
+
});
|
|
14161
|
+
};
|
|
14162
|
+
const results = await runBucketedMaterialization(
|
|
14163
|
+
candidates,
|
|
14164
|
+
options,
|
|
14165
|
+
(candidate) => candidate.physicalTable,
|
|
14166
|
+
(candidate) => materializeEntry(candidate)
|
|
14167
|
+
);
|
|
14168
|
+
await refreshStatisticsAfterCreation(backend, results, options, concurrent);
|
|
14169
|
+
return { results };
|
|
14170
|
+
}
|
|
14171
|
+
async function computeSystemIndexSignature(dialect, targetTableName, declaration) {
|
|
14172
|
+
const json = JSON.stringify(
|
|
14173
|
+
{ dialect, targetTableName, declaration },
|
|
14174
|
+
sortedReplacer
|
|
14175
|
+
);
|
|
14176
|
+
return sha256Hex(json, 16);
|
|
14177
|
+
}
|
|
12042
14178
|
async function computeIndexSignature(dialect, targetTableName, declaration) {
|
|
12043
14179
|
const hashable = {
|
|
12044
14180
|
dialect,
|
|
@@ -12267,13 +14403,13 @@ function buildEmbeddingTableCleanup(ctx, row) {
|
|
|
12267
14403
|
const slots = await resolveRemovedKindEmbeddingSlots(ctx.backend, row);
|
|
12268
14404
|
await Promise.all(
|
|
12269
14405
|
slots.map(
|
|
12270
|
-
(slot) => dropVectorSlotStorage(vectorStrategy, executeDdl, slot)
|
|
14406
|
+
(slot) => dropVectorSlotStorage(ctx.backend, vectorStrategy, executeDdl, slot)
|
|
12271
14407
|
)
|
|
12272
14408
|
);
|
|
12273
14409
|
})();
|
|
12274
14410
|
return [cleanup];
|
|
12275
14411
|
}
|
|
12276
|
-
async function dropVectorSlotStorage(strategy, executeDdl, slot) {
|
|
14412
|
+
async function dropVectorSlotStorage(backend, strategy, executeDdl, slot) {
|
|
12277
14413
|
try {
|
|
12278
14414
|
for (const ddl of strategy.buildDropStorage(slot)) {
|
|
12279
14415
|
await executeDdl(ddl);
|
|
@@ -12281,6 +14417,7 @@ async function dropVectorSlotStorage(strategy, executeDdl, slot) {
|
|
|
12281
14417
|
} catch (error) {
|
|
12282
14418
|
if (!isMissingTableError(error)) throw error;
|
|
12283
14419
|
}
|
|
14420
|
+
await backend.deleteVectorSlotContribution?.(slot);
|
|
12284
14421
|
}
|
|
12285
14422
|
async function resolveRemovedKindEmbeddingSlots(backend, row) {
|
|
12286
14423
|
const priorVersion = row.schemaVersion - 1;
|
|
@@ -12356,7 +14493,12 @@ async function reclaimRemovedVectorFieldTables(context) {
|
|
|
12356
14493
|
const results = [];
|
|
12357
14494
|
for (const field2 of orphans) {
|
|
12358
14495
|
try {
|
|
12359
|
-
await dropVectorSlotStorage(
|
|
14496
|
+
await dropVectorSlotStorage(
|
|
14497
|
+
backend,
|
|
14498
|
+
vectorStrategy,
|
|
14499
|
+
executeDdl,
|
|
14500
|
+
field2.slot
|
|
14501
|
+
);
|
|
12360
14502
|
results.push({
|
|
12361
14503
|
kind: field2.kind,
|
|
12362
14504
|
fieldPath: field2.fieldPath,
|
|
@@ -12697,21 +14839,45 @@ async function executeEdgeCreateBatch(ctx, inputs, backend) {
|
|
|
12697
14839
|
return rows.map((row) => rowToEdge(row));
|
|
12698
14840
|
});
|
|
12699
14841
|
}
|
|
14842
|
+
function computeEdgeUpdate(ctx, kind, id, existingProps, inputProps) {
|
|
14843
|
+
const registration = getEdgeRegistration(ctx.graph, kind);
|
|
14844
|
+
return validateEdgeProps(
|
|
14845
|
+
registration.type.schema,
|
|
14846
|
+
{ ...existingProps, ...inputProps },
|
|
14847
|
+
{ kind, operation: "update", id }
|
|
14848
|
+
);
|
|
14849
|
+
}
|
|
14850
|
+
function resolveEdgeUpdateProps(ctx, existing, inputProps) {
|
|
14851
|
+
const existingProps = rowPropsToObject(existing.props);
|
|
14852
|
+
const validatedProps = computeEdgeUpdate(
|
|
14853
|
+
ctx,
|
|
14854
|
+
existing.kind,
|
|
14855
|
+
existing.id,
|
|
14856
|
+
existingProps,
|
|
14857
|
+
inputProps
|
|
14858
|
+
);
|
|
14859
|
+
return { existingProps, validatedProps };
|
|
14860
|
+
}
|
|
14861
|
+
function edgeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps) {
|
|
14862
|
+
const validatedProps = computeEdgeUpdate(
|
|
14863
|
+
ctx,
|
|
14864
|
+
kind,
|
|
14865
|
+
id,
|
|
14866
|
+
existingProps,
|
|
14867
|
+
inputProps
|
|
14868
|
+
);
|
|
14869
|
+
return {
|
|
14870
|
+
validatedProps,
|
|
14871
|
+
unchanged: canonicalEqual(validatedProps, existingProps)
|
|
14872
|
+
};
|
|
14873
|
+
}
|
|
12700
14874
|
async function performEdgeUpdate(ctx, input, target, options) {
|
|
12701
14875
|
const id = input.id;
|
|
12702
14876
|
const existing = await target.getEdge(ctx.graphId, id);
|
|
12703
14877
|
if (!existing || !options?.clearDeleted && existing.deleted_at) {
|
|
12704
14878
|
throw new EdgeNotFoundError("unknown", id);
|
|
12705
14879
|
}
|
|
12706
|
-
const
|
|
12707
|
-
const edgeKind = registration.type;
|
|
12708
|
-
const existingProps = rowPropsToObject(existing.props);
|
|
12709
|
-
const mergedProps = { ...existingProps, ...input.props };
|
|
12710
|
-
const validatedProps = validateEdgeProps(edgeKind.schema, mergedProps, {
|
|
12711
|
-
kind: existing.kind,
|
|
12712
|
-
operation: "update",
|
|
12713
|
-
id
|
|
12714
|
-
});
|
|
14880
|
+
const { validatedProps } = resolveEdgeUpdateProps(ctx, existing, input.props);
|
|
12715
14881
|
const validTo = validateOptionalCanonicalIsoDate(input.validTo, "validTo");
|
|
12716
14882
|
const updateParams = {
|
|
12717
14883
|
graphId: ctx.graphId,
|
|
@@ -13369,27 +15535,48 @@ function increment(counts, kind, count2) {
|
|
|
13369
15535
|
counts[kind] = (counts[kind] ?? 0) + count2;
|
|
13370
15536
|
}
|
|
13371
15537
|
function createTransactionReceiptRecorder() {
|
|
13372
|
-
const
|
|
13373
|
-
|
|
13374
|
-
|
|
15538
|
+
const counters = {
|
|
15539
|
+
nodes: createCountBucket(),
|
|
15540
|
+
edges: createCountBucket(),
|
|
15541
|
+
total: 0
|
|
15542
|
+
};
|
|
15543
|
+
let sealed = false;
|
|
13375
15544
|
return {
|
|
13376
15545
|
recordNode(kind, count2) {
|
|
13377
|
-
increment(nodes, kind, count2);
|
|
13378
|
-
total += count2;
|
|
15546
|
+
increment(counters.nodes, kind, count2);
|
|
15547
|
+
counters.total += count2;
|
|
13379
15548
|
},
|
|
13380
15549
|
recordEdge(kind, count2) {
|
|
13381
|
-
increment(edges, kind, count2);
|
|
13382
|
-
total += count2;
|
|
15550
|
+
increment(counters.edges, kind, count2);
|
|
15551
|
+
counters.total += count2;
|
|
13383
15552
|
},
|
|
13384
15553
|
snapshot(recorded) {
|
|
13385
15554
|
return Object.freeze({
|
|
13386
15555
|
writes: Object.freeze({
|
|
13387
|
-
nodes: Object.freeze(
|
|
13388
|
-
|
|
13389
|
-
|
|
15556
|
+
nodes: Object.freeze(
|
|
15557
|
+
Object.assign(createCountBucket(), counters.nodes)
|
|
15558
|
+
),
|
|
15559
|
+
edges: Object.freeze(
|
|
15560
|
+
Object.assign(createCountBucket(), counters.edges)
|
|
15561
|
+
),
|
|
15562
|
+
total: counters.total
|
|
13390
15563
|
}),
|
|
13391
15564
|
...recorded === void 0 ? {} : { recorded }
|
|
13392
15565
|
});
|
|
15566
|
+
},
|
|
15567
|
+
seal() {
|
|
15568
|
+
sealed = true;
|
|
15569
|
+
},
|
|
15570
|
+
assertWritable() {
|
|
15571
|
+
if (sealed) {
|
|
15572
|
+
throw new ConfigurationError(
|
|
15573
|
+
"Transaction context is sealed: a write happened through the receipt-tracked collections after the transaction callback returned.",
|
|
15574
|
+
{},
|
|
15575
|
+
{
|
|
15576
|
+
suggestion: "Perform all writes inside the transactionWithReceipt / withRecordedTransaction callback; do not reuse the transaction context after it returns."
|
|
15577
|
+
}
|
|
15578
|
+
);
|
|
15579
|
+
}
|
|
13393
15580
|
}
|
|
13394
15581
|
};
|
|
13395
15582
|
}
|
|
@@ -13423,7 +15610,7 @@ function memoizedProxyGet(target, shouldWrap, wrap) {
|
|
|
13423
15610
|
}
|
|
13424
15611
|
});
|
|
13425
15612
|
}
|
|
13426
|
-
function wrapWriteCollection(collection, kind, surface, record) {
|
|
15613
|
+
function wrapWriteCollection(collection, kind, surface, record, assertWritable) {
|
|
13427
15614
|
return memoizedProxyGet(
|
|
13428
15615
|
collection,
|
|
13429
15616
|
(property) => surface.methodNames.has(property),
|
|
@@ -13431,6 +15618,7 @@ function wrapWriteCollection(collection, kind, surface, record) {
|
|
|
13431
15618
|
if (!isWrappedMethod(value)) return value;
|
|
13432
15619
|
const method = property;
|
|
13433
15620
|
const wrapped = async (...args) => {
|
|
15621
|
+
assertWritable();
|
|
13434
15622
|
const count2 = surface.counters[method](args);
|
|
13435
15623
|
const result = await Reflect.apply(value, collection, args);
|
|
13436
15624
|
record(kind, count2);
|
|
@@ -13455,7 +15643,8 @@ function wrapTransactionCollections(nodes, edges, recorder) {
|
|
|
13455
15643
|
collection,
|
|
13456
15644
|
kind,
|
|
13457
15645
|
NODE_WRITE_SURFACE,
|
|
13458
|
-
recorder.recordNode
|
|
15646
|
+
recorder.recordNode,
|
|
15647
|
+
recorder.assertWritable
|
|
13459
15648
|
)
|
|
13460
15649
|
),
|
|
13461
15650
|
edges: wrapCollections(
|
|
@@ -13464,7 +15653,8 @@ function wrapTransactionCollections(nodes, edges, recorder) {
|
|
|
13464
15653
|
collection,
|
|
13465
15654
|
kind,
|
|
13466
15655
|
EDGE_WRITE_SURFACE,
|
|
13467
|
-
recorder.recordEdge
|
|
15656
|
+
recorder.recordEdge,
|
|
15657
|
+
recorder.assertWritable
|
|
13468
15658
|
)
|
|
13469
15659
|
)
|
|
13470
15660
|
};
|
|
@@ -13505,6 +15695,7 @@ function rowMapperConfigFor(backend) {
|
|
|
13505
15695
|
function asError(error) {
|
|
13506
15696
|
return error instanceof Error ? error : new Error(String(error));
|
|
13507
15697
|
}
|
|
15698
|
+
var EMPTY_RECORDED_FLUSH_INSTANTS = /* @__PURE__ */ new Map();
|
|
13508
15699
|
function transactionOutcome(result, recorder, recordedByGraph, graphId) {
|
|
13509
15700
|
const recorded = recordedByGraph?.get(graphId);
|
|
13510
15701
|
return {
|
|
@@ -13691,9 +15882,11 @@ var Store = class _Store {
|
|
|
13691
15882
|
* Tier 1 graph algorithms: shortest path, reachability, neighborhoods, and
|
|
13692
15883
|
* degree centrality.
|
|
13693
15884
|
*
|
|
13694
|
-
*
|
|
13695
|
-
*
|
|
13696
|
-
*
|
|
15885
|
+
* Traversal calls use the iterative graph-operation substrate: a set-based
|
|
15886
|
+
* breadth-first frontier backed by a temporary working table, or a chunked
|
|
15887
|
+
* inline relation when the backend cannot pin a transactional connection.
|
|
15888
|
+
* `degree` uses a single count query. The facade is built lazily on first
|
|
15889
|
+
* access and cached for the lifetime of the store.
|
|
13697
15890
|
*
|
|
13698
15891
|
* @example
|
|
13699
15892
|
* ```typescript
|
|
@@ -13899,6 +16092,10 @@ var Store = class _Store {
|
|
|
13899
16092
|
backend,
|
|
13900
16093
|
options
|
|
13901
16094
|
),
|
|
16095
|
+
// Present only when opted in; its absence is the coalesce off switch.
|
|
16096
|
+
...this.#options?.coalesceUnchangedUpserts === true && {
|
|
16097
|
+
upsertDirtyCheck: (kind, id, existingProps, inputProps) => nodeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps)
|
|
16098
|
+
},
|
|
13902
16099
|
executeDelete: (kind, id, backend) => executeNodeDelete(ctx, kind, id, backend),
|
|
13903
16100
|
executeHardDelete: (kind, id, backend) => executeNodeHardDelete(ctx, kind, id, backend),
|
|
13904
16101
|
temporalRowMatcher: (options) => this.#temporalRowMatcher(options),
|
|
@@ -13953,6 +16150,10 @@ var Store = class _Store {
|
|
|
13953
16150
|
executeCreateNoReturnBatch: (inputs, backend) => executeEdgeCreateNoReturnBatch(ctx, inputs, backend),
|
|
13954
16151
|
executeUpdate: (input, backend) => executeEdgeUpdate(ctx, input, backend),
|
|
13955
16152
|
executeUpsertUpdate: (input, backend, options) => executeEdgeUpsertUpdate(ctx, input, backend, options),
|
|
16153
|
+
// Present only when opted in; its absence is the coalesce off switch.
|
|
16154
|
+
...this.#options?.coalesceUnchangedUpserts === true && {
|
|
16155
|
+
upsertDirtyCheck: (kind, id, existingProps, inputProps) => edgeUpsertDirtyCheck(ctx, kind, id, existingProps, inputProps)
|
|
16156
|
+
},
|
|
13956
16157
|
executeDelete: (id, backend) => executeEdgeDelete(ctx, id, backend),
|
|
13957
16158
|
executeHardDelete: (id, backend) => executeEdgeHardDelete(ctx, id, backend),
|
|
13958
16159
|
temporalRowMatcher: (options) => this.#temporalRowMatcher(options),
|
|
@@ -14422,9 +16623,11 @@ var Store = class _Store {
|
|
|
14422
16623
|
receiptRecorder
|
|
14423
16624
|
));
|
|
14424
16625
|
}
|
|
14425
|
-
const
|
|
16626
|
+
const fallbackContext = {
|
|
14426
16627
|
nodes,
|
|
14427
16628
|
edges,
|
|
16629
|
+
// No real transaction: `tx.sql` is absent and there is no atomicity.
|
|
16630
|
+
sqlAvailability: "unavailable",
|
|
14428
16631
|
backend: this.#backend,
|
|
14429
16632
|
getNodeCollection: (kind) => this.#resolveDynamicNodeCollection(nodes, kind),
|
|
14430
16633
|
// No transaction, no deferral: operations apply as they happen, so
|
|
@@ -14433,7 +16636,10 @@ var Store = class _Store {
|
|
|
14433
16636
|
this.#createOperationContext(operation, "node", kind, id),
|
|
14434
16637
|
hookedFunction
|
|
14435
16638
|
)
|
|
14436
|
-
}
|
|
16639
|
+
};
|
|
16640
|
+
const result = await invoke(
|
|
16641
|
+
receiptRecorder === void 0 ? fallbackContext : this.#attachMeasure(fallbackContext)
|
|
16642
|
+
);
|
|
14437
16643
|
return { result, recordedByGraph: void 0 };
|
|
14438
16644
|
}
|
|
14439
16645
|
const pending = [];
|
|
@@ -14444,10 +16650,10 @@ var Store = class _Store {
|
|
|
14444
16650
|
}) : backendOptions;
|
|
14445
16651
|
try {
|
|
14446
16652
|
const result = await this.#backend.transaction(
|
|
14447
|
-
async (txBackend,
|
|
16653
|
+
async (txBackend, sql19) => invoke(
|
|
14448
16654
|
this.#buildTransactionContext(
|
|
14449
16655
|
txBackend,
|
|
14450
|
-
|
|
16656
|
+
sql19,
|
|
14451
16657
|
runHooks,
|
|
14452
16658
|
receiptRecorder
|
|
14453
16659
|
)
|
|
@@ -14468,83 +16674,9 @@ var Store = class _Store {
|
|
|
14468
16674
|
throw error;
|
|
14469
16675
|
}
|
|
14470
16676
|
}
|
|
14471
|
-
/**
|
|
14472
|
-
* Adopts a caller-owned, already-open Drizzle transaction so the graph
|
|
14473
|
-
* store and the caller's relational writes commit or roll back as one
|
|
14474
|
-
* Postgres/SQLite transaction (#134).
|
|
14475
|
-
*
|
|
14476
|
-
* Use this when the **relational layer owns the transaction**: the
|
|
14477
|
-
* caller has opened a transaction and needs TypeGraph writes enlisted
|
|
14478
|
-
* on the *same* connection. Unlike {@link transaction}, this opens no
|
|
14479
|
-
* transaction — the caller's transaction is the single commit/rollback
|
|
14480
|
-
* boundary.
|
|
14481
|
-
*
|
|
14482
|
-
* `withTransaction` is driver-agnostic; how the caller opens the
|
|
14483
|
-
* transaction is not. **Async drivers** (node-postgres,
|
|
14484
|
-
* `neon-serverless` Pool, libsql) use `db.transaction(async …)`.
|
|
14485
|
-
* **Synchronous `better-sqlite3`** cannot — its driver rejects an
|
|
14486
|
-
* `async` transaction callback (`Transaction function cannot return a
|
|
14487
|
-
* promise`) and the async continuation would run outside the
|
|
14488
|
-
* rolled-back transaction — so the caller opens the transaction with
|
|
14489
|
-
* explicit `BEGIN`/`COMMIT`/`ROLLBACK` on the single connection
|
|
14490
|
-
* instead. See the "Cross-Store Transactions" recipe for both shapes.
|
|
14491
|
-
*
|
|
14492
|
-
* The returned context reuses this store's already-resolved
|
|
14493
|
-
* schema/registry: it runs **no** schema bootstrap, `evolve`, or
|
|
14494
|
-
* migration, and emits **no DDL** inside the caller's transaction.
|
|
14495
|
-
* Fulltext operations assert the durable materialization marker (a
|
|
14496
|
-
* cached SELECT) and throw {@link StoreNotInitializedError} on a
|
|
14497
|
-
* missing/stale/failed marker rather than migrating mid-transaction —
|
|
14498
|
-
* so boot the parent store via `createStoreWithSchema` once at
|
|
14499
|
-
* startup.
|
|
14500
|
-
*
|
|
14501
|
-
* @example
|
|
14502
|
-
* ```typescript
|
|
14503
|
-
* // Async driver (Postgres / libsql):
|
|
14504
|
-
* await db.transaction(async (sqlTx) => {
|
|
14505
|
-
* const connector = await createConnectorRow(sqlTx, input); // Drizzle
|
|
14506
|
-
* const txStore = store.withTransaction(sqlTx);
|
|
14507
|
-
* await txStore.nodes.ArtifactSource.create({ // TypeGraph
|
|
14508
|
-
* connectorId: connector.id,
|
|
14509
|
-
* });
|
|
14510
|
-
* }); // one COMMIT / ROLLBACK across both layers
|
|
14511
|
-
* ```
|
|
14512
|
-
*
|
|
14513
|
-
* **The caller owns the connection — don't overlap writes on it.** The graph
|
|
14514
|
-
* store and your Drizzle writes share the one connection the caller's
|
|
14515
|
-
* transaction pinned. TypeGraph serializes the statements *its* collections
|
|
14516
|
-
* issue, but your raw Drizzle statements (and any graph write run alongside
|
|
14517
|
-
* them) are yours to sequence: a `Promise.all` mixing the two races two
|
|
14518
|
-
* queries on that connection — the overlap Postgres removes in `pg@9`. Await
|
|
14519
|
-
* each write before starting the next.
|
|
14520
|
-
*
|
|
14521
|
-
* Not available when the store was created with `{ history: true }`: a
|
|
14522
|
-
* caller-owned transaction context would let writes happen after capture has
|
|
14523
|
-
* lost its flush point, so this throws `ConfigurationError`
|
|
14524
|
-
* (`RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION`). Use
|
|
14525
|
-
* {@link Store.withRecordedTransaction} on a history-enabled store, which
|
|
14526
|
-
* adopts the same external transaction but flushes recorded-time capture
|
|
14527
|
-
* before the caller commits.
|
|
14528
|
-
*
|
|
14529
|
-
* @throws {ConfigurationError} when the store has history capture enabled
|
|
14530
|
-
* (use {@link Store.withRecordedTransaction} instead), or when the backend
|
|
14531
|
-
* cannot adopt an external transaction — either it is not a Drizzle
|
|
14532
|
-
* Postgres/SQLite backend, or `backend.capabilities.transactions` is `false`
|
|
14533
|
-
* (`drizzle-orm/neon-http`, Cloudflare D1, SQLite
|
|
14534
|
-
* `transactionMode: "none"`). A non-atomic fallback is deliberately
|
|
14535
|
-
* not offered here: the caller's relational write *would* still
|
|
14536
|
-
* commit, defeating the purpose of cross-store atomicity.
|
|
14537
|
-
* @throws {StoreNotInitializedError} at point of use, when a fulltext
|
|
14538
|
-
* operation on the returned context observes a missing/stale/failed
|
|
14539
|
-
* durable materialization marker (parent store not booted via
|
|
14540
|
-
* `createStoreWithSchema`). Rolls back the caller's transaction
|
|
14541
|
-
* without emitting any DDL.
|
|
14542
|
-
*/
|
|
14543
16677
|
withTransaction(externalTx) {
|
|
14544
16678
|
if (this.#captureEnabled) {
|
|
14545
|
-
throw recordedCaptureRequiresCallbackTransactionError(
|
|
14546
|
-
code: "RECORDED_CAPTURE_REQUIRES_CALLBACK_TRANSACTION"
|
|
14547
|
-
});
|
|
16679
|
+
throw recordedCaptureRequiresCallbackTransactionError();
|
|
14548
16680
|
}
|
|
14549
16681
|
const adopt = this.#backend.adoptTransaction;
|
|
14550
16682
|
if (!adopt) {
|
|
@@ -14572,17 +16704,62 @@ var Store = class _Store {
|
|
|
14572
16704
|
}
|
|
14573
16705
|
const scope = this.#captureEnabled ? createRecordedTransactionScope(txBackend, this.#sqlSchema()) : {
|
|
14574
16706
|
backend: txBackend,
|
|
14575
|
-
flush: () => Promise.resolve()
|
|
16707
|
+
flush: () => Promise.resolve(EMPTY_RECORDED_FLUSH_INSTANTS)
|
|
14576
16708
|
};
|
|
14577
16709
|
if (this.#captureEnabled) {
|
|
14578
16710
|
await lockRecordedGraphWrite(scope.backend, this.graphId);
|
|
14579
16711
|
}
|
|
16712
|
+
const receiptRecorder = createTransactionReceiptRecorder();
|
|
14580
16713
|
const invoke = fn;
|
|
14581
16714
|
const result = await invoke(
|
|
14582
|
-
this.#buildTransactionContext(
|
|
16715
|
+
this.#buildTransactionContext(
|
|
16716
|
+
scope.backend,
|
|
16717
|
+
externalTx,
|
|
16718
|
+
void 0,
|
|
16719
|
+
receiptRecorder
|
|
16720
|
+
)
|
|
14583
16721
|
);
|
|
14584
|
-
await scope.flush();
|
|
14585
|
-
|
|
16722
|
+
const recordedByGraph = await scope.flush();
|
|
16723
|
+
if (!this.#captureEnabled) receiptRecorder.seal();
|
|
16724
|
+
return transactionOutcome(
|
|
16725
|
+
result,
|
|
16726
|
+
receiptRecorder,
|
|
16727
|
+
recordedByGraph,
|
|
16728
|
+
this.graphId
|
|
16729
|
+
);
|
|
16730
|
+
}
|
|
16731
|
+
/**
|
|
16732
|
+
* Decorates a receipt-enabled transaction `context` with `measure`.
|
|
16733
|
+
* Attribution is structural: `measure` wraps the context's *own* (already
|
|
16734
|
+
* outer-recording) collections a second time with a fresh scope recorder, so a
|
|
16735
|
+
* write through the scoped context counts in the scope and — via the inner
|
|
16736
|
+
* wrapper it delegates to — the outer receipt, while a write through the outer
|
|
16737
|
+
* `context` never reaches the scope recorder. This makes overlapping/concurrent
|
|
16738
|
+
* measures safe by construction (each holds its own scope recorder) and lets
|
|
16739
|
+
* scopes nest: the scoped context is itself decorated, so `scoped.measure(...)`
|
|
16740
|
+
* chains one more wrapper. The scoped context's `getNodeCollection` resolves
|
|
16741
|
+
* against the scope-wrapped map too, so dynamic-kind writes are attributed like
|
|
16742
|
+
* `scoped.nodes.<Kind>`. The scope receipt's `recorded` is always undefined —
|
|
16743
|
+
* the recorded instant is a whole-transaction flush concern.
|
|
16744
|
+
*/
|
|
16745
|
+
#attachMeasure(context) {
|
|
16746
|
+
const measure = async (fn) => {
|
|
16747
|
+
const scopeRecorder = createTransactionReceiptRecorder();
|
|
16748
|
+
const { nodes, edges } = wrapTransactionCollections(
|
|
16749
|
+
context.nodes,
|
|
16750
|
+
context.edges,
|
|
16751
|
+
scopeRecorder
|
|
16752
|
+
);
|
|
16753
|
+
const scoped = this.#attachMeasure({
|
|
16754
|
+
...context,
|
|
16755
|
+
nodes,
|
|
16756
|
+
edges,
|
|
16757
|
+
getNodeCollection: (kind) => this.#resolveDynamicNodeCollection(nodes, kind)
|
|
16758
|
+
});
|
|
16759
|
+
const result = await fn(scoped);
|
|
16760
|
+
return { result, receipt: scopeRecorder.snapshot() };
|
|
16761
|
+
};
|
|
16762
|
+
return { ...context, measure };
|
|
14586
16763
|
}
|
|
14587
16764
|
/**
|
|
14588
16765
|
* Builds the `{ nodes, edges, sql }` projection bound to a
|
|
@@ -14594,7 +16771,7 @@ var Store = class _Store {
|
|
|
14594
16771
|
* (#140); when history capture is enabled it is replaced with a fail-loud
|
|
14595
16772
|
* guard because raw graph writes would bypass recorded-time capture.
|
|
14596
16773
|
*/
|
|
14597
|
-
#buildTransactionContext(txBackend,
|
|
16774
|
+
#buildTransactionContext(txBackend, sql19, runHooks = this.#immediateHookRunner(), receiptRecorder) {
|
|
14598
16775
|
const txNodeOperations = {
|
|
14599
16776
|
...this.#buildNodeOperations(this.#createNodeOperationContext(runHooks)),
|
|
14600
16777
|
createQuery: () => this.#createQueryForBackend(txBackend),
|
|
@@ -14628,24 +16805,20 @@ var Store = class _Store {
|
|
|
14628
16805
|
));
|
|
14629
16806
|
}
|
|
14630
16807
|
const getNodeCollection = (kind) => this.#resolveDynamicNodeCollection(nodes, kind);
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
nodes,
|
|
14634
|
-
edges,
|
|
14635
|
-
backend: txBackend,
|
|
14636
|
-
getNodeCollection,
|
|
14637
|
-
runNodeOperationHooks
|
|
14638
|
-
};
|
|
14639
|
-
}
|
|
14640
|
-
const exposedSql = this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql17;
|
|
14641
|
-
return {
|
|
16808
|
+
const sqlAvailability = this.#captureEnabled ? "history" : this.#revisionTrackingEnabled ? "revisionTracking" : sql19 === void 0 ? "unavailable" : "available";
|
|
16809
|
+
const base = {
|
|
14642
16810
|
nodes,
|
|
14643
16811
|
edges,
|
|
14644
|
-
|
|
16812
|
+
sqlAvailability,
|
|
14645
16813
|
backend: txBackend,
|
|
14646
16814
|
getNodeCollection,
|
|
14647
16815
|
runNodeOperationHooks
|
|
14648
16816
|
};
|
|
16817
|
+
const withSql = sql19 === void 0 ? base : {
|
|
16818
|
+
...base,
|
|
16819
|
+
sql: this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql19
|
|
16820
|
+
};
|
|
16821
|
+
return receiptRecorder === void 0 ? withSql : this.#attachMeasure(withSql);
|
|
14649
16822
|
}
|
|
14650
16823
|
// === Graph Lifecycle ===
|
|
14651
16824
|
/**
|
|
@@ -14834,6 +17007,7 @@ var Store = class _Store {
|
|
|
14834
17007
|
merged,
|
|
14835
17008
|
activeRow.version
|
|
14836
17009
|
);
|
|
17010
|
+
await materializeVectorContributions(this.#backend, merged);
|
|
14837
17011
|
const evolved = this.#cloneWithGraph(
|
|
14838
17012
|
merged,
|
|
14839
17013
|
options?.ref,
|
|
@@ -14936,6 +17110,29 @@ var Store = class _Store {
|
|
|
14936
17110
|
options ?? {}
|
|
14937
17111
|
);
|
|
14938
17112
|
}
|
|
17113
|
+
/**
|
|
17114
|
+
* Materializes TypeGraph's own base-relation indexes
|
|
17115
|
+
* (`SYSTEM_INDEX_DECLARATIONS`) against the live database.
|
|
17116
|
+
*
|
|
17117
|
+
* Bootstrap DDL runs only on first boot, so a system index shipped in a
|
|
17118
|
+
* newer library version never reaches an already-initialized database on
|
|
17119
|
+
* its own. `createStoreWithSchema` runs this automatically; deployments
|
|
17120
|
+
* that boot with plain `createStore` / `createVerifiedStore` (zero-DDL by
|
|
17121
|
+
* contract) adopt new system indexes by calling this once under a role
|
|
17122
|
+
* that may run DDL. Same status-table, drift-signature, and Postgres
|
|
17123
|
+
* `CREATE INDEX CONCURRENTLY` claim semantics as `materializeIndexes`.
|
|
17124
|
+
*/
|
|
17125
|
+
async materializeSystemIndexes(options) {
|
|
17126
|
+
const { activeRow } = await this.#loadCaughtUp("materialize");
|
|
17127
|
+
return materializeSystemIndexes(
|
|
17128
|
+
{
|
|
17129
|
+
backend: this.#baseBackend,
|
|
17130
|
+
graphId: this.graphId,
|
|
17131
|
+
schemaVersion: activeRow.version
|
|
17132
|
+
},
|
|
17133
|
+
options ?? {}
|
|
17134
|
+
);
|
|
17135
|
+
}
|
|
14939
17136
|
/**
|
|
14940
17137
|
* Recreate a vector field's per-field storage at its current declared
|
|
14941
17138
|
* dimension, then optionally re-embed existing rows.
|
|
@@ -14992,10 +17189,14 @@ var Store = class _Store {
|
|
|
14992
17189
|
for (const ddl of strategy.buildDropStorage(slot)) {
|
|
14993
17190
|
await backend.executeDdl(ddl);
|
|
14994
17191
|
}
|
|
14995
|
-
|
|
14996
|
-
for (const
|
|
14997
|
-
|
|
17192
|
+
if (backend.ensureVectorSlotContribution === void 0) {
|
|
17193
|
+
for (const contribution of strategy.ownedTables(slot)) {
|
|
17194
|
+
for (const ddl of contribution.createDdl) {
|
|
17195
|
+
await backend.executeDdl(ddl);
|
|
17196
|
+
}
|
|
14998
17197
|
}
|
|
17198
|
+
} else {
|
|
17199
|
+
await backend.ensureVectorSlotContribution(slot, { force: true });
|
|
14999
17200
|
}
|
|
15000
17201
|
const declaredIndexMaterializes = declaration.indexType !== "none" && backend.capabilities.vector?.indexTypes.includes(
|
|
15001
17202
|
declaration.indexType
|
|
@@ -15477,6 +17678,20 @@ async function materializeRuntimeContributions(backend, graphId) {
|
|
|
15477
17678
|
}
|
|
15478
17679
|
await backend.ensureFulltextTable?.(graphId);
|
|
15479
17680
|
}
|
|
17681
|
+
async function materializeVectorContributions(backend, graph) {
|
|
17682
|
+
if (backend.capabilities.vector?.supported !== true) return;
|
|
17683
|
+
const slots = resolveGraphVectorSlots(graph);
|
|
17684
|
+
const ensureVectorSlotContributions = backend.ensureVectorSlotContributions;
|
|
17685
|
+
if (ensureVectorSlotContributions !== void 0) {
|
|
17686
|
+
await ensureVectorSlotContributions(slots, { onDrift: "skip" });
|
|
17687
|
+
return;
|
|
17688
|
+
}
|
|
17689
|
+
const ensureVectorSlotContribution = backend.ensureVectorSlotContribution;
|
|
17690
|
+
if (ensureVectorSlotContribution === void 0) return;
|
|
17691
|
+
for (const slot of slots) {
|
|
17692
|
+
await ensureVectorSlotContribution(slot, { onDrift: "skip" });
|
|
17693
|
+
}
|
|
17694
|
+
}
|
|
15480
17695
|
async function createStoreWithSchema(graph, backend, options) {
|
|
15481
17696
|
const {
|
|
15482
17697
|
graph: merged,
|
|
@@ -15488,6 +17703,10 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
15488
17703
|
preloaded: { activeRow, storedSchema }
|
|
15489
17704
|
});
|
|
15490
17705
|
await materializeRuntimeContributions(backend, merged.id);
|
|
17706
|
+
await materializeVectorContributions(backend, merged);
|
|
17707
|
+
if (options?.systemIndexes !== "skip") {
|
|
17708
|
+
await materializeSystemIndexesOnBoot(backend, merged.id, result);
|
|
17709
|
+
}
|
|
15491
17710
|
const store = new Store(
|
|
15492
17711
|
merged,
|
|
15493
17712
|
backend,
|
|
@@ -15496,6 +17715,30 @@ async function createStoreWithSchema(graph, backend, options) {
|
|
|
15496
17715
|
);
|
|
15497
17716
|
return [store, result];
|
|
15498
17717
|
}
|
|
17718
|
+
async function materializeSystemIndexesOnBoot(backend, graphId, result) {
|
|
17719
|
+
if (result.status === "breaking") return;
|
|
17720
|
+
if (!backendSupportsIndexMaterialization(backend)) return;
|
|
17721
|
+
const schemaVersion = result.status === "migrated" ? result.toVersion : result.version;
|
|
17722
|
+
try {
|
|
17723
|
+
const { results } = await materializeSystemIndexes(
|
|
17724
|
+
{ backend: asRawBackend(backend), graphId, schemaVersion },
|
|
17725
|
+
{}
|
|
17726
|
+
);
|
|
17727
|
+
const failed = results.filter(
|
|
17728
|
+
(entryResult) => entryResult.status === "failed"
|
|
17729
|
+
);
|
|
17730
|
+
if (failed.length === 0) return;
|
|
17731
|
+
console.warn(
|
|
17732
|
+
`[typegraph] ${String(failed.length)} system index(es) failed to materialize at boot (${failed.map((entryResult) => entryResult.indexName).join(", ")}); the store is usable but the affected access paths fall back to scans. Retry via store.materializeSystemIndexes().`,
|
|
17733
|
+
failed[0]?.error
|
|
17734
|
+
);
|
|
17735
|
+
} catch (error) {
|
|
17736
|
+
console.warn(
|
|
17737
|
+
"[typegraph] system-index materialization failed at boot; the store is usable but new system indexes were not adopted. Retry via store.materializeSystemIndexes().",
|
|
17738
|
+
error
|
|
17739
|
+
);
|
|
17740
|
+
}
|
|
17741
|
+
}
|
|
15499
17742
|
async function createVerifiedStore(graph, backend, options) {
|
|
15500
17743
|
const {
|
|
15501
17744
|
graph: merged,
|
|
@@ -15512,5 +17755,5 @@ async function createVerifiedStore(graph, backend, options) {
|
|
|
15512
17755
|
}
|
|
15513
17756
|
|
|
15514
17757
|
export { ExecutableAggregateQuery, ExecutableQuery, MAX_EXPLICIT_RECURSIVE_DEPTH, MAX_RECURSIVE_DEPTH, PreparedQuery, QueryBuilder, RecordedStoreView, StoreSearch, StoreView, UnionableQuery, avg, composeFragments, count, countDistinct, countDistinctEdges, countEdges, createFragment, createQueryBuilder, createStore, createStoreWithSchema, createVerifiedStore, defineSubgraphProject, field, having, havingEq, havingGt, havingGte, havingLt, havingLte, limitFragment, max, min, offsetFragment, orderByFragment, sum };
|
|
15515
|
-
//# sourceMappingURL=chunk-
|
|
15516
|
-
//# sourceMappingURL=chunk-
|
|
17758
|
+
//# sourceMappingURL=chunk-552KNYQJ.js.map
|
|
17759
|
+
//# sourceMappingURL=chunk-552KNYQJ.js.map
|