@nicia-ai/typegraph 0.34.0 → 0.35.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 +29 -29
- package/dist/backend/postgres/index.d.cts +39 -7
- package/dist/backend/postgres/index.d.ts +39 -7
- package/dist/backend/postgres/index.js +16 -16
- package/dist/backend/postgres/pglite.cjs +21 -21
- package/dist/backend/postgres/pglite.d.cts +4 -4
- package/dist/backend/postgres/pglite.d.ts +4 -4
- package/dist/backend/postgres/pglite.js +16 -16
- package/dist/backend/sqlite/index.cjs +25 -26
- package/dist/backend/sqlite/index.d.cts +8 -8
- package/dist/backend/sqlite/index.d.ts +8 -8
- package/dist/backend/sqlite/index.js +13 -14
- package/dist/backend/sqlite/libsql.cjs +19 -19
- package/dist/backend/sqlite/libsql.d.cts +4 -4
- package/dist/backend/sqlite/libsql.d.ts +4 -4
- package/dist/backend/sqlite/libsql.js +15 -15
- package/dist/backend/sqlite/local.cjs +101 -21
- package/dist/backend/sqlite/local.cjs.map +1 -1
- package/dist/backend/sqlite/local.d.cts +95 -5
- package/dist/backend/sqlite/local.d.ts +95 -5
- package/dist/backend/sqlite/local.js +95 -16
- package/dist/backend/sqlite/local.js.map +1 -1
- package/dist/{chunk-ZV5N4DBG.js → chunk-3TWF3SH4.js} +300 -58
- package/dist/chunk-3TWF3SH4.js.map +1 -0
- package/dist/chunk-4JEZZJ6S.cjs +1271 -0
- package/dist/chunk-4JEZZJ6S.cjs.map +1 -0
- package/dist/{chunk-UNNPKQUG.cjs → chunk-4TOD4LBZ.cjs} +246 -155
- package/dist/chunk-4TOD4LBZ.cjs.map +1 -0
- package/dist/{chunk-QV245VMN.js → chunk-55MZDDZV.js} +100 -6
- package/dist/chunk-55MZDDZV.js.map +1 -0
- package/dist/{chunk-GQANNXXT.cjs → chunk-5A6FNS6N.cjs} +75 -20
- package/dist/chunk-5A6FNS6N.cjs.map +1 -0
- package/dist/{chunk-3Z6TW3CT.cjs → chunk-5AQIKK56.cjs} +65 -25
- package/dist/chunk-5AQIKK56.cjs.map +1 -0
- package/dist/{chunk-BVTWD66D.js → chunk-5SEGFTZX.js} +3 -3
- package/dist/{chunk-BVTWD66D.js.map → chunk-5SEGFTZX.js.map} +1 -1
- package/dist/{chunk-6GNSY7AV.js → chunk-6FR2MMS6.js} +7 -7
- package/dist/chunk-6FR2MMS6.js.map +1 -0
- package/dist/chunk-7YSP4V4R.cjs +69 -0
- package/dist/chunk-7YSP4V4R.cjs.map +1 -0
- package/dist/chunk-A42GCBGX.cjs +28 -0
- package/dist/chunk-A42GCBGX.cjs.map +1 -0
- package/dist/{chunk-RHZWOJVA.cjs → chunk-BLZDPRXT.cjs} +52 -4
- package/dist/chunk-BLZDPRXT.cjs.map +1 -0
- package/dist/chunk-BYPOGQWK.js +25 -0
- package/dist/chunk-BYPOGQWK.js.map +1 -0
- package/dist/{chunk-6HVOHZHS.cjs → chunk-CK5BVQOW.cjs} +213 -168
- package/dist/chunk-CK5BVQOW.cjs.map +1 -0
- package/dist/{chunk-JJRCLAUN.cjs → chunk-CTTSDJLY.cjs} +116 -10
- package/dist/chunk-CTTSDJLY.cjs.map +1 -0
- package/dist/chunk-CUHM5CWJ.cjs +12 -0
- package/dist/chunk-CUHM5CWJ.cjs.map +1 -0
- package/dist/{chunk-A7SZZFAG.js → chunk-CZQ3FO2Q.js} +409 -99
- package/dist/chunk-CZQ3FO2Q.js.map +1 -0
- package/dist/{chunk-2JXB3NYJ.cjs → chunk-D7ZEOS56.cjs} +25 -32
- package/dist/chunk-D7ZEOS56.cjs.map +1 -0
- package/dist/chunk-DFVLCOP6.js +10 -0
- package/dist/chunk-DFVLCOP6.js.map +1 -0
- package/dist/{chunk-UJLOZYKT.cjs → chunk-EM3CWPND.cjs} +441 -115
- package/dist/chunk-EM3CWPND.cjs.map +1 -0
- package/dist/chunk-EMF62TZG.cjs +1176 -0
- package/dist/chunk-EMF62TZG.cjs.map +1 -0
- package/dist/chunk-EOLERH7Y.cjs +40 -0
- package/dist/chunk-EOLERH7Y.cjs.map +1 -0
- package/dist/{chunk-TGJE22RM.cjs → chunk-FACJJWGF.cjs} +24 -6
- package/dist/chunk-FACJJWGF.cjs.map +1 -0
- package/dist/{chunk-HK5SATYM.cjs → chunk-FL472AMI.cjs} +16 -109
- package/dist/chunk-FL472AMI.cjs.map +1 -0
- package/dist/{chunk-WVC2Q2FD.cjs → chunk-FN35DLCN.cjs} +31 -9
- package/dist/chunk-FN35DLCN.cjs.map +1 -0
- package/dist/{chunk-KUTNI6SI.js → chunk-FPIZFYHE.js} +10 -18
- package/dist/chunk-FPIZFYHE.js.map +1 -0
- package/dist/{chunk-VCGO4V26.js → chunk-FRV5Q5IO.js} +64 -9
- package/dist/chunk-FRV5Q5IO.js.map +1 -0
- package/dist/{chunk-K5P5OZXS.js → chunk-G6Q5UM7U.js} +2 -2
- package/dist/{chunk-K5P5OZXS.js.map → chunk-G6Q5UM7U.js.map} +1 -1
- package/dist/chunk-GSQ6NPH6.cjs +100 -0
- package/dist/chunk-GSQ6NPH6.cjs.map +1 -0
- package/dist/chunk-GUR2FNB5.cjs +12 -0
- package/dist/{chunk-HMFK4BXR.cjs.map → chunk-GUR2FNB5.cjs.map} +1 -1
- package/dist/{chunk-JQGIPGVL.cjs → chunk-H7YNSMA7.cjs} +43 -2
- package/dist/chunk-H7YNSMA7.cjs.map +1 -0
- package/dist/{chunk-2MRAX3UE.js → chunk-I7ZUZKFD.js} +47 -4
- package/dist/chunk-I7ZUZKFD.js.map +1 -0
- package/dist/{chunk-3BRVBQQD.cjs → chunk-J7WFGBJR.cjs} +2445 -2367
- package/dist/chunk-J7WFGBJR.cjs.map +1 -0
- package/dist/{chunk-V3QNTB7Z.js → chunk-JBTOYDMN.js} +194 -114
- package/dist/chunk-JBTOYDMN.js.map +1 -0
- package/dist/{chunk-K2TGS3EN.js → chunk-K3PPLHY7.js} +138 -89
- package/dist/chunk-K3PPLHY7.js.map +1 -0
- package/dist/{chunk-Z5XBMDXU.js → chunk-KBGQIPRQ.js} +42 -3
- package/dist/chunk-KBGQIPRQ.js.map +1 -0
- package/dist/chunk-KMI7LYAF.js +1252 -0
- package/dist/chunk-KMI7LYAF.js.map +1 -0
- package/dist/{chunk-JIEJCTDS.cjs → chunk-LGEALRC6.cjs} +21 -4
- package/dist/chunk-LGEALRC6.cjs.map +1 -0
- package/dist/{chunk-TQOYM4LN.js → chunk-MP2JQKSG.js} +21 -5
- package/dist/chunk-MP2JQKSG.js.map +1 -0
- package/dist/{chunk-ZP5WHI4C.cjs → chunk-O7UN7OXA.cjs} +358 -116
- package/dist/chunk-O7UN7OXA.cjs.map +1 -0
- package/dist/chunk-OHMP6N2J.js +47 -0
- package/dist/chunk-OHMP6N2J.js.map +1 -0
- package/dist/{chunk-4DIBPQNJ.js → chunk-OR5ZA5WP.js} +135 -25
- package/dist/chunk-OR5ZA5WP.js.map +1 -0
- package/dist/{chunk-DEQFL35G.js → chunk-Q5TEXCTB.js} +61 -7
- package/dist/chunk-Q5TEXCTB.js.map +1 -0
- package/dist/chunk-QNKSQTKQ.js +1312 -0
- package/dist/chunk-QNKSQTKQ.js.map +1 -0
- package/dist/{chunk-ZURTTSCM.js → chunk-QOTI55H7.js} +6 -94
- package/dist/chunk-QOTI55H7.js.map +1 -0
- package/dist/{chunk-CHXTTSC6.js → chunk-QWEUYILR.js} +104 -5
- package/dist/chunk-QWEUYILR.js.map +1 -0
- package/dist/{chunk-LANHCOXB.js → chunk-RGP33ZJ5.js} +485 -83
- package/dist/chunk-RGP33ZJ5.js.map +1 -0
- package/dist/chunk-RGPG7EOJ.js +94 -0
- package/dist/chunk-RGPG7EOJ.js.map +1 -0
- package/dist/{chunk-G6SOJEDI.cjs → chunk-S55OV6PW.cjs} +15 -15
- package/dist/chunk-S55OV6PW.cjs.map +1 -0
- package/dist/{chunk-3E7SNSIA.cjs → chunk-SJDMVITW.cjs} +4 -4
- package/dist/{chunk-3E7SNSIA.cjs.map → chunk-SJDMVITW.cjs.map} +1 -1
- package/dist/{chunk-XLUNB2TZ.js → chunk-SNXVDZAJ.js} +3 -3
- package/dist/{chunk-XLUNB2TZ.js.map → chunk-SNXVDZAJ.js.map} +1 -1
- package/dist/{chunk-RBV7YCQD.js → chunk-T2WUSPDA.js} +49 -9
- package/dist/chunk-T2WUSPDA.js.map +1 -0
- package/dist/{chunk-KGC4LY5T.cjs → chunk-TCOCYN2P.cjs} +75 -21
- package/dist/chunk-TCOCYN2P.cjs.map +1 -0
- package/dist/{chunk-FLRCVI2G.cjs → chunk-TDXIBCGY.cjs} +2 -2
- package/dist/{chunk-FLRCVI2G.cjs.map → chunk-TDXIBCGY.cjs.map} +1 -1
- package/dist/{chunk-ZY4GXIVN.cjs → chunk-TEDRJXOG.cjs} +5 -5
- package/dist/{chunk-ZY4GXIVN.cjs.map → chunk-TEDRJXOG.cjs.map} +1 -1
- package/dist/{chunk-WLRIWYHQ.js → chunk-UES55P7G.js} +1863 -1785
- package/dist/chunk-UES55P7G.js.map +1 -0
- package/dist/{chunk-WL2SG7ZV.cjs → chunk-UMDVATGY.cjs} +6 -6
- package/dist/{chunk-WL2SG7ZV.cjs.map → chunk-UMDVATGY.cjs.map} +1 -1
- package/dist/chunk-UT3S4SEN.js +31 -0
- package/dist/chunk-UT3S4SEN.js.map +1 -0
- package/dist/{chunk-RRKQAKD6.cjs → chunk-UUT7UN2N.cjs} +149 -39
- package/dist/chunk-UUT7UN2N.cjs.map +1 -0
- package/dist/{chunk-SHLY4MCM.js → chunk-UZV3AZKF.js} +21 -3
- package/dist/chunk-UZV3AZKF.js.map +1 -0
- package/dist/{chunk-CYX2JTDG.js → chunk-VHMPG6UL.js} +29 -7
- package/dist/chunk-VHMPG6UL.js.map +1 -0
- package/dist/{chunk-T6SPUYLX.cjs → chunk-VPYHY3LJ.cjs} +116 -10
- package/dist/chunk-VPYHY3LJ.cjs.map +1 -0
- package/dist/{chunk-T46GR2VY.js → chunk-VSPCZAXF.js} +3 -3
- package/dist/{chunk-T46GR2VY.js.map → chunk-VSPCZAXF.js.map} +1 -1
- package/dist/{chunk-K34OPSJK.js → chunk-W63OVYY4.js} +3 -3
- package/dist/{chunk-K34OPSJK.js.map → chunk-W63OVYY4.js.map} +1 -1
- package/dist/{chunk-RY2VIWAM.js → chunk-WDBCBWC4.js} +355 -32
- package/dist/chunk-WDBCBWC4.js.map +1 -0
- package/dist/chunk-WJT7OU7E.cjs +1334 -0
- package/dist/chunk-WJT7OU7E.cjs.map +1 -0
- package/dist/{chunk-5IQIX7LU.cjs → chunk-XOX22WHL.cjs} +476 -163
- package/dist/chunk-XOX22WHL.cjs.map +1 -0
- package/dist/{ddl-BrmPg61z.d.ts → ddl-C2rZ-Aen.d.ts} +4 -4
- package/dist/{ddl-j1-sH6lE.d.cts → ddl-CZz7a0E8.d.cts} +4 -4
- package/dist/{extension-types-YGFJPrzK.d.cts → extension-types-ixRpJVUp.d.cts} +24 -1
- package/dist/{extension-types-YGFJPrzK.d.ts → extension-types-ixRpJVUp.d.ts} +24 -1
- package/dist/{types-DkNnF-7K.d.cts → fulltext-strategy-B57yvC5Q.d.ts} +2562 -2133
- package/dist/{types-D1Sqeyxy.d.ts → fulltext-strategy-CDkSSYhq.d.cts} +2562 -2133
- package/dist/graph-extension/index.cjs +20 -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 +5 -4
- package/dist/graph-merge/index.cjs +255 -164
- package/dist/graph-merge/index.cjs.map +1 -1
- package/dist/graph-merge/index.d.cts +63 -33
- package/dist/graph-merge/index.d.ts +63 -33
- package/dist/graph-merge/index.js +135 -44
- package/dist/graph-merge/index.js.map +1 -1
- package/dist/{index-DGX6D9j1.d.ts → index-6CSUAq-x.d.ts} +234 -93
- package/dist/{index-SEvUHDA6.d.cts → index-Bks_3HHK.d.cts} +234 -93
- package/dist/index.cjs +208 -188
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +33 -33
- package/dist/indexes/index.cjs +19 -20
- package/dist/indexes/index.d.cts +5 -5
- package/dist/indexes/index.d.ts +5 -5
- package/dist/indexes/index.js +10 -11
- package/dist/interchange/index.cjs +47 -26
- package/dist/interchange/index.d.cts +126 -12
- package/dist/interchange/index.d.ts +126 -12
- package/dist/interchange/index.js +14 -13
- package/dist/{manager-ms9cKdc6.d.cts → manager-DwQi8eV0.d.cts} +9 -2
- package/dist/{manager-BZUeN_BU.d.ts → manager-ejUCB52T.d.ts} +9 -2
- package/dist/{materialize-indexes-D20xNluI.d.cts → materialize-indexes-B4e9360H.d.cts} +20 -4
- package/dist/{materialize-indexes-CAkIYFV-.d.ts → materialize-indexes-te9TPMMJ.d.ts} +20 -4
- package/dist/{postgres-BhpxGMC5.d.cts → postgres-CfEqsRMp.d.cts} +152 -1
- package/dist/{postgres-CWHLwNl1.d.ts → postgres-DQ5R6rzV.d.ts} +152 -1
- package/dist/profiler/index.cjs +11 -11
- package/dist/profiler/index.d.cts +7 -7
- package/dist/profiler/index.d.ts +7 -7
- package/dist/profiler/index.js +4 -4
- package/dist/provenance/index.cjs +40 -41
- 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 +11 -12
- package/dist/provenance/index.js.map +1 -1
- package/dist/schema/index.cjs +53 -40
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +57 -57
- package/dist/schema/index.d.ts +57 -57
- package/dist/schema/index.js +28 -15
- package/dist/schema/index.js.map +1 -1
- package/dist/{sqlite-XrDxNaa7.d.cts → sqlite-D7zBinNR.d.cts} +92 -1
- package/dist/{sqlite-CGu5SLsw.d.ts → sqlite-DvQndLky.d.ts} +92 -1
- package/dist/{store-view-D09po4H6.d.cts → store-view-BSpqvAxK.d.cts} +415 -44
- package/dist/{store-view-BlgnhEYf.d.ts → store-view-CKbHFRnn.d.ts} +415 -44
- package/dist/{types-DiE1AO7p.d.cts → types-Bv9DcS_E.d.cts} +2 -2
- package/dist/{types-B1TbZJYS.d.ts → types-CoxuV7EJ.d.ts} +2 -2
- package/dist/{where-DKjuofPr.d.cts → where-7RKFOsXt.d.ts} +2 -2
- package/dist/{where-DrUIIWHb.d.ts → where-DdMIXOtH.d.cts} +2 -2
- package/package.json +14 -13
- package/dist/chunk-2EBMPJBO.js +0 -16
- package/dist/chunk-2EBMPJBO.js.map +0 -1
- package/dist/chunk-2JXB3NYJ.cjs.map +0 -1
- package/dist/chunk-2MRAX3UE.js.map +0 -1
- package/dist/chunk-3BRVBQQD.cjs.map +0 -1
- package/dist/chunk-3EUKJEQ3.js +0 -77
- package/dist/chunk-3EUKJEQ3.js.map +0 -1
- package/dist/chunk-3Z6TW3CT.cjs.map +0 -1
- package/dist/chunk-4DIBPQNJ.js.map +0 -1
- package/dist/chunk-4S2FXGBJ.js +0 -9
- package/dist/chunk-4S2FXGBJ.js.map +0 -1
- package/dist/chunk-5IQIX7LU.cjs.map +0 -1
- package/dist/chunk-6GNSY7AV.js.map +0 -1
- package/dist/chunk-6HVOHZHS.cjs.map +0 -1
- package/dist/chunk-6SCVW4AS.js +0 -20
- package/dist/chunk-6SCVW4AS.js.map +0 -1
- package/dist/chunk-A7SZZFAG.js.map +0 -1
- package/dist/chunk-CHXTTSC6.js.map +0 -1
- package/dist/chunk-CYX2JTDG.js.map +0 -1
- package/dist/chunk-DEQFL35G.js.map +0 -1
- package/dist/chunk-G6SOJEDI.cjs.map +0 -1
- package/dist/chunk-GQANNXXT.cjs.map +0 -1
- package/dist/chunk-GZCNZ7QW.js +0 -24
- package/dist/chunk-GZCNZ7QW.js.map +0 -1
- package/dist/chunk-HK5SATYM.cjs.map +0 -1
- package/dist/chunk-HMFK4BXR.cjs +0 -12
- package/dist/chunk-HTGDWIDE.cjs +0 -89
- package/dist/chunk-HTGDWIDE.cjs.map +0 -1
- package/dist/chunk-JIEJCTDS.cjs.map +0 -1
- package/dist/chunk-JJRCLAUN.cjs.map +0 -1
- package/dist/chunk-JQGIPGVL.cjs.map +0 -1
- package/dist/chunk-K2TGS3EN.js.map +0 -1
- package/dist/chunk-KGC4LY5T.cjs.map +0 -1
- package/dist/chunk-KNHYNQQ3.cjs +0 -663
- package/dist/chunk-KNHYNQQ3.cjs.map +0 -1
- package/dist/chunk-KUTNI6SI.js.map +0 -1
- package/dist/chunk-LANHCOXB.js.map +0 -1
- package/dist/chunk-N6WTUSBV.cjs +0 -23
- package/dist/chunk-N6WTUSBV.cjs.map +0 -1
- package/dist/chunk-NAUA4PW5.cjs +0 -28
- package/dist/chunk-NAUA4PW5.cjs.map +0 -1
- package/dist/chunk-QV245VMN.js.map +0 -1
- package/dist/chunk-RBV7YCQD.js.map +0 -1
- package/dist/chunk-RHZWOJVA.cjs.map +0 -1
- package/dist/chunk-RRKQAKD6.cjs.map +0 -1
- package/dist/chunk-RY2VIWAM.js.map +0 -1
- package/dist/chunk-SHLY4MCM.js.map +0 -1
- package/dist/chunk-T2DWPQNE.js +0 -649
- package/dist/chunk-T2DWPQNE.js.map +0 -1
- package/dist/chunk-T6SPUYLX.cjs.map +0 -1
- package/dist/chunk-TGJE22RM.cjs.map +0 -1
- package/dist/chunk-TOTPVGOG.cjs +0 -774
- package/dist/chunk-TOTPVGOG.cjs.map +0 -1
- package/dist/chunk-TQOYM4LN.js.map +0 -1
- package/dist/chunk-UJLOZYKT.cjs.map +0 -1
- package/dist/chunk-UNNPKQUG.cjs.map +0 -1
- package/dist/chunk-V3QNTB7Z.js.map +0 -1
- package/dist/chunk-VCGO4V26.js.map +0 -1
- package/dist/chunk-WLRIWYHQ.js.map +0 -1
- package/dist/chunk-WVC2Q2FD.cjs.map +0 -1
- package/dist/chunk-XXVEULWX.js +0 -39
- package/dist/chunk-XXVEULWX.js.map +0 -1
- package/dist/chunk-YAZE7QZR.cjs +0 -32
- package/dist/chunk-YAZE7QZR.cjs.map +0 -1
- package/dist/chunk-YC6PWTHY.cjs +0 -44
- package/dist/chunk-YC6PWTHY.cjs.map +0 -1
- package/dist/chunk-Z5HBYM7U.cjs +0 -11
- package/dist/chunk-Z5HBYM7U.cjs.map +0 -1
- package/dist/chunk-Z5XBMDXU.js.map +0 -1
- package/dist/chunk-ZP5WHI4C.cjs.map +0 -1
- package/dist/chunk-ZURTTSCM.js.map +0 -1
- package/dist/chunk-ZV5N4DBG.js.map +0 -1
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { quoteIdentifier, EMPTY_REQUIRED_COLUMNS, addRequiredColumn,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, computeClosuresFromOntology, createEmptyClosures, KindRegistry, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, validateNodeProps, validateEdgeProps } from './chunk-K2TGS3EN.js';
|
|
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-55MZDDZV.js';
|
|
2
|
+
import { rowToNode, executeNodeBulkFindByIndex, executeNodeBulkFindByConstraint, executeNodeFindByConstraint, executeNodeBulkGetOrCreateByConstraint, executeNodeGetOrCreateByConstraint, executeNodeHardDelete, executeNodeDelete, executeNodeUpsertUpdate, executeNodeUpdate, executeNodeCreateNoReturnBatch, executeNodeCreateBatch, executeNodeCreate, rowToEdge, runInWriteTransaction, checkCardinalityConstraint, runHookedWriteOperation, rowToNodeMeta, rowToEdgeMeta } from './chunk-QNKSQTKQ.js';
|
|
3
|
+
import { loadAndMergeGraphExtensionDocument, ensureSchema, loadAndVerifyGraph, buildKindRegistry, mergeGraphExtension, commitNewSchemaVersion, applyDeprecatedKinds, loadActiveSchemaWithBootstrap, parseSerializedSchema, extensionKindNames, serializeSchemaProperties, buildGraphExtensionOntologyKeySet, compileTimeOntologyKey, getTypeName, serializeIndexDeclaration } from './chunk-K3PPLHY7.js';
|
|
5
4
|
import { sortedReplacer, canonicalEqual } from './chunk-UR7W5YQK.js';
|
|
6
|
-
import { isKnownKind,
|
|
7
|
-
import { generateIndexDDL } from './chunk-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { createSchemaIntrospector } from './chunk-
|
|
12
|
-
import { IncompatibleChangeError,
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
5
|
+
import { isKnownKind, validateEdgeProps } from './chunk-QWEUYILR.js';
|
|
6
|
+
import { generateIndexDDL } from './chunk-UZV3AZKF.js';
|
|
7
|
+
import { NODE_META_KEYS, EDGE_META_KEYS } from './chunk-OR5ZA5WP.js';
|
|
8
|
+
import { sha256Hex, fnv1aBase36 } from './chunk-W63OVYY4.js';
|
|
9
|
+
import { getDialect, assertVectorMinScore, vectorScoreExpression, vectorMinScoreCondition } from './chunk-FPIZFYHE.js';
|
|
10
|
+
import { createSchemaIntrospector } from './chunk-5SEGFTZX.js';
|
|
11
|
+
import { IncompatibleChangeError, RemoveCompileTimeKindError, KindHasReferentsError } from './chunk-QOTI55H7.js';
|
|
12
|
+
import { validateProjectionField } from './chunk-RGPG7EOJ.js';
|
|
13
|
+
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, assertValidRecordedInstant, validateEdgeEndpoints, recordedBindParamBudget, closeRecordedHardDeletedKind, ALIAS_CTE_PREFIX, EMBEDDINGS_CTE_ALIAS, FULLTEXT_CTE_ALIAS, HYBRID_CANDIDATES_CTE_ALIAS } from './chunk-CZQ3FO2Q.js';
|
|
15
|
+
import { markAnnIndexScan, asCompiledSelectSql, isRawExecutable, annIndexScanTypes, asCompiledRowsSql, markForceCustomPlan, asCompiledStatementSql } from './chunk-UT3S4SEN.js';
|
|
16
|
+
import { isParameterRef, fieldRef, buildFieldBuilderForTypeInfo, stringField, createFulltextAccessor, CURRENT_READ_INSTANT_PLACEHOLDER, withPinnedReadInstant, runOptionallyInTransaction, baseField, objectField, arrayField, dateField, numberField, rowPropsToObject, createBackendOverlay, compileKindFilter, currentReadInstant, compileTemporalFilter, sqlValueList, chunk, buildPredicateIndex, compileFieldValue, extractVectorSimilarityPredicates, extractFulltextMatchPredicates, currentReadInstantFor, extractTemporalOptions, compilePredicateExpression, getPredicatesForAlias, getNodeKindsForAlias, getHybridTargetAlias, compilePredicateClauses } from './chunk-JBTOYDMN.js';
|
|
17
|
+
import { withRecordedRelationsPrecondition, nowIso, validateOptionalCanonicalIsoDate, isMissingTableError } from './chunk-I7ZUZKFD.js';
|
|
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-KBGQIPRQ.js';
|
|
20
|
+
import { ConfigurationError, UnsupportedPredicateError, ValidationError, KindNotFoundError, EndpointError, EagerMaterializationError, CompilerInvariantError, CardinalityError, EdgeNotFoundError, DatabaseOperationError, EndpointNotFoundError } from './chunk-MP2JQKSG.js';
|
|
21
|
+
import { jsonPointer, parseJsonPointer, resolveJsonPointer } from './chunk-G6Q5UM7U.js';
|
|
22
22
|
import { Placeholder, sql } from 'drizzle-orm';
|
|
23
23
|
|
|
24
24
|
// src/store/collection-surface.ts
|
|
@@ -27,11 +27,38 @@ var CURRENT_ONLY_READ_NAMES = [
|
|
|
27
27
|
"bulkFindByConstraint",
|
|
28
28
|
"bulkFindByIndex"
|
|
29
29
|
];
|
|
30
|
+
var NODE_WRITE_NAMES = [
|
|
31
|
+
"create",
|
|
32
|
+
"createFromRecord",
|
|
33
|
+
"update",
|
|
34
|
+
"delete",
|
|
35
|
+
"hardDelete",
|
|
36
|
+
"upsertById",
|
|
37
|
+
"upsertByIdFromRecord",
|
|
38
|
+
"bulkCreate",
|
|
39
|
+
"bulkUpsertById",
|
|
40
|
+
"bulkInsert",
|
|
41
|
+
"bulkDelete",
|
|
42
|
+
"getOrCreateByConstraint",
|
|
43
|
+
"bulkGetOrCreateByConstraint"
|
|
44
|
+
];
|
|
30
45
|
var EDGE_BATCH_READ_NAMES = [
|
|
31
46
|
"batchFindFrom",
|
|
32
47
|
"batchFindTo",
|
|
33
48
|
"batchFindByEndpoints"
|
|
34
49
|
];
|
|
50
|
+
var EDGE_WRITE_NAMES = [
|
|
51
|
+
"create",
|
|
52
|
+
"update",
|
|
53
|
+
"delete",
|
|
54
|
+
"hardDelete",
|
|
55
|
+
"bulkCreate",
|
|
56
|
+
"bulkUpsertById",
|
|
57
|
+
"bulkInsert",
|
|
58
|
+
"bulkDelete",
|
|
59
|
+
"getOrCreateByEndpoints",
|
|
60
|
+
"bulkGetOrCreateByEndpoints"
|
|
61
|
+
];
|
|
35
62
|
|
|
36
63
|
// src/store/recorded-coordinate-guard.ts
|
|
37
64
|
function hasOwnRecordedAsOf(options) {
|
|
@@ -211,9 +238,6 @@ function pinnedNodeCollection(live, coordinate) {
|
|
|
211
238
|
const reads = {
|
|
212
239
|
getById: (id) => live.getById(id, temporal),
|
|
213
240
|
getByIds: (ids) => live.getByIds(ids, temporal),
|
|
214
|
-
// `live.find` is the NodeCollection read, not Array#find — the
|
|
215
|
-
// (filter, temporal) shape trips the array-method heuristics.
|
|
216
|
-
// eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument
|
|
217
241
|
find: (filter) => live.find(filter, temporal),
|
|
218
242
|
count: () => live.count(temporal)
|
|
219
243
|
};
|
|
@@ -229,9 +253,6 @@ function pinnedEdgeCollection(live, coordinate) {
|
|
|
229
253
|
const reads = {
|
|
230
254
|
getById: (id) => live.getById(id, temporal),
|
|
231
255
|
getByIds: (ids) => live.getByIds(ids, temporal),
|
|
232
|
-
// `live.find` is the EdgeCollection read, not Array#find — the
|
|
233
|
-
// (filter, temporal) shape trips the array-method heuristics.
|
|
234
|
-
// eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument
|
|
235
256
|
find: (filter) => live.find(filter, temporal),
|
|
236
257
|
count: (filter) => live.count(filter, temporal),
|
|
237
258
|
findFrom: (from) => live.findFrom(from, temporal),
|
|
@@ -885,6 +906,29 @@ function emitStandardQuerySql(input) {
|
|
|
885
906
|
return sql.join(parts, sql` `);
|
|
886
907
|
}
|
|
887
908
|
|
|
909
|
+
// src/utils/identifier.ts
|
|
910
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
911
|
+
function truncateToBytes(value, maxBytes) {
|
|
912
|
+
const encoded = TEXT_ENCODER.encode(value);
|
|
913
|
+
if (encoded.byteLength <= maxBytes) return value;
|
|
914
|
+
let end = maxBytes;
|
|
915
|
+
while (end > 0 && encoded[end] >= 128 && encoded[end] < 192) {
|
|
916
|
+
end--;
|
|
917
|
+
}
|
|
918
|
+
return new TextDecoder().decode(encoded.slice(0, end));
|
|
919
|
+
}
|
|
920
|
+
function boundPgIdentifier(name, hashInput) {
|
|
921
|
+
if (TEXT_ENCODER.encode(name).byteLength <= MAX_PG_IDENTIFIER_LENGTH) {
|
|
922
|
+
return name;
|
|
923
|
+
}
|
|
924
|
+
const hash = fnv1aBase36(hashInput);
|
|
925
|
+
const truncated = truncateToBytes(
|
|
926
|
+
name,
|
|
927
|
+
MAX_PG_IDENTIFIER_LENGTH - 1 - hash.length
|
|
928
|
+
);
|
|
929
|
+
return `${truncated}_${hash}`;
|
|
930
|
+
}
|
|
931
|
+
|
|
888
932
|
// src/query/ast.ts
|
|
889
933
|
var DEFAULT_RRF_K = 60;
|
|
890
934
|
var DEFAULT_RRF_WEIGHT = 1;
|
|
@@ -926,6 +970,14 @@ function compileTypedJsonExtract(input) {
|
|
|
926
970
|
}
|
|
927
971
|
}
|
|
928
972
|
}
|
|
973
|
+
function compileSelectivePropsExtraction(field2, column, dialect) {
|
|
974
|
+
return compileTypedJsonExtract({
|
|
975
|
+
column,
|
|
976
|
+
dialect,
|
|
977
|
+
pointer: jsonPointer([field2.field]),
|
|
978
|
+
valueType: field2.valueType
|
|
979
|
+
});
|
|
980
|
+
}
|
|
929
981
|
|
|
930
982
|
// src/query/compiler/emitter/standard-builders.ts
|
|
931
983
|
function compileColumnReference(tableAlias, column) {
|
|
@@ -938,6 +990,11 @@ function qualifyColumn(owner, name) {
|
|
|
938
990
|
return sql`${quoteIdentifier(owner)}.${quoteIdentifier(name)}`;
|
|
939
991
|
}
|
|
940
992
|
var SCOPED_RELEVANCE_NODES_ALIAS = "scoped_relevance_nodes";
|
|
993
|
+
function selectivePropsCteColumnName(field2) {
|
|
994
|
+
const { alias, field: fieldName } = field2;
|
|
995
|
+
const encoded = `__tg_${alias.length}:${alias}:${fieldName.length}:${fieldName}`;
|
|
996
|
+
return boundPgIdentifier(encoded, `${alias}\0${fieldName}`);
|
|
997
|
+
}
|
|
941
998
|
function buildScopedNodeIdsSubquery(nodeAlias) {
|
|
942
999
|
const cteAlias = `${ALIAS_CTE_PREFIX}${nodeAlias}`;
|
|
943
1000
|
return sql`
|
|
@@ -949,19 +1006,52 @@ function buildScopedNodeIdsSubquery(nodeAlias) {
|
|
|
949
1006
|
) AS ${sql.raw(SCOPED_RELEVANCE_NODES_ALIAS)}
|
|
950
1007
|
`;
|
|
951
1008
|
}
|
|
952
|
-
function
|
|
953
|
-
|
|
1009
|
+
function compileSelectivePropsSelectColumns(input) {
|
|
1010
|
+
const { alias, dialect, selectiveFields, tableAlias } = input;
|
|
1011
|
+
const propsFields = selectiveFields?.filter(
|
|
1012
|
+
(field2) => !field2.isSystemField && field2.alias === alias
|
|
1013
|
+
) ?? [];
|
|
1014
|
+
if (propsFields.length === 0) return [];
|
|
1015
|
+
const propsColumn = compileColumnReference(tableAlias, "props");
|
|
1016
|
+
return propsFields.map((field2) => {
|
|
1017
|
+
const extracted = compileSelectivePropsExtraction(
|
|
1018
|
+
field2,
|
|
1019
|
+
propsColumn,
|
|
1020
|
+
dialect
|
|
1021
|
+
);
|
|
1022
|
+
return sql`${extracted} AS ${quoteIdentifier(selectivePropsCteColumnName(field2))}`;
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
function appendSelectivePropsColumns(baseColumns, input) {
|
|
1026
|
+
return [...baseColumns, ...compileSelectivePropsSelectColumns(input)];
|
|
1027
|
+
}
|
|
1028
|
+
function compileNodeSelectColumns(input) {
|
|
1029
|
+
const { alias, dialect, requiredColumns, selectiveFields, tableAlias } = input;
|
|
1030
|
+
const baseColumns = NODE_COLUMNS.filter(
|
|
954
1031
|
(column) => column === "id" || column === "kind" || shouldProjectColumn(requiredColumns, column)
|
|
955
1032
|
).map(
|
|
956
1033
|
(column) => sql`${compileColumnReference(tableAlias, column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
957
1034
|
);
|
|
1035
|
+
return appendSelectivePropsColumns(baseColumns, {
|
|
1036
|
+
alias,
|
|
1037
|
+
dialect,
|
|
1038
|
+
selectiveFields,
|
|
1039
|
+
tableAlias
|
|
1040
|
+
});
|
|
958
1041
|
}
|
|
959
|
-
function compileEdgeSelectColumns(
|
|
960
|
-
|
|
1042
|
+
function compileEdgeSelectColumns(input) {
|
|
1043
|
+
const { alias, dialect, requiredColumns, selectiveFields, tableAlias } = input;
|
|
1044
|
+
const baseColumns = EDGE_COLUMNS.filter(
|
|
961
1045
|
(column) => shouldProjectColumn(requiredColumns, column)
|
|
962
1046
|
).map(
|
|
963
1047
|
(column) => sql`${compileColumnReference(tableAlias, column)} AS ${sql.raw(`${alias}_${column}`)}`
|
|
964
1048
|
);
|
|
1049
|
+
return appendSelectivePropsColumns(baseColumns, {
|
|
1050
|
+
alias,
|
|
1051
|
+
dialect,
|
|
1052
|
+
selectiveFields,
|
|
1053
|
+
tableAlias
|
|
1054
|
+
});
|
|
965
1055
|
}
|
|
966
1056
|
function buildStandardStartCte(input) {
|
|
967
1057
|
const { ast, ctx, graphId, predicateIndex, requiredColumnsByAlias } = input;
|
|
@@ -990,7 +1080,13 @@ function buildStandardStartCte(input) {
|
|
|
990
1080
|
return sql`
|
|
991
1081
|
cte_${sql.raw(alias)} AS ${cteMaterialization}(
|
|
992
1082
|
SELECT ${sql.join(
|
|
993
|
-
compileNodeSelectColumns(
|
|
1083
|
+
compileNodeSelectColumns({
|
|
1084
|
+
alias,
|
|
1085
|
+
dialect: ctx.dialect,
|
|
1086
|
+
requiredColumns: effectiveRequiredColumns,
|
|
1087
|
+
selectiveFields: ast.selectiveFields,
|
|
1088
|
+
tableAlias: void 0
|
|
1089
|
+
}),
|
|
994
1090
|
sql`, `
|
|
995
1091
|
)}
|
|
996
1092
|
FROM ${ctx.schema.nodesTable}
|
|
@@ -1055,8 +1151,20 @@ function buildStandardTraversalCte(input) {
|
|
|
1055
1151
|
];
|
|
1056
1152
|
const selectColumns = [
|
|
1057
1153
|
...previousRowColumns,
|
|
1058
|
-
...compileEdgeSelectColumns(
|
|
1059
|
-
|
|
1154
|
+
...compileEdgeSelectColumns({
|
|
1155
|
+
alias: edgeAlias,
|
|
1156
|
+
dialect: ctx.dialect,
|
|
1157
|
+
requiredColumns: requiredEdgeColumns,
|
|
1158
|
+
selectiveFields: ast.selectiveFields,
|
|
1159
|
+
tableAlias: "e"
|
|
1160
|
+
}),
|
|
1161
|
+
...compileNodeSelectColumns({
|
|
1162
|
+
alias: nodeAlias,
|
|
1163
|
+
dialect: ctx.dialect,
|
|
1164
|
+
requiredColumns: requiredNodeColumns,
|
|
1165
|
+
selectiveFields: ast.selectiveFields,
|
|
1166
|
+
tableAlias: "n"
|
|
1167
|
+
})
|
|
1060
1168
|
];
|
|
1061
1169
|
const cteMaterialization = materializeCte ? sql`MATERIALIZED ` : ctx.dialect.capabilities.emitNotMaterializedHint ? sql`NOT MATERIALIZED ` : sql``;
|
|
1062
1170
|
function compileTraversalBranch(branch) {
|
|
@@ -1196,7 +1304,6 @@ function buildStandardProjection(input) {
|
|
|
1196
1304
|
if (ast.selectiveFields && ast.selectiveFields.length > 0) {
|
|
1197
1305
|
return compileSelectiveProjection(
|
|
1198
1306
|
ast.selectiveFields,
|
|
1199
|
-
dialect,
|
|
1200
1307
|
ast,
|
|
1201
1308
|
collapsedTraversalCteAlias
|
|
1202
1309
|
);
|
|
@@ -1221,7 +1328,7 @@ function buildAliasToCteMap(ast) {
|
|
|
1221
1328
|
}
|
|
1222
1329
|
return map;
|
|
1223
1330
|
}
|
|
1224
|
-
function compileSelectiveProjection(fields,
|
|
1331
|
+
function compileSelectiveProjection(fields, ast, collapsedTraversalCteAlias) {
|
|
1225
1332
|
const aliasToCte = buildAliasToCteMap(ast);
|
|
1226
1333
|
const columns = fields.map((field2) => {
|
|
1227
1334
|
const cteAlias = collapsedTraversalCteAlias ?? aliasToCte.get(field2.alias) ?? `cte_${field2.alias}`;
|
|
@@ -1229,19 +1336,20 @@ function compileSelectiveProjection(fields, dialect, ast, collapsedTraversalCteA
|
|
|
1229
1336
|
const dbColumn = mapSelectiveSystemFieldToColumn(field2.field);
|
|
1230
1337
|
return sql`${sql.raw(cteAlias)}.${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1231
1338
|
}
|
|
1232
|
-
|
|
1233
|
-
const column = sql`${sql.raw(cteAlias)}.${sql.raw(propsColumn)}`;
|
|
1234
|
-
const pointer = jsonPointer([field2.field]);
|
|
1235
|
-
const extracted = compileTypedJsonExtract({
|
|
1236
|
-
column,
|
|
1237
|
-
dialect,
|
|
1238
|
-
pointer,
|
|
1239
|
-
valueType: field2.valueType
|
|
1240
|
-
});
|
|
1241
|
-
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1339
|
+
return sql`${sql.raw(cteAlias)}.${quoteIdentifier(selectivePropsCteColumnName(field2))} AS ${quoteIdentifier(field2.outputName)}`;
|
|
1242
1340
|
});
|
|
1243
1341
|
return sql.join(columns, sql`, `);
|
|
1244
1342
|
}
|
|
1343
|
+
function compileOrderFieldValue(ast, field2, dialect, cteAlias) {
|
|
1344
|
+
const selectiveField = findSelectivePropsFieldForFieldRef(
|
|
1345
|
+
ast.selectiveFields,
|
|
1346
|
+
field2
|
|
1347
|
+
);
|
|
1348
|
+
if (selectiveField !== void 0) {
|
|
1349
|
+
return sql`${sql.raw(cteAlias)}.${quoteIdentifier(selectivePropsCteColumnName(selectiveField))}`;
|
|
1350
|
+
}
|
|
1351
|
+
return compileFieldValue(field2, dialect, field2.valueType, cteAlias);
|
|
1352
|
+
}
|
|
1245
1353
|
function buildRelevanceJoins(vectorPredicate, fulltextPredicate) {
|
|
1246
1354
|
const hybridTargetAlias = getHybridTargetAlias(
|
|
1247
1355
|
vectorPredicate,
|
|
@@ -1309,23 +1417,25 @@ function buildStandardFromClause(input) {
|
|
|
1309
1417
|
}
|
|
1310
1418
|
function buildStandardOrderBy(input) {
|
|
1311
1419
|
const { ast, collapsedTraversalCteAlias, dialect } = input;
|
|
1312
|
-
|
|
1420
|
+
const fieldOrderBy = ast.orderBy ?? [];
|
|
1421
|
+
const aggregateOrderBy = ast.aggregateOrderBy ?? [];
|
|
1422
|
+
if (fieldOrderBy.length === 0 && aggregateOrderBy.length === 0) {
|
|
1313
1423
|
return void 0;
|
|
1314
1424
|
}
|
|
1315
|
-
const aliasToCte = buildAliasToCteMap(ast);
|
|
1425
|
+
const aliasToCte = fieldOrderBy.length > 0 ? buildAliasToCteMap(ast) : void 0;
|
|
1316
1426
|
const parts = [];
|
|
1317
|
-
for (const orderSpec of
|
|
1427
|
+
for (const orderSpec of fieldOrderBy) {
|
|
1318
1428
|
const valueType = orderSpec.field.valueType;
|
|
1319
1429
|
if (valueType === "array" || valueType === "object") {
|
|
1320
1430
|
throw new UnsupportedPredicateError(
|
|
1321
1431
|
"Ordering by JSON arrays or objects is not supported"
|
|
1322
1432
|
);
|
|
1323
1433
|
}
|
|
1324
|
-
const cteAlias = collapsedTraversalCteAlias ?? aliasToCte
|
|
1325
|
-
const field2 =
|
|
1434
|
+
const cteAlias = collapsedTraversalCteAlias ?? aliasToCte?.get(orderSpec.field.alias) ?? `cte_${orderSpec.field.alias}`;
|
|
1435
|
+
const field2 = compileOrderFieldValue(
|
|
1436
|
+
ast,
|
|
1326
1437
|
orderSpec.field,
|
|
1327
1438
|
dialect,
|
|
1328
|
-
valueType,
|
|
1329
1439
|
cteAlias
|
|
1330
1440
|
);
|
|
1331
1441
|
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
@@ -1336,6 +1446,15 @@ function buildStandardOrderBy(input) {
|
|
|
1336
1446
|
sql`${field2} ${direction}`
|
|
1337
1447
|
);
|
|
1338
1448
|
}
|
|
1449
|
+
for (const orderSpec of aggregateOrderBy) {
|
|
1450
|
+
const column = quoteIdentifier(orderSpec.outputName);
|
|
1451
|
+
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
1452
|
+
const nulls = orderSpec.nulls ?? (orderSpec.direction === "asc" ? "last" : "first");
|
|
1453
|
+
const nullsKeyword = sql.raw(
|
|
1454
|
+
nulls === "first" ? "NULLS FIRST" : "NULLS LAST"
|
|
1455
|
+
);
|
|
1456
|
+
parts.push(sql`${column} ${direction} ${nullsKeyword}`);
|
|
1457
|
+
}
|
|
1339
1458
|
return sql`ORDER BY ${sql.join(parts, sql`, `)}`;
|
|
1340
1459
|
}
|
|
1341
1460
|
function fieldRefKey(field2) {
|
|
@@ -1418,12 +1537,48 @@ function buildStandardEmbeddingsCte(input) {
|
|
|
1418
1537
|
);
|
|
1419
1538
|
const branches = declaringKinds.map((kind) => {
|
|
1420
1539
|
const tableName = vectorStrategy.tableName(graphId, kind, fieldPath);
|
|
1421
|
-
const
|
|
1540
|
+
const slotDescriptor = vectorSlots?.get(vectorSlotKey(kind, fieldPath));
|
|
1541
|
+
const branchMetric = metric ?? slotDescriptor?.metric ?? "cosine";
|
|
1542
|
+
const engineFormEligible = vectorPredicate.approximate === true || vectorStrategy.searchIsExact === true;
|
|
1543
|
+
const annSlot = engineFormEligible && branchMetric === slotDescriptor?.metric ? slotDescriptor : void 0;
|
|
1544
|
+
if (annSlot !== void 0) {
|
|
1545
|
+
ctx.annIndexTypes?.add(annSlot.indexType);
|
|
1546
|
+
const candidateCte = `${ALIAS_CTE_PREFIX}${field2.alias}`;
|
|
1547
|
+
const kindCandidates = sql`SELECT ${qualifyColumn(candidateCte, `${field2.alias}_id`)} FROM ${quoteIdentifier(candidateCte)} WHERE ${qualifyColumn(candidateCte, `${field2.alias}_kind`)} = ${kind}`;
|
|
1548
|
+
const annSql = vectorStrategy.buildSearch(
|
|
1549
|
+
{
|
|
1550
|
+
graphId,
|
|
1551
|
+
nodeKind: kind,
|
|
1552
|
+
fieldPath,
|
|
1553
|
+
dimensions: annSlot.dimensions,
|
|
1554
|
+
metric: branchMetric,
|
|
1555
|
+
indexType: annSlot.indexType
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
graphId,
|
|
1559
|
+
nodeKind: kind,
|
|
1560
|
+
fieldPath,
|
|
1561
|
+
queryEmbedding,
|
|
1562
|
+
metric: branchMetric,
|
|
1563
|
+
dimensions: annSlot.dimensions,
|
|
1564
|
+
indexType: annSlot.indexType,
|
|
1565
|
+
limit: vectorPredicate.limit,
|
|
1566
|
+
...minScore === void 0 ? {} : { minScore }
|
|
1567
|
+
},
|
|
1568
|
+
kindCandidates
|
|
1569
|
+
);
|
|
1570
|
+
const distanceFromScore = branchMetric === "cosine" ? sql.raw("(1.0 - score)") : sql.raw("score");
|
|
1571
|
+
return sql`
|
|
1572
|
+
SELECT node_id, ${kind} AS node_kind, ${distanceFromScore} AS distance, score
|
|
1573
|
+
FROM (${annSql}) AS tg_ann_src
|
|
1574
|
+
`;
|
|
1575
|
+
}
|
|
1422
1576
|
const distanceExpr = vectorStrategy.distanceExpression(
|
|
1423
1577
|
qualifyColumn(tableName, "embedding"),
|
|
1424
1578
|
queryEmbedding,
|
|
1425
1579
|
branchMetric
|
|
1426
1580
|
);
|
|
1581
|
+
const exactDistanceExpr = sql`(${distanceExpr} + 0.0)`;
|
|
1427
1582
|
const scoreExpr = vectorScoreExpression(distanceExpr, branchMetric);
|
|
1428
1583
|
const conditions = [
|
|
1429
1584
|
sql`${qualifyColumn(tableName, "graph_id")} = ${graphId}`
|
|
@@ -1437,7 +1592,7 @@ function buildStandardEmbeddingsCte(input) {
|
|
|
1437
1592
|
SELECT
|
|
1438
1593
|
${qualifyColumn(tableName, "node_id")} AS node_id,
|
|
1439
1594
|
${kind} AS node_kind,
|
|
1440
|
-
${
|
|
1595
|
+
${exactDistanceExpr} AS distance,
|
|
1441
1596
|
${scoreExpr} AS score
|
|
1442
1597
|
FROM ${quoteIdentifier(tableName)}
|
|
1443
1598
|
INNER JOIN ${scopedNodes}
|
|
@@ -1496,17 +1651,18 @@ function buildStandardFulltextCte(input) {
|
|
|
1496
1651
|
`Fulltext match predicates are not supported for dialect "${dialect.name}"`
|
|
1497
1652
|
);
|
|
1498
1653
|
}
|
|
1654
|
+
const effectiveLanguage = language ?? sharedDeclaredLanguage(ctx.fulltextLanguages, nodeKinds);
|
|
1499
1655
|
const matchCondition = fulltextStrategy.matchCondition(
|
|
1500
1656
|
tableName,
|
|
1501
1657
|
query,
|
|
1502
1658
|
mode,
|
|
1503
|
-
|
|
1659
|
+
effectiveLanguage
|
|
1504
1660
|
);
|
|
1505
1661
|
const rankExpression = fulltextStrategy.rankExpression(
|
|
1506
1662
|
tableName,
|
|
1507
1663
|
query,
|
|
1508
1664
|
mode,
|
|
1509
|
-
|
|
1665
|
+
effectiveLanguage
|
|
1510
1666
|
);
|
|
1511
1667
|
const conditions = [
|
|
1512
1668
|
sql`${qualifyColumn(tableName, "graph_id")} = ${graphId}`,
|
|
@@ -1541,6 +1697,16 @@ function buildStandardFulltextCte(input) {
|
|
|
1541
1697
|
)
|
|
1542
1698
|
`;
|
|
1543
1699
|
}
|
|
1700
|
+
function sharedDeclaredLanguage(fulltextLanguages, nodeKinds) {
|
|
1701
|
+
if (fulltextLanguages === void 0) return void 0;
|
|
1702
|
+
const languages = /* @__PURE__ */ new Set();
|
|
1703
|
+
for (const kind of nodeKinds) {
|
|
1704
|
+
const language = fulltextLanguages.get(kind);
|
|
1705
|
+
if (language !== void 0) languages.add(language);
|
|
1706
|
+
}
|
|
1707
|
+
if (languages.size !== 1) return void 0;
|
|
1708
|
+
return [...languages][0];
|
|
1709
|
+
}
|
|
1544
1710
|
function buildStandardHybridCandidateCte() {
|
|
1545
1711
|
return sql`
|
|
1546
1712
|
${sql.raw(HYBRID_CANDIDATES_CTE_ALIAS)} AS (
|
|
@@ -1562,10 +1728,10 @@ function compileUserOrderBy(ast, dialect) {
|
|
|
1562
1728
|
);
|
|
1563
1729
|
}
|
|
1564
1730
|
const cteAlias = aliasToCte.get(orderSpec.field.alias) ?? `${ALIAS_CTE_PREFIX}${orderSpec.field.alias}`;
|
|
1565
|
-
const field2 =
|
|
1731
|
+
const field2 = compileOrderFieldValue(
|
|
1732
|
+
ast,
|
|
1566
1733
|
orderSpec.field,
|
|
1567
1734
|
dialect,
|
|
1568
|
-
valueType,
|
|
1569
1735
|
cteAlias
|
|
1570
1736
|
);
|
|
1571
1737
|
const direction = sql.raw(orderSpec.direction.toUpperCase());
|
|
@@ -1712,51 +1878,10 @@ function runCompilerPass(state, pass) {
|
|
|
1712
1878
|
state: pass.update(state, output)
|
|
1713
1879
|
};
|
|
1714
1880
|
}
|
|
1715
|
-
function compileTemporalFilter(options) {
|
|
1716
|
-
const { mode, asOf, tableAlias, currentTimestamp, recordedAsOf } = options;
|
|
1717
|
-
const prefix = tableAlias ? sql.raw(`${tableAlias}.`) : sql.raw("");
|
|
1718
|
-
const deletedAt = sql`${prefix}deleted_at`;
|
|
1719
|
-
const validFrom = sql`${prefix}valid_from`;
|
|
1720
|
-
const validTo = sql`${prefix}valid_to`;
|
|
1721
|
-
const validFilter = (() => {
|
|
1722
|
-
switch (mode) {
|
|
1723
|
-
case "current": {
|
|
1724
|
-
const now = recordedAsOf === void 0 ? currentTimestamp ?? sql`CURRENT_TIMESTAMP` : sql`${recordedAsOf}`;
|
|
1725
|
-
return sql`${deletedAt} IS NULL AND (${validFrom} IS NULL OR ${validFrom} <= ${now}) AND (${validTo} IS NULL OR ${validTo} > ${now})`;
|
|
1726
|
-
}
|
|
1727
|
-
case "asOf": {
|
|
1728
|
-
if (asOf === void 0) {
|
|
1729
|
-
throw new Error(
|
|
1730
|
-
`asOf timestamp is required for temporal mode "asOf"`
|
|
1731
|
-
);
|
|
1732
|
-
}
|
|
1733
|
-
const timestamp = asOf;
|
|
1734
|
-
return sql`${deletedAt} IS NULL AND (${validFrom} IS NULL OR ${validFrom} <= ${timestamp}) AND (${validTo} IS NULL OR ${validTo} > ${timestamp})`;
|
|
1735
|
-
}
|
|
1736
|
-
case "includeEnded": {
|
|
1737
|
-
return sql`${deletedAt} IS NULL`;
|
|
1738
|
-
}
|
|
1739
|
-
case "includeTombstones": {
|
|
1740
|
-
return sql.raw("1=1");
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
})();
|
|
1744
|
-
if (recordedAsOf === void 0) return validFilter;
|
|
1745
|
-
const recordedFrom = sql`${prefix}recorded_from`;
|
|
1746
|
-
const recordedTo = sql`${prefix}recorded_to`;
|
|
1747
|
-
return sql`(${validFilter}) AND ${recordedFrom} <= ${recordedAsOf} AND ${recordedAsOf} < ${recordedTo}`;
|
|
1748
|
-
}
|
|
1749
|
-
function extractTemporalOptions(ast, tableAlias) {
|
|
1750
|
-
return {
|
|
1751
|
-
mode: ast.temporalMode.mode,
|
|
1752
|
-
asOf: ast.temporalMode.asOf,
|
|
1753
|
-
recordedAsOf: ast.recordedAsOf,
|
|
1754
|
-
tableAlias
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
1881
|
|
|
1758
1882
|
// src/query/compiler/passes/temporal.ts
|
|
1759
|
-
function createTemporalFilterPass(ast,
|
|
1883
|
+
function createTemporalFilterPass(ast, readInstant = "literal") {
|
|
1884
|
+
const currentTimestamp = currentReadInstantFor(readInstant);
|
|
1760
1885
|
return {
|
|
1761
1886
|
forAlias(tableAlias) {
|
|
1762
1887
|
return compileTemporalFilter({
|
|
@@ -1873,12 +1998,14 @@ function appendAggregateSortLimitAndProjectNodes(currentNode, ast, nextPlanNodeI
|
|
|
1873
1998
|
};
|
|
1874
1999
|
node = ast.having === void 0 ? aggregateNode : { ...aggregateNode, having: ast.having };
|
|
1875
2000
|
}
|
|
1876
|
-
|
|
2001
|
+
const hasFieldOrderBy = ast.orderBy !== void 0 && ast.orderBy.length > 0;
|
|
2002
|
+
const hasAggregateOrderBy = ast.aggregateOrderBy !== void 0 && ast.aggregateOrderBy.length > 0;
|
|
2003
|
+
if (hasFieldOrderBy || hasAggregateOrderBy) {
|
|
1877
2004
|
node = {
|
|
1878
2005
|
id: nextPlanNodeId(),
|
|
1879
2006
|
input: node,
|
|
1880
2007
|
op: "sort",
|
|
1881
|
-
orderBy: ast.orderBy
|
|
2008
|
+
orderBy: ast.orderBy ?? []
|
|
1882
2009
|
};
|
|
1883
2010
|
}
|
|
1884
2011
|
if (limit !== void 0 || ast.offset !== void 0) {
|
|
@@ -2237,7 +2364,7 @@ function runRecursiveQueryPassPipeline(ast, graphId, ctx) {
|
|
|
2237
2364
|
execute(currentState) {
|
|
2238
2365
|
return createTemporalFilterPass(
|
|
2239
2366
|
currentState.ast,
|
|
2240
|
-
currentState.ctx.
|
|
2367
|
+
currentState.ctx.readInstant
|
|
2241
2368
|
);
|
|
2242
2369
|
},
|
|
2243
2370
|
update(currentState, temporalFilterPass) {
|
|
@@ -2645,12 +2772,7 @@ function compileRecursiveSelectiveProjection(fields, ast, traversal, dialect) {
|
|
|
2645
2772
|
return sql`${sql.raw(`${field2.alias}_${dbColumn}`)} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2646
2773
|
}
|
|
2647
2774
|
const column = sql.raw(`${field2.alias}_props`);
|
|
2648
|
-
const extracted =
|
|
2649
|
-
column,
|
|
2650
|
-
dialect,
|
|
2651
|
-
pointer: jsonPointer([field2.field]),
|
|
2652
|
-
valueType: field2.valueType
|
|
2653
|
-
});
|
|
2775
|
+
const extracted = compileSelectivePropsExtraction(field2, column, dialect);
|
|
2654
2776
|
return sql`${extracted} AS ${quoteIdentifier(field2.outputName)}`;
|
|
2655
2777
|
});
|
|
2656
2778
|
const vl = traversal.variableLength;
|
|
@@ -2758,7 +2880,7 @@ function buildReachableCte(options) {
|
|
|
2758
2880
|
sql.raw("e.kind"),
|
|
2759
2881
|
options.edgeKinds
|
|
2760
2882
|
);
|
|
2761
|
-
const currentTimestamp =
|
|
2883
|
+
const currentTimestamp = currentReadInstant();
|
|
2762
2884
|
const nodeTemporalFilter = compileTemporalFilter({
|
|
2763
2885
|
mode: options.temporalMode,
|
|
2764
2886
|
asOf: options.asOf,
|
|
@@ -2871,66 +2993,8 @@ function buildBidirectionalBranch(options) {
|
|
|
2871
2993
|
return sql`${options.selectClause} FROM reachable r JOIN ${options.schema.edgesTable} e ON (e.from_id = r.id OR e.to_id = r.id) ${nodeJoin} WHERE ${sql.join([...options.whereClauses], sql` AND `)}`;
|
|
2872
2994
|
}
|
|
2873
2995
|
|
|
2874
|
-
// src/store/row-mappers.ts
|
|
2875
|
-
function nullToUndefined2(value) {
|
|
2876
|
-
return value === null ? void 0 : value;
|
|
2877
|
-
}
|
|
2878
|
-
function rowToNode(row) {
|
|
2879
|
-
const rawProps = JSON.parse(row.props);
|
|
2880
|
-
const props = filterReservedKeys(rawProps, RESERVED_NODE_KEYS);
|
|
2881
|
-
return {
|
|
2882
|
-
kind: row.kind,
|
|
2883
|
-
id: row.id,
|
|
2884
|
-
meta: rowToNodeMeta(row),
|
|
2885
|
-
...props
|
|
2886
|
-
};
|
|
2887
|
-
}
|
|
2888
|
-
function rowToNodeMeta(row) {
|
|
2889
|
-
return {
|
|
2890
|
-
version: row.version,
|
|
2891
|
-
validFrom: nullToUndefined2(row.valid_from),
|
|
2892
|
-
validTo: nullToUndefined2(row.valid_to),
|
|
2893
|
-
createdAt: row.created_at,
|
|
2894
|
-
updatedAt: row.updated_at,
|
|
2895
|
-
deletedAt: nullToUndefined2(row.deleted_at)
|
|
2896
|
-
};
|
|
2897
|
-
}
|
|
2898
|
-
function rowToEdge(row) {
|
|
2899
|
-
const rawProps = JSON.parse(row.props);
|
|
2900
|
-
const props = filterReservedKeys(rawProps, RESERVED_EDGE_KEYS);
|
|
2901
|
-
return {
|
|
2902
|
-
id: row.id,
|
|
2903
|
-
kind: row.kind,
|
|
2904
|
-
fromKind: row.from_kind,
|
|
2905
|
-
fromId: row.from_id,
|
|
2906
|
-
toKind: row.to_kind,
|
|
2907
|
-
toId: row.to_id,
|
|
2908
|
-
meta: rowToEdgeMeta(row),
|
|
2909
|
-
...props
|
|
2910
|
-
};
|
|
2911
|
-
}
|
|
2912
|
-
function rowToEdgeMeta(row) {
|
|
2913
|
-
return {
|
|
2914
|
-
validFrom: nullToUndefined2(row.valid_from),
|
|
2915
|
-
validTo: nullToUndefined2(row.valid_to),
|
|
2916
|
-
createdAt: row.created_at,
|
|
2917
|
-
updatedAt: row.updated_at,
|
|
2918
|
-
deletedAt: nullToUndefined2(row.deleted_at)
|
|
2919
|
-
};
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
2996
|
// src/store/subgraph.ts
|
|
2923
2997
|
var DEFAULT_SUBGRAPH_MAX_DEPTH = 10;
|
|
2924
|
-
var TEXT_ENCODER = new TextEncoder();
|
|
2925
|
-
function truncateToBytes(value, maxBytes) {
|
|
2926
|
-
const encoded = TEXT_ENCODER.encode(value);
|
|
2927
|
-
if (encoded.byteLength <= maxBytes) return value;
|
|
2928
|
-
let end = maxBytes;
|
|
2929
|
-
while (end > 0 && encoded[end] >= 128 && encoded[end] < 192) {
|
|
2930
|
-
end--;
|
|
2931
|
-
}
|
|
2932
|
-
return new TextDecoder().decode(encoded.slice(0, end));
|
|
2933
|
-
}
|
|
2934
2998
|
function projectionAlias(entityPrefix, kind, field2) {
|
|
2935
2999
|
const prefix = entityPrefix === "node" ? "sg_n" : "sg_e";
|
|
2936
3000
|
const hash = fnv1aBase36(`${kind}\0${field2}`);
|
|
@@ -3010,9 +3074,31 @@ async function executeSubgraph(params) {
|
|
|
3010
3074
|
...ctx.recordedReadBinding === void 0 ? {} : { recordedReadBinding: ctx.recordedReadBinding }
|
|
3011
3075
|
});
|
|
3012
3076
|
const includedIdsCte = buildIncludedIdsCte(ctx);
|
|
3077
|
+
let membership;
|
|
3078
|
+
if (ctx.dialect.name === "postgres") {
|
|
3079
|
+
const includedIds = await fetchIncludedIds(
|
|
3080
|
+
ctx,
|
|
3081
|
+
reachableCte,
|
|
3082
|
+
includedIdsCte
|
|
3083
|
+
);
|
|
3084
|
+
const idsArray = textArrayParam(includedIds);
|
|
3085
|
+
membership = {
|
|
3086
|
+
prefix: sql``,
|
|
3087
|
+
idFilter: (column) => sql`EXISTS (SELECT 1 FROM unnest(${idsArray}) AS tg_included(id) WHERE tg_included.id = ${column})`,
|
|
3088
|
+
parameterDependentPlan: true
|
|
3089
|
+
};
|
|
3090
|
+
} else {
|
|
3091
|
+
membership = {
|
|
3092
|
+
prefix: sql`${reachableCte}${includedIdsCte} `,
|
|
3093
|
+
// SQLite: `column IN (subquery)` evaluates via a transient index —
|
|
3094
|
+
// optimal as-is. (Postgres takes the parameterized text[] form above.)
|
|
3095
|
+
idFilter: (column) => sql`${column} IN (SELECT id FROM included_ids)`,
|
|
3096
|
+
parameterDependentPlan: false
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3013
3099
|
const [nodeRows, edgeRows] = await Promise.all([
|
|
3014
|
-
fetchSubgraphNodes(ctx,
|
|
3015
|
-
fetchSubgraphEdges(ctx,
|
|
3100
|
+
fetchSubgraphNodes(ctx, membership, nodeProjectionPlan),
|
|
3101
|
+
fetchSubgraphEdges(ctx, membership, edgeProjectionPlan)
|
|
3016
3102
|
]);
|
|
3017
3103
|
const nodesMap = /* @__PURE__ */ new Map();
|
|
3018
3104
|
for (const row of nodeRows) {
|
|
@@ -3112,13 +3198,27 @@ function buildIncludedIdsCte(ctx) {
|
|
|
3112
3198
|
const whereClause = filters.length > 0 ? sql` WHERE ${sql.join(filters, sql` AND `)}` : sql``;
|
|
3113
3199
|
return sql`, included_ids AS (SELECT DISTINCT id FROM reachable${whereClause})`;
|
|
3114
3200
|
}
|
|
3115
|
-
|
|
3201
|
+
function textArrayParam(values) {
|
|
3202
|
+
const elements = values.map(
|
|
3203
|
+
(value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', String.raw`\"`)}"`
|
|
3204
|
+
);
|
|
3205
|
+
return sql`${`{${elements.join(",")}}`}::text[]`;
|
|
3206
|
+
}
|
|
3207
|
+
async function fetchIncludedIds(ctx, reachableCte, includedIdsCte) {
|
|
3208
|
+
const rows = await ctx.backend.execute(
|
|
3209
|
+
asCompiledRowsSql(
|
|
3210
|
+
sql`${reachableCte}${includedIdsCte} SELECT id FROM included_ids`
|
|
3211
|
+
)
|
|
3212
|
+
);
|
|
3213
|
+
return rows.map((row) => row.id);
|
|
3214
|
+
}
|
|
3215
|
+
async function fetchSubgraphNodes(ctx, membership, projectionPlan) {
|
|
3116
3216
|
const nodeTemporalFilter = compileTemporalFilter({
|
|
3117
3217
|
mode: ctx.temporalMode,
|
|
3118
3218
|
asOf: ctx.asOf,
|
|
3119
3219
|
recordedAsOf: ctx.recordedAsOf,
|
|
3120
3220
|
tableAlias: "n",
|
|
3121
|
-
currentTimestamp:
|
|
3221
|
+
currentTimestamp: currentReadInstant()
|
|
3122
3222
|
});
|
|
3123
3223
|
const columns = [
|
|
3124
3224
|
sql`n.kind`,
|
|
@@ -3134,19 +3234,20 @@ async function fetchSubgraphNodes(ctx, reachableCte, includedIdsCte, projectionP
|
|
|
3134
3234
|
]),
|
|
3135
3235
|
...buildProjectedPropertyColumns("n", projectionPlan, ctx.dialect)
|
|
3136
3236
|
];
|
|
3137
|
-
const query = sql`${
|
|
3237
|
+
const query = sql`${membership.prefix}SELECT ${sql.join(columns, sql`, `)} FROM ${ctx.schema.nodesTable} n WHERE n.graph_id = ${ctx.graphId} AND ${nodeTemporalFilter} AND ${membership.idFilter(sql.raw("n.id"))}`;
|
|
3238
|
+
if (membership.parameterDependentPlan) markForceCustomPlan(query);
|
|
3138
3239
|
return ctx.backend.execute(
|
|
3139
3240
|
asCompiledRowsSql(query)
|
|
3140
3241
|
);
|
|
3141
3242
|
}
|
|
3142
|
-
async function fetchSubgraphEdges(ctx,
|
|
3243
|
+
async function fetchSubgraphEdges(ctx, membership, projectionPlan) {
|
|
3143
3244
|
const edgeKindFilter = compileKindFilter(sql.raw("e.kind"), ctx.edgeKinds);
|
|
3144
3245
|
const edgeTemporalFilter = compileTemporalFilter({
|
|
3145
3246
|
mode: ctx.temporalMode,
|
|
3146
3247
|
asOf: ctx.asOf,
|
|
3147
3248
|
recordedAsOf: ctx.recordedAsOf,
|
|
3148
3249
|
tableAlias: "e",
|
|
3149
|
-
currentTimestamp:
|
|
3250
|
+
currentTimestamp: currentReadInstant()
|
|
3150
3251
|
});
|
|
3151
3252
|
const columns = [
|
|
3152
3253
|
sql`e.id`,
|
|
@@ -3165,7 +3266,8 @@ async function fetchSubgraphEdges(ctx, reachableCte, includedIdsCte, projectionP
|
|
|
3165
3266
|
]),
|
|
3166
3267
|
...buildProjectedPropertyColumns("e", projectionPlan, ctx.dialect)
|
|
3167
3268
|
];
|
|
3168
|
-
const query = sql`${
|
|
3269
|
+
const query = sql`${membership.prefix}SELECT ${sql.join(columns, sql`, `)} FROM ${ctx.schema.edgesTable} e WHERE e.graph_id = ${ctx.graphId} AND ${edgeKindFilter} AND ${edgeTemporalFilter} AND ${membership.idFilter(sql.raw("e.from_id"))} AND ${membership.idFilter(sql.raw("e.to_id"))}`;
|
|
3270
|
+
if (membership.parameterDependentPlan) markForceCustomPlan(query);
|
|
3169
3271
|
return ctx.backend.execute(
|
|
3170
3272
|
asCompiledRowsSql(query)
|
|
3171
3273
|
);
|
|
@@ -3515,9 +3617,16 @@ function collectRequiredColumnsByAlias2(ast) {
|
|
|
3515
3617
|
for (const traversal of ast.traversals) {
|
|
3516
3618
|
addRequiredColumn(requiredColumnsByAlias, traversal.nodeAlias, "id");
|
|
3517
3619
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3620
|
+
const hasSelectiveFields = (ast.selectiveFields?.length ?? 0) > 0;
|
|
3621
|
+
if (hasSelectiveFields) {
|
|
3622
|
+
for (const field2 of ast.selectiveFields ?? []) {
|
|
3623
|
+
if (field2.isSystemField) {
|
|
3624
|
+
addRequiredColumn(
|
|
3625
|
+
requiredColumnsByAlias,
|
|
3626
|
+
field2.alias,
|
|
3627
|
+
mapSelectiveSystemFieldToColumn(field2.field)
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3521
3630
|
}
|
|
3522
3631
|
} else {
|
|
3523
3632
|
for (const projectedField of ast.projection.fields) {
|
|
@@ -3537,14 +3646,25 @@ function collectRequiredColumnsByAlias2(ast) {
|
|
|
3537
3646
|
}
|
|
3538
3647
|
if (ast.orderBy) {
|
|
3539
3648
|
for (const orderSpec of ast.orderBy) {
|
|
3649
|
+
if (findSelectivePropsFieldForFieldRef(
|
|
3650
|
+
ast.selectiveFields,
|
|
3651
|
+
orderSpec.field
|
|
3652
|
+
) !== void 0) {
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3540
3655
|
markFieldRefAsRequired(requiredColumnsByAlias, orderSpec.field);
|
|
3541
3656
|
}
|
|
3542
3657
|
}
|
|
3543
3658
|
if (ast.having) {
|
|
3544
3659
|
markPredicateFieldsAsRequired(requiredColumnsByAlias, ast.having);
|
|
3545
3660
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3661
|
+
if (!hasSelectiveFields) {
|
|
3662
|
+
for (const predicate of ast.predicates) {
|
|
3663
|
+
markPredicateFieldsAsRequired(
|
|
3664
|
+
requiredColumnsByAlias,
|
|
3665
|
+
predicate.expression
|
|
3666
|
+
);
|
|
3667
|
+
}
|
|
3548
3668
|
}
|
|
3549
3669
|
return requiredColumnsByAlias;
|
|
3550
3670
|
}
|
|
@@ -3724,10 +3844,7 @@ function runStandardQueryPassPipeline(ast, graphId, ctx) {
|
|
|
3724
3844
|
const temporalPass = runCompilerPass(state, {
|
|
3725
3845
|
name: "temporal_filters",
|
|
3726
3846
|
execute(currentState) {
|
|
3727
|
-
return createTemporalFilterPass(
|
|
3728
|
-
currentState.ast,
|
|
3729
|
-
currentState.ctx.dialect.currentTimestamp()
|
|
3730
|
-
);
|
|
3847
|
+
return createTemporalFilterPass(currentState.ast, ctx.readInstant);
|
|
3731
3848
|
},
|
|
3732
3849
|
update(currentState, temporalFilterPass) {
|
|
3733
3850
|
return {
|
|
@@ -3838,9 +3955,12 @@ function compileQuery(ast, graphId, options = "sqlite") {
|
|
|
3838
3955
|
"recorded-query"
|
|
3839
3956
|
);
|
|
3840
3957
|
const adapter = resolveDialectAdapter(dialect, options_.fulltextStrategy);
|
|
3958
|
+
const annIndexTypes = /* @__PURE__ */ new Set();
|
|
3841
3959
|
const ctx = {
|
|
3842
3960
|
dialect: adapter,
|
|
3843
3961
|
schema,
|
|
3962
|
+
annIndexTypes,
|
|
3963
|
+
readInstant: options_.readInstant ?? "literal",
|
|
3844
3964
|
compileQuery: (subAst, subGraphId) => compileQuery(
|
|
3845
3965
|
inheritRecordedAsOf(subAst, ast.recordedAsOf),
|
|
3846
3966
|
subGraphId,
|
|
@@ -3848,16 +3968,23 @@ function compileQuery(ast, graphId, options = "sqlite") {
|
|
|
3848
3968
|
),
|
|
3849
3969
|
...options_.vectorStrategy === void 0 ? {} : { vectorStrategy: options_.vectorStrategy },
|
|
3850
3970
|
windowFunctions: options_.windowFunctions ?? true,
|
|
3851
|
-
...options_.vectorSlots === void 0 ? {} : { vectorSlots: options_.vectorSlots }
|
|
3971
|
+
...options_.vectorSlots === void 0 ? {} : { vectorSlots: options_.vectorSlots },
|
|
3972
|
+
...options_.fulltextLanguages === void 0 ? {} : { fulltextLanguages: options_.fulltextLanguages }
|
|
3852
3973
|
};
|
|
3974
|
+
function finish(compiled) {
|
|
3975
|
+
if (annIndexTypes.size > 0) {
|
|
3976
|
+
markAnnIndexScan(compiled, [...annIndexTypes]);
|
|
3977
|
+
}
|
|
3978
|
+
return asCompiledSelectSql(compiled);
|
|
3979
|
+
}
|
|
3853
3980
|
if (hasVariableLengthTraversal(ast)) {
|
|
3854
3981
|
const lowered = tryLowerSingleHopRecursiveTraversal(ast);
|
|
3855
3982
|
if (lowered !== void 0) {
|
|
3856
|
-
return
|
|
3983
|
+
return finish(compileStandardQuery(lowered, graphId, ctx));
|
|
3857
3984
|
}
|
|
3858
|
-
return
|
|
3985
|
+
return finish(compileVariableLengthQuery(ast, graphId, ctx));
|
|
3859
3986
|
}
|
|
3860
|
-
return
|
|
3987
|
+
return finish(compileStandardQuery(ast, graphId, ctx));
|
|
3861
3988
|
}
|
|
3862
3989
|
function tryLowerSingleHopRecursiveTraversal(ast) {
|
|
3863
3990
|
if (ast.traversals.length !== 1) {
|
|
@@ -3884,15 +4011,21 @@ function compileSetOperation2(op, graphId, options = "sqlite") {
|
|
|
3884
4011
|
const options_ = typeof options === "string" ? { dialect: options } : options;
|
|
3885
4012
|
const dialect = options_.dialect ?? "sqlite";
|
|
3886
4013
|
const adapter = resolveDialectAdapter(dialect, options_.fulltextStrategy);
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
4014
|
+
const annIndexTypes = /* @__PURE__ */ new Set();
|
|
4015
|
+
const compileOperand = (ast, gid) => {
|
|
4016
|
+
const compiled = compileQuery(ast, gid, propagateOptions(options_));
|
|
4017
|
+
for (const indexType of annIndexScanTypes(compiled) ?? []) {
|
|
4018
|
+
annIndexTypes.add(indexType);
|
|
4019
|
+
}
|
|
4020
|
+
return compiled;
|
|
4021
|
+
};
|
|
4022
|
+
const combined = withPinnedReadInstant(
|
|
4023
|
+
() => compileSetOperation(op, graphId, adapter, compileOperand, options_.vectorStrategy)
|
|
3895
4024
|
);
|
|
4025
|
+
if (annIndexTypes.size > 0) {
|
|
4026
|
+
markAnnIndexScan(combined, [...annIndexTypes]);
|
|
4027
|
+
}
|
|
4028
|
+
return asCompiledSelectSql(combined);
|
|
3896
4029
|
}
|
|
3897
4030
|
function resolveDialectAdapter(dialect, fulltextStrategy) {
|
|
3898
4031
|
const baseAdapter = getDialect(dialect);
|
|
@@ -3935,7 +4068,9 @@ function propagateOptions(options_) {
|
|
|
3935
4068
|
...options_.vectorStrategy === void 0 ? {} : { vectorStrategy: options_.vectorStrategy },
|
|
3936
4069
|
windowFunctions: options_.windowFunctions ?? true,
|
|
3937
4070
|
...options_.vectorSlots === void 0 ? {} : { vectorSlots: options_.vectorSlots },
|
|
3938
|
-
...options_.
|
|
4071
|
+
...options_.fulltextLanguages === void 0 ? {} : { fulltextLanguages: options_.fulltextLanguages },
|
|
4072
|
+
...options_.recordedReadBinding === void 0 ? {} : { recordedReadBinding: options_.recordedReadBinding },
|
|
4073
|
+
...options_.readInstant === void 0 ? {} : { readInstant: options_.readInstant }
|
|
3939
4074
|
};
|
|
3940
4075
|
}
|
|
3941
4076
|
function resolveCountAggregateFastPath(ast) {
|
|
@@ -4097,7 +4232,7 @@ function compileCountAggregateFastPath(ast, graphId, ctx, logicalPlan, requiredC
|
|
|
4097
4232
|
sql`COUNT(DISTINCT e.id) AS ${sql.raw(edgeCountDistinctColumn)}`
|
|
4098
4233
|
);
|
|
4099
4234
|
}
|
|
4100
|
-
const startCteLimit = traversal.optional && ast.orderBy === void 0 && ast.limit !== void 0 ? { limit: ast.limit, offset: ast.offset } : void 0;
|
|
4235
|
+
const startCteLimit = traversal.optional && ast.orderBy === void 0 && ast.aggregateOrderBy === void 0 && ast.limit !== void 0 ? { limit: ast.limit, offset: ast.offset } : void 0;
|
|
4101
4236
|
const startCte = buildStandardStartCte({
|
|
4102
4237
|
ast,
|
|
4103
4238
|
ctx,
|
|
@@ -4437,9 +4572,9 @@ function transformPathColumns(rows, state, _dialect) {
|
|
|
4437
4572
|
}
|
|
4438
4573
|
return changed ? result : rows;
|
|
4439
4574
|
}
|
|
4440
|
-
var
|
|
4441
|
-
var
|
|
4442
|
-
function
|
|
4575
|
+
var RESERVED_NODE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
|
|
4576
|
+
var RESERVED_EDGE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "fromId", "toId", "meta"]);
|
|
4577
|
+
function nullToUndefined2(value) {
|
|
4443
4578
|
return value === null ? void 0 : value;
|
|
4444
4579
|
}
|
|
4445
4580
|
function assignPropsExcludingReserved(target, props, reservedKeys) {
|
|
@@ -4455,13 +4590,13 @@ function buildSelectableNode(row, alias) {
|
|
|
4455
4590
|
const propsRaw = row[`${alias}_props`];
|
|
4456
4591
|
const rawProps = typeof propsRaw === "string" ? JSON.parse(propsRaw) : propsRaw ?? {};
|
|
4457
4592
|
const version = row[`${alias}_version`];
|
|
4458
|
-
const validFrom =
|
|
4593
|
+
const validFrom = nullToUndefined2(
|
|
4459
4594
|
row[`${alias}_valid_from`]
|
|
4460
4595
|
);
|
|
4461
|
-
const validTo =
|
|
4596
|
+
const validTo = nullToUndefined2(row[`${alias}_valid_to`]);
|
|
4462
4597
|
const createdAt = row[`${alias}_created_at`];
|
|
4463
4598
|
const updatedAt = row[`${alias}_updated_at`];
|
|
4464
|
-
const deletedAt =
|
|
4599
|
+
const deletedAt = nullToUndefined2(
|
|
4465
4600
|
row[`${alias}_deleted_at`]
|
|
4466
4601
|
);
|
|
4467
4602
|
const result = {
|
|
@@ -4476,7 +4611,7 @@ function buildSelectableNode(row, alias) {
|
|
|
4476
4611
|
deletedAt
|
|
4477
4612
|
}
|
|
4478
4613
|
};
|
|
4479
|
-
assignPropsExcludingReserved(result, rawProps,
|
|
4614
|
+
assignPropsExcludingReserved(result, rawProps, RESERVED_NODE_KEYS);
|
|
4480
4615
|
return result;
|
|
4481
4616
|
}
|
|
4482
4617
|
function buildSelectableNodeOrUndefined(row, alias) {
|
|
@@ -4496,13 +4631,13 @@ function buildSelectableEdge(row, alias) {
|
|
|
4496
4631
|
const toId = row[`${alias}_to_id`];
|
|
4497
4632
|
const propsRaw = row[`${alias}_props`];
|
|
4498
4633
|
const rawProps = typeof propsRaw === "string" ? JSON.parse(propsRaw) : propsRaw ?? {};
|
|
4499
|
-
const validFrom =
|
|
4634
|
+
const validFrom = nullToUndefined2(
|
|
4500
4635
|
row[`${alias}_valid_from`]
|
|
4501
4636
|
);
|
|
4502
|
-
const validTo =
|
|
4637
|
+
const validTo = nullToUndefined2(row[`${alias}_valid_to`]);
|
|
4503
4638
|
const createdAt = row[`${alias}_created_at`];
|
|
4504
4639
|
const updatedAt = row[`${alias}_updated_at`];
|
|
4505
|
-
const deletedAt =
|
|
4640
|
+
const deletedAt = nullToUndefined2(
|
|
4506
4641
|
row[`${alias}_deleted_at`]
|
|
4507
4642
|
);
|
|
4508
4643
|
const result = {
|
|
@@ -4518,7 +4653,7 @@ function buildSelectableEdge(row, alias) {
|
|
|
4518
4653
|
deletedAt
|
|
4519
4654
|
}
|
|
4520
4655
|
};
|
|
4521
|
-
assignPropsExcludingReserved(result, rawProps,
|
|
4656
|
+
assignPropsExcludingReserved(result, rawProps, RESERVED_EDGE_KEYS);
|
|
4522
4657
|
return result;
|
|
4523
4658
|
}
|
|
4524
4659
|
function buildSelectContext(row, startAlias, traversals) {
|
|
@@ -5259,6 +5394,61 @@ function createGuardedProxy(target, debugPath) {
|
|
|
5259
5394
|
}
|
|
5260
5395
|
});
|
|
5261
5396
|
}
|
|
5397
|
+
function templateHasReadInstant(template) {
|
|
5398
|
+
return template.params.some(
|
|
5399
|
+
(parameter) => parameter instanceof Placeholder && parameter.name === CURRENT_READ_INSTANT_PLACEHOLDER
|
|
5400
|
+
);
|
|
5401
|
+
}
|
|
5402
|
+
function queryAstNeedsCurrentReadInstant(ast) {
|
|
5403
|
+
return ast.temporalMode.mode === "current" && ast.recordedAsOf === void 0;
|
|
5404
|
+
}
|
|
5405
|
+
function composableNeedsCurrentReadInstant(query) {
|
|
5406
|
+
if ("__type" in query) {
|
|
5407
|
+
return composableNeedsCurrentReadInstant(query.left) || composableNeedsCurrentReadInstant(query.right);
|
|
5408
|
+
}
|
|
5409
|
+
return queryAstNeedsCurrentReadInstant(query);
|
|
5410
|
+
}
|
|
5411
|
+
function buildReadInstantTemplate(args) {
|
|
5412
|
+
const { backend } = args;
|
|
5413
|
+
if (backend?.compileSql === void 0) return void 0;
|
|
5414
|
+
const compiled = args.compile();
|
|
5415
|
+
if (!isRawExecutable(compiled)) return void 0;
|
|
5416
|
+
const template = backend.compileSql(compiled);
|
|
5417
|
+
if (args.needsReadInstant && !templateHasReadInstant(template)) {
|
|
5418
|
+
return void 0;
|
|
5419
|
+
}
|
|
5420
|
+
return template;
|
|
5421
|
+
}
|
|
5422
|
+
function buildQueryTemplate(ast, graphId, compileOptions, backend) {
|
|
5423
|
+
return buildReadInstantTemplate({
|
|
5424
|
+
compile: () => compileQuery(ast, graphId, {
|
|
5425
|
+
...compileOptions,
|
|
5426
|
+
readInstant: "placeholder"
|
|
5427
|
+
}),
|
|
5428
|
+
backend,
|
|
5429
|
+
needsReadInstant: queryAstNeedsCurrentReadInstant(ast)
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5432
|
+
function fillTemplateParams(params, bindings, dialect) {
|
|
5433
|
+
const adapter = getDialect(dialect);
|
|
5434
|
+
let readInstant;
|
|
5435
|
+
return params.map((parameter) => {
|
|
5436
|
+
if (!(parameter instanceof Placeholder)) return parameter;
|
|
5437
|
+
const name = parameter.name;
|
|
5438
|
+
if (name === CURRENT_READ_INSTANT_PLACEHOLDER) {
|
|
5439
|
+
readInstant ??= nowIso();
|
|
5440
|
+
return readInstant;
|
|
5441
|
+
}
|
|
5442
|
+
const value = bindings[name];
|
|
5443
|
+
if (value === void 0) {
|
|
5444
|
+
throw new ConfigurationError(`Missing binding for parameter "${name}"`, {
|
|
5445
|
+
parameterName: name
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
if (value instanceof Date) return value.toISOString();
|
|
5449
|
+
return adapter.bindValue(value);
|
|
5450
|
+
});
|
|
5451
|
+
}
|
|
5262
5452
|
|
|
5263
5453
|
// src/query/builder/prepared-query.ts
|
|
5264
5454
|
function toLiteral(value) {
|
|
@@ -5399,31 +5589,9 @@ function substituteParameters(ast, bindings) {
|
|
|
5399
5589
|
}
|
|
5400
5590
|
};
|
|
5401
5591
|
}
|
|
5402
|
-
function fillPlaceholders(params, bindings, dialect) {
|
|
5403
|
-
const adapter = getDialect(dialect);
|
|
5404
|
-
return params.map((parameter) => {
|
|
5405
|
-
if (parameter instanceof Placeholder) {
|
|
5406
|
-
const name = parameter.name;
|
|
5407
|
-
const value = bindings[name];
|
|
5408
|
-
if (value === void 0) {
|
|
5409
|
-
throw new ConfigurationError(
|
|
5410
|
-
`Missing binding for parameter "${name}"`,
|
|
5411
|
-
{ parameterName: name }
|
|
5412
|
-
);
|
|
5413
|
-
}
|
|
5414
|
-
if (value instanceof Date) return value.toISOString();
|
|
5415
|
-
return adapter.bindValue(value);
|
|
5416
|
-
}
|
|
5417
|
-
return parameter;
|
|
5418
|
-
});
|
|
5419
|
-
}
|
|
5420
5592
|
var PreparedQuery = class {
|
|
5421
5593
|
#ast;
|
|
5422
5594
|
#unoptimizedAst;
|
|
5423
|
-
#sqlText;
|
|
5424
|
-
#sqlParams;
|
|
5425
|
-
#unoptimizedSqlText;
|
|
5426
|
-
#unoptimizedSqlParams;
|
|
5427
5595
|
#backend;
|
|
5428
5596
|
#dialect;
|
|
5429
5597
|
#graphId;
|
|
@@ -5433,13 +5601,17 @@ var PreparedQuery = class {
|
|
|
5433
5601
|
#selectFn;
|
|
5434
5602
|
#schemaIntrospector;
|
|
5435
5603
|
#parameterMetadata;
|
|
5604
|
+
/**
|
|
5605
|
+
* Per-AST cached placeholder template, keyed by AST reference so the
|
|
5606
|
+
* optimized (`#ast`) and unoptimized (`#unoptimizedAst`) variants cache
|
|
5607
|
+
* independently. A cached `undefined` records "no fast-path template" (the
|
|
5608
|
+
* backend lacks `compileSql`, or the statement was not safely cacheable) so
|
|
5609
|
+
* we don't rebuild it on every call.
|
|
5610
|
+
*/
|
|
5611
|
+
#templateCache = /* @__PURE__ */ new Map();
|
|
5436
5612
|
constructor(config) {
|
|
5437
5613
|
this.#ast = config.ast;
|
|
5438
5614
|
this.#unoptimizedAst = config.unoptimizedAst;
|
|
5439
|
-
this.#sqlText = config.sqlText;
|
|
5440
|
-
this.#sqlParams = config.sqlParams;
|
|
5441
|
-
this.#unoptimizedSqlText = config.unoptimizedSqlText;
|
|
5442
|
-
this.#unoptimizedSqlParams = config.unoptimizedSqlParams;
|
|
5443
5615
|
this.#backend = config.backend;
|
|
5444
5616
|
this.#dialect = config.dialect;
|
|
5445
5617
|
this.#graphId = config.graphId;
|
|
@@ -5450,6 +5622,26 @@ var PreparedQuery = class {
|
|
|
5450
5622
|
this.#schemaIntrospector = config.schemaIntrospector;
|
|
5451
5623
|
this.#parameterMetadata = collectParameterMetadata(this.#ast);
|
|
5452
5624
|
}
|
|
5625
|
+
/**
|
|
5626
|
+
* The cached placeholder template for `ast`, or `undefined` when no fast
|
|
5627
|
+
* path applies. Compiled once (in placeholder mode) and reused across every
|
|
5628
|
+
* `execute()`; the read instant is refreshed per call by
|
|
5629
|
+
* {@link fillTemplateParams}, never frozen into the cache.
|
|
5630
|
+
*/
|
|
5631
|
+
#template(ast) {
|
|
5632
|
+
if (this.#templateCache.has(ast)) return this.#templateCache.get(ast);
|
|
5633
|
+
const template = this.#buildTemplate(ast);
|
|
5634
|
+
this.#templateCache.set(ast, template);
|
|
5635
|
+
return template;
|
|
5636
|
+
}
|
|
5637
|
+
#buildTemplate(ast) {
|
|
5638
|
+
return buildQueryTemplate(
|
|
5639
|
+
ast,
|
|
5640
|
+
this.#graphId,
|
|
5641
|
+
this.#compileOptions,
|
|
5642
|
+
this.#backend
|
|
5643
|
+
);
|
|
5644
|
+
}
|
|
5453
5645
|
/** The set of parameter names required by this prepared query. */
|
|
5454
5646
|
get parameterNames() {
|
|
5455
5647
|
return this.#parameterMetadata.names;
|
|
@@ -5482,26 +5674,7 @@ var PreparedQuery = class {
|
|
|
5482
5674
|
return this.#executeUnoptimized(bindings);
|
|
5483
5675
|
}
|
|
5484
5676
|
async #executeSelectiveRows(bindings) {
|
|
5485
|
-
|
|
5486
|
-
const filledParams = fillPlaceholders(
|
|
5487
|
-
this.#sqlParams,
|
|
5488
|
-
bindings,
|
|
5489
|
-
this.#dialect
|
|
5490
|
-
);
|
|
5491
|
-
const rawRows2 = await this.#backend.executeRaw(
|
|
5492
|
-
this.#sqlText,
|
|
5493
|
-
filledParams
|
|
5494
|
-
);
|
|
5495
|
-
return transformPathColumns(rawRows2, this.#state, this.#dialect);
|
|
5496
|
-
}
|
|
5497
|
-
const concreteAst = substituteParameters(this.#ast, bindings);
|
|
5498
|
-
const compiled = compileQuery(
|
|
5499
|
-
concreteAst,
|
|
5500
|
-
this.#graphId,
|
|
5501
|
-
this.#compileOptions
|
|
5502
|
-
);
|
|
5503
|
-
const rawRows = await this.#backend.execute(compiled);
|
|
5504
|
-
return transformPathColumns(rawRows, this.#state, this.#dialect);
|
|
5677
|
+
return this.#executeRows(this.#ast, bindings);
|
|
5505
5678
|
}
|
|
5506
5679
|
async #executeUnoptimized(bindings) {
|
|
5507
5680
|
const rows = await this.#executeUnoptimizedRows(bindings);
|
|
@@ -5513,19 +5686,29 @@ var PreparedQuery = class {
|
|
|
5513
5686
|
);
|
|
5514
5687
|
}
|
|
5515
5688
|
async #executeUnoptimizedRows(bindings) {
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5689
|
+
return this.#executeRows(this.#unoptimizedAst, bindings);
|
|
5690
|
+
}
|
|
5691
|
+
/**
|
|
5692
|
+
* Runs one AST variant with the given bindings. Prefers the cached template
|
|
5693
|
+
* + `executeRaw` fast path; falls back to substituting bindings into the AST
|
|
5694
|
+
* and compiling fresh when the backend cannot execute raw SQL text.
|
|
5695
|
+
*/
|
|
5696
|
+
async #executeRows(ast, bindings) {
|
|
5697
|
+
const executeRaw = this.#backend.executeRaw;
|
|
5698
|
+
const template = executeRaw === void 0 ? void 0 : this.#template(ast);
|
|
5699
|
+
if (template !== void 0 && executeRaw !== void 0) {
|
|
5700
|
+
const params = fillTemplateParams(
|
|
5701
|
+
template.params,
|
|
5519
5702
|
bindings,
|
|
5520
5703
|
this.#dialect
|
|
5521
5704
|
);
|
|
5522
5705
|
const rawRows2 = await this.#backend.executeRaw(
|
|
5523
|
-
|
|
5524
|
-
|
|
5706
|
+
template.sql,
|
|
5707
|
+
params
|
|
5525
5708
|
);
|
|
5526
5709
|
return transformPathColumns(rawRows2, this.#state, this.#dialect);
|
|
5527
5710
|
}
|
|
5528
|
-
const concreteAst = substituteParameters(
|
|
5711
|
+
const concreteAst = substituteParameters(ast, bindings);
|
|
5529
5712
|
const compiled = compileQuery(
|
|
5530
5713
|
concreteAst,
|
|
5531
5714
|
this.#graphId,
|
|
@@ -5969,6 +6152,9 @@ function buildQueryAst(config, state) {
|
|
|
5969
6152
|
...state.offset !== void 0 && { offset: state.offset },
|
|
5970
6153
|
...state.groupBy !== void 0 && { groupBy: state.groupBy },
|
|
5971
6154
|
...state.having !== void 0 && { having: state.having },
|
|
6155
|
+
...state.aggregateOrderBy.length > 0 && {
|
|
6156
|
+
aggregateOrderBy: state.aggregateOrderBy
|
|
6157
|
+
},
|
|
5972
6158
|
...state.fusion !== void 0 && { fusion: state.fusion }
|
|
5973
6159
|
};
|
|
5974
6160
|
}
|
|
@@ -5994,12 +6180,27 @@ function buildCompileOptions(config) {
|
|
|
5994
6180
|
dialect: config.dialect ?? "sqlite",
|
|
5995
6181
|
schema: config.schema,
|
|
5996
6182
|
windowFunctions: config.backend?.capabilities.windowFunctions ?? true,
|
|
5997
|
-
...fulltextStrategy === void 0 ? {} : {
|
|
6183
|
+
...fulltextStrategy === void 0 ? {} : {
|
|
6184
|
+
fulltextStrategy,
|
|
6185
|
+
fulltextLanguages: buildFulltextLanguages(config.registry)
|
|
6186
|
+
},
|
|
5998
6187
|
...vectorStrategy === void 0 ? {} : { vectorStrategy, vectorSlots: buildVectorSlots(config.registry) },
|
|
5999
6188
|
...recordedReadBinding === void 0 ? {} : { recordedReadBinding }
|
|
6000
6189
|
};
|
|
6001
6190
|
}
|
|
6002
6191
|
var vectorSlotsCache = /* @__PURE__ */ new WeakMap();
|
|
6192
|
+
var fulltextLanguagesCache = /* @__PURE__ */ new WeakMap();
|
|
6193
|
+
function buildFulltextLanguages(registry) {
|
|
6194
|
+
const cached = fulltextLanguagesCache.get(registry);
|
|
6195
|
+
if (cached !== void 0) return cached;
|
|
6196
|
+
const languages = /* @__PURE__ */ new Map();
|
|
6197
|
+
for (const [nodeKind, nodeType] of registry.nodeKinds) {
|
|
6198
|
+
const language = resolveDeclaredFulltextLanguage(nodeType.schema);
|
|
6199
|
+
if (language !== void 0) languages.set(nodeKind, language);
|
|
6200
|
+
}
|
|
6201
|
+
fulltextLanguagesCache.set(registry, languages);
|
|
6202
|
+
return languages;
|
|
6203
|
+
}
|
|
6003
6204
|
function buildVectorSlots(registry) {
|
|
6004
6205
|
const cached = vectorSlotsCache.get(registry);
|
|
6005
6206
|
if (cached !== void 0) return cached;
|
|
@@ -6027,20 +6228,35 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6027
6228
|
#config;
|
|
6028
6229
|
#state;
|
|
6029
6230
|
#selectFn;
|
|
6030
|
-
#cachedCompiled = NOT_COMPUTED;
|
|
6031
|
-
#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6032
6231
|
#cachedSelectiveFieldsForExecute = NOT_COMPUTED;
|
|
6033
6232
|
#cachedSelectiveFieldsForPagination = NOT_COMPUTED;
|
|
6233
|
+
// The instance is immutable (every builder method returns a new instance),
|
|
6234
|
+
// so the AST and its param-ref check are invariant — compute each once and
|
|
6235
|
+
// reuse across execute()/paginate()/stream() instead of rebuilding per call.
|
|
6236
|
+
#cachedAst;
|
|
6237
|
+
#cachedHasParameterReferences;
|
|
6238
|
+
// Per-instance compiled placeholder templates for the full and
|
|
6239
|
+
// selective-field ASTs (NOT_COMPUTED = not yet built; undefined = no fast
|
|
6240
|
+
// path). Reused across execute()/executeOn() calls so a repeated query
|
|
6241
|
+
// compiles once; the read instant is filled fresh per call, never cached.
|
|
6242
|
+
#fullTemplate = NOT_COMPUTED;
|
|
6243
|
+
#selectiveTemplate = NOT_COMPUTED;
|
|
6034
6244
|
constructor(config, state, selectFunction) {
|
|
6035
6245
|
this.#config = config;
|
|
6036
6246
|
this.#state = state;
|
|
6037
6247
|
this.#selectFn = selectFunction;
|
|
6038
6248
|
}
|
|
6039
6249
|
/**
|
|
6040
|
-
* Builds the query AST.
|
|
6250
|
+
* Builds the query AST (memoized — the instance is immutable).
|
|
6041
6251
|
*/
|
|
6042
6252
|
toAst() {
|
|
6043
|
-
return buildQueryAst(this.#config, this.#state);
|
|
6253
|
+
return this.#cachedAst ??= buildQueryAst(this.#config, this.#state);
|
|
6254
|
+
}
|
|
6255
|
+
/** Whether this query uses `param()` refs (memoized). */
|
|
6256
|
+
#hasParameterReferences() {
|
|
6257
|
+
return this.#cachedHasParameterReferences ??= hasParameterReferences(
|
|
6258
|
+
this.toAst()
|
|
6259
|
+
);
|
|
6044
6260
|
}
|
|
6045
6261
|
/**
|
|
6046
6262
|
* Orders results.
|
|
@@ -6214,19 +6430,16 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6214
6430
|
* with db.all(), db.get(), etc.
|
|
6215
6431
|
*/
|
|
6216
6432
|
compile() {
|
|
6217
|
-
if (this.#cachedCompiled !== NOT_COMPUTED) return this.#cachedCompiled;
|
|
6218
6433
|
const ast = this.toAst();
|
|
6219
|
-
|
|
6220
|
-
ast,
|
|
6221
|
-
this.#config.graphId,
|
|
6222
|
-
this.#compileOptions()
|
|
6223
|
-
);
|
|
6224
|
-
this.#cachedCompiled = compiled;
|
|
6225
|
-
return compiled;
|
|
6434
|
+
return compileQuery(ast, this.#config.graphId, this.#compileOptions());
|
|
6226
6435
|
}
|
|
6227
6436
|
/**
|
|
6228
|
-
* Creates a prepared (pre-
|
|
6229
|
-
*
|
|
6437
|
+
* Creates a prepared (pre-validated) query that can be executed multiple
|
|
6438
|
+
* times with different parameter bindings. Builds and structurally
|
|
6439
|
+
* validates the AST once (a malformed query fails fast, here, instead of on
|
|
6440
|
+
* first use); the prepared query then compiles once into a reusable template
|
|
6441
|
+
* and fills a fresh read instant per execute() — see PreparedQuery's class
|
|
6442
|
+
* doc comment.
|
|
6230
6443
|
*
|
|
6231
6444
|
* Use `param("name")` in predicates to create parameterized slots,
|
|
6232
6445
|
* then pass values via `prepared.execute({ name: "value" })`.
|
|
@@ -6253,36 +6466,16 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6253
6466
|
"Cannot prepare query: no backend configured. Use store.query() or pass a backend to createQueryBuilder()."
|
|
6254
6467
|
);
|
|
6255
6468
|
}
|
|
6256
|
-
const baseAst =
|
|
6469
|
+
const baseAst = this.toAst();
|
|
6257
6470
|
const selectiveFields = this.#getSelectiveFieldsForExecute();
|
|
6258
6471
|
const ast = selectiveFields === void 0 ? baseAst : { ...baseAst, selectiveFields };
|
|
6259
6472
|
const unoptimizedAst = baseAst;
|
|
6260
6473
|
const compileOptions = this.#compileOptions();
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
let sqlParams;
|
|
6264
|
-
let unoptimizedSqlText;
|
|
6265
|
-
let unoptimizedSqlParams;
|
|
6266
|
-
if (this.#config.backend.compileSql !== void 0) {
|
|
6267
|
-
const result = this.#config.backend.compileSql(compiled);
|
|
6268
|
-
sqlText = result.sql;
|
|
6269
|
-
sqlParams = result.params;
|
|
6270
|
-
const unoptimizedCompiled = compileQuery(
|
|
6271
|
-
unoptimizedAst,
|
|
6272
|
-
this.#config.graphId,
|
|
6273
|
-
compileOptions
|
|
6274
|
-
);
|
|
6275
|
-
const unoptimizedResult = this.#config.backend.compileSql(unoptimizedCompiled);
|
|
6276
|
-
unoptimizedSqlText = unoptimizedResult.sql;
|
|
6277
|
-
unoptimizedSqlParams = unoptimizedResult.params;
|
|
6278
|
-
}
|
|
6474
|
+
compileQuery(ast, this.#config.graphId, compileOptions);
|
|
6475
|
+
compileQuery(unoptimizedAst, this.#config.graphId, compileOptions);
|
|
6279
6476
|
return new PreparedQuery({
|
|
6280
6477
|
ast,
|
|
6281
6478
|
unoptimizedAst,
|
|
6282
|
-
sqlText,
|
|
6283
|
-
sqlParams,
|
|
6284
|
-
unoptimizedSqlText,
|
|
6285
|
-
unoptimizedSqlParams,
|
|
6286
6479
|
backend: this.#config.backend,
|
|
6287
6480
|
dialect: this.#config.dialect ?? "sqlite",
|
|
6288
6481
|
graphId: this.#config.graphId,
|
|
@@ -6316,6 +6509,59 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6316
6509
|
surface
|
|
6317
6510
|
});
|
|
6318
6511
|
}
|
|
6512
|
+
#dialect() {
|
|
6513
|
+
return this.#config.dialect ?? "sqlite";
|
|
6514
|
+
}
|
|
6515
|
+
/**
|
|
6516
|
+
* The cached placeholder template for one AST variant (`"full"` or
|
|
6517
|
+
* `"selective"`), or `undefined` when no fast path applies. Built once per
|
|
6518
|
+
* instance from the store backend's compiler; reusable across executions and
|
|
6519
|
+
* across store/transaction backends of the same dialect, since SQL text and
|
|
6520
|
+
* parameters depend only on the AST and dialect. The read instant is filled
|
|
6521
|
+
* fresh per call by {@link fillTemplateParams}, never frozen into the cache.
|
|
6522
|
+
*/
|
|
6523
|
+
#templateFor(ast, slot) {
|
|
6524
|
+
const cached = slot === "full" ? this.#fullTemplate : this.#selectiveTemplate;
|
|
6525
|
+
if (cached !== NOT_COMPUTED) return cached;
|
|
6526
|
+
const built = this.#buildTemplate(ast);
|
|
6527
|
+
if (slot === "full") this.#fullTemplate = built;
|
|
6528
|
+
else this.#selectiveTemplate = built;
|
|
6529
|
+
return built;
|
|
6530
|
+
}
|
|
6531
|
+
#buildTemplate(ast) {
|
|
6532
|
+
return buildQueryTemplate(
|
|
6533
|
+
ast,
|
|
6534
|
+
this.#config.graphId,
|
|
6535
|
+
this.#compileOptions(),
|
|
6536
|
+
this.#config.backend
|
|
6537
|
+
);
|
|
6538
|
+
}
|
|
6539
|
+
/**
|
|
6540
|
+
* Fetches raw rows for one AST variant, path-column-normalized. Prefers the
|
|
6541
|
+
* cached template + `executeRaw` fast path; falls back to a fresh literal
|
|
6542
|
+
* compile via `backend.execute` when the backend cannot run raw SQL text (in
|
|
6543
|
+
* which case no template is built). Callers map the returned rows to typed
|
|
6544
|
+
* results.
|
|
6545
|
+
*/
|
|
6546
|
+
async #fetchRows(backend, ast, slot, surface) {
|
|
6547
|
+
const executeRaw = backend.executeRaw;
|
|
6548
|
+
const template = executeRaw === void 0 ? void 0 : this.#templateFor(ast, slot);
|
|
6549
|
+
const rawRows = await this.#executeOnBackend(
|
|
6550
|
+
backend,
|
|
6551
|
+
template !== void 0 && executeRaw !== void 0 ? (
|
|
6552
|
+
// Method call (not the detached `executeRaw` local) so a this-using
|
|
6553
|
+
// backend implementation keeps its receiver.
|
|
6554
|
+
backend.executeRaw(
|
|
6555
|
+
template.sql,
|
|
6556
|
+
fillTemplateParams(template.params, {}, this.#dialect())
|
|
6557
|
+
)
|
|
6558
|
+
) : backend.execute(
|
|
6559
|
+
compileQuery(ast, this.#config.graphId, this.#compileOptions())
|
|
6560
|
+
),
|
|
6561
|
+
surface
|
|
6562
|
+
);
|
|
6563
|
+
return transformPathColumns(rawRows, this.#state, this.#dialect());
|
|
6564
|
+
}
|
|
6319
6565
|
/**
|
|
6320
6566
|
* Executes the query and returns typed results.
|
|
6321
6567
|
*
|
|
@@ -6333,7 +6579,8 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6333
6579
|
);
|
|
6334
6580
|
}
|
|
6335
6581
|
const backend = this.#config.backend;
|
|
6336
|
-
|
|
6582
|
+
const ast = this.toAst();
|
|
6583
|
+
if (this.#hasParameterReferences()) {
|
|
6337
6584
|
throw new Error(
|
|
6338
6585
|
"Query contains param() references. Use .prepare().execute({...}) instead of .execute()."
|
|
6339
6586
|
);
|
|
@@ -6342,14 +6589,7 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6342
6589
|
if (optimizedResult !== void 0) {
|
|
6343
6590
|
return optimizedResult;
|
|
6344
6591
|
}
|
|
6345
|
-
const
|
|
6346
|
-
const rawRows = await this.#executeOnBackend(
|
|
6347
|
-
backend,
|
|
6348
|
-
backend.execute(compiled),
|
|
6349
|
-
"recorded-query"
|
|
6350
|
-
);
|
|
6351
|
-
this.#config.dialect ?? "sqlite";
|
|
6352
|
-
const rows = transformPathColumns(rawRows, this.#state);
|
|
6592
|
+
const rows = await this.#fetchRows(backend, ast, "full", "recorded-query");
|
|
6353
6593
|
return mapResults(
|
|
6354
6594
|
rows,
|
|
6355
6595
|
this.#state.startAlias,
|
|
@@ -6366,7 +6606,7 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6366
6606
|
*/
|
|
6367
6607
|
async executeOn(backend) {
|
|
6368
6608
|
const ast = this.toAst();
|
|
6369
|
-
if (hasParameterReferences(
|
|
6609
|
+
if (this.#hasParameterReferences()) {
|
|
6370
6610
|
throw new Error(
|
|
6371
6611
|
"Query contains param() references. Use .prepare().execute({...}) instead of .execute()."
|
|
6372
6612
|
);
|
|
@@ -6375,14 +6615,12 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6375
6615
|
if (optimizedResult !== void 0) {
|
|
6376
6616
|
return optimizedResult;
|
|
6377
6617
|
}
|
|
6378
|
-
const
|
|
6379
|
-
const rawRows = await this.#executeOnBackend(
|
|
6618
|
+
const rows = await this.#fetchRows(
|
|
6380
6619
|
backend,
|
|
6381
|
-
|
|
6620
|
+
ast,
|
|
6621
|
+
"full",
|
|
6382
6622
|
"recorded-batch-query"
|
|
6383
6623
|
);
|
|
6384
|
-
this.#config.dialect ?? "sqlite";
|
|
6385
|
-
const rows = transformPathColumns(rawRows, this.#state);
|
|
6386
6624
|
return mapResults(
|
|
6387
6625
|
rows,
|
|
6388
6626
|
this.#state.startAlias,
|
|
@@ -6401,30 +6639,15 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6401
6639
|
if (selectiveFields === void 0) {
|
|
6402
6640
|
return void 0;
|
|
6403
6641
|
}
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
const baseAst = buildQueryAst(this.#config, this.#state);
|
|
6407
|
-
const selectiveAst = {
|
|
6408
|
-
...baseAst,
|
|
6409
|
-
selectiveFields
|
|
6410
|
-
};
|
|
6411
|
-
compiled = compileQuery(
|
|
6412
|
-
selectiveAst,
|
|
6413
|
-
this.#config.graphId,
|
|
6414
|
-
this.#compileOptions()
|
|
6415
|
-
);
|
|
6416
|
-
this.#cachedOptimizedCompiled = compiled;
|
|
6417
|
-
} else {
|
|
6418
|
-
compiled = this.#cachedOptimizedCompiled;
|
|
6419
|
-
}
|
|
6642
|
+
const baseAst = this.toAst();
|
|
6643
|
+
const selectiveAst = { ...baseAst, selectiveFields };
|
|
6420
6644
|
const backend = this.#requireBackend();
|
|
6421
|
-
const
|
|
6645
|
+
const rows = await this.#fetchRows(
|
|
6422
6646
|
backend,
|
|
6423
|
-
|
|
6647
|
+
selectiveAst,
|
|
6648
|
+
"selective",
|
|
6424
6649
|
"recorded-query"
|
|
6425
6650
|
);
|
|
6426
|
-
this.#config.dialect ?? "sqlite";
|
|
6427
|
-
const rows = transformPathColumns(rawSelectiveRows, this.#state);
|
|
6428
6651
|
try {
|
|
6429
6652
|
return mapSelectiveResults(
|
|
6430
6653
|
rows,
|
|
@@ -6436,12 +6659,10 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6436
6659
|
} catch (error) {
|
|
6437
6660
|
if (error instanceof MissingSelectiveFieldError) {
|
|
6438
6661
|
this.#cachedSelectiveFieldsForExecute = void 0;
|
|
6439
|
-
this.#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6440
6662
|
return void 0;
|
|
6441
6663
|
}
|
|
6442
6664
|
if (error instanceof UnsupportedPredicateError) {
|
|
6443
6665
|
this.#cachedSelectiveFieldsForExecute = void 0;
|
|
6444
|
-
this.#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6445
6666
|
return void 0;
|
|
6446
6667
|
}
|
|
6447
6668
|
throw error;
|
|
@@ -6456,29 +6677,14 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6456
6677
|
if (selectiveFields === void 0) {
|
|
6457
6678
|
return void 0;
|
|
6458
6679
|
}
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
const selectiveAst = {
|
|
6463
|
-
...baseAst,
|
|
6464
|
-
selectiveFields
|
|
6465
|
-
};
|
|
6466
|
-
compiled = compileQuery(
|
|
6467
|
-
selectiveAst,
|
|
6468
|
-
this.#config.graphId,
|
|
6469
|
-
this.#compileOptions()
|
|
6470
|
-
);
|
|
6471
|
-
this.#cachedOptimizedCompiled = compiled;
|
|
6472
|
-
} else {
|
|
6473
|
-
compiled = this.#cachedOptimizedCompiled;
|
|
6474
|
-
}
|
|
6475
|
-
const rawSelectiveRows = await this.#executeOnBackend(
|
|
6680
|
+
const baseAst = this.toAst();
|
|
6681
|
+
const selectiveAst = { ...baseAst, selectiveFields };
|
|
6682
|
+
const rows = await this.#fetchRows(
|
|
6476
6683
|
backend,
|
|
6477
|
-
|
|
6684
|
+
selectiveAst,
|
|
6685
|
+
"selective",
|
|
6478
6686
|
"recorded-batch-query"
|
|
6479
6687
|
);
|
|
6480
|
-
this.#config.dialect ?? "sqlite";
|
|
6481
|
-
const rows = transformPathColumns(rawSelectiveRows, this.#state);
|
|
6482
6688
|
try {
|
|
6483
6689
|
return mapSelectiveResults(
|
|
6484
6690
|
rows,
|
|
@@ -6490,12 +6696,10 @@ var ExecutableQuery = class _ExecutableQuery {
|
|
|
6490
6696
|
} catch (error) {
|
|
6491
6697
|
if (error instanceof MissingSelectiveFieldError) {
|
|
6492
6698
|
this.#cachedSelectiveFieldsForExecute = void 0;
|
|
6493
|
-
this.#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6494
6699
|
return void 0;
|
|
6495
6700
|
}
|
|
6496
6701
|
if (error instanceof UnsupportedPredicateError) {
|
|
6497
6702
|
this.#cachedSelectiveFieldsForExecute = void 0;
|
|
6498
|
-
this.#cachedOptimizedCompiled = NOT_COMPUTED;
|
|
6499
6703
|
return void 0;
|
|
6500
6704
|
}
|
|
6501
6705
|
throw error;
|
|
@@ -7003,7 +7207,10 @@ var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
|
|
|
7003
7207
|
#config;
|
|
7004
7208
|
#state;
|
|
7005
7209
|
#fields;
|
|
7006
|
-
|
|
7210
|
+
// Per-instance compiled placeholder template, reused across execute() calls
|
|
7211
|
+
// (see #resolveTemplate). NOT_COMPUTED = not yet built; undefined = no fast
|
|
7212
|
+
// path.
|
|
7213
|
+
#template = NOT_COMPUTED2;
|
|
7007
7214
|
constructor(config, state, fields) {
|
|
7008
7215
|
this.#config = config;
|
|
7009
7216
|
this.#state = state;
|
|
@@ -7015,6 +7222,43 @@ var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
|
|
|
7015
7222
|
toAst() {
|
|
7016
7223
|
return buildQueryAst(this.#config, this.#state);
|
|
7017
7224
|
}
|
|
7225
|
+
/**
|
|
7226
|
+
* Orders results by a grouped field or aggregate alias.
|
|
7227
|
+
*
|
|
7228
|
+
* `key` is one of the output names passed to `.aggregate({...})` — either
|
|
7229
|
+
* a grouped field (e.g. `genre`) or an aggregate alias (e.g. `bookCount`).
|
|
7230
|
+
* Both are ordered the same way: by referencing the SELECT-list output
|
|
7231
|
+
* column, since every `.aggregate()` field is projected with an alias.
|
|
7232
|
+
*
|
|
7233
|
+
* Chain multiple calls to sort by more than one key, in call order:
|
|
7234
|
+
* `.orderBy("genre").orderBy("bookCount", "desc")` sorts by genre first,
|
|
7235
|
+
* then by book count within each genre.
|
|
7236
|
+
*
|
|
7237
|
+
* @example
|
|
7238
|
+
* ```typescript
|
|
7239
|
+
* // Top 2 authors by book count
|
|
7240
|
+
* store.query()
|
|
7241
|
+
* .from("Author", "a")
|
|
7242
|
+
* .traverse("wrote", "e")
|
|
7243
|
+
* .to("Book", "b")
|
|
7244
|
+
* .groupByNode("a")
|
|
7245
|
+
* .aggregate({ author: field("a", "name"), bookCount: count("b") })
|
|
7246
|
+
* .orderBy("bookCount", "desc")
|
|
7247
|
+
* .limit(2)
|
|
7248
|
+
* .execute();
|
|
7249
|
+
* ```
|
|
7250
|
+
*/
|
|
7251
|
+
orderBy(key, direction = "asc") {
|
|
7252
|
+
const orderSpec = { outputName: key, direction };
|
|
7253
|
+
return new _ExecutableAggregateQuery(
|
|
7254
|
+
this.#config,
|
|
7255
|
+
{
|
|
7256
|
+
...this.#state,
|
|
7257
|
+
aggregateOrderBy: [...this.#state.aggregateOrderBy, orderSpec]
|
|
7258
|
+
},
|
|
7259
|
+
this.#fields
|
|
7260
|
+
);
|
|
7261
|
+
}
|
|
7018
7262
|
/**
|
|
7019
7263
|
* Limits the number of results.
|
|
7020
7264
|
*/
|
|
@@ -7053,34 +7297,61 @@ var ExecutableAggregateQuery = class _ExecutableAggregateQuery {
|
|
|
7053
7297
|
* Compiles the query to a Drizzle SQL object.
|
|
7054
7298
|
*/
|
|
7055
7299
|
compile() {
|
|
7056
|
-
if (this.#cachedCompiled !== NOT_COMPUTED2) {
|
|
7057
|
-
return this.#cachedCompiled;
|
|
7058
|
-
}
|
|
7059
7300
|
const ast = this.toAst();
|
|
7060
|
-
|
|
7061
|
-
ast,
|
|
7062
|
-
this.#config.graphId,
|
|
7063
|
-
this.#compileOptions()
|
|
7064
|
-
);
|
|
7065
|
-
this.#cachedCompiled = compiled;
|
|
7066
|
-
return compiled;
|
|
7301
|
+
return compileQuery(ast, this.#config.graphId, this.#compileOptions());
|
|
7067
7302
|
}
|
|
7068
7303
|
#compileOptions() {
|
|
7069
7304
|
return buildCompileOptions(this.#config);
|
|
7070
7305
|
}
|
|
7306
|
+
/**
|
|
7307
|
+
* The cached placeholder template for this aggregate query, or `undefined`
|
|
7308
|
+
* when no fast path applies. Built once per instance; the read instant is
|
|
7309
|
+
* filled fresh per execution by {@link fillTemplateParams}.
|
|
7310
|
+
*/
|
|
7311
|
+
#resolveTemplate(ast) {
|
|
7312
|
+
if (this.#template !== NOT_COMPUTED2) return this.#template;
|
|
7313
|
+
this.#template = buildQueryTemplate(
|
|
7314
|
+
ast,
|
|
7315
|
+
this.#config.graphId,
|
|
7316
|
+
this.#compileOptions(),
|
|
7317
|
+
this.#config.backend
|
|
7318
|
+
);
|
|
7319
|
+
return this.#template;
|
|
7320
|
+
}
|
|
7071
7321
|
/**
|
|
7072
7322
|
* Executes the query and returns typed results.
|
|
7073
7323
|
*
|
|
7074
7324
|
* @throws Error if no backend is configured
|
|
7075
7325
|
*/
|
|
7076
7326
|
async execute() {
|
|
7077
|
-
|
|
7327
|
+
const backend = this.#config.backend;
|
|
7328
|
+
if (!backend) {
|
|
7078
7329
|
throw new Error(
|
|
7079
7330
|
"Cannot execute query: no backend configured. Use store.query() or pass a backend to createQueryBuilder()."
|
|
7080
7331
|
);
|
|
7081
7332
|
}
|
|
7082
|
-
const
|
|
7083
|
-
|
|
7333
|
+
const ast = this.toAst();
|
|
7334
|
+
if (hasParameterReferences(ast)) {
|
|
7335
|
+
throw new Error(
|
|
7336
|
+
"Aggregate queries do not support param() references; bind a concrete value instead."
|
|
7337
|
+
);
|
|
7338
|
+
}
|
|
7339
|
+
const executeRaw = backend.executeRaw;
|
|
7340
|
+
const template = executeRaw === void 0 ? void 0 : this.#resolveTemplate(ast);
|
|
7341
|
+
const rows = template !== void 0 && executeRaw !== void 0 ? (
|
|
7342
|
+
// Method call (not the detached `executeRaw` local) so a this-using
|
|
7343
|
+
// backend implementation keeps its receiver.
|
|
7344
|
+
await backend.executeRaw(
|
|
7345
|
+
template.sql,
|
|
7346
|
+
fillTemplateParams(
|
|
7347
|
+
template.params,
|
|
7348
|
+
{},
|
|
7349
|
+
this.#config.dialect ?? "sqlite"
|
|
7350
|
+
)
|
|
7351
|
+
)
|
|
7352
|
+
) : await backend.execute(
|
|
7353
|
+
compileQuery(ast, this.#config.graphId, this.#compileOptions())
|
|
7354
|
+
);
|
|
7084
7355
|
return this.#mapResults(rows);
|
|
7085
7356
|
}
|
|
7086
7357
|
/**
|
|
@@ -8234,7 +8505,6 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
8234
8505
|
};
|
|
8235
8506
|
|
|
8236
8507
|
// src/query/builder/unionable-query.ts
|
|
8237
|
-
var NOT_COMPUTED3 = /* @__PURE__ */ Symbol("NOT_COMPUTED");
|
|
8238
8508
|
function executeOnBackend(backend, recordedAsOf, promise, surface) {
|
|
8239
8509
|
if (recordedAsOf === void 0) return promise;
|
|
8240
8510
|
return withRecordedRelationsPrecondition(promise, {
|
|
@@ -8248,10 +8518,14 @@ function recordedAsOfForComposableQuery(query) {
|
|
|
8248
8518
|
}
|
|
8249
8519
|
return query.recordedAsOf;
|
|
8250
8520
|
}
|
|
8521
|
+
var NOT_COMPUTED3 = /* @__PURE__ */ Symbol("NOT_COMPUTED");
|
|
8251
8522
|
var UnionableQuery = class _UnionableQuery {
|
|
8252
8523
|
#config;
|
|
8253
8524
|
#state;
|
|
8254
|
-
|
|
8525
|
+
// Per-instance compiled placeholder template, reused across
|
|
8526
|
+
// execute()/executeOn() calls; the read instant is filled fresh per call.
|
|
8527
|
+
// NOT_COMPUTED = not yet built; undefined = no fast path.
|
|
8528
|
+
#template = NOT_COMPUTED3;
|
|
8255
8529
|
constructor(config, state) {
|
|
8256
8530
|
this.#config = config;
|
|
8257
8531
|
this.#state = state;
|
|
@@ -8379,20 +8653,76 @@ var UnionableQuery = class _UnionableQuery {
|
|
|
8379
8653
|
* Compiles the set operation to SQL.
|
|
8380
8654
|
*/
|
|
8381
8655
|
compile() {
|
|
8382
|
-
|
|
8383
|
-
return this.#cachedCompiled;
|
|
8384
|
-
}
|
|
8385
|
-
const compiled = compileSetOperation2(
|
|
8656
|
+
return compileSetOperation2(
|
|
8386
8657
|
this.toAst(),
|
|
8387
8658
|
this.#config.graphId,
|
|
8388
8659
|
this.#compileOptions()
|
|
8389
8660
|
);
|
|
8390
|
-
this.#cachedCompiled = compiled;
|
|
8391
|
-
return compiled;
|
|
8392
8661
|
}
|
|
8393
8662
|
#compileOptions() {
|
|
8394
8663
|
return buildCompileOptions(this.#config);
|
|
8395
8664
|
}
|
|
8665
|
+
/**
|
|
8666
|
+
* The cached placeholder template for this set operation, or `undefined`
|
|
8667
|
+
* when no fast path applies. Built once per instance; every operand's read
|
|
8668
|
+
* instant is the shared placeholder, filled fresh per execution by
|
|
8669
|
+
* {@link fillTemplateParams}.
|
|
8670
|
+
*/
|
|
8671
|
+
#resolveTemplate(ast) {
|
|
8672
|
+
if (this.#template !== NOT_COMPUTED3) return this.#template;
|
|
8673
|
+
this.#template = buildReadInstantTemplate({
|
|
8674
|
+
compile: () => compileSetOperation2(ast, this.#config.graphId, {
|
|
8675
|
+
...this.#compileOptions(),
|
|
8676
|
+
readInstant: "placeholder"
|
|
8677
|
+
}),
|
|
8678
|
+
backend: this.#config.backend,
|
|
8679
|
+
needsReadInstant: composableNeedsCurrentReadInstant(ast)
|
|
8680
|
+
});
|
|
8681
|
+
return this.#template;
|
|
8682
|
+
}
|
|
8683
|
+
/**
|
|
8684
|
+
* Fetches raw rows for the set operation: cached template + `executeRaw`
|
|
8685
|
+
* when available, else a fresh literal compile via `backend.execute`.
|
|
8686
|
+
*/
|
|
8687
|
+
async #fetchRows(backend, ast, surface) {
|
|
8688
|
+
const executeRaw = backend.executeRaw;
|
|
8689
|
+
const template = executeRaw === void 0 ? void 0 : this.#resolveTemplate(ast);
|
|
8690
|
+
return executeOnBackend(
|
|
8691
|
+
backend,
|
|
8692
|
+
recordedAsOfForComposableQuery(ast),
|
|
8693
|
+
template !== void 0 && executeRaw !== void 0 ? (
|
|
8694
|
+
// Method call (not the detached `executeRaw` local) so a this-using
|
|
8695
|
+
// backend implementation keeps its receiver.
|
|
8696
|
+
backend.executeRaw(
|
|
8697
|
+
template.sql,
|
|
8698
|
+
fillTemplateParams(
|
|
8699
|
+
template.params,
|
|
8700
|
+
{},
|
|
8701
|
+
this.#config.dialect ?? "sqlite"
|
|
8702
|
+
)
|
|
8703
|
+
)
|
|
8704
|
+
) : backend.execute(
|
|
8705
|
+
compileSetOperation2(
|
|
8706
|
+
ast,
|
|
8707
|
+
this.#config.graphId,
|
|
8708
|
+
this.#compileOptions()
|
|
8709
|
+
)
|
|
8710
|
+
),
|
|
8711
|
+
surface
|
|
8712
|
+
);
|
|
8713
|
+
}
|
|
8714
|
+
/** Applies the select-function transformation, if one is attached. */
|
|
8715
|
+
#mapRows(rows) {
|
|
8716
|
+
if (this.#state.selectFn && this.#state.startAlias) {
|
|
8717
|
+
return mapResults(
|
|
8718
|
+
rows,
|
|
8719
|
+
this.#state.startAlias,
|
|
8720
|
+
this.#state.traversals ?? [],
|
|
8721
|
+
this.#state.selectFn
|
|
8722
|
+
);
|
|
8723
|
+
}
|
|
8724
|
+
return rows;
|
|
8725
|
+
}
|
|
8396
8726
|
/**
|
|
8397
8727
|
* Executes the combined query.
|
|
8398
8728
|
*/
|
|
@@ -8409,22 +8739,7 @@ var UnionableQuery = class _UnionableQuery {
|
|
|
8409
8739
|
"Query contains param() references. Use .prepare().execute({...}) instead of .execute()."
|
|
8410
8740
|
);
|
|
8411
8741
|
}
|
|
8412
|
-
|
|
8413
|
-
const rows = await executeOnBackend(
|
|
8414
|
-
backend,
|
|
8415
|
-
recordedAsOfForComposableQuery(ast),
|
|
8416
|
-
backend.execute(compiled),
|
|
8417
|
-
"recorded-query"
|
|
8418
|
-
);
|
|
8419
|
-
if (this.#state.selectFn && this.#state.startAlias) {
|
|
8420
|
-
return mapResults(
|
|
8421
|
-
rows,
|
|
8422
|
-
this.#state.startAlias,
|
|
8423
|
-
this.#state.traversals ?? [],
|
|
8424
|
-
this.#state.selectFn
|
|
8425
|
-
);
|
|
8426
|
-
}
|
|
8427
|
-
return rows;
|
|
8742
|
+
return this.#mapRows(await this.#fetchRows(backend, ast, "recorded-query"));
|
|
8428
8743
|
}
|
|
8429
8744
|
/**
|
|
8430
8745
|
* Executes the combined query against a provided backend.
|
|
@@ -8438,23 +8753,9 @@ var UnionableQuery = class _UnionableQuery {
|
|
|
8438
8753
|
"Query contains param() references. Use .prepare().execute({...}) instead of .execute()."
|
|
8439
8754
|
);
|
|
8440
8755
|
}
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
const rows = await executeOnBackend(
|
|
8444
|
-
backend,
|
|
8445
|
-
recordedAsOf,
|
|
8446
|
-
backend.execute(compiled),
|
|
8447
|
-
"recorded-batch-query"
|
|
8756
|
+
return this.#mapRows(
|
|
8757
|
+
await this.#fetchRows(backend, ast, "recorded-batch-query")
|
|
8448
8758
|
);
|
|
8449
|
-
if (this.#state.selectFn && this.#state.startAlias) {
|
|
8450
|
-
return mapResults(
|
|
8451
|
-
rows,
|
|
8452
|
-
this.#state.startAlias,
|
|
8453
|
-
this.#state.traversals ?? [],
|
|
8454
|
-
this.#state.selectFn
|
|
8455
|
-
);
|
|
8456
|
-
}
|
|
8457
|
-
return rows;
|
|
8458
8759
|
}
|
|
8459
8760
|
};
|
|
8460
8761
|
|
|
@@ -8674,6 +8975,7 @@ function createQueryBuilderWithContext(graphId, registry, options) {
|
|
|
8674
8975
|
predicates: [],
|
|
8675
8976
|
projection: [],
|
|
8676
8977
|
orderBy: [],
|
|
8978
|
+
aggregateOrderBy: [],
|
|
8677
8979
|
limit: void 0,
|
|
8678
8980
|
offset: void 0,
|
|
8679
8981
|
temporalMode: sealed?.mode ?? "current",
|
|
@@ -8836,7 +9138,7 @@ function processPage(schema, rows) {
|
|
|
8836
9138
|
}
|
|
8837
9139
|
let parsed;
|
|
8838
9140
|
try {
|
|
8839
|
-
parsed =
|
|
9141
|
+
parsed = rowPropsToObject(row.props);
|
|
8840
9142
|
} catch {
|
|
8841
9143
|
skipped += 1;
|
|
8842
9144
|
skippedIds.push(row.id);
|
|
@@ -8860,21 +9162,84 @@ function processPage(schema, rows) {
|
|
|
8860
9162
|
}
|
|
8861
9163
|
return { toUpsert, toDelete, skipped, skippedIds };
|
|
8862
9164
|
}
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
9165
|
+
var SEARCH_CANDIDATE_ALIAS = "_sc";
|
|
9166
|
+
function buildKindCandidates(ctx, nodeKind, where) {
|
|
9167
|
+
if (where === void 0) return void 0;
|
|
9168
|
+
if (ctx.createQuery === void 0) {
|
|
9169
|
+
throw new ConfigurationError(
|
|
9170
|
+
"search with a where predicate requires a query-capable store",
|
|
9171
|
+
{ capability: "search", graphId: ctx.graphId }
|
|
9172
|
+
);
|
|
9173
|
+
}
|
|
9174
|
+
const chain = ctx.createQuery().from(nodeKind, SEARCH_CANDIDATE_ALIAS).whereNode(SEARCH_CANDIDATE_ALIAS, where);
|
|
9175
|
+
const compiled = chain.select(
|
|
9176
|
+
(aliases) => aliases[SEARCH_CANDIDATE_ALIAS]
|
|
9177
|
+
).compile();
|
|
9178
|
+
return sql`SELECT ${sql.raw(`"${SEARCH_CANDIDATE_ALIAS}_id"`)} AS node_id FROM (${compiled}) AS tg_search_candidates`;
|
|
9179
|
+
}
|
|
9180
|
+
function resolveSearchKinds(ctx, nodeKind, includeSubClasses) {
|
|
9181
|
+
if (includeSubClasses !== true) return [nodeKind];
|
|
9182
|
+
if (ctx.createQuery === void 0) {
|
|
9183
|
+
throw new ConfigurationError(
|
|
9184
|
+
"search with includeSubClasses requires a query-capable store",
|
|
9185
|
+
{ capability: "search", graphId: ctx.graphId }
|
|
9186
|
+
);
|
|
9187
|
+
}
|
|
9188
|
+
return ctx.registry.expandSubClasses(nodeKind);
|
|
9189
|
+
}
|
|
9190
|
+
function assertSearchOffset(offset, label) {
|
|
9191
|
+
if (offset === void 0) return;
|
|
9192
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
9193
|
+
throw new RangeError(
|
|
9194
|
+
`${label} must be a non-negative integer, got: ${offset}`
|
|
9195
|
+
);
|
|
9196
|
+
}
|
|
9197
|
+
}
|
|
9198
|
+
function effectiveFulltextLanguage(ctx, nodeKind, override) {
|
|
9199
|
+
if (override !== void 0) return override;
|
|
8866
9200
|
const nodeType = ctx.registry.getNodeType(nodeKind);
|
|
8867
|
-
if (nodeType
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
9201
|
+
if (nodeType === void 0) return void 0;
|
|
9202
|
+
return resolveDeclaredFulltextLanguage(nodeType.schema);
|
|
9203
|
+
}
|
|
9204
|
+
function searchNodeKey(kind, nodeId) {
|
|
9205
|
+
return `${kind}\0${nodeId}`;
|
|
9206
|
+
}
|
|
9207
|
+
function scoreDescending(metric) {
|
|
9208
|
+
return metric === "cosine";
|
|
9209
|
+
}
|
|
9210
|
+
async function fetchNodesForRows(backend, graphId, rows) {
|
|
9211
|
+
const idsByKind = /* @__PURE__ */ new Map();
|
|
9212
|
+
for (const row of rows) {
|
|
9213
|
+
const ids = idsByKind.get(row.kind) ?? /* @__PURE__ */ new Set();
|
|
9214
|
+
ids.add(row.nodeId);
|
|
9215
|
+
idsByKind.set(row.kind, ids);
|
|
8877
9216
|
}
|
|
9217
|
+
const map = /* @__PURE__ */ new Map();
|
|
9218
|
+
await Promise.all(
|
|
9219
|
+
[...idsByKind].map(async ([kind, ids]) => {
|
|
9220
|
+
const kindMap = await fetchNodesByIds(backend, graphId, kind, [...ids]);
|
|
9221
|
+
for (const [id, node] of kindMap) {
|
|
9222
|
+
map.set(searchNodeKey(kind, id), node);
|
|
9223
|
+
}
|
|
9224
|
+
})
|
|
9225
|
+
);
|
|
9226
|
+
return map;
|
|
9227
|
+
}
|
|
9228
|
+
function tryResolveSearchSlot(ctx, nodeKind, fieldPath) {
|
|
9229
|
+
const nodeType = ctx.registry.getNodeType(nodeKind);
|
|
9230
|
+
if (nodeType === void 0) return void 0;
|
|
9231
|
+
const fields = getEmbeddingFields(nodeType.schema);
|
|
9232
|
+
const field2 = fields.find((entry2) => entry2.fieldPath === fieldPath);
|
|
9233
|
+
if (field2 === void 0) return void 0;
|
|
9234
|
+
return {
|
|
9235
|
+
dimensions: field2.dimensions,
|
|
9236
|
+
metric: field2.metric,
|
|
9237
|
+
indexType: field2.indexType
|
|
9238
|
+
};
|
|
9239
|
+
}
|
|
9240
|
+
function resolveSearchSlot(ctx, nodeKind, fieldPath) {
|
|
9241
|
+
const slot = tryResolveSearchSlot(ctx, nodeKind, fieldPath);
|
|
9242
|
+
if (slot !== void 0) return slot;
|
|
8878
9243
|
throw new ConfigurationError(
|
|
8879
9244
|
`Node kind "${nodeKind}" has no embedding field "${fieldPath}". Declare it with embedding(dimensions) on the node schema.`,
|
|
8880
9245
|
{ capability: "vector", graphId: ctx.graphId }
|
|
@@ -8893,29 +9258,50 @@ async function executeFulltextSearch(ctx, nodeKind, options) {
|
|
|
8893
9258
|
`fulltextSearch.limit must be a positive integer, got: ${options.limit}`
|
|
8894
9259
|
);
|
|
8895
9260
|
}
|
|
9261
|
+
assertSearchOffset(options.offset, "fulltextSearch.offset");
|
|
8896
9262
|
validateFulltextCallOptions(backend, {
|
|
8897
9263
|
mode: options.mode,
|
|
8898
9264
|
includeSnippets: options.includeSnippets,
|
|
8899
9265
|
language: options.language
|
|
8900
9266
|
});
|
|
8901
|
-
const
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
9267
|
+
const kinds = resolveSearchKinds(ctx, nodeKind, options.includeSubClasses);
|
|
9268
|
+
const offset = options.offset ?? 0;
|
|
9269
|
+
const singleKind = kinds.length === 1;
|
|
9270
|
+
const perKindRows = await Promise.all(
|
|
9271
|
+
kinds.map(async (kind) => {
|
|
9272
|
+
const candidates = buildKindCandidates(ctx, kind, options.where);
|
|
9273
|
+
const language = effectiveFulltextLanguage(ctx, kind, options.language);
|
|
9274
|
+
const rows = await backend.fulltextSearch({
|
|
9275
|
+
graphId,
|
|
9276
|
+
nodeKind: kind,
|
|
9277
|
+
query: options.query,
|
|
9278
|
+
// A single kind pushes the page into SQL; multiple kinds fetch
|
|
9279
|
+
// each kind's full page-covering prefix and re-slice after merge.
|
|
9280
|
+
limit: singleKind ? options.limit : options.limit + offset,
|
|
9281
|
+
...singleKind && offset > 0 ? { offset } : {},
|
|
9282
|
+
...candidates === void 0 ? {} : { candidates },
|
|
9283
|
+
...options.mode ? { mode: options.mode } : {},
|
|
9284
|
+
...language === void 0 ? {} : { language },
|
|
9285
|
+
...options.minScore === void 0 ? {} : { minScore: options.minScore },
|
|
9286
|
+
...options.includeSnippets === void 0 ? {} : { includeSnippets: options.includeSnippets }
|
|
9287
|
+
});
|
|
9288
|
+
return rows.map((row) => ({
|
|
9289
|
+
kind,
|
|
9290
|
+
nodeId: row.nodeId,
|
|
9291
|
+
score: row.score,
|
|
9292
|
+
...row.snippet === void 0 ? {} : { snippet: row.snippet }
|
|
9293
|
+
}));
|
|
9294
|
+
})
|
|
9295
|
+
);
|
|
9296
|
+
const merged = singleKind ? perKindRows[0] : perKindRows.flat().toSorted(
|
|
9297
|
+
(a, b) => b.score - a.score || compareCodePoints(a.kind, b.kind) || compareCodePoints(a.nodeId, b.nodeId)
|
|
9298
|
+
).slice(offset, offset + options.limit);
|
|
9299
|
+
if (merged.length === 0) return [];
|
|
9300
|
+
const nodeMap = await fetchNodesForRows(backend, graphId, merged);
|
|
8915
9301
|
const hits = [];
|
|
8916
9302
|
let rank = 1;
|
|
8917
|
-
for (const row of
|
|
8918
|
-
const node = nodeMap.get(row.nodeId);
|
|
9303
|
+
for (const row of merged) {
|
|
9304
|
+
const node = nodeMap.get(searchNodeKey(row.kind, row.nodeId));
|
|
8919
9305
|
if (!node) continue;
|
|
8920
9306
|
hits.push({
|
|
8921
9307
|
node,
|
|
@@ -8941,46 +9327,106 @@ async function executeVectorSearch(ctx, nodeKind, options) {
|
|
|
8941
9327
|
);
|
|
8942
9328
|
}
|
|
8943
9329
|
assertEfSearch(options.efSearch, "vectorSearch.efSearch");
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
9330
|
+
assertSearchOffset(options.offset, "vectorSearch.offset");
|
|
9331
|
+
const searchKinds = resolveVectorSearchKinds(
|
|
9332
|
+
ctx,
|
|
9333
|
+
nodeKind,
|
|
9334
|
+
options.fieldPath,
|
|
9335
|
+
options.includeSubClasses,
|
|
8949
9336
|
"vectorSearch"
|
|
8950
9337
|
);
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
9338
|
+
for (const { slot } of searchKinds) {
|
|
9339
|
+
assertVectorQueryCompatible(
|
|
9340
|
+
backend,
|
|
9341
|
+
slot,
|
|
9342
|
+
{ metric: options.metric, queryEmbedding: options.queryEmbedding },
|
|
9343
|
+
"vectorSearch"
|
|
9344
|
+
);
|
|
9345
|
+
assertMinScore(
|
|
9346
|
+
options.minScore,
|
|
9347
|
+
options.metric ?? slot.metric,
|
|
9348
|
+
"vectorSearch.minScore"
|
|
9349
|
+
);
|
|
9350
|
+
}
|
|
9351
|
+
const offset = options.offset ?? 0;
|
|
9352
|
+
const singleKind = searchKinds.length === 1;
|
|
9353
|
+
const metric = options.metric ?? searchKinds[0].slot.metric;
|
|
9354
|
+
const perKindRows = await Promise.all(
|
|
9355
|
+
searchKinds.map(
|
|
9356
|
+
async ({ kind, slot }) => {
|
|
9357
|
+
const candidates = buildKindCandidates(ctx, kind, options.where);
|
|
9358
|
+
const rows = await backend.vectorSearch({
|
|
9359
|
+
graphId,
|
|
9360
|
+
nodeKind: kind,
|
|
9361
|
+
fieldPath: options.fieldPath,
|
|
9362
|
+
queryEmbedding: options.queryEmbedding,
|
|
9363
|
+
// Default to the field's DECLARED metric (the metric its index was
|
|
9364
|
+
// built for); only an explicit caller override changes it.
|
|
9365
|
+
// Defaulting to cosine here would mis-rank l2 / inner_product
|
|
9366
|
+
// fields and bypass their ANN index.
|
|
9367
|
+
metric,
|
|
9368
|
+
dimensions: slot.dimensions,
|
|
9369
|
+
indexType: slot.indexType,
|
|
9370
|
+
// A single kind pushes the page into SQL; multiple kinds fetch
|
|
9371
|
+
// each kind's full page-covering prefix and re-slice after merge.
|
|
9372
|
+
limit: singleKind ? options.limit : options.limit + offset,
|
|
9373
|
+
...singleKind && offset > 0 ? { offset } : {},
|
|
9374
|
+
...candidates === void 0 ? {} : { candidates },
|
|
9375
|
+
...options.minScore === void 0 ? {} : { minScore: options.minScore },
|
|
9376
|
+
...options.efSearch === void 0 ? {} : { efSearch: options.efSearch }
|
|
9377
|
+
});
|
|
9378
|
+
return rows.map((row) => ({
|
|
9379
|
+
kind,
|
|
9380
|
+
nodeId: row.nodeId,
|
|
9381
|
+
score: row.score
|
|
9382
|
+
}));
|
|
9383
|
+
}
|
|
9384
|
+
)
|
|
9385
|
+
);
|
|
9386
|
+
const merged = singleKind ? perKindRows[0] : mergeVectorRows(perKindRows, metric).slice(
|
|
9387
|
+
offset,
|
|
9388
|
+
offset + options.limit
|
|
8955
9389
|
);
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
nodeKind,
|
|
8959
|
-
fieldPath: options.fieldPath,
|
|
8960
|
-
queryEmbedding: options.queryEmbedding,
|
|
8961
|
-
// Default to the field's DECLARED metric (the metric its index was built
|
|
8962
|
-
// for); only an explicit caller override changes it. Defaulting to cosine
|
|
8963
|
-
// here would mis-rank l2 / inner_product fields and bypass their ANN index.
|
|
8964
|
-
metric: options.metric ?? slot.metric,
|
|
8965
|
-
dimensions: slot.dimensions,
|
|
8966
|
-
indexType: slot.indexType,
|
|
8967
|
-
limit: options.limit,
|
|
8968
|
-
...options.minScore === void 0 ? {} : { minScore: options.minScore },
|
|
8969
|
-
...options.efSearch === void 0 ? {} : { efSearch: options.efSearch }
|
|
8970
|
-
});
|
|
8971
|
-
if (rows.length === 0) return [];
|
|
8972
|
-
const ids = rows.map((row) => row.nodeId);
|
|
8973
|
-
const nodeMap = await fetchNodesByIds(backend, graphId, nodeKind, ids);
|
|
9390
|
+
if (merged.length === 0) return [];
|
|
9391
|
+
const nodeMap = await fetchNodesForRows(backend, graphId, merged);
|
|
8974
9392
|
const hits = [];
|
|
8975
9393
|
let rank = 1;
|
|
8976
|
-
for (const row of
|
|
8977
|
-
const node = nodeMap.get(row.nodeId);
|
|
9394
|
+
for (const row of merged) {
|
|
9395
|
+
const node = nodeMap.get(searchNodeKey(row.kind, row.nodeId));
|
|
8978
9396
|
if (!node) continue;
|
|
8979
9397
|
hits.push({ node, score: row.score, rank });
|
|
8980
9398
|
rank += 1;
|
|
8981
9399
|
}
|
|
8982
9400
|
return hits;
|
|
8983
9401
|
}
|
|
9402
|
+
function resolveVectorSearchKinds(ctx, nodeKind, fieldPath, includeSubClasses, label) {
|
|
9403
|
+
const kinds = resolveSearchKinds(ctx, nodeKind, includeSubClasses);
|
|
9404
|
+
const resolved = [];
|
|
9405
|
+
for (const kind of kinds) {
|
|
9406
|
+
const slot = tryResolveSearchSlot(ctx, kind, fieldPath);
|
|
9407
|
+
if (slot !== void 0) resolved.push({ kind, slot });
|
|
9408
|
+
}
|
|
9409
|
+
if (resolved.length === 0) resolveSearchSlot(ctx, nodeKind, fieldPath);
|
|
9410
|
+
const metrics = new Set(resolved.map(({ slot }) => slot.metric));
|
|
9411
|
+
if (metrics.size > 1) {
|
|
9412
|
+
throw new ConfigurationError(
|
|
9413
|
+
`${label}: kinds expanded from "${nodeKind}" declare different metrics for "${fieldPath}" (${[...metrics].join(", ")}). Cross-kind vector ranking requires one shared declared metric \u2014 search the kinds separately.`,
|
|
9414
|
+
{ capability: "vector", graphId: ctx.graphId }
|
|
9415
|
+
);
|
|
9416
|
+
}
|
|
9417
|
+
return resolved;
|
|
9418
|
+
}
|
|
9419
|
+
function mergeVectorRows(perKindRows, metric) {
|
|
9420
|
+
const descending = scoreDescending(metric);
|
|
9421
|
+
return perKindRows.flat().toSorted(
|
|
9422
|
+
(a, b) => (descending ? b.score - a.score : a.score - b.score) || compareCodePoints(a.kind, b.kind) || compareCodePoints(a.nodeId, b.nodeId)
|
|
9423
|
+
);
|
|
9424
|
+
}
|
|
9425
|
+
function mergeFulltextRows(perKindRows) {
|
|
9426
|
+
return perKindRows.flat().toSorted(
|
|
9427
|
+
(a, b) => b.score - a.score || compareCodePoints(a.kind, b.kind) || compareCodePoints(a.nodeId, b.nodeId)
|
|
9428
|
+
);
|
|
9429
|
+
}
|
|
8984
9430
|
async function executeHybridSearch(ctx, nodeKind, options) {
|
|
8985
9431
|
const { backend, graphId } = ctx;
|
|
8986
9432
|
if (!backend.vectorSearch) {
|
|
@@ -9009,50 +9455,165 @@ async function executeHybridSearch(ctx, nodeKind, options) {
|
|
|
9009
9455
|
includeSnippets: options.fulltext.includeSnippets,
|
|
9010
9456
|
language: options.fulltext.language
|
|
9011
9457
|
});
|
|
9458
|
+
assertSearchOffset(options.offset, "hybridSearch.offset");
|
|
9012
9459
|
const fusionK = options.fusion?.k ?? DEFAULT_RRF_K;
|
|
9013
9460
|
const vectorWeight = options.fusion?.weights?.vector ?? DEFAULT_RRF_WEIGHT;
|
|
9014
9461
|
const fulltextWeight = options.fusion?.weights?.fulltext ?? DEFAULT_RRF_WEIGHT;
|
|
9462
|
+
const offset = options.offset ?? 0;
|
|
9015
9463
|
const overFetchMultiplier = 4;
|
|
9016
|
-
const
|
|
9017
|
-
const
|
|
9018
|
-
const
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
vectorSlot,
|
|
9022
|
-
{
|
|
9023
|
-
metric: options.vector.metric,
|
|
9024
|
-
queryEmbedding: options.vector.queryEmbedding
|
|
9025
|
-
},
|
|
9026
|
-
"hybridSearch.vector"
|
|
9027
|
-
);
|
|
9028
|
-
assertMinScore(
|
|
9029
|
-
options.vector.minScore,
|
|
9030
|
-
options.vector.metric ?? vectorSlot.metric,
|
|
9031
|
-
"hybridSearch.vector.minScore"
|
|
9032
|
-
);
|
|
9033
|
-
const vectorPromise = backend.vectorSearch({
|
|
9034
|
-
graphId,
|
|
9464
|
+
const pageLimit = options.limit + offset;
|
|
9465
|
+
const vectorK = options.vector.k ?? pageLimit * overFetchMultiplier;
|
|
9466
|
+
const fulltextK = options.fulltext.k ?? pageLimit * overFetchMultiplier;
|
|
9467
|
+
const fulltextKinds = resolveSearchKinds(
|
|
9468
|
+
ctx,
|
|
9035
9469
|
nodeKind,
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
dimensions: vectorSlot.dimensions,
|
|
9041
|
-
indexType: vectorSlot.indexType,
|
|
9042
|
-
limit: vectorK,
|
|
9043
|
-
...options.vector.minScore === void 0 ? {} : { minScore: options.vector.minScore },
|
|
9044
|
-
...options.vector.efSearch === void 0 ? {} : { efSearch: options.vector.efSearch }
|
|
9045
|
-
});
|
|
9046
|
-
const fulltextPromise = backend.fulltextSearch({
|
|
9047
|
-
graphId,
|
|
9470
|
+
options.includeSubClasses
|
|
9471
|
+
);
|
|
9472
|
+
const vectorKinds = resolveVectorSearchKinds(
|
|
9473
|
+
ctx,
|
|
9048
9474
|
nodeKind,
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9475
|
+
options.vector.fieldPath,
|
|
9476
|
+
options.includeSubClasses,
|
|
9477
|
+
"hybridSearch.vector"
|
|
9478
|
+
);
|
|
9479
|
+
for (const { slot } of vectorKinds) {
|
|
9480
|
+
assertVectorQueryCompatible(
|
|
9481
|
+
backend,
|
|
9482
|
+
slot,
|
|
9483
|
+
{
|
|
9484
|
+
metric: options.vector.metric,
|
|
9485
|
+
queryEmbedding: options.vector.queryEmbedding
|
|
9486
|
+
},
|
|
9487
|
+
"hybridSearch.vector"
|
|
9488
|
+
);
|
|
9489
|
+
assertMinScore(
|
|
9490
|
+
options.vector.minScore,
|
|
9491
|
+
options.vector.metric ?? slot.metric,
|
|
9492
|
+
"hybridSearch.vector.minScore"
|
|
9493
|
+
);
|
|
9494
|
+
}
|
|
9495
|
+
const vectorMetric = options.vector.metric ?? vectorKinds[0].slot.metric;
|
|
9496
|
+
const candidatesByKind = /* @__PURE__ */ new Map();
|
|
9497
|
+
for (const kind of fulltextKinds) {
|
|
9498
|
+
candidatesByKind.set(kind, buildKindCandidates(ctx, kind, options.where));
|
|
9499
|
+
}
|
|
9500
|
+
for (const { kind } of vectorKinds) {
|
|
9501
|
+
if (!candidatesByKind.has(kind)) {
|
|
9502
|
+
candidatesByKind.set(kind, buildKindCandidates(ctx, kind, options.where));
|
|
9503
|
+
}
|
|
9504
|
+
}
|
|
9505
|
+
if (backend.hybridSearch !== void 0 && fulltextKinds.length === 1 && vectorKinds.length === 1 && fulltextKinds[0] === vectorKinds[0].kind) {
|
|
9506
|
+
const kind = fulltextKinds[0];
|
|
9507
|
+
const { slot } = vectorKinds[0];
|
|
9508
|
+
const candidates = candidatesByKind.get(kind);
|
|
9509
|
+
const hybridFulltextLanguage = effectiveFulltextLanguage(
|
|
9510
|
+
ctx,
|
|
9511
|
+
kind,
|
|
9512
|
+
options.fulltext.language
|
|
9513
|
+
);
|
|
9514
|
+
const rows = await backend.hybridSearch({
|
|
9515
|
+
graphId,
|
|
9516
|
+
nodeKind: kind,
|
|
9517
|
+
vector: {
|
|
9518
|
+
fieldPath: options.vector.fieldPath,
|
|
9519
|
+
queryEmbedding: options.vector.queryEmbedding,
|
|
9520
|
+
metric: vectorMetric,
|
|
9521
|
+
dimensions: slot.dimensions,
|
|
9522
|
+
indexType: slot.indexType,
|
|
9523
|
+
k: vectorK,
|
|
9524
|
+
...options.vector.minScore === void 0 ? {} : { minScore: options.vector.minScore },
|
|
9525
|
+
...options.vector.efSearch === void 0 ? {} : { efSearch: options.vector.efSearch }
|
|
9526
|
+
},
|
|
9527
|
+
fulltext: {
|
|
9528
|
+
query: options.fulltext.query,
|
|
9529
|
+
k: fulltextK,
|
|
9530
|
+
...options.fulltext.mode ? { mode: options.fulltext.mode } : {},
|
|
9531
|
+
...hybridFulltextLanguage === void 0 ? {} : { language: hybridFulltextLanguage },
|
|
9532
|
+
...options.fulltext.minScore === void 0 ? {} : { minScore: options.fulltext.minScore },
|
|
9533
|
+
...options.fulltext.includeSnippets === void 0 ? {} : { includeSnippets: options.fulltext.includeSnippets }
|
|
9534
|
+
},
|
|
9535
|
+
fusion: { k: fusionK, vectorWeight, fulltextWeight },
|
|
9536
|
+
limit: options.limit,
|
|
9537
|
+
...offset === 0 ? {} : { offset },
|
|
9538
|
+
...candidates === void 0 ? {} : { candidates }
|
|
9539
|
+
});
|
|
9540
|
+
return rows.map((row, index) => {
|
|
9541
|
+
const typedNode = rowToNode(row.node);
|
|
9542
|
+
return {
|
|
9543
|
+
node: typedNode,
|
|
9544
|
+
score: row.fusedScore,
|
|
9545
|
+
rank: index + 1,
|
|
9546
|
+
...row.vectorRank !== void 0 && row.vectorScore !== void 0 ? {
|
|
9547
|
+
vector: {
|
|
9548
|
+
node: typedNode,
|
|
9549
|
+
score: row.vectorScore,
|
|
9550
|
+
rank: row.vectorRank
|
|
9551
|
+
}
|
|
9552
|
+
} : {},
|
|
9553
|
+
...row.fulltextRank !== void 0 && row.fulltextScore !== void 0 ? {
|
|
9554
|
+
fulltext: {
|
|
9555
|
+
node: typedNode,
|
|
9556
|
+
score: row.fulltextScore,
|
|
9557
|
+
rank: row.fulltextRank,
|
|
9558
|
+
...row.snippet === void 0 ? {} : { snippet: row.snippet }
|
|
9559
|
+
}
|
|
9560
|
+
} : {}
|
|
9561
|
+
};
|
|
9562
|
+
});
|
|
9563
|
+
}
|
|
9564
|
+
const vectorPromise = Promise.all(
|
|
9565
|
+
vectorKinds.map(
|
|
9566
|
+
async ({ kind, slot }) => {
|
|
9567
|
+
const candidates = candidatesByKind.get(kind);
|
|
9568
|
+
const rows = await backend.vectorSearch({
|
|
9569
|
+
graphId,
|
|
9570
|
+
nodeKind: kind,
|
|
9571
|
+
fieldPath: options.vector.fieldPath,
|
|
9572
|
+
queryEmbedding: options.vector.queryEmbedding,
|
|
9573
|
+
// Default to the field's declared metric (see executeVectorSearch).
|
|
9574
|
+
metric: vectorMetric,
|
|
9575
|
+
dimensions: slot.dimensions,
|
|
9576
|
+
indexType: slot.indexType,
|
|
9577
|
+
limit: vectorK,
|
|
9578
|
+
...candidates === void 0 ? {} : { candidates },
|
|
9579
|
+
...options.vector.minScore === void 0 ? {} : { minScore: options.vector.minScore },
|
|
9580
|
+
...options.vector.efSearch === void 0 ? {} : { efSearch: options.vector.efSearch }
|
|
9581
|
+
});
|
|
9582
|
+
return rows.map((row) => ({
|
|
9583
|
+
kind,
|
|
9584
|
+
nodeId: row.nodeId,
|
|
9585
|
+
score: row.score
|
|
9586
|
+
}));
|
|
9587
|
+
}
|
|
9588
|
+
)
|
|
9589
|
+
).then((perKind) => mergeVectorRows(perKind, vectorMetric).slice(0, vectorK));
|
|
9590
|
+
const fulltextPromise = Promise.all(
|
|
9591
|
+
fulltextKinds.map(async (kind) => {
|
|
9592
|
+
const candidates = candidatesByKind.get(kind);
|
|
9593
|
+
const language = effectiveFulltextLanguage(
|
|
9594
|
+
ctx,
|
|
9595
|
+
kind,
|
|
9596
|
+
options.fulltext.language
|
|
9597
|
+
);
|
|
9598
|
+
const rows = await backend.fulltextSearch({
|
|
9599
|
+
graphId,
|
|
9600
|
+
nodeKind: kind,
|
|
9601
|
+
query: options.fulltext.query,
|
|
9602
|
+
limit: fulltextK,
|
|
9603
|
+
...candidates === void 0 ? {} : { candidates },
|
|
9604
|
+
...options.fulltext.mode ? { mode: options.fulltext.mode } : {},
|
|
9605
|
+
...language === void 0 ? {} : { language },
|
|
9606
|
+
...options.fulltext.minScore === void 0 ? {} : { minScore: options.fulltext.minScore },
|
|
9607
|
+
...options.fulltext.includeSnippets === void 0 ? {} : { includeSnippets: options.fulltext.includeSnippets }
|
|
9608
|
+
});
|
|
9609
|
+
return rows.map((row) => ({
|
|
9610
|
+
kind,
|
|
9611
|
+
nodeId: row.nodeId,
|
|
9612
|
+
score: row.score,
|
|
9613
|
+
...row.snippet === void 0 ? {} : { snippet: row.snippet }
|
|
9614
|
+
}));
|
|
9615
|
+
})
|
|
9616
|
+
).then((perKind) => mergeFulltextRows(perKind).slice(0, fulltextK));
|
|
9056
9617
|
const [vectorRows, fulltextRows] = await Promise.all([
|
|
9057
9618
|
vectorPromise,
|
|
9058
9619
|
fulltextPromise
|
|
@@ -9061,19 +9622,23 @@ async function executeHybridSearch(ctx, nodeKind, options) {
|
|
|
9061
9622
|
for (const [index, row] of vectorRows.entries()) {
|
|
9062
9623
|
const rank2 = index + 1;
|
|
9063
9624
|
const contribution = vectorWeight / (fusionK + rank2);
|
|
9064
|
-
const
|
|
9625
|
+
const key = searchNodeKey(row.kind, row.nodeId);
|
|
9626
|
+
const entry2 = fused.get(key) ?? {
|
|
9627
|
+
kind: row.kind,
|
|
9065
9628
|
nodeId: row.nodeId,
|
|
9066
9629
|
fusedScore: 0
|
|
9067
9630
|
};
|
|
9068
9631
|
entry2.fusedScore += contribution;
|
|
9069
9632
|
entry2.vectorRank = rank2;
|
|
9070
9633
|
entry2.vectorScore = row.score;
|
|
9071
|
-
fused.set(
|
|
9634
|
+
fused.set(key, entry2);
|
|
9072
9635
|
}
|
|
9073
9636
|
for (const [index, row] of fulltextRows.entries()) {
|
|
9074
9637
|
const rank2 = index + 1;
|
|
9075
9638
|
const contribution = fulltextWeight / (fusionK + rank2);
|
|
9076
|
-
const
|
|
9639
|
+
const key = searchNodeKey(row.kind, row.nodeId);
|
|
9640
|
+
const entry2 = fused.get(key) ?? {
|
|
9641
|
+
kind: row.kind,
|
|
9077
9642
|
nodeId: row.nodeId,
|
|
9078
9643
|
fusedScore: 0
|
|
9079
9644
|
};
|
|
@@ -9083,17 +9648,24 @@ async function executeHybridSearch(ctx, nodeKind, options) {
|
|
|
9083
9648
|
if (row.snippet !== void 0) {
|
|
9084
9649
|
entry2.fulltextSnippet = row.snippet;
|
|
9085
9650
|
}
|
|
9086
|
-
fused.set(
|
|
9651
|
+
fused.set(key, entry2);
|
|
9087
9652
|
}
|
|
9088
9653
|
const ranked = [...fused.values()].toSorted(
|
|
9089
|
-
(a, b) => b.fusedScore - a.fusedScore ||
|
|
9090
|
-
).slice(
|
|
9091
|
-
const
|
|
9092
|
-
|
|
9654
|
+
(a, b) => b.fusedScore - a.fusedScore || compareCodePoints(a.kind, b.kind) || compareCodePoints(a.nodeId, b.nodeId)
|
|
9655
|
+
).slice(offset, offset + options.limit);
|
|
9656
|
+
const nodeMap = await fetchNodesForRows(
|
|
9657
|
+
backend,
|
|
9658
|
+
graphId,
|
|
9659
|
+
ranked.map((entry2) => ({
|
|
9660
|
+
kind: entry2.kind,
|
|
9661
|
+
nodeId: entry2.nodeId,
|
|
9662
|
+
score: entry2.fusedScore
|
|
9663
|
+
}))
|
|
9664
|
+
);
|
|
9093
9665
|
const hits = [];
|
|
9094
9666
|
let rank = 1;
|
|
9095
9667
|
for (const entry2 of ranked) {
|
|
9096
|
-
const node = nodeMap.get(entry2.nodeId);
|
|
9668
|
+
const node = nodeMap.get(searchNodeKey(entry2.kind, entry2.nodeId));
|
|
9097
9669
|
if (!node) continue;
|
|
9098
9670
|
const typedNode = node;
|
|
9099
9671
|
const hit = {
|
|
@@ -9259,12 +9831,10 @@ var StoreSearch = class {
|
|
|
9259
9831
|
async fulltext(nodeKind, options) {
|
|
9260
9832
|
this.#assertKindRegistered(nodeKind);
|
|
9261
9833
|
return executeFulltextSearch(
|
|
9262
|
-
|
|
9263
|
-
graphId: this.#context.graphId,
|
|
9264
|
-
backend: this.#context.backend,
|
|
9265
|
-
registry: this.#context.registry
|
|
9266
|
-
},
|
|
9834
|
+
this.#context,
|
|
9267
9835
|
nodeKind,
|
|
9836
|
+
// Contravariance: the narrowed accessor callback is intentionally
|
|
9837
|
+
// wider than the base instantiation the core helpers take.
|
|
9268
9838
|
options
|
|
9269
9839
|
);
|
|
9270
9840
|
}
|
|
@@ -9282,12 +9852,10 @@ var StoreSearch = class {
|
|
|
9282
9852
|
async vector(nodeKind, options) {
|
|
9283
9853
|
this.#assertKindRegistered(nodeKind);
|
|
9284
9854
|
return executeVectorSearch(
|
|
9285
|
-
|
|
9286
|
-
graphId: this.#context.graphId,
|
|
9287
|
-
backend: this.#context.backend,
|
|
9288
|
-
registry: this.#context.registry
|
|
9289
|
-
},
|
|
9855
|
+
this.#context,
|
|
9290
9856
|
nodeKind,
|
|
9857
|
+
// Contravariance: the narrowed accessor callback is intentionally
|
|
9858
|
+
// wider than the base instantiation the core helpers take.
|
|
9291
9859
|
options
|
|
9292
9860
|
);
|
|
9293
9861
|
}
|
|
@@ -9303,12 +9871,10 @@ var StoreSearch = class {
|
|
|
9303
9871
|
async hybrid(nodeKind, options) {
|
|
9304
9872
|
this.#assertKindRegistered(nodeKind);
|
|
9305
9873
|
return executeHybridSearch(
|
|
9306
|
-
|
|
9307
|
-
graphId: this.#context.graphId,
|
|
9308
|
-
backend: this.#context.backend,
|
|
9309
|
-
registry: this.#context.registry
|
|
9310
|
-
},
|
|
9874
|
+
this.#context,
|
|
9311
9875
|
nodeKind,
|
|
9876
|
+
// Contravariance: the narrowed accessor callback is intentionally
|
|
9877
|
+
// wider than the base instantiation the core helpers take.
|
|
9312
9878
|
options
|
|
9313
9879
|
);
|
|
9314
9880
|
}
|
|
@@ -9888,30 +10454,6 @@ function buildCompileTimeReferentIndex(graph, runtimeEdgeNames) {
|
|
|
9888
10454
|
return index;
|
|
9889
10455
|
}
|
|
9890
10456
|
|
|
9891
|
-
// src/registry/builders.ts
|
|
9892
|
-
function buildKindRegistry(graph) {
|
|
9893
|
-
const nodeTypes = extractNodeTypes(graph);
|
|
9894
|
-
const edgeTypes = extractEdgeTypes(graph);
|
|
9895
|
-
const closures = graph.ontology.length > 0 ? computeClosuresFromOntology(graph.ontology) : createEmptyClosures();
|
|
9896
|
-
return new KindRegistry(nodeTypes, edgeTypes, closures);
|
|
9897
|
-
}
|
|
9898
|
-
function extractNodeTypes(graph) {
|
|
9899
|
-
const result = /* @__PURE__ */ new Map();
|
|
9900
|
-
for (const typeName of getNodeKinds(graph)) {
|
|
9901
|
-
const registration = graph.nodes[typeName];
|
|
9902
|
-
result.set(typeName, registration.type);
|
|
9903
|
-
}
|
|
9904
|
-
return result;
|
|
9905
|
-
}
|
|
9906
|
-
function extractEdgeTypes(graph) {
|
|
9907
|
-
const result = /* @__PURE__ */ new Map();
|
|
9908
|
-
for (const typeName of getEdgeKinds(graph)) {
|
|
9909
|
-
const registration = graph.edges[typeName];
|
|
9910
|
-
result.set(typeName, registration.type);
|
|
9911
|
-
}
|
|
9912
|
-
return result;
|
|
9913
|
-
}
|
|
9914
|
-
|
|
9915
10457
|
// src/store/algorithms/context.ts
|
|
9916
10458
|
var DEFAULT_ALGORITHM_MAX_HOPS = 10;
|
|
9917
10459
|
var DEFAULT_NEIGHBOR_DEPTH = 1;
|
|
@@ -9944,7 +10486,7 @@ function resolveTemporalFilter(ctx, options, tableAlias) {
|
|
|
9944
10486
|
asOf: resolved.asOf,
|
|
9945
10487
|
recordedAsOf: resolved.recordedAsOf,
|
|
9946
10488
|
tableAlias,
|
|
9947
|
-
currentTimestamp:
|
|
10489
|
+
currentTimestamp: currentReadInstant()
|
|
9948
10490
|
};
|
|
9949
10491
|
return compileTemporalFilter(filterOptions);
|
|
9950
10492
|
}
|
|
@@ -9982,15 +10524,17 @@ function assertEdgeKinds(edges) {
|
|
|
9982
10524
|
// src/store/algorithms/degree.ts
|
|
9983
10525
|
async function executeDegree(ctx, nodeId, options = {}) {
|
|
9984
10526
|
const direction = options.direction ?? "both";
|
|
10527
|
+
const edgeKinds = options.edges ?? Object.keys(ctx.graph.edges);
|
|
10528
|
+
if (edgeKinds.length === 0) return 0;
|
|
10529
|
+
const schema = resolveReadSchema(ctx, options);
|
|
9985
10530
|
const whereClauses = [
|
|
9986
10531
|
sql`graph_id = ${ctx.graphId}`,
|
|
9987
10532
|
resolveTemporalFilter(ctx, options),
|
|
9988
|
-
compileDirectionFilter(direction, nodeId)
|
|
10533
|
+
compileDirectionFilter(ctx, direction, nodeId, schema.nodesTable)
|
|
9989
10534
|
];
|
|
9990
10535
|
if (options.edges !== void 0) {
|
|
9991
10536
|
whereClauses.push(compileKindFilter(sql.raw("kind"), options.edges));
|
|
9992
10537
|
}
|
|
9993
|
-
const schema = resolveReadSchema(ctx, options);
|
|
9994
10538
|
const countExpr = direction === "both" ? sql`COUNT(DISTINCT id)` : sql`COUNT(*)`;
|
|
9995
10539
|
const query = sql`SELECT ${countExpr} AS count FROM ${schema.edgesTable} WHERE ${sql.join(whereClauses, sql` AND `)}`;
|
|
9996
10540
|
const rows = await ctx.backend.execute(
|
|
@@ -9998,16 +10542,22 @@ async function executeDegree(ctx, nodeId, options = {}) {
|
|
|
9998
10542
|
);
|
|
9999
10543
|
return Number(rows[0]?.count ?? 0);
|
|
10000
10544
|
}
|
|
10001
|
-
function
|
|
10545
|
+
function nodeKindSubquery(ctx, nodeId, nodesTable) {
|
|
10546
|
+
return sql`(SELECT kind FROM ${nodesTable} WHERE graph_id = ${ctx.graphId} AND id = ${nodeId} LIMIT 1)`;
|
|
10547
|
+
}
|
|
10548
|
+
function compileDirectionFilter(ctx, direction, nodeId, nodesTable) {
|
|
10549
|
+
const nodeKind = nodeKindSubquery(ctx, nodeId, nodesTable);
|
|
10550
|
+
const fromSide = sql`(from_kind = ${nodeKind} AND from_id = ${nodeId})`;
|
|
10551
|
+
const toSide = sql`(to_kind = ${nodeKind} AND to_id = ${nodeId})`;
|
|
10002
10552
|
switch (direction) {
|
|
10003
10553
|
case "out": {
|
|
10004
|
-
return
|
|
10554
|
+
return fromSide;
|
|
10005
10555
|
}
|
|
10006
10556
|
case "in": {
|
|
10007
|
-
return
|
|
10557
|
+
return toSide;
|
|
10008
10558
|
}
|
|
10009
10559
|
case "both": {
|
|
10010
|
-
return sql`(
|
|
10560
|
+
return sql`(${fromSide} OR ${toSide})`;
|
|
10011
10561
|
}
|
|
10012
10562
|
}
|
|
10013
10563
|
}
|
|
@@ -10161,6 +10711,8 @@ function createGraphAlgorithms(params) {
|
|
|
10161
10711
|
const allowRecordedAsOf = params.allowRecordedAsOf === true;
|
|
10162
10712
|
const ctx = {
|
|
10163
10713
|
graphId: params.graphId,
|
|
10714
|
+
graph: params.graph,
|
|
10715
|
+
registry: params.registry,
|
|
10164
10716
|
backend: params.backend,
|
|
10165
10717
|
dialect: getDialect(params.backend.dialect),
|
|
10166
10718
|
schema: params.schema ?? createSqlSchema(params.backend.tableNames),
|
|
@@ -10391,9 +10943,11 @@ function createEdgeCollection(config) {
|
|
|
10391
10943
|
const results2 = await backend.transaction(
|
|
10392
10944
|
async (txBackend) => executeEdgeCreateBatch2(batchInputs, txBackend)
|
|
10393
10945
|
);
|
|
10946
|
+
await config.maybeRefreshStatisticsAfterBulk?.(results2.length);
|
|
10394
10947
|
return narrowEdges(results2);
|
|
10395
10948
|
}
|
|
10396
10949
|
const results = await executeEdgeCreateBatch2(batchInputs, backend);
|
|
10950
|
+
await config.maybeRefreshStatisticsAfterBulk?.(results.length);
|
|
10397
10951
|
return narrowEdges(results);
|
|
10398
10952
|
},
|
|
10399
10953
|
async bulkUpsertById(items) {
|
|
@@ -10426,26 +10980,25 @@ function createEdgeCollection(config) {
|
|
|
10426
10980
|
}
|
|
10427
10981
|
itemIndex++;
|
|
10428
10982
|
}
|
|
10429
|
-
const
|
|
10983
|
+
const results2 = Array.from({ length: items.length });
|
|
10430
10984
|
if (toCreate.length > 0) {
|
|
10431
10985
|
const createInputs = toCreate.map((entry2) => entry2.input);
|
|
10432
10986
|
const created = await executeEdgeCreateBatch2(createInputs, target);
|
|
10433
10987
|
for (const [index, entry2] of toCreate.entries()) {
|
|
10434
|
-
|
|
10988
|
+
results2[entry2.index] = narrowEdge(created[index]);
|
|
10435
10989
|
}
|
|
10436
10990
|
}
|
|
10437
10991
|
for (const entry2 of toUpdate) {
|
|
10438
10992
|
const result = await executeEdgeUpsertUpdate2(entry2.input, target, {
|
|
10439
10993
|
clearDeleted: entry2.clearDeleted
|
|
10440
10994
|
});
|
|
10441
|
-
|
|
10995
|
+
results2[entry2.index] = narrowEdge(result);
|
|
10442
10996
|
}
|
|
10443
|
-
return
|
|
10997
|
+
return results2;
|
|
10444
10998
|
};
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
return upsertAll(backend);
|
|
10999
|
+
const results = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
|
|
11000
|
+
await config.maybeRefreshStatisticsAfterBulk?.(results.length);
|
|
11001
|
+
return results;
|
|
10449
11002
|
},
|
|
10450
11003
|
async bulkInsert(items) {
|
|
10451
11004
|
const batchInputs = mapBulkEdgeInputs(kind, items);
|
|
@@ -10453,9 +11006,11 @@ function createEdgeCollection(config) {
|
|
|
10453
11006
|
await backend.transaction(async (txBackend) => {
|
|
10454
11007
|
await executeEdgeCreateNoReturnBatch2(batchInputs, txBackend);
|
|
10455
11008
|
});
|
|
11009
|
+
await config.maybeRefreshStatisticsAfterBulk?.(batchInputs.length);
|
|
10456
11010
|
return;
|
|
10457
11011
|
}
|
|
10458
11012
|
await executeEdgeCreateNoReturnBatch2(batchInputs, backend);
|
|
11013
|
+
await config.maybeRefreshStatisticsAfterBulk?.(batchInputs.length);
|
|
10459
11014
|
},
|
|
10460
11015
|
async bulkDelete(ids) {
|
|
10461
11016
|
if (ids.length === 0) return;
|
|
@@ -10683,9 +11238,11 @@ function createNodeCollection(config) {
|
|
|
10683
11238
|
const results2 = await backend.transaction(
|
|
10684
11239
|
async (txBackend) => executeNodeCreateBatch2(batchInputs, txBackend)
|
|
10685
11240
|
);
|
|
11241
|
+
await config.maybeRefreshStatisticsAfterBulk?.(results2.length);
|
|
10686
11242
|
return narrowNodes(results2);
|
|
10687
11243
|
}
|
|
10688
11244
|
const results = await executeNodeCreateBatch2(batchInputs, backend);
|
|
11245
|
+
await config.maybeRefreshStatisticsAfterBulk?.(results.length);
|
|
10689
11246
|
return narrowNodes(results);
|
|
10690
11247
|
},
|
|
10691
11248
|
async bulkUpsertById(items) {
|
|
@@ -10725,26 +11282,25 @@ function createNodeCollection(config) {
|
|
|
10725
11282
|
}
|
|
10726
11283
|
itemIndex++;
|
|
10727
11284
|
}
|
|
10728
|
-
const
|
|
11285
|
+
const results2 = Array.from({ length: items.length });
|
|
10729
11286
|
if (toCreate.length > 0) {
|
|
10730
11287
|
const createInputs = toCreate.map((entry2) => entry2.input);
|
|
10731
11288
|
const created = await executeNodeCreateBatch2(createInputs, target);
|
|
10732
11289
|
for (const [index, entry2] of toCreate.entries()) {
|
|
10733
|
-
|
|
11290
|
+
results2[entry2.index] = narrowNode(created[index]);
|
|
10734
11291
|
}
|
|
10735
11292
|
}
|
|
10736
11293
|
for (const entry2 of toUpdate) {
|
|
10737
11294
|
const result = await executeNodeUpsertUpdate2(entry2.input, target, {
|
|
10738
11295
|
clearDeleted: entry2.clearDeleted
|
|
10739
11296
|
});
|
|
10740
|
-
|
|
11297
|
+
results2[entry2.index] = narrowNode(result);
|
|
10741
11298
|
}
|
|
10742
|
-
return
|
|
11299
|
+
return results2;
|
|
10743
11300
|
};
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
return upsertAll(backend);
|
|
11301
|
+
const results = backend.capabilities.transactions && "transaction" in backend ? await backend.transaction(async (txBackend) => upsertAll(txBackend)) : await upsertAll(backend);
|
|
11302
|
+
await config.maybeRefreshStatisticsAfterBulk?.(results.length);
|
|
11303
|
+
return results;
|
|
10748
11304
|
},
|
|
10749
11305
|
async bulkInsert(items) {
|
|
10750
11306
|
const batchInputs = mapBulkNodeInputs(kind, items);
|
|
@@ -10752,9 +11308,11 @@ function createNodeCollection(config) {
|
|
|
10752
11308
|
await backend.transaction(async (txBackend) => {
|
|
10753
11309
|
await executeNodeCreateNoReturnBatch2(batchInputs, txBackend);
|
|
10754
11310
|
});
|
|
11311
|
+
await config.maybeRefreshStatisticsAfterBulk?.(batchInputs.length);
|
|
10755
11312
|
return;
|
|
10756
11313
|
}
|
|
10757
11314
|
await executeNodeCreateNoReturnBatch2(batchInputs, backend);
|
|
11315
|
+
await config.maybeRefreshStatisticsAfterBulk?.(batchInputs.length);
|
|
10758
11316
|
},
|
|
10759
11317
|
async bulkDelete(ids) {
|
|
10760
11318
|
if (ids.length === 0) return;
|
|
@@ -10969,6 +11527,17 @@ async function runMaterialization(items, options, runOne) {
|
|
|
10969
11527
|
}
|
|
10970
11528
|
|
|
10971
11529
|
// src/store/materialize-indexes.ts
|
|
11530
|
+
var CLAIM_LEASE_MS = 15 * 6e4;
|
|
11531
|
+
var CLAIM_RETRY_DELAY_MS = 200;
|
|
11532
|
+
var CLAIM_WAIT_TIMEOUT_MS = CLAIM_LEASE_MS + 6e4;
|
|
11533
|
+
function hasIndexBuildClaimProtocol(backend) {
|
|
11534
|
+
return backend.claimIndexMaterialization !== void 0 && backend.releaseIndexMaterializationClaim !== void 0;
|
|
11535
|
+
}
|
|
11536
|
+
function delay(ms) {
|
|
11537
|
+
return new Promise((resolve) => {
|
|
11538
|
+
setTimeout(resolve, ms);
|
|
11539
|
+
});
|
|
11540
|
+
}
|
|
10972
11541
|
async function materializeIndexes(context, options = {}) {
|
|
10973
11542
|
const { graph, graphId, backend, schemaVersion } = context;
|
|
10974
11543
|
if (backend.executeDdl === void 0) {
|
|
@@ -11024,12 +11593,18 @@ async function materializeIndexes(context, options = {}) {
|
|
|
11024
11593
|
backend,
|
|
11025
11594
|
statusKeys
|
|
11026
11595
|
);
|
|
11596
|
+
const relationalPhysicalNames = candidates.filter((declaration) => declaration.entity !== "vector").map((declaration) => declaration.name);
|
|
11597
|
+
const invalidLeftovers = await preloadInvalidIndexLeftovers(
|
|
11598
|
+
backend,
|
|
11599
|
+
relationalPhysicalNames
|
|
11600
|
+
);
|
|
11027
11601
|
const materializeEntry = (declaration) => declaration.entity === "vector" ? materializeVectorIndex(
|
|
11028
11602
|
declaration,
|
|
11029
11603
|
backend,
|
|
11030
11604
|
graphId,
|
|
11031
11605
|
schemaVersion,
|
|
11032
|
-
existingByStatusKey
|
|
11606
|
+
existingByStatusKey,
|
|
11607
|
+
invalidLeftovers
|
|
11033
11608
|
) : materializeRelationalIndex(
|
|
11034
11609
|
declaration,
|
|
11035
11610
|
backend,
|
|
@@ -11037,16 +11612,23 @@ async function materializeIndexes(context, options = {}) {
|
|
|
11037
11612
|
ddlOptions,
|
|
11038
11613
|
graphId,
|
|
11039
11614
|
schemaVersion,
|
|
11040
|
-
existingByStatusKey
|
|
11615
|
+
existingByStatusKey,
|
|
11616
|
+
invalidLeftovers
|
|
11041
11617
|
);
|
|
11042
11618
|
if (options.stopOnError === true) {
|
|
11043
|
-
const
|
|
11619
|
+
const results2 = [];
|
|
11044
11620
|
for (const declaration of candidates) {
|
|
11045
11621
|
const entry2 = await materializeEntry(declaration);
|
|
11046
|
-
|
|
11622
|
+
results2.push(entry2);
|
|
11047
11623
|
if (entry2.status === "failed") break;
|
|
11048
11624
|
}
|
|
11049
|
-
|
|
11625
|
+
await refreshStatisticsAfterCreation(
|
|
11626
|
+
backend,
|
|
11627
|
+
results2,
|
|
11628
|
+
options,
|
|
11629
|
+
ddlOptions.concurrent
|
|
11630
|
+
);
|
|
11631
|
+
return { results: results2 };
|
|
11050
11632
|
}
|
|
11051
11633
|
const buckets = /* @__PURE__ */ new Map();
|
|
11052
11634
|
for (const declaration of candidates) {
|
|
@@ -11073,14 +11655,46 @@ async function materializeIndexes(context, options = {}) {
|
|
|
11073
11655
|
}
|
|
11074
11656
|
bucketIndex += 1;
|
|
11075
11657
|
}
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11658
|
+
const results = candidates.map(
|
|
11659
|
+
(declaration) => byDeclaration.get(declaration)
|
|
11660
|
+
);
|
|
11661
|
+
await refreshStatisticsAfterCreation(
|
|
11662
|
+
backend,
|
|
11663
|
+
results,
|
|
11664
|
+
options,
|
|
11665
|
+
ddlOptions.concurrent
|
|
11666
|
+
);
|
|
11667
|
+
return { results };
|
|
11668
|
+
}
|
|
11669
|
+
async function refreshStatisticsAfterCreation(backend, results, options, usesConcurrentBuilds) {
|
|
11670
|
+
if (options.refreshStatistics === false) return;
|
|
11671
|
+
if (usesConcurrentBuilds && !hasIndexBuildClaimProtocol(backend)) return;
|
|
11672
|
+
if (!results.some((entry2) => entry2.status === "created")) return;
|
|
11673
|
+
try {
|
|
11674
|
+
await backend.refreshStatistics();
|
|
11675
|
+
} catch (error) {
|
|
11676
|
+
if (typeof console === "undefined" || typeof console.warn !== "function") {
|
|
11677
|
+
return;
|
|
11678
|
+
}
|
|
11679
|
+
console.warn(
|
|
11680
|
+
"[typegraph] materializeIndexes created its indexes but the follow-up statistics refresh failed; run store.refreshStatistics() to give the planner fresh statistics.",
|
|
11681
|
+
error
|
|
11682
|
+
);
|
|
11683
|
+
}
|
|
11079
11684
|
}
|
|
11080
11685
|
function parallelBucketKey(declaration) {
|
|
11081
11686
|
return declaration.entity;
|
|
11082
11687
|
}
|
|
11083
|
-
async function materializeRelationalIndex(declaration, backend, dialect, ddlOptions, graphId, schemaVersion, existingByStatusKey) {
|
|
11688
|
+
async function materializeRelationalIndex(declaration, backend, dialect, ddlOptions, graphId, schemaVersion, existingByStatusKey, invalidLeftovers) {
|
|
11689
|
+
if (declaration.method !== void 0 && dialect !== "postgres") {
|
|
11690
|
+
return {
|
|
11691
|
+
indexName: declaration.name,
|
|
11692
|
+
entity: declaration.entity,
|
|
11693
|
+
kind: declaration.kind,
|
|
11694
|
+
status: "skipped",
|
|
11695
|
+
reason: `Index method "${declaration.method}" requires PostgreSQL (expression GIN${declaration.method === "trigram" ? " + pg_trgm" : ""}); SQLite serves substring search via FTS5 fulltext instead.`
|
|
11696
|
+
};
|
|
11697
|
+
}
|
|
11084
11698
|
const ddl = generateIndexDDL(declaration, dialect, ddlOptions);
|
|
11085
11699
|
const targetTable = declaration.entity === "node" ? ddlOptions.nodesTableName ?? "typegraph_nodes" : ddlOptions.edgesTableName ?? "typegraph_edges";
|
|
11086
11700
|
const signature = await computeIndexSignature(
|
|
@@ -11092,11 +11706,17 @@ async function materializeRelationalIndex(declaration, backend, dialect, ddlOpti
|
|
|
11092
11706
|
statusKey: declaration.name,
|
|
11093
11707
|
signature,
|
|
11094
11708
|
driftLabel: "Index",
|
|
11095
|
-
run: () =>
|
|
11096
|
-
|
|
11709
|
+
run: async () => {
|
|
11710
|
+
if (declaration.method === "trigram") {
|
|
11711
|
+
await backend.executeDdl("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
|
|
11712
|
+
}
|
|
11713
|
+
await backend.executeDdl(ddl);
|
|
11714
|
+
},
|
|
11715
|
+
existingByStatusKey,
|
|
11716
|
+
invalidLeftovers
|
|
11097
11717
|
});
|
|
11098
11718
|
}
|
|
11099
|
-
async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey) {
|
|
11719
|
+
async function materializeVectorIndex(declaration, backend, graphId, schemaVersion, existingByStatusKey, invalidLeftovers) {
|
|
11100
11720
|
if (declaration.indexType === "none") {
|
|
11101
11721
|
return skippedEntry(
|
|
11102
11722
|
declaration,
|
|
@@ -11150,33 +11770,35 @@ async function materializeVectorIndex(declaration, backend, graphId, schemaVersi
|
|
|
11150
11770
|
signature,
|
|
11151
11771
|
driftLabel: "Vector index",
|
|
11152
11772
|
run: () => backend.createVectorIndex(params),
|
|
11153
|
-
existingByStatusKey
|
|
11773
|
+
existingByStatusKey,
|
|
11774
|
+
invalidLeftovers
|
|
11154
11775
|
});
|
|
11155
11776
|
}
|
|
11156
11777
|
async function materializeOne(declaration, backend, graphId, schemaVersion, action) {
|
|
11157
11778
|
const recordIndexMaterialization = backend.recordIndexMaterialization;
|
|
11158
11779
|
const { statusKey, signature, driftLabel, run, existingByStatusKey } = action;
|
|
11159
11780
|
const statusOverride = statusKey === declaration.name ? void 0 : { statusName: statusKey };
|
|
11160
|
-
const
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11781
|
+
const settled = await settleAgainstExisting(
|
|
11782
|
+
existingByStatusKey.get(statusKey),
|
|
11783
|
+
declaration,
|
|
11784
|
+
backend,
|
|
11785
|
+
graphId,
|
|
11786
|
+
schemaVersion,
|
|
11787
|
+
{
|
|
11788
|
+
statusKey,
|
|
11789
|
+
signature,
|
|
11790
|
+
driftLabel,
|
|
11791
|
+
isInvalidLeftover: (physicalIndexName) => action.invalidLeftovers.has(physicalIndexName)
|
|
11792
|
+
}
|
|
11793
|
+
);
|
|
11794
|
+
if (settled !== void 0) return settled;
|
|
11795
|
+
if (hasIndexBuildClaimProtocol(backend)) {
|
|
11796
|
+
return materializeWithClaim(declaration, backend, graphId, schemaVersion, {
|
|
11797
|
+
statusKey,
|
|
11798
|
+
signature,
|
|
11799
|
+
driftLabel,
|
|
11800
|
+
run
|
|
11801
|
+
});
|
|
11180
11802
|
}
|
|
11181
11803
|
try {
|
|
11182
11804
|
await run();
|
|
@@ -11210,6 +11832,161 @@ async function materializeOne(declaration, backend, graphId, schemaVersion, acti
|
|
|
11210
11832
|
return entry(declaration, "failed", error);
|
|
11211
11833
|
}
|
|
11212
11834
|
}
|
|
11835
|
+
async function settleAgainstExisting(existing, declaration, backend, graphId, schemaVersion, action) {
|
|
11836
|
+
if (existing?.materializedAt === void 0) return void 0;
|
|
11837
|
+
const { statusKey, signature, driftLabel, isInvalidLeftover } = action;
|
|
11838
|
+
if (existing.signature === signature) {
|
|
11839
|
+
if (declaration.entity !== "vector" && hasIndexBuildClaimProtocol(backend) && await isInvalidLeftover(declaration.name)) {
|
|
11840
|
+
return void 0;
|
|
11841
|
+
}
|
|
11842
|
+
return entry(declaration, "alreadyMaterialized");
|
|
11843
|
+
}
|
|
11844
|
+
const error = new Error(
|
|
11845
|
+
`${driftLabel} "${declaration.name}" already materialized with a different signature (recorded by graph "${existing.graphId}" at version ${existing.schemaVersion}). Drop the index manually and retry, or rename the new declaration.`
|
|
11846
|
+
);
|
|
11847
|
+
await backend.recordIndexMaterialization(
|
|
11848
|
+
buildAttempt({
|
|
11849
|
+
declaration,
|
|
11850
|
+
graphId,
|
|
11851
|
+
signature,
|
|
11852
|
+
schemaVersion,
|
|
11853
|
+
materializedAt: void 0,
|
|
11854
|
+
error,
|
|
11855
|
+
...statusKey === declaration.name ? {} : { statusName: statusKey }
|
|
11856
|
+
})
|
|
11857
|
+
);
|
|
11858
|
+
return entry(declaration, "failed", error);
|
|
11859
|
+
}
|
|
11860
|
+
async function materializeWithClaim(declaration, backend, graphId, schemaVersion, action) {
|
|
11861
|
+
const { statusKey, signature, driftLabel, run } = action;
|
|
11862
|
+
const statusOverride = statusKey === declaration.name ? void 0 : { statusName: statusKey };
|
|
11863
|
+
const recordIndexMaterialization = backend.recordIndexMaterialization;
|
|
11864
|
+
const token = `${statusKey}:${nowIso()}:${Math.floor(performance.now() * 1e3)}`;
|
|
11865
|
+
const deadline = Date.now() + CLAIM_WAIT_TIMEOUT_MS;
|
|
11866
|
+
for (; ; ) {
|
|
11867
|
+
const claimed = await backend.claimIndexMaterialization({
|
|
11868
|
+
indexName: statusKey,
|
|
11869
|
+
graphId,
|
|
11870
|
+
entity: declaration.entity,
|
|
11871
|
+
kind: declaration.kind,
|
|
11872
|
+
signature,
|
|
11873
|
+
schemaVersion,
|
|
11874
|
+
token,
|
|
11875
|
+
leaseMs: CLAIM_LEASE_MS
|
|
11876
|
+
});
|
|
11877
|
+
if (!claimed) {
|
|
11878
|
+
if (Date.now() >= deadline) {
|
|
11879
|
+
return entry(
|
|
11880
|
+
declaration,
|
|
11881
|
+
"failed",
|
|
11882
|
+
new Error(
|
|
11883
|
+
`Timed out waiting for a concurrent materializer's claim on "${statusKey}" (waited ${String(CLAIM_WAIT_TIMEOUT_MS)}ms). If its holder crashed, retry after the lease expires.`
|
|
11884
|
+
)
|
|
11885
|
+
);
|
|
11886
|
+
}
|
|
11887
|
+
await delay(CLAIM_RETRY_DELAY_MS);
|
|
11888
|
+
continue;
|
|
11889
|
+
}
|
|
11890
|
+
try {
|
|
11891
|
+
const fresh = await backend.getIndexMaterialization(statusKey);
|
|
11892
|
+
const settled = await settleAgainstExisting(
|
|
11893
|
+
fresh,
|
|
11894
|
+
declaration,
|
|
11895
|
+
backend,
|
|
11896
|
+
graphId,
|
|
11897
|
+
schemaVersion,
|
|
11898
|
+
{
|
|
11899
|
+
statusKey,
|
|
11900
|
+
signature,
|
|
11901
|
+
driftLabel,
|
|
11902
|
+
// Post-claim the bulk preload is stale (we may have waited on
|
|
11903
|
+
// another builder's claim), so re-check this index fresh.
|
|
11904
|
+
isInvalidLeftover: (physicalIndexName) => hasInvalidIndexLeftover(backend, physicalIndexName)
|
|
11905
|
+
}
|
|
11906
|
+
);
|
|
11907
|
+
if (settled !== void 0) return settled;
|
|
11908
|
+
if (declaration.entity !== "vector") {
|
|
11909
|
+
await dropInvalidIndexLeftover(backend, declaration.name);
|
|
11910
|
+
}
|
|
11911
|
+
try {
|
|
11912
|
+
await run();
|
|
11913
|
+
const attemptedAt = nowIso();
|
|
11914
|
+
await recordIndexMaterialization(
|
|
11915
|
+
buildAttempt({
|
|
11916
|
+
declaration,
|
|
11917
|
+
graphId,
|
|
11918
|
+
signature,
|
|
11919
|
+
schemaVersion,
|
|
11920
|
+
materializedAt: attemptedAt,
|
|
11921
|
+
error: void 0,
|
|
11922
|
+
attemptedAt,
|
|
11923
|
+
...statusOverride
|
|
11924
|
+
})
|
|
11925
|
+
);
|
|
11926
|
+
return entry(declaration, "created");
|
|
11927
|
+
} catch (error_) {
|
|
11928
|
+
const error = error_ instanceof Error ? error_ : new Error(String(error_));
|
|
11929
|
+
await recordIndexMaterialization(
|
|
11930
|
+
buildAttempt({
|
|
11931
|
+
declaration,
|
|
11932
|
+
graphId,
|
|
11933
|
+
signature,
|
|
11934
|
+
schemaVersion,
|
|
11935
|
+
materializedAt: void 0,
|
|
11936
|
+
error,
|
|
11937
|
+
...statusOverride
|
|
11938
|
+
})
|
|
11939
|
+
);
|
|
11940
|
+
return entry(declaration, "failed", error);
|
|
11941
|
+
}
|
|
11942
|
+
} finally {
|
|
11943
|
+
try {
|
|
11944
|
+
await backend.releaseIndexMaterializationClaim({
|
|
11945
|
+
indexName: statusKey,
|
|
11946
|
+
token
|
|
11947
|
+
});
|
|
11948
|
+
} catch (releaseError) {
|
|
11949
|
+
console.warn(
|
|
11950
|
+
`typegraph: failed to release the index materialization claim for "${statusKey}"; it will expire on its lease and be reclaimed automatically.`,
|
|
11951
|
+
releaseError
|
|
11952
|
+
);
|
|
11953
|
+
}
|
|
11954
|
+
}
|
|
11955
|
+
}
|
|
11956
|
+
}
|
|
11957
|
+
async function hasInvalidIndexLeftover(backend, physicalIndexName) {
|
|
11958
|
+
if (backend.dialect !== "postgres") return false;
|
|
11959
|
+
const rows = await backend.execute(
|
|
11960
|
+
asCompiledRowsSql(sql`
|
|
11961
|
+
SELECT NOT i.indisvalid AS invalid
|
|
11962
|
+
FROM pg_class c
|
|
11963
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
11964
|
+
WHERE c.relname = ${physicalIndexName}
|
|
11965
|
+
`)
|
|
11966
|
+
);
|
|
11967
|
+
return rows[0]?.invalid === true;
|
|
11968
|
+
}
|
|
11969
|
+
async function preloadInvalidIndexLeftovers(backend, physicalIndexNames) {
|
|
11970
|
+
if (backend.dialect !== "postgres" || physicalIndexNames.length === 0) {
|
|
11971
|
+
return /* @__PURE__ */ new Set();
|
|
11972
|
+
}
|
|
11973
|
+
const rows = await backend.execute(
|
|
11974
|
+
asCompiledRowsSql(sql`
|
|
11975
|
+
SELECT c.relname AS name
|
|
11976
|
+
FROM pg_class c
|
|
11977
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
11978
|
+
WHERE c.relname IN (${sqlValueList(physicalIndexNames)})
|
|
11979
|
+
AND NOT i.indisvalid
|
|
11980
|
+
`)
|
|
11981
|
+
);
|
|
11982
|
+
return new Set(rows.map((row) => row.name));
|
|
11983
|
+
}
|
|
11984
|
+
async function dropInvalidIndexLeftover(backend, physicalIndexName) {
|
|
11985
|
+
if (backend.executeDdl === void 0) return;
|
|
11986
|
+
if (!await hasInvalidIndexLeftover(backend, physicalIndexName)) return;
|
|
11987
|
+
const quoted = `"${physicalIndexName.replaceAll('"', '""')}"`;
|
|
11988
|
+
await backend.executeDdl(`DROP INDEX CONCURRENTLY IF EXISTS ${quoted};`);
|
|
11989
|
+
}
|
|
11213
11990
|
function entry(declaration, status, error) {
|
|
11214
11991
|
return {
|
|
11215
11992
|
indexName: declaration.name,
|
|
@@ -11263,7 +12040,11 @@ function buildAttempt(args) {
|
|
|
11263
12040
|
};
|
|
11264
12041
|
}
|
|
11265
12042
|
async function computeIndexSignature(dialect, targetTableName, declaration) {
|
|
11266
|
-
const hashable = {
|
|
12043
|
+
const hashable = {
|
|
12044
|
+
dialect,
|
|
12045
|
+
targetTableName,
|
|
12046
|
+
declaration: serializeIndexDeclaration(declaration)
|
|
12047
|
+
};
|
|
11267
12048
|
const json = JSON.stringify(hashable, sortedReplacer);
|
|
11268
12049
|
return sha256Hex(json, 16);
|
|
11269
12050
|
}
|
|
@@ -11607,85 +12388,6 @@ function literal(value) {
|
|
|
11607
12388
|
return `'${value.replaceAll("'", "''")}'`;
|
|
11608
12389
|
}
|
|
11609
12390
|
|
|
11610
|
-
// src/store/constraints.ts
|
|
11611
|
-
async function checkDisjointnessConstraint(ctx, kind, id) {
|
|
11612
|
-
const disjointKinds = ctx.registry.getDisjointKinds(kind);
|
|
11613
|
-
for (const disjointKind of disjointKinds) {
|
|
11614
|
-
const existing = await ctx.backend.getNode(ctx.graphId, disjointKind, id);
|
|
11615
|
-
if (existing && !existing.deleted_at) {
|
|
11616
|
-
const error = checkDisjointness(id, kind, [disjointKind], ctx.registry);
|
|
11617
|
-
if (error) throw error;
|
|
11618
|
-
}
|
|
11619
|
-
}
|
|
11620
|
-
}
|
|
11621
|
-
async function checkCardinalityConstraint(ctx, edgeKind, cardinality, fromKind, fromId, toKind, toId, validTo) {
|
|
11622
|
-
switch (cardinality) {
|
|
11623
|
-
case "many": {
|
|
11624
|
-
return;
|
|
11625
|
-
}
|
|
11626
|
-
case "one": {
|
|
11627
|
-
const count2 = await ctx.backend.countEdgesFrom({
|
|
11628
|
-
graphId: ctx.graphId,
|
|
11629
|
-
edgeKind,
|
|
11630
|
-
fromKind,
|
|
11631
|
-
fromId
|
|
11632
|
-
});
|
|
11633
|
-
const error = checkCardinality(
|
|
11634
|
-
edgeKind,
|
|
11635
|
-
fromKind,
|
|
11636
|
-
fromId,
|
|
11637
|
-
"one",
|
|
11638
|
-
count2,
|
|
11639
|
-
false
|
|
11640
|
-
);
|
|
11641
|
-
if (error) throw error;
|
|
11642
|
-
return;
|
|
11643
|
-
}
|
|
11644
|
-
case "unique": {
|
|
11645
|
-
const exists = await ctx.backend.edgeExistsBetween({
|
|
11646
|
-
graphId: ctx.graphId,
|
|
11647
|
-
edgeKind,
|
|
11648
|
-
fromKind,
|
|
11649
|
-
fromId,
|
|
11650
|
-
toKind,
|
|
11651
|
-
toId
|
|
11652
|
-
});
|
|
11653
|
-
const error = checkUniqueEdge(
|
|
11654
|
-
edgeKind,
|
|
11655
|
-
fromKind,
|
|
11656
|
-
fromId,
|
|
11657
|
-
toKind,
|
|
11658
|
-
toId,
|
|
11659
|
-
exists ? 1 : 0
|
|
11660
|
-
);
|
|
11661
|
-
if (error) throw error;
|
|
11662
|
-
return;
|
|
11663
|
-
}
|
|
11664
|
-
case "oneActive": {
|
|
11665
|
-
if (validTo !== void 0) {
|
|
11666
|
-
return;
|
|
11667
|
-
}
|
|
11668
|
-
const count2 = await ctx.backend.countEdgesFrom({
|
|
11669
|
-
graphId: ctx.graphId,
|
|
11670
|
-
edgeKind,
|
|
11671
|
-
fromKind,
|
|
11672
|
-
fromId,
|
|
11673
|
-
activeOnly: true
|
|
11674
|
-
});
|
|
11675
|
-
const error = checkCardinality(
|
|
11676
|
-
edgeKind,
|
|
11677
|
-
fromKind,
|
|
11678
|
-
fromId,
|
|
11679
|
-
"oneActive",
|
|
11680
|
-
count2,
|
|
11681
|
-
count2 > 0
|
|
11682
|
-
);
|
|
11683
|
-
if (error) throw error;
|
|
11684
|
-
return;
|
|
11685
|
-
}
|
|
11686
|
-
}
|
|
11687
|
-
}
|
|
11688
|
-
|
|
11689
12391
|
// src/store/operations/edge-operations.ts
|
|
11690
12392
|
function getEdgeRegistration(graph, kind) {
|
|
11691
12393
|
const registration = graph.edges[kind];
|
|
@@ -11740,6 +12442,11 @@ function createEdgeBatchValidationBackend(backend) {
|
|
|
11740
12442
|
endpointCache.set(cacheKey, node);
|
|
11741
12443
|
return node;
|
|
11742
12444
|
}
|
|
12445
|
+
function seedEndpointRow(graphId, kind, id, row) {
|
|
12446
|
+
const cacheKey = buildEdgeEndpointCacheKey(graphId, kind, id);
|
|
12447
|
+
if (endpointCache.has(cacheKey)) return;
|
|
12448
|
+
endpointCache.set(cacheKey, row);
|
|
12449
|
+
}
|
|
11743
12450
|
async function countEdgesFromCached(params) {
|
|
11744
12451
|
const cacheKey = buildCountEdgesFromCacheKey(params);
|
|
11745
12452
|
let baseCount = countEdgesFromCache.get(cacheKey);
|
|
@@ -11811,7 +12518,8 @@ function createEdgeBatchValidationBackend(backend) {
|
|
|
11811
12518
|
});
|
|
11812
12519
|
return {
|
|
11813
12520
|
backend: validationBackend,
|
|
11814
|
-
registerPendingEdgeForCardinality
|
|
12521
|
+
registerPendingEdgeForCardinality,
|
|
12522
|
+
seedEndpointRow
|
|
11815
12523
|
};
|
|
11816
12524
|
}
|
|
11817
12525
|
async function validateAndPrepareEdgeCreate(ctx, input, id, backend) {
|
|
@@ -11911,29 +12619,63 @@ async function executeEdgeCreate(ctx, input, backend) {
|
|
|
11911
12619
|
}
|
|
11912
12620
|
return result;
|
|
11913
12621
|
}
|
|
12622
|
+
async function primeEdgeBatchValidationCache(ctx, inputs, backend, seedEndpointRow) {
|
|
12623
|
+
if (backend.getNodes === void 0) return;
|
|
12624
|
+
const idsByKind = /* @__PURE__ */ new Map();
|
|
12625
|
+
const addEndpoint = (kind, id) => {
|
|
12626
|
+
const ids = idsByKind.get(kind) ?? /* @__PURE__ */ new Set();
|
|
12627
|
+
ids.add(id);
|
|
12628
|
+
idsByKind.set(kind, ids);
|
|
12629
|
+
};
|
|
12630
|
+
for (const input of inputs) {
|
|
12631
|
+
addEndpoint(input.fromKind, input.fromId);
|
|
12632
|
+
addEndpoint(input.toKind, input.toId);
|
|
12633
|
+
}
|
|
12634
|
+
for (const [kind, ids] of idsByKind) {
|
|
12635
|
+
const orderedIds = [...ids];
|
|
12636
|
+
const rows = await backend.getNodes(ctx.graphId, kind, orderedIds);
|
|
12637
|
+
const rowsById = new Map(rows.map((row) => [row.id, row]));
|
|
12638
|
+
for (const id of orderedIds) {
|
|
12639
|
+
seedEndpointRow(ctx.graphId, kind, id, rowsById.get(id));
|
|
12640
|
+
}
|
|
12641
|
+
}
|
|
12642
|
+
}
|
|
12643
|
+
async function prepareEdgeBatchCreates(ctx, inputs, backend) {
|
|
12644
|
+
const {
|
|
12645
|
+
backend: validationBackend,
|
|
12646
|
+
registerPendingEdgeForCardinality,
|
|
12647
|
+
seedEndpointRow
|
|
12648
|
+
} = createEdgeBatchValidationBackend(backend);
|
|
12649
|
+
await primeEdgeBatchValidationCache(ctx, inputs, backend, seedEndpointRow);
|
|
12650
|
+
const preparedCreates = [];
|
|
12651
|
+
for (const input of inputs) {
|
|
12652
|
+
const id = input.id ?? generateId();
|
|
12653
|
+
const prepared = await validateAndPrepareEdgeCreate(
|
|
12654
|
+
ctx,
|
|
12655
|
+
input,
|
|
12656
|
+
id,
|
|
12657
|
+
validationBackend
|
|
12658
|
+
);
|
|
12659
|
+
preparedCreates.push(prepared);
|
|
12660
|
+
registerPendingEdgeForCardinality(
|
|
12661
|
+
prepared.insertParams,
|
|
12662
|
+
prepared.cardinality
|
|
12663
|
+
);
|
|
12664
|
+
}
|
|
12665
|
+
const batchInsertParams = preparedCreates.map(
|
|
12666
|
+
(prepared) => prepared.insertParams
|
|
12667
|
+
);
|
|
12668
|
+
return { preparedCreates, batchInsertParams };
|
|
12669
|
+
}
|
|
11914
12670
|
async function executeEdgeCreateNoReturnBatch(ctx, inputs, backend) {
|
|
11915
12671
|
if (inputs.length === 0) {
|
|
11916
12672
|
return;
|
|
11917
12673
|
}
|
|
11918
12674
|
await runInWriteTransaction(ctx, backend, async (target) => {
|
|
11919
|
-
const {
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
const prepared = await validateAndPrepareEdgeCreate(
|
|
11924
|
-
ctx,
|
|
11925
|
-
input,
|
|
11926
|
-
id,
|
|
11927
|
-
validationBackend
|
|
11928
|
-
);
|
|
11929
|
-
preparedCreates.push(prepared);
|
|
11930
|
-
registerPendingEdgeForCardinality(
|
|
11931
|
-
prepared.insertParams,
|
|
11932
|
-
prepared.cardinality
|
|
11933
|
-
);
|
|
11934
|
-
}
|
|
11935
|
-
const batchInsertParams = preparedCreates.map(
|
|
11936
|
-
(prepared) => prepared.insertParams
|
|
12675
|
+
const { batchInsertParams } = await prepareEdgeBatchCreates(
|
|
12676
|
+
ctx,
|
|
12677
|
+
inputs,
|
|
12678
|
+
target
|
|
11937
12679
|
);
|
|
11938
12680
|
await runInsertBatch(edgeInsertDispatch(target), batchInsertParams);
|
|
11939
12681
|
});
|
|
@@ -11943,24 +12685,10 @@ async function executeEdgeCreateBatch(ctx, inputs, backend) {
|
|
|
11943
12685
|
return [];
|
|
11944
12686
|
}
|
|
11945
12687
|
return runInWriteTransaction(ctx, backend, async (target) => {
|
|
11946
|
-
const {
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
const prepared = await validateAndPrepareEdgeCreate(
|
|
11951
|
-
ctx,
|
|
11952
|
-
input,
|
|
11953
|
-
id,
|
|
11954
|
-
validationBackend
|
|
11955
|
-
);
|
|
11956
|
-
preparedCreates.push(prepared);
|
|
11957
|
-
registerPendingEdgeForCardinality(
|
|
11958
|
-
prepared.insertParams,
|
|
11959
|
-
prepared.cardinality
|
|
11960
|
-
);
|
|
11961
|
-
}
|
|
11962
|
-
const batchInsertParams = preparedCreates.map(
|
|
11963
|
-
(prepared) => prepared.insertParams
|
|
12688
|
+
const { batchInsertParams } = await prepareEdgeBatchCreates(
|
|
12689
|
+
ctx,
|
|
12690
|
+
inputs,
|
|
12691
|
+
target
|
|
11964
12692
|
);
|
|
11965
12693
|
const rows = await runInsertBatchReturning(
|
|
11966
12694
|
edgeInsertDispatch(target),
|
|
@@ -11977,7 +12705,7 @@ async function performEdgeUpdate(ctx, input, target, options) {
|
|
|
11977
12705
|
}
|
|
11978
12706
|
const registration = getEdgeRegistration(ctx.graph, existing.kind);
|
|
11979
12707
|
const edgeKind = registration.type;
|
|
11980
|
-
const existingProps =
|
|
12708
|
+
const existingProps = rowPropsToObject(existing.props);
|
|
11981
12709
|
const mergedProps = { ...existingProps, ...input.props };
|
|
11982
12710
|
const validatedProps = validateEdgeProps(edgeKind.schema, mergedProps, {
|
|
11983
12711
|
kind: existing.kind,
|
|
@@ -12021,10 +12749,6 @@ async function executeEdgeDelete(ctx, id, backend) {
|
|
|
12021
12749
|
if (!gate || gate.deleted_at) return;
|
|
12022
12750
|
const opContext = ctx.createOperationContext("delete", "edge", gate.kind, id);
|
|
12023
12751
|
return runHookedWriteOperation(ctx, opContext, backend, async (target) => {
|
|
12024
|
-
const existing = await target.getEdge(ctx.graphId, id);
|
|
12025
|
-
if (!existing || existing.deleted_at) {
|
|
12026
|
-
return;
|
|
12027
|
-
}
|
|
12028
12752
|
await target.deleteEdge({
|
|
12029
12753
|
graphId: ctx.graphId,
|
|
12030
12754
|
id
|
|
@@ -12036,10 +12760,6 @@ async function executeEdgeHardDelete(ctx, id, backend) {
|
|
|
12036
12760
|
if (!gate) return;
|
|
12037
12761
|
const opContext = ctx.createOperationContext("delete", "edge", gate.kind, id);
|
|
12038
12762
|
return runHookedWriteOperation(ctx, opContext, backend, async (target) => {
|
|
12039
|
-
const existing = await target.getEdge(ctx.graphId, id);
|
|
12040
|
-
if (!existing) {
|
|
12041
|
-
return;
|
|
12042
|
-
}
|
|
12043
12763
|
await target.hardDeleteEdge({
|
|
12044
12764
|
graphId: ctx.graphId,
|
|
12045
12765
|
id
|
|
@@ -12109,7 +12829,7 @@ function findMatchingEdge(rows, matchOn, inputProps) {
|
|
|
12109
12829
|
let deletedRow;
|
|
12110
12830
|
for (const row of rows) {
|
|
12111
12831
|
if (matchOn.length > 0) {
|
|
12112
|
-
const rowProps =
|
|
12832
|
+
const rowProps = rowPropsToObject(row.props);
|
|
12113
12833
|
const matches = matchOn.every(
|
|
12114
12834
|
(field2) => stableStringify(rowProps[field2]) === stableStringify(inputProps[field2])
|
|
12115
12835
|
);
|
|
@@ -12446,1033 +13166,64 @@ async function executeEdgeBulkGetOrCreateByEndpoints(ctx, kind, items, backend,
|
|
|
12446
13166
|
return results;
|
|
12447
13167
|
});
|
|
12448
13168
|
}
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
return
|
|
13169
|
+
var RECORDED_POINT_READ_FIXED_BIND_PARAMS = 8;
|
|
13170
|
+
function recordedPointReadIdChunk(backend) {
|
|
13171
|
+
const budget = recordedBindParamBudget(backend);
|
|
13172
|
+
return Math.max(1, budget - RECORDED_POINT_READ_FIXED_BIND_PARAMS);
|
|
13173
|
+
}
|
|
13174
|
+
function recordedRelationErrorDetails(backend, surface) {
|
|
13175
|
+
return { dialect: backend.dialect, surface };
|
|
13176
|
+
}
|
|
13177
|
+
function withRelationsPrecondition(backend, promise, surface) {
|
|
13178
|
+
return withRecordedRelationsPrecondition(
|
|
13179
|
+
promise,
|
|
13180
|
+
recordedRelationErrorDetails(backend, surface)
|
|
13181
|
+
);
|
|
12453
13182
|
}
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
13183
|
+
function createRecordedReadBackend(backend, surface) {
|
|
13184
|
+
return createBackendOverlay(backend, {
|
|
13185
|
+
execute: (query) => withRelationsPrecondition(backend, backend.execute(query), surface),
|
|
13186
|
+
...backend.executeRaw === void 0 ? {} : {
|
|
13187
|
+
executeRaw: (sqlText, params) => withRelationsPrecondition(
|
|
13188
|
+
backend,
|
|
13189
|
+
backend.executeRaw(sqlText, params),
|
|
13190
|
+
surface
|
|
13191
|
+
)
|
|
13192
|
+
}
|
|
13193
|
+
});
|
|
12457
13194
|
}
|
|
12458
|
-
function
|
|
12459
|
-
|
|
13195
|
+
function recordedTemporalFilter(backend, coordinate, tableAlias) {
|
|
13196
|
+
const recordedAsOf = coordinate.recorded?.asOf;
|
|
13197
|
+
if (recordedAsOf === void 0) {
|
|
13198
|
+
throw new ConfigurationError(
|
|
13199
|
+
"Recorded point reads require a recorded-time coordinate.",
|
|
13200
|
+
{ code: "RECORDED_POINT_READ_MISSING_COORDINATE" }
|
|
13201
|
+
);
|
|
13202
|
+
}
|
|
13203
|
+
return compileTemporalFilter({
|
|
13204
|
+
mode: coordinate.valid.mode,
|
|
13205
|
+
asOf: coordinate.valid.asOf,
|
|
13206
|
+
recordedAsOf,
|
|
13207
|
+
tableAlias,
|
|
13208
|
+
currentTimestamp: currentReadInstant()
|
|
13209
|
+
});
|
|
12460
13210
|
}
|
|
12461
|
-
function
|
|
12462
|
-
return new
|
|
12463
|
-
|
|
13211
|
+
function recordedRelationInvariantError(params) {
|
|
13212
|
+
return new ConfigurationError(
|
|
13213
|
+
"Recorded relation invariant violation: more than one recorded row matched the requested point read.",
|
|
12464
13214
|
{
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
13215
|
+
code: "RECORDED_RELATION_INVARIANT_VIOLATION",
|
|
13216
|
+
graphId: params.graphId,
|
|
13217
|
+
entity: params.entity,
|
|
13218
|
+
kind: params.kind,
|
|
13219
|
+
id: params.id,
|
|
13220
|
+
validMode: params.coordinate.valid.mode,
|
|
13221
|
+
validAsOf: params.coordinate.valid.asOf,
|
|
13222
|
+
recordedAsOf: params.coordinate.recorded?.asOf
|
|
12470
13223
|
},
|
|
12471
|
-
{
|
|
12472
|
-
|
|
12473
|
-
}
|
|
12474
|
-
function buildInsertNodeParams(graphId, kind, id, props, validFrom, validTo) {
|
|
12475
|
-
const insertParams = {
|
|
12476
|
-
graphId,
|
|
12477
|
-
kind,
|
|
12478
|
-
id,
|
|
12479
|
-
props
|
|
12480
|
-
};
|
|
12481
|
-
if (validFrom !== void 0) insertParams.validFrom = validFrom;
|
|
12482
|
-
if (validTo !== void 0) insertParams.validTo = validTo;
|
|
12483
|
-
return insertParams;
|
|
12484
|
-
}
|
|
12485
|
-
function createPendingUniqueRow(graphId, nodeKind, constraintName, key, nodeId) {
|
|
12486
|
-
return {
|
|
12487
|
-
graph_id: graphId,
|
|
12488
|
-
node_kind: nodeKind,
|
|
12489
|
-
constraint_name: constraintName,
|
|
12490
|
-
key,
|
|
12491
|
-
node_id: nodeId,
|
|
12492
|
-
concrete_kind: nodeKind,
|
|
12493
|
-
deleted_at: void 0
|
|
12494
|
-
};
|
|
12495
|
-
}
|
|
12496
|
-
function resolveConstraint(graph, kind, constraintName) {
|
|
12497
|
-
const registration = getNodeRegistration(graph, kind);
|
|
12498
|
-
const constraints = registration.unique ?? [];
|
|
12499
|
-
const constraint = constraints.find(
|
|
12500
|
-
(candidate) => candidate.name === constraintName
|
|
12501
|
-
);
|
|
12502
|
-
if (constraint === void 0) {
|
|
12503
|
-
throw new NodeConstraintNotFoundError(constraintName, kind);
|
|
12504
|
-
}
|
|
12505
|
-
return constraint;
|
|
12506
|
-
}
|
|
12507
|
-
function createNodeBatchValidationBackend(graphId, registry, backend) {
|
|
12508
|
-
const nodeCache = /* @__PURE__ */ new Map();
|
|
12509
|
-
const pendingNodes = /* @__PURE__ */ new Map();
|
|
12510
|
-
const uniqueCache = /* @__PURE__ */ new Map();
|
|
12511
|
-
const pendingUniqueOwners = /* @__PURE__ */ new Map();
|
|
12512
|
-
async function getNodeCached(lookupGraphId, kind, id) {
|
|
12513
|
-
const cacheKey = buildNodeCacheKey(lookupGraphId, kind, id);
|
|
12514
|
-
const pendingNode = pendingNodes.get(cacheKey);
|
|
12515
|
-
if (pendingNode !== void 0) return pendingNode;
|
|
12516
|
-
if (nodeCache.has(cacheKey)) return nodeCache.get(cacheKey);
|
|
12517
|
-
const existing = await backend.getNode(lookupGraphId, kind, id);
|
|
12518
|
-
nodeCache.set(cacheKey, existing);
|
|
12519
|
-
return existing;
|
|
12520
|
-
}
|
|
12521
|
-
async function checkUniqueCached(params) {
|
|
12522
|
-
const cacheKey = buildUniqueCacheKey(
|
|
12523
|
-
params.graphId,
|
|
12524
|
-
params.nodeKind,
|
|
12525
|
-
params.constraintName,
|
|
12526
|
-
params.key
|
|
12527
|
-
);
|
|
12528
|
-
const pendingOwner = pendingUniqueOwners.get(cacheKey);
|
|
12529
|
-
if (pendingOwner !== void 0) {
|
|
12530
|
-
return createPendingUniqueRow(
|
|
12531
|
-
params.graphId,
|
|
12532
|
-
params.nodeKind,
|
|
12533
|
-
params.constraintName,
|
|
12534
|
-
params.key,
|
|
12535
|
-
pendingOwner
|
|
12536
|
-
);
|
|
12537
|
-
}
|
|
12538
|
-
if (uniqueCache.has(cacheKey)) return uniqueCache.get(cacheKey);
|
|
12539
|
-
const existing = await backend.checkUnique(params);
|
|
12540
|
-
uniqueCache.set(cacheKey, existing);
|
|
12541
|
-
return existing;
|
|
12542
|
-
}
|
|
12543
|
-
function registerPendingNode(params) {
|
|
12544
|
-
const cacheKey = buildNodeCacheKey(params.graphId, params.kind, params.id);
|
|
12545
|
-
pendingNodes.set(cacheKey, {
|
|
12546
|
-
graph_id: params.graphId,
|
|
12547
|
-
kind: params.kind,
|
|
12548
|
-
id: params.id,
|
|
12549
|
-
props: JSON.stringify(params.props),
|
|
12550
|
-
version: 1,
|
|
12551
|
-
valid_from: params.validFrom,
|
|
12552
|
-
valid_to: params.validTo,
|
|
12553
|
-
created_at: "",
|
|
12554
|
-
updated_at: "",
|
|
12555
|
-
deleted_at: void 0
|
|
12556
|
-
});
|
|
12557
|
-
}
|
|
12558
|
-
function registerPendingUniqueEntries(kind, id, props, constraints) {
|
|
12559
|
-
for (const constraint of constraints) {
|
|
12560
|
-
if (!checkWherePredicate(constraint, props)) continue;
|
|
12561
|
-
const key = computeUniqueKey(
|
|
12562
|
-
props,
|
|
12563
|
-
constraint.fields,
|
|
12564
|
-
constraint.collation
|
|
12565
|
-
);
|
|
12566
|
-
const concreteEntryKey = buildUniqueCacheKey(
|
|
12567
|
-
graphId,
|
|
12568
|
-
kind,
|
|
12569
|
-
constraint.name,
|
|
12570
|
-
key
|
|
12571
|
-
);
|
|
12572
|
-
pendingUniqueOwners.set(concreteEntryKey, id);
|
|
12573
|
-
if (constraint.scope !== "kind") {
|
|
12574
|
-
const kindsToCheck = getKindsForUniquenessCheck(
|
|
12575
|
-
kind,
|
|
12576
|
-
constraint.scope,
|
|
12577
|
-
registry
|
|
12578
|
-
);
|
|
12579
|
-
for (const kindToCheck of kindsToCheck) {
|
|
12580
|
-
const inheritedEntryKey = buildUniqueCacheKey(
|
|
12581
|
-
graphId,
|
|
12582
|
-
kindToCheck,
|
|
12583
|
-
constraint.name,
|
|
12584
|
-
key
|
|
12585
|
-
);
|
|
12586
|
-
pendingUniqueOwners.set(inheritedEntryKey, id);
|
|
12587
|
-
}
|
|
12588
|
-
}
|
|
12589
|
-
}
|
|
12590
|
-
}
|
|
12591
|
-
const validationBackend = createBackendOverlay(backend, {
|
|
12592
|
-
getNode: getNodeCached,
|
|
12593
|
-
checkUnique: checkUniqueCached
|
|
12594
|
-
});
|
|
12595
|
-
return {
|
|
12596
|
-
backend: validationBackend,
|
|
12597
|
-
registerPendingNode,
|
|
12598
|
-
registerPendingUniqueEntries
|
|
12599
|
-
};
|
|
12600
|
-
}
|
|
12601
|
-
async function validateAndPrepareNodeCreate(ctx, input, id, backend) {
|
|
12602
|
-
const kind = input.kind;
|
|
12603
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
12604
|
-
const nodeKind = registration.type;
|
|
12605
|
-
const validatedProps = validateNodeProps(nodeKind.schema, input.props, {
|
|
12606
|
-
kind,
|
|
12607
|
-
operation: "create"
|
|
12608
|
-
});
|
|
12609
|
-
const validFrom = validateOptionalCanonicalIsoDate(
|
|
12610
|
-
input.validFrom,
|
|
12611
|
-
"validFrom"
|
|
12612
|
-
);
|
|
12613
|
-
const validTo = validateOptionalCanonicalIsoDate(input.validTo, "validTo");
|
|
12614
|
-
const existingNode = await backend.getNode(ctx.graphId, kind, id);
|
|
12615
|
-
if (existingNode && !existingNode.deleted_at) {
|
|
12616
|
-
throw createNodeAlreadyExistsError(kind, id);
|
|
12617
|
-
}
|
|
12618
|
-
const constraintContext = {
|
|
12619
|
-
graphId: ctx.graphId,
|
|
12620
|
-
registry: ctx.registry,
|
|
12621
|
-
backend
|
|
12622
|
-
};
|
|
12623
|
-
await checkDisjointnessConstraint(constraintContext, kind, id);
|
|
12624
|
-
const uniqueConstraints = registration.unique ?? [];
|
|
12625
|
-
await checkUniquenessConstraints(
|
|
12626
|
-
createUniquenessContext(ctx.graphId, ctx.registry, backend),
|
|
12627
|
-
kind,
|
|
12628
|
-
id,
|
|
12629
|
-
validatedProps,
|
|
12630
|
-
uniqueConstraints
|
|
12631
|
-
);
|
|
12632
|
-
return {
|
|
12633
|
-
kind,
|
|
12634
|
-
id,
|
|
12635
|
-
nodeKind,
|
|
12636
|
-
validatedProps,
|
|
12637
|
-
uniqueConstraints,
|
|
12638
|
-
insertParams: buildInsertNodeParams(
|
|
12639
|
-
ctx.graphId,
|
|
12640
|
-
kind,
|
|
12641
|
-
id,
|
|
12642
|
-
validatedProps,
|
|
12643
|
-
validFrom,
|
|
12644
|
-
validTo
|
|
12645
|
-
)
|
|
12646
|
-
};
|
|
12647
|
-
}
|
|
12648
|
-
async function finalizeNodeCreate(ctx, prepared, backend, lock) {
|
|
12649
|
-
await applyNodeInsertSideEffects(
|
|
12650
|
-
createNodeWriteContext(ctx.graphId, ctx.registry, lock),
|
|
12651
|
-
{
|
|
12652
|
-
kind: prepared.kind,
|
|
12653
|
-
id: prepared.id,
|
|
12654
|
-
schema: prepared.nodeKind.schema,
|
|
12655
|
-
props: prepared.validatedProps,
|
|
12656
|
-
uniqueConstraints: prepared.uniqueConstraints
|
|
12657
|
-
},
|
|
12658
|
-
backend
|
|
12659
|
-
);
|
|
12660
|
-
}
|
|
12661
|
-
async function performNodeUpdate(ctx, input, backend, lock, options) {
|
|
12662
|
-
const { kind, id } = input;
|
|
12663
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
12664
|
-
const existing = await backend.getNode(ctx.graphId, kind, id);
|
|
12665
|
-
if (!existing) throw new NodeNotFoundError(kind, id);
|
|
12666
|
-
const existingProps = JSON.parse(existing.props);
|
|
12667
|
-
const mergedProps = { ...existingProps, ...input.props };
|
|
12668
|
-
const nodeKind = registration.type;
|
|
12669
|
-
const validatedProps = validateNodeProps(nodeKind.schema, mergedProps, {
|
|
12670
|
-
kind,
|
|
12671
|
-
operation: "update",
|
|
12672
|
-
id
|
|
12673
|
-
});
|
|
12674
|
-
const validTo = validateOptionalCanonicalIsoDate(input.validTo, "validTo");
|
|
12675
|
-
const writeContext = createNodeWriteContext(ctx.graphId, ctx.registry, lock);
|
|
12676
|
-
const shared = {
|
|
12677
|
-
schema: nodeKind.schema,
|
|
12678
|
-
validatedProps,
|
|
12679
|
-
uniqueConstraints: registration.unique ?? [],
|
|
12680
|
-
...validTo !== void 0 && { validTo }
|
|
12681
|
-
};
|
|
12682
|
-
if (options?.clearDeleted) {
|
|
12683
|
-
const row2 = await applyNodeUpdate(
|
|
12684
|
-
writeContext,
|
|
12685
|
-
{ ...shared, existing, clearDeleted: true },
|
|
12686
|
-
backend
|
|
12687
|
-
);
|
|
12688
|
-
return rowToNode(row2);
|
|
12689
|
-
}
|
|
12690
|
-
if (!isLiveNodeRow(existing)) throw new NodeNotFoundError(kind, id);
|
|
12691
|
-
const row = await applyNodeUpdate(
|
|
12692
|
-
writeContext,
|
|
12693
|
-
{ ...shared, existing },
|
|
12694
|
-
backend
|
|
12695
|
-
);
|
|
12696
|
-
return rowToNode(row);
|
|
12697
|
-
}
|
|
12698
|
-
async function prepareBatchCreates(ctx, inputs, backend) {
|
|
12699
|
-
const {
|
|
12700
|
-
backend: validationBackend,
|
|
12701
|
-
registerPendingNode,
|
|
12702
|
-
registerPendingUniqueEntries
|
|
12703
|
-
} = createNodeBatchValidationBackend(ctx.graphId, ctx.registry, backend);
|
|
12704
|
-
const preparedCreates = [];
|
|
12705
|
-
for (const input of inputs) {
|
|
12706
|
-
const id = input.id ?? generateId();
|
|
12707
|
-
const prepared = await validateAndPrepareNodeCreate(
|
|
12708
|
-
ctx,
|
|
12709
|
-
input,
|
|
12710
|
-
id,
|
|
12711
|
-
validationBackend
|
|
12712
|
-
);
|
|
12713
|
-
preparedCreates.push(prepared);
|
|
12714
|
-
registerPendingNode(prepared.insertParams);
|
|
12715
|
-
registerPendingUniqueEntries(
|
|
12716
|
-
prepared.kind,
|
|
12717
|
-
prepared.id,
|
|
12718
|
-
prepared.validatedProps,
|
|
12719
|
-
prepared.uniqueConstraints
|
|
12720
|
-
);
|
|
12721
|
-
}
|
|
12722
|
-
const batchInsertParams = preparedCreates.map(
|
|
12723
|
-
(prepared) => prepared.insertParams
|
|
12724
|
-
);
|
|
12725
|
-
return { preparedCreates, batchInsertParams };
|
|
12726
|
-
}
|
|
12727
|
-
async function findUniqueRowAcrossKinds(backend, graphId, constraintName, key, kindsToCheck, includeDeleted) {
|
|
12728
|
-
for (const kindToCheck of kindsToCheck) {
|
|
12729
|
-
const row = await backend.checkUnique({
|
|
12730
|
-
graphId,
|
|
12731
|
-
nodeKind: kindToCheck,
|
|
12732
|
-
constraintName,
|
|
12733
|
-
key,
|
|
12734
|
-
includeDeleted
|
|
12735
|
-
});
|
|
12736
|
-
if (row !== void 0) return row;
|
|
12737
|
-
}
|
|
12738
|
-
return void 0;
|
|
12739
|
-
}
|
|
12740
|
-
async function batchCheckUniqueAcrossKinds(backend, graphId, constraintName, uniqueKeys, kindsToCheck, includeDeleted) {
|
|
12741
|
-
const existingByKey = /* @__PURE__ */ new Map();
|
|
12742
|
-
for (const kindToCheck of kindsToCheck) {
|
|
12743
|
-
if (backend.checkUniqueBatch === void 0) {
|
|
12744
|
-
for (const key of uniqueKeys) {
|
|
12745
|
-
if (existingByKey.has(key)) continue;
|
|
12746
|
-
const row = await backend.checkUnique({
|
|
12747
|
-
graphId,
|
|
12748
|
-
nodeKind: kindToCheck,
|
|
12749
|
-
constraintName,
|
|
12750
|
-
key,
|
|
12751
|
-
includeDeleted
|
|
12752
|
-
});
|
|
12753
|
-
if (row !== void 0) {
|
|
12754
|
-
existingByKey.set(row.key, row);
|
|
12755
|
-
}
|
|
12756
|
-
}
|
|
12757
|
-
} else {
|
|
12758
|
-
const rows = await backend.checkUniqueBatch({
|
|
12759
|
-
graphId,
|
|
12760
|
-
nodeKind: kindToCheck,
|
|
12761
|
-
constraintName,
|
|
12762
|
-
keys: uniqueKeys,
|
|
12763
|
-
includeDeleted
|
|
12764
|
-
});
|
|
12765
|
-
for (const row of rows) {
|
|
12766
|
-
if (!existingByKey.has(row.key)) {
|
|
12767
|
-
existingByKey.set(row.key, row);
|
|
12768
|
-
}
|
|
12769
|
-
}
|
|
12770
|
-
}
|
|
12771
|
-
}
|
|
12772
|
-
return existingByKey;
|
|
12773
|
-
}
|
|
12774
|
-
async function executeNodeCreateInternal(ctx, input, backend, options) {
|
|
12775
|
-
const kind = input.kind;
|
|
12776
|
-
const id = input.id ?? generateId();
|
|
12777
|
-
const opContext = ctx.createOperationContext("create", "node", kind, id);
|
|
12778
|
-
return runHookedWriteOperation(
|
|
12779
|
-
ctx,
|
|
12780
|
-
opContext,
|
|
12781
|
-
backend,
|
|
12782
|
-
async (target, lock) => {
|
|
12783
|
-
const prepared = await validateAndPrepareNodeCreate(
|
|
12784
|
-
ctx,
|
|
12785
|
-
input,
|
|
12786
|
-
id,
|
|
12787
|
-
target
|
|
12788
|
-
);
|
|
12789
|
-
let row;
|
|
12790
|
-
{
|
|
12791
|
-
row = await target.insertNode(prepared.insertParams);
|
|
12792
|
-
}
|
|
12793
|
-
await finalizeNodeCreate(ctx, prepared, target, lock);
|
|
12794
|
-
if (row === void 0) return;
|
|
12795
|
-
return rowToNode(row);
|
|
12796
|
-
}
|
|
12797
|
-
);
|
|
12798
|
-
}
|
|
12799
|
-
async function executeNodeCreate(ctx, input, backend) {
|
|
12800
|
-
const result = await executeNodeCreateInternal(ctx, input, backend);
|
|
12801
|
-
if (!result) {
|
|
12802
|
-
throw new DatabaseOperationError(
|
|
12803
|
-
"Node create failed: expected created node row",
|
|
12804
|
-
{ operation: "insert", entity: "node" }
|
|
12805
|
-
);
|
|
12806
|
-
}
|
|
12807
|
-
return result;
|
|
12808
|
-
}
|
|
12809
|
-
async function executeNodeCreateNoReturnBatch(ctx, inputs, backend) {
|
|
12810
|
-
if (inputs.length === 0) return;
|
|
12811
|
-
await runInWriteTransaction(ctx, backend, async (target, lock) => {
|
|
12812
|
-
const { preparedCreates, batchInsertParams } = await prepareBatchCreates(
|
|
12813
|
-
ctx,
|
|
12814
|
-
inputs,
|
|
12815
|
-
target
|
|
12816
|
-
);
|
|
12817
|
-
await runInsertBatch(nodeInsertDispatch(target), batchInsertParams);
|
|
12818
|
-
for (const prepared of preparedCreates) {
|
|
12819
|
-
await finalizeNodeCreate(ctx, prepared, target, lock);
|
|
12820
|
-
}
|
|
12821
|
-
});
|
|
12822
|
-
}
|
|
12823
|
-
async function executeNodeCreateBatch(ctx, inputs, backend) {
|
|
12824
|
-
if (inputs.length === 0) return [];
|
|
12825
|
-
return runInWriteTransaction(ctx, backend, async (target, lock) => {
|
|
12826
|
-
const { preparedCreates, batchInsertParams } = await prepareBatchCreates(
|
|
12827
|
-
ctx,
|
|
12828
|
-
inputs,
|
|
12829
|
-
target
|
|
12830
|
-
);
|
|
12831
|
-
const rows = await runInsertBatchReturning(
|
|
12832
|
-
nodeInsertDispatch(target),
|
|
12833
|
-
batchInsertParams
|
|
12834
|
-
);
|
|
12835
|
-
for (const prepared of preparedCreates) {
|
|
12836
|
-
await finalizeNodeCreate(ctx, prepared, target, lock);
|
|
12837
|
-
}
|
|
12838
|
-
return rows.map((row) => rowToNode(row));
|
|
12839
|
-
});
|
|
12840
|
-
}
|
|
12841
|
-
async function executeNodeUpdate(ctx, input, backend, options) {
|
|
12842
|
-
const opContext = ctx.createOperationContext(
|
|
12843
|
-
"update",
|
|
12844
|
-
"node",
|
|
12845
|
-
input.kind,
|
|
12846
|
-
input.id
|
|
12847
|
-
);
|
|
12848
|
-
return runHookedWriteOperation(
|
|
12849
|
-
ctx,
|
|
12850
|
-
opContext,
|
|
12851
|
-
backend,
|
|
12852
|
-
(target, lock) => performNodeUpdate(ctx, input, target, lock, options)
|
|
12853
|
-
);
|
|
12854
|
-
}
|
|
12855
|
-
async function executeNodeUpsertUpdate(ctx, input, backend, options) {
|
|
12856
|
-
return runInWriteTransaction(
|
|
12857
|
-
ctx,
|
|
12858
|
-
backend,
|
|
12859
|
-
(target, lock) => performNodeUpdate(ctx, input, target, lock, options)
|
|
12860
|
-
);
|
|
12861
|
-
}
|
|
12862
|
-
async function executeNodeDelete(ctx, kind, id, backend) {
|
|
12863
|
-
const gate = await backend.getNode(ctx.graphId, kind, id);
|
|
12864
|
-
if (!gate || gate.deleted_at) return;
|
|
12865
|
-
const opContext = ctx.createOperationContext("delete", "node", kind, id);
|
|
12866
|
-
return runHookedWriteOperation(
|
|
12867
|
-
ctx,
|
|
12868
|
-
opContext,
|
|
12869
|
-
backend,
|
|
12870
|
-
async (target, lock) => {
|
|
12871
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
12872
|
-
const preflight = await target.getNode(ctx.graphId, kind, id);
|
|
12873
|
-
if (!preflight || !isLiveNodeRow(preflight)) return;
|
|
12874
|
-
await applyNodeSoftDelete(
|
|
12875
|
-
createNodeWriteContext(ctx.graphId, ctx.registry, lock),
|
|
12876
|
-
{
|
|
12877
|
-
existing: preflight,
|
|
12878
|
-
schema: registration.type.schema,
|
|
12879
|
-
uniqueConstraints: registration.unique ?? [],
|
|
12880
|
-
onDelete: registration.onDelete
|
|
12881
|
-
},
|
|
12882
|
-
target
|
|
12883
|
-
);
|
|
12884
|
-
}
|
|
12885
|
-
);
|
|
12886
|
-
}
|
|
12887
|
-
async function executeNodeHardDelete(ctx, kind, id, backend) {
|
|
12888
|
-
const gate = await backend.getNode(ctx.graphId, kind, id);
|
|
12889
|
-
if (!gate) return;
|
|
12890
|
-
const opContext = ctx.createOperationContext("delete", "node", kind, id);
|
|
12891
|
-
return runHookedWriteOperation(
|
|
12892
|
-
ctx,
|
|
12893
|
-
opContext,
|
|
12894
|
-
backend,
|
|
12895
|
-
async (target, lock) => {
|
|
12896
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
12897
|
-
const preflight = await target.getNode(ctx.graphId, kind, id);
|
|
12898
|
-
if (!preflight) return;
|
|
12899
|
-
await applyNodeHardDelete(
|
|
12900
|
-
createNodeWriteContext(ctx.graphId, ctx.registry, lock),
|
|
12901
|
-
{
|
|
12902
|
-
kind,
|
|
12903
|
-
id,
|
|
12904
|
-
schema: registration.type.schema,
|
|
12905
|
-
onDelete: registration.onDelete
|
|
12906
|
-
},
|
|
12907
|
-
target
|
|
12908
|
-
);
|
|
12909
|
-
}
|
|
12910
|
-
);
|
|
12911
|
-
}
|
|
12912
|
-
async function executeNodeGetOrCreateByConstraint(ctx, kind, constraintName, props, backend, options) {
|
|
12913
|
-
const ifExists = options?.ifExists ?? "return";
|
|
12914
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
12915
|
-
const nodeKind = registration.type;
|
|
12916
|
-
const validatedProps = validateNodeProps(nodeKind.schema, props, {
|
|
12917
|
-
kind,
|
|
12918
|
-
operation: "create"
|
|
12919
|
-
});
|
|
12920
|
-
const constraint = resolveConstraint(ctx.graph, kind, constraintName);
|
|
12921
|
-
if (!checkWherePredicate(constraint, validatedProps)) {
|
|
12922
|
-
const node = await executeNodeCreate(
|
|
12923
|
-
ctx,
|
|
12924
|
-
{ kind, props: validatedProps },
|
|
12925
|
-
backend
|
|
12926
|
-
);
|
|
12927
|
-
return { node, action: "created" };
|
|
12928
|
-
}
|
|
12929
|
-
const key = computeUniqueKey(
|
|
12930
|
-
validatedProps,
|
|
12931
|
-
constraint.fields,
|
|
12932
|
-
constraint.collation
|
|
12933
|
-
);
|
|
12934
|
-
const kindsToCheck = getKindsForUniquenessCheck(
|
|
12935
|
-
kind,
|
|
12936
|
-
constraint.scope,
|
|
12937
|
-
ctx.registry
|
|
12938
|
-
);
|
|
12939
|
-
async function attempt() {
|
|
12940
|
-
const existingUniqueRow = await findUniqueRowAcrossKinds(
|
|
12941
|
-
backend,
|
|
12942
|
-
ctx.graphId,
|
|
12943
|
-
constraint.name,
|
|
12944
|
-
key,
|
|
12945
|
-
kindsToCheck,
|
|
12946
|
-
true
|
|
12947
|
-
);
|
|
12948
|
-
if (existingUniqueRow === void 0) {
|
|
12949
|
-
const node = await executeNodeCreate(
|
|
12950
|
-
ctx,
|
|
12951
|
-
{ kind, props: validatedProps },
|
|
12952
|
-
backend
|
|
12953
|
-
);
|
|
12954
|
-
return { node, action: "created" };
|
|
12955
|
-
}
|
|
12956
|
-
const existingRow = await backend.getNode(
|
|
12957
|
-
ctx.graphId,
|
|
12958
|
-
existingUniqueRow.concrete_kind,
|
|
12959
|
-
existingUniqueRow.node_id
|
|
12960
|
-
);
|
|
12961
|
-
if (existingRow === void 0) {
|
|
12962
|
-
const node = await executeNodeCreate(
|
|
12963
|
-
ctx,
|
|
12964
|
-
{ kind, props: validatedProps },
|
|
12965
|
-
backend
|
|
12966
|
-
);
|
|
12967
|
-
return { node, action: "created" };
|
|
12968
|
-
}
|
|
12969
|
-
const isSoftDeleted = existingRow.deleted_at !== void 0;
|
|
12970
|
-
if (isSoftDeleted || ifExists === "update") {
|
|
12971
|
-
const concreteKind = existingUniqueRow.concrete_kind;
|
|
12972
|
-
const node = await executeNodeUpsertUpdate(
|
|
12973
|
-
ctx,
|
|
12974
|
-
{
|
|
12975
|
-
kind: concreteKind,
|
|
12976
|
-
id: existingRow.id,
|
|
12977
|
-
props: validatedProps
|
|
12978
|
-
},
|
|
12979
|
-
backend,
|
|
12980
|
-
{ clearDeleted: isSoftDeleted }
|
|
12981
|
-
);
|
|
12982
|
-
return { node, action: isSoftDeleted ? "resurrected" : "updated" };
|
|
12983
|
-
}
|
|
12984
|
-
return { node: rowToNode(existingRow), action: "found" };
|
|
12985
|
-
}
|
|
12986
|
-
try {
|
|
12987
|
-
return await attempt();
|
|
12988
|
-
} catch (error) {
|
|
12989
|
-
if (!(error instanceof UniquenessError)) throw error;
|
|
12990
|
-
return attempt();
|
|
12991
|
-
}
|
|
12992
|
-
}
|
|
12993
|
-
async function executeNodeFindByConstraint(ctx, kind, constraintName, props, backend) {
|
|
12994
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
12995
|
-
const nodeKind = registration.type;
|
|
12996
|
-
const validatedProps = validateNodeProps(nodeKind.schema, props, {
|
|
12997
|
-
kind,
|
|
12998
|
-
operation: "create"
|
|
12999
|
-
});
|
|
13000
|
-
const constraint = resolveConstraint(ctx.graph, kind, constraintName);
|
|
13001
|
-
if (!checkWherePredicate(constraint, validatedProps)) return void 0;
|
|
13002
|
-
const key = computeUniqueKey(
|
|
13003
|
-
validatedProps,
|
|
13004
|
-
constraint.fields,
|
|
13005
|
-
constraint.collation
|
|
13006
|
-
);
|
|
13007
|
-
const kindsToCheck = getKindsForUniquenessCheck(
|
|
13008
|
-
kind,
|
|
13009
|
-
constraint.scope,
|
|
13010
|
-
ctx.registry
|
|
13011
|
-
);
|
|
13012
|
-
const existingUniqueRow = await findUniqueRowAcrossKinds(
|
|
13013
|
-
backend,
|
|
13014
|
-
ctx.graphId,
|
|
13015
|
-
constraint.name,
|
|
13016
|
-
key,
|
|
13017
|
-
kindsToCheck,
|
|
13018
|
-
false
|
|
13019
|
-
);
|
|
13020
|
-
if (existingUniqueRow === void 0) return void 0;
|
|
13021
|
-
const existingRow = await backend.getNode(
|
|
13022
|
-
ctx.graphId,
|
|
13023
|
-
existingUniqueRow.concrete_kind,
|
|
13024
|
-
existingUniqueRow.node_id
|
|
13025
|
-
);
|
|
13026
|
-
if (existingRow === void 0 || existingRow.deleted_at !== void 0)
|
|
13027
|
-
return void 0;
|
|
13028
|
-
return rowToNode(existingRow);
|
|
13029
|
-
}
|
|
13030
|
-
function validateAndComputeKeys(nodeKind, kind, constraint, items) {
|
|
13031
|
-
const validated = [];
|
|
13032
|
-
for (const item of items) {
|
|
13033
|
-
const validatedProps = validateNodeProps(nodeKind.schema, item.props, {
|
|
13034
|
-
kind,
|
|
13035
|
-
operation: "create"
|
|
13036
|
-
});
|
|
13037
|
-
const applies = checkWherePredicate(constraint, validatedProps);
|
|
13038
|
-
const key = applies ? computeUniqueKey(
|
|
13039
|
-
validatedProps,
|
|
13040
|
-
constraint.fields,
|
|
13041
|
-
constraint.collation
|
|
13042
|
-
) : void 0;
|
|
13043
|
-
validated.push({ validatedProps, key });
|
|
13044
|
-
}
|
|
13045
|
-
return validated;
|
|
13046
|
-
}
|
|
13047
|
-
function collectUniqueKeys(validated) {
|
|
13048
|
-
return [
|
|
13049
|
-
...new Set(
|
|
13050
|
-
validated.map((entry2) => entry2.key).filter((key) => key !== void 0)
|
|
13051
|
-
)
|
|
13052
|
-
];
|
|
13053
|
-
}
|
|
13054
|
-
async function executeNodeBulkFindByConstraint(ctx, kind, constraintName, items, backend) {
|
|
13055
|
-
if (items.length === 0) return [];
|
|
13056
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
13057
|
-
const nodeKind = registration.type;
|
|
13058
|
-
const constraint = resolveConstraint(ctx.graph, kind, constraintName);
|
|
13059
|
-
const validated = validateAndComputeKeys(nodeKind, kind, constraint, items);
|
|
13060
|
-
const uniqueKeys = collectUniqueKeys(validated);
|
|
13061
|
-
const kindsToCheck = getKindsForUniquenessCheck(
|
|
13062
|
-
kind,
|
|
13063
|
-
constraint.scope,
|
|
13064
|
-
ctx.registry
|
|
13065
|
-
);
|
|
13066
|
-
const existingByKey = uniqueKeys.length > 0 ? await batchCheckUniqueAcrossKinds(
|
|
13067
|
-
backend,
|
|
13068
|
-
ctx.graphId,
|
|
13069
|
-
constraint.name,
|
|
13070
|
-
uniqueKeys,
|
|
13071
|
-
kindsToCheck,
|
|
13072
|
-
false
|
|
13073
|
-
) : /* @__PURE__ */ new Map();
|
|
13074
|
-
const results = Array.from({ length: items.length });
|
|
13075
|
-
const seenKeys = /* @__PURE__ */ new Map();
|
|
13076
|
-
for (const [index, { key }] of validated.entries()) {
|
|
13077
|
-
if (key === void 0) {
|
|
13078
|
-
results[index] = void 0;
|
|
13079
|
-
continue;
|
|
13080
|
-
}
|
|
13081
|
-
const previousIndex = seenKeys.get(key);
|
|
13082
|
-
if (previousIndex !== void 0) {
|
|
13083
|
-
results[index] = results[previousIndex];
|
|
13084
|
-
continue;
|
|
13085
|
-
}
|
|
13086
|
-
seenKeys.set(key, index);
|
|
13087
|
-
const existing = existingByKey.get(key);
|
|
13088
|
-
if (existing === void 0) {
|
|
13089
|
-
results[index] = void 0;
|
|
13090
|
-
continue;
|
|
13091
|
-
}
|
|
13092
|
-
const existingRow = await backend.getNode(
|
|
13093
|
-
ctx.graphId,
|
|
13094
|
-
existing.concrete_kind,
|
|
13095
|
-
existing.node_id
|
|
13096
|
-
);
|
|
13097
|
-
if (existingRow === void 0 || existingRow.deleted_at !== void 0) {
|
|
13098
|
-
results[index] = void 0;
|
|
13099
|
-
continue;
|
|
13100
|
-
}
|
|
13101
|
-
results[index] = rowToNode(existingRow);
|
|
13102
|
-
}
|
|
13103
|
-
return results;
|
|
13104
|
-
}
|
|
13105
|
-
function resolveNodeIndex(graph, kind, indexName) {
|
|
13106
|
-
getNodeRegistration(graph, kind);
|
|
13107
|
-
const declaration = graph.indexes?.find(
|
|
13108
|
-
(candidate) => candidate.entity === "node" && candidate.kind === kind && candidate.name === indexName
|
|
13109
|
-
);
|
|
13110
|
-
if (declaration?.entity !== "node") {
|
|
13111
|
-
throw new NodeIndexNotFoundError(indexName, kind);
|
|
13112
|
-
}
|
|
13113
|
-
return declaration;
|
|
13114
|
-
}
|
|
13115
|
-
var INDEX_PROBE_EXPECTED_TYPEOF = {
|
|
13116
|
-
string: "string",
|
|
13117
|
-
number: "number",
|
|
13118
|
-
boolean: "boolean"
|
|
13119
|
-
};
|
|
13120
|
-
function validateIndexProbeValue(value, valueType, pointer, kind) {
|
|
13121
|
-
if (value === void 0 || value === null) return;
|
|
13122
|
-
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && !(value instanceof Date)) {
|
|
13123
|
-
throw indexProbeTypeError(
|
|
13124
|
-
pointer,
|
|
13125
|
-
kind,
|
|
13126
|
-
"a scalar (string, number, boolean, or Date)",
|
|
13127
|
-
value
|
|
13128
|
-
);
|
|
13129
|
-
}
|
|
13130
|
-
if (valueType === "date") {
|
|
13131
|
-
if (value instanceof Date || typeof value === "string") return;
|
|
13132
|
-
throw indexProbeTypeError(
|
|
13133
|
-
pointer,
|
|
13134
|
-
kind,
|
|
13135
|
-
"date (Date or ISO string)",
|
|
13136
|
-
value
|
|
13137
|
-
);
|
|
13138
|
-
}
|
|
13139
|
-
const expected = INDEX_PROBE_EXPECTED_TYPEOF[valueType ?? "unknown"];
|
|
13140
|
-
if (expected === void 0) return;
|
|
13141
|
-
if (typeof value !== expected) {
|
|
13142
|
-
throw indexProbeTypeError(pointer, kind, expected, value);
|
|
13143
|
-
}
|
|
13144
|
-
}
|
|
13145
|
-
function indexProbeTypeError(pointer, kind, expected, value) {
|
|
13146
|
-
return new ValidationError(
|
|
13147
|
-
`Index probe value for "${pointer}" on node kind "${kind}" has an incompatible type`,
|
|
13148
|
-
{
|
|
13149
|
-
entityType: "node",
|
|
13150
|
-
kind,
|
|
13151
|
-
issues: [
|
|
13152
|
-
{
|
|
13153
|
-
path: pointer,
|
|
13154
|
-
message: `Expected ${expected}, received ${typeof value}`,
|
|
13155
|
-
code: "invalid_type"
|
|
13156
|
-
}
|
|
13157
|
-
]
|
|
13158
|
-
}
|
|
13159
|
-
);
|
|
13160
|
-
}
|
|
13161
|
-
function coerceIndexProbeBind(value, adapter) {
|
|
13162
|
-
return adapter.bindValue(normalizeProbeScalar(value));
|
|
13163
|
-
}
|
|
13164
|
-
function normalizeProbeScalar(value) {
|
|
13165
|
-
return value instanceof Date ? value.toISOString() : value;
|
|
13166
|
-
}
|
|
13167
|
-
var PROBE_NULL_TAG = 0;
|
|
13168
|
-
var PROBE_VALUE_TAG = 1;
|
|
13169
|
-
function canonicalIndexProbeKey(probe) {
|
|
13170
|
-
return JSON.stringify(
|
|
13171
|
-
probe.map(
|
|
13172
|
-
(value) => value === void 0 || value === null ? [PROBE_NULL_TAG] : [PROBE_VALUE_TAG, normalizeProbeScalar(value)]
|
|
13173
|
-
)
|
|
13174
|
-
);
|
|
13175
|
-
}
|
|
13176
|
-
async function executeNodeBulkFindByIndex(ctx, kind, indexName, items, backend, options) {
|
|
13177
|
-
if (items.length === 0) return [];
|
|
13178
|
-
const index = resolveNodeIndex(ctx.graph, kind, indexName);
|
|
13179
|
-
if (index.fieldValueTypes.includes("date")) {
|
|
13180
|
-
throw new ConfigurationError(
|
|
13181
|
-
`bulkFindByIndex does not support date-typed key fields on index "${indexName}" (node kind "${kind}")`,
|
|
13182
|
-
{ indexName, kind },
|
|
13183
|
-
{
|
|
13184
|
-
suggestion: "Date index keys compare differently across SQLite and PostgreSQL. Use a string-encoded key field, or query date predicates via store.query(...).where(...)."
|
|
13185
|
-
}
|
|
13186
|
-
);
|
|
13187
|
-
}
|
|
13188
|
-
const limitPerInput = options?.limitPerInput;
|
|
13189
|
-
if (limitPerInput !== void 0 && (!Number.isInteger(limitPerInput) || limitPerInput <= 0)) {
|
|
13190
|
-
throw new ValidationError(
|
|
13191
|
-
"bulkFindByIndex limitPerInput must be a positive integer",
|
|
13192
|
-
{
|
|
13193
|
-
entityType: "node",
|
|
13194
|
-
kind,
|
|
13195
|
-
issues: [
|
|
13196
|
-
{
|
|
13197
|
-
path: "limitPerInput",
|
|
13198
|
-
message: `Expected a positive integer, received ${String(limitPerInput)}`,
|
|
13199
|
-
code: "invalid_value"
|
|
13200
|
-
}
|
|
13201
|
-
]
|
|
13202
|
-
}
|
|
13203
|
-
);
|
|
13204
|
-
}
|
|
13205
|
-
const adapter = getDialect(backend.dialect);
|
|
13206
|
-
const probes = items.map(
|
|
13207
|
-
(item) => index.fields.map((pointer, position) => {
|
|
13208
|
-
const value = resolveJsonPointer(item.props, pointer);
|
|
13209
|
-
validateIndexProbeValue(
|
|
13210
|
-
value,
|
|
13211
|
-
index.fieldValueTypes[position],
|
|
13212
|
-
pointer,
|
|
13213
|
-
kind
|
|
13214
|
-
);
|
|
13215
|
-
return value;
|
|
13216
|
-
})
|
|
13217
|
-
);
|
|
13218
|
-
const groupByKey = /* @__PURE__ */ new Map();
|
|
13219
|
-
const groupProbes = [];
|
|
13220
|
-
const groupToInputs = [];
|
|
13221
|
-
for (const [inputIndex, probe] of probes.entries()) {
|
|
13222
|
-
const key = canonicalIndexProbeKey(probe);
|
|
13223
|
-
const existing = groupByKey.get(key);
|
|
13224
|
-
if (existing === void 0) {
|
|
13225
|
-
groupByKey.set(key, groupProbes.length);
|
|
13226
|
-
groupProbes.push(probe);
|
|
13227
|
-
groupToInputs.push([inputIndex]);
|
|
13228
|
-
continue;
|
|
13229
|
-
}
|
|
13230
|
-
groupToInputs[existing]?.push(inputIndex);
|
|
13231
|
-
}
|
|
13232
|
-
const schema = backend.tableNames ? createSqlSchema(backend.tableNames) : DEFAULT_SQL_SCHEMA;
|
|
13233
|
-
const compileContext = {
|
|
13234
|
-
dialect: backend.dialect,
|
|
13235
|
-
propsColumn: sql.raw(`"props"`),
|
|
13236
|
-
systemColumn: (column) => sql.raw(`"${column}"`)
|
|
13237
|
-
};
|
|
13238
|
-
const fieldKeys = compileNodeIndexFieldKeys(index, compileContext);
|
|
13239
|
-
const groupPredicates = groupProbes.map(
|
|
13240
|
-
(probe) => sql`(${sql.join(
|
|
13241
|
-
fieldKeys.map((fieldKey, position) => {
|
|
13242
|
-
const value = probe[position];
|
|
13243
|
-
if (value === void 0 || value === null) {
|
|
13244
|
-
return sql`${fieldKey} IS NULL`;
|
|
13245
|
-
}
|
|
13246
|
-
return adapter.nullSafeEquals(
|
|
13247
|
-
fieldKey,
|
|
13248
|
-
sql`${coerceIndexProbeBind(value, adapter)}`
|
|
13249
|
-
);
|
|
13250
|
-
}),
|
|
13251
|
-
sql` AND `
|
|
13252
|
-
)})`
|
|
13253
|
-
);
|
|
13254
|
-
const caseBranches = groupPredicates.map(
|
|
13255
|
-
(predicate, group) => sql`WHEN ${predicate} THEN ${sql.raw(String(group))}`
|
|
13256
|
-
);
|
|
13257
|
-
const probeIndexExpr = sql`CASE ${sql.join(caseBranches, sql` `)} ELSE NULL END`;
|
|
13258
|
-
const conditions = [
|
|
13259
|
-
sql`"graph_id" = ${ctx.graphId}`,
|
|
13260
|
-
sql`"kind" = ${kind}`,
|
|
13261
|
-
sql`"deleted_at" IS NULL`
|
|
13262
|
-
];
|
|
13263
|
-
if (index.where !== void 0) {
|
|
13264
|
-
conditions.push(compileIndexWhere(compileContext, index.where));
|
|
13265
|
-
}
|
|
13266
|
-
conditions.push(sql`(${sql.join(groupPredicates, sql` OR `)})`);
|
|
13267
|
-
const whereClause = sql.join(conditions, sql` AND `);
|
|
13268
|
-
const probedSelect = sql`SELECT "id", ${probeIndexExpr} AS probe_idx FROM ${schema.nodesTable} WHERE ${whereClause}`;
|
|
13269
|
-
const capInSql = limitPerInput !== void 0 && backend.capabilities.windowFunctions;
|
|
13270
|
-
const query = capInSql ? sql`SELECT "id", probe_idx FROM (SELECT "id", probe_idx, ROW_NUMBER() OVER (PARTITION BY probe_idx ORDER BY "id") AS probe_rank FROM (${probedSelect}) AS probed) AS ranked WHERE probe_rank <= ${limitPerInput} ORDER BY probe_idx, "id"` : sql`${probedSelect} ORDER BY probe_idx, "id"`;
|
|
13271
|
-
const rawMatches = await backend.execute(
|
|
13272
|
-
asCompiledRowsSql(query)
|
|
13273
|
-
);
|
|
13274
|
-
const matches = limitPerInput !== void 0 && !capInSql ? capMatchesPerGroup(rawMatches, limitPerInput) : rawMatches;
|
|
13275
|
-
const nodesById = await hydrateNodesById(
|
|
13276
|
-
backend,
|
|
13277
|
-
ctx.graphId,
|
|
13278
|
-
kind,
|
|
13279
|
-
matches.map((match) => match.id)
|
|
13280
|
-
);
|
|
13281
|
-
const results = Array.from({ length: items.length }, () => []);
|
|
13282
|
-
for (const match of matches) {
|
|
13283
|
-
const node = nodesById.get(match.id);
|
|
13284
|
-
if (node === void 0) continue;
|
|
13285
|
-
const inputs = groupToInputs[match.probe_idx];
|
|
13286
|
-
if (inputs === void 0) continue;
|
|
13287
|
-
for (const inputIndex of inputs) {
|
|
13288
|
-
results[inputIndex]?.push(node);
|
|
13289
|
-
}
|
|
13290
|
-
}
|
|
13291
|
-
return results;
|
|
13292
|
-
}
|
|
13293
|
-
function capMatchesPerGroup(matches, limitPerInput) {
|
|
13294
|
-
const perGroupCount = /* @__PURE__ */ new Map();
|
|
13295
|
-
const capped = [];
|
|
13296
|
-
for (const match of matches) {
|
|
13297
|
-
const count2 = perGroupCount.get(match.probe_idx) ?? 0;
|
|
13298
|
-
if (count2 >= limitPerInput) continue;
|
|
13299
|
-
perGroupCount.set(match.probe_idx, count2 + 1);
|
|
13300
|
-
capped.push(match);
|
|
13301
|
-
}
|
|
13302
|
-
return capped;
|
|
13303
|
-
}
|
|
13304
|
-
async function hydrateNodesById(backend, graphId, kind, ids) {
|
|
13305
|
-
const rowsById = await getNodeRowsByIds(backend, graphId, kind, ids);
|
|
13306
|
-
const nodesById = /* @__PURE__ */ new Map();
|
|
13307
|
-
for (const [id, row] of rowsById) {
|
|
13308
|
-
if (row.deleted_at !== void 0) continue;
|
|
13309
|
-
nodesById.set(id, rowToNode(row));
|
|
13310
|
-
}
|
|
13311
|
-
return nodesById;
|
|
13312
|
-
}
|
|
13313
|
-
async function executeNodeBulkGetOrCreateByConstraint(ctx, kind, constraintName, items, backend, options) {
|
|
13314
|
-
if (items.length === 0) return [];
|
|
13315
|
-
const ifExists = options?.ifExists ?? "return";
|
|
13316
|
-
const registration = getNodeRegistration(ctx.graph, kind);
|
|
13317
|
-
const nodeKind = registration.type;
|
|
13318
|
-
const constraint = resolveConstraint(ctx.graph, kind, constraintName);
|
|
13319
|
-
const validated = validateAndComputeKeys(nodeKind, kind, constraint, items);
|
|
13320
|
-
const uniqueKeys = collectUniqueKeys(validated);
|
|
13321
|
-
const kindsToCheck = getKindsForUniquenessCheck(
|
|
13322
|
-
kind,
|
|
13323
|
-
constraint.scope,
|
|
13324
|
-
ctx.registry
|
|
13325
|
-
);
|
|
13326
|
-
const existingByKey = uniqueKeys.length > 0 ? await batchCheckUniqueAcrossKinds(
|
|
13327
|
-
backend,
|
|
13328
|
-
ctx.graphId,
|
|
13329
|
-
constraint.name,
|
|
13330
|
-
uniqueKeys,
|
|
13331
|
-
kindsToCheck,
|
|
13332
|
-
true
|
|
13333
|
-
) : /* @__PURE__ */ new Map();
|
|
13334
|
-
const toCreate = [];
|
|
13335
|
-
const toFetch = [];
|
|
13336
|
-
const duplicateOf = [];
|
|
13337
|
-
const seenKeys = /* @__PURE__ */ new Map();
|
|
13338
|
-
for (const [index, { validatedProps, key }] of validated.entries()) {
|
|
13339
|
-
if (key === void 0) {
|
|
13340
|
-
toCreate.push({ index, input: { kind, props: validatedProps } });
|
|
13341
|
-
continue;
|
|
13342
|
-
}
|
|
13343
|
-
const previousIndex = seenKeys.get(key);
|
|
13344
|
-
if (previousIndex !== void 0) {
|
|
13345
|
-
duplicateOf.push({ index, sourceIndex: previousIndex });
|
|
13346
|
-
continue;
|
|
13347
|
-
}
|
|
13348
|
-
seenKeys.set(key, index);
|
|
13349
|
-
const existing = existingByKey.get(key);
|
|
13350
|
-
if (existing === void 0) {
|
|
13351
|
-
toCreate.push({ index, input: { kind, props: validatedProps } });
|
|
13352
|
-
} else {
|
|
13353
|
-
toFetch.push({
|
|
13354
|
-
index,
|
|
13355
|
-
nodeId: existing.node_id,
|
|
13356
|
-
concreteKind: existing.concrete_kind,
|
|
13357
|
-
validatedProps,
|
|
13358
|
-
isSoftDeleted: existing.deleted_at !== void 0
|
|
13359
|
-
});
|
|
13360
|
-
}
|
|
13361
|
-
}
|
|
13362
|
-
const results = Array.from({ length: items.length });
|
|
13363
|
-
if (toCreate.length > 0) {
|
|
13364
|
-
const createInputs = toCreate.map((entry2) => entry2.input);
|
|
13365
|
-
const createdNodes = await executeNodeCreateBatch(
|
|
13366
|
-
ctx,
|
|
13367
|
-
createInputs,
|
|
13368
|
-
backend
|
|
13369
|
-
);
|
|
13370
|
-
for (const [batchIndex, entry2] of toCreate.entries()) {
|
|
13371
|
-
results[entry2.index] = {
|
|
13372
|
-
node: createdNodes[batchIndex],
|
|
13373
|
-
action: "created"
|
|
13374
|
-
};
|
|
13375
|
-
}
|
|
13376
|
-
}
|
|
13377
|
-
for (const entry2 of toFetch) {
|
|
13378
|
-
const { index, concreteKind, validatedProps, isSoftDeleted, nodeId } = entry2;
|
|
13379
|
-
const existingRow = await backend.getNode(
|
|
13380
|
-
ctx.graphId,
|
|
13381
|
-
concreteKind,
|
|
13382
|
-
nodeId
|
|
13383
|
-
);
|
|
13384
|
-
if (existingRow === void 0) {
|
|
13385
|
-
const node = await executeNodeCreate(
|
|
13386
|
-
ctx,
|
|
13387
|
-
{ kind, props: validatedProps },
|
|
13388
|
-
backend
|
|
13389
|
-
);
|
|
13390
|
-
results[index] = { node, action: "created" };
|
|
13391
|
-
continue;
|
|
13392
|
-
}
|
|
13393
|
-
if (isSoftDeleted || ifExists === "update") {
|
|
13394
|
-
const node = await executeNodeUpsertUpdate(
|
|
13395
|
-
ctx,
|
|
13396
|
-
{
|
|
13397
|
-
kind: concreteKind,
|
|
13398
|
-
id: existingRow.id,
|
|
13399
|
-
props: validatedProps
|
|
13400
|
-
},
|
|
13401
|
-
backend,
|
|
13402
|
-
{ clearDeleted: isSoftDeleted }
|
|
13403
|
-
);
|
|
13404
|
-
results[index] = {
|
|
13405
|
-
node,
|
|
13406
|
-
action: isSoftDeleted ? "resurrected" : "updated"
|
|
13407
|
-
};
|
|
13408
|
-
} else {
|
|
13409
|
-
results[index] = { node: rowToNode(existingRow), action: "found" };
|
|
13410
|
-
}
|
|
13411
|
-
}
|
|
13412
|
-
for (const { index, sourceIndex } of duplicateOf) {
|
|
13413
|
-
const sourceResult = results[sourceIndex];
|
|
13414
|
-
results[index] = { node: sourceResult.node, action: "found" };
|
|
13415
|
-
}
|
|
13416
|
-
return results;
|
|
13417
|
-
}
|
|
13418
|
-
var RECORDED_POINT_READ_FIXED_BIND_PARAMS = 8;
|
|
13419
|
-
function recordedPointReadIdChunk(backend) {
|
|
13420
|
-
const budget = recordedBindParamBudget(backend);
|
|
13421
|
-
return Math.max(1, budget - RECORDED_POINT_READ_FIXED_BIND_PARAMS);
|
|
13422
|
-
}
|
|
13423
|
-
function recordedRelationErrorDetails(backend, surface) {
|
|
13424
|
-
return { dialect: backend.dialect, surface };
|
|
13425
|
-
}
|
|
13426
|
-
function withRelationsPrecondition(backend, promise, surface) {
|
|
13427
|
-
return withRecordedRelationsPrecondition(
|
|
13428
|
-
promise,
|
|
13429
|
-
recordedRelationErrorDetails(backend, surface)
|
|
13430
|
-
);
|
|
13431
|
-
}
|
|
13432
|
-
function createRecordedReadBackend(backend, surface) {
|
|
13433
|
-
return createBackendOverlay(backend, {
|
|
13434
|
-
execute: (query) => withRelationsPrecondition(backend, backend.execute(query), surface),
|
|
13435
|
-
...backend.executeRaw === void 0 ? {} : {
|
|
13436
|
-
executeRaw: (sqlText, params) => withRelationsPrecondition(
|
|
13437
|
-
backend,
|
|
13438
|
-
backend.executeRaw(sqlText, params),
|
|
13439
|
-
surface
|
|
13440
|
-
)
|
|
13441
|
-
}
|
|
13442
|
-
});
|
|
13443
|
-
}
|
|
13444
|
-
function recordedTemporalFilter(backend, coordinate, tableAlias) {
|
|
13445
|
-
const recordedAsOf = coordinate.recorded?.asOf;
|
|
13446
|
-
if (recordedAsOf === void 0) {
|
|
13447
|
-
throw new ConfigurationError(
|
|
13448
|
-
"Recorded point reads require a recorded-time coordinate.",
|
|
13449
|
-
{ code: "RECORDED_POINT_READ_MISSING_COORDINATE" }
|
|
13450
|
-
);
|
|
13451
|
-
}
|
|
13452
|
-
return compileTemporalFilter({
|
|
13453
|
-
mode: coordinate.valid.mode,
|
|
13454
|
-
asOf: coordinate.valid.asOf,
|
|
13455
|
-
recordedAsOf,
|
|
13456
|
-
tableAlias,
|
|
13457
|
-
currentTimestamp: getDialect(backend.dialect).currentTimestamp()
|
|
13458
|
-
});
|
|
13459
|
-
}
|
|
13460
|
-
function recordedRelationInvariantError(params) {
|
|
13461
|
-
return new ConfigurationError(
|
|
13462
|
-
"Recorded relation invariant violation: more than one recorded row matched the requested point read.",
|
|
13463
|
-
{
|
|
13464
|
-
code: "RECORDED_RELATION_INVARIANT_VIOLATION",
|
|
13465
|
-
graphId: params.graphId,
|
|
13466
|
-
entity: params.entity,
|
|
13467
|
-
kind: params.kind,
|
|
13468
|
-
id: params.id,
|
|
13469
|
-
validMode: params.coordinate.valid.mode,
|
|
13470
|
-
validAsOf: params.coordinate.valid.asOf,
|
|
13471
|
-
recordedAsOf: params.coordinate.recorded?.asOf
|
|
13472
|
-
},
|
|
13473
|
-
{
|
|
13474
|
-
suggestion: "Repair overlapping recorded_from/recorded_to intervals for this entity before using recorded-time reads."
|
|
13475
|
-
}
|
|
13224
|
+
{
|
|
13225
|
+
suggestion: "Repair overlapping recorded_from/recorded_to intervals for this entity before using recorded-time reads."
|
|
13226
|
+
}
|
|
13476
13227
|
);
|
|
13477
13228
|
}
|
|
13478
13229
|
function createRecordedReadService(params) {
|
|
@@ -13573,6 +13324,155 @@ function createRecordedReadService(params) {
|
|
|
13573
13324
|
};
|
|
13574
13325
|
}
|
|
13575
13326
|
|
|
13327
|
+
// src/store/transaction-receipt.ts
|
|
13328
|
+
function inputLength(value) {
|
|
13329
|
+
return Array.isArray(value) ? value.length : 0;
|
|
13330
|
+
}
|
|
13331
|
+
function countSingleWrite() {
|
|
13332
|
+
return 1;
|
|
13333
|
+
}
|
|
13334
|
+
function countBulkInputAt(index) {
|
|
13335
|
+
return (args) => inputLength(args[index]);
|
|
13336
|
+
}
|
|
13337
|
+
var NODE_WRITE_INTENT_COUNTERS = {
|
|
13338
|
+
create: countSingleWrite,
|
|
13339
|
+
createFromRecord: countSingleWrite,
|
|
13340
|
+
update: countSingleWrite,
|
|
13341
|
+
delete: countSingleWrite,
|
|
13342
|
+
hardDelete: countSingleWrite,
|
|
13343
|
+
upsertById: countSingleWrite,
|
|
13344
|
+
upsertByIdFromRecord: countSingleWrite,
|
|
13345
|
+
bulkCreate: countBulkInputAt(0),
|
|
13346
|
+
bulkUpsertById: countBulkInputAt(0),
|
|
13347
|
+
bulkInsert: countBulkInputAt(0),
|
|
13348
|
+
bulkDelete: countBulkInputAt(0),
|
|
13349
|
+
getOrCreateByConstraint: countSingleWrite,
|
|
13350
|
+
bulkGetOrCreateByConstraint: countBulkInputAt(1)
|
|
13351
|
+
};
|
|
13352
|
+
var EDGE_WRITE_INTENT_COUNTERS = {
|
|
13353
|
+
create: countSingleWrite,
|
|
13354
|
+
update: countSingleWrite,
|
|
13355
|
+
delete: countSingleWrite,
|
|
13356
|
+
hardDelete: countSingleWrite,
|
|
13357
|
+
bulkCreate: countBulkInputAt(0),
|
|
13358
|
+
bulkUpsertById: countBulkInputAt(0),
|
|
13359
|
+
bulkInsert: countBulkInputAt(0),
|
|
13360
|
+
bulkDelete: countBulkInputAt(0),
|
|
13361
|
+
getOrCreateByEndpoints: countSingleWrite,
|
|
13362
|
+
bulkGetOrCreateByEndpoints: countBulkInputAt(0)
|
|
13363
|
+
};
|
|
13364
|
+
function createCountBucket() {
|
|
13365
|
+
return /* @__PURE__ */ Object.create(null);
|
|
13366
|
+
}
|
|
13367
|
+
function increment(counts, kind, count2) {
|
|
13368
|
+
if (count2 === 0) return;
|
|
13369
|
+
counts[kind] = (counts[kind] ?? 0) + count2;
|
|
13370
|
+
}
|
|
13371
|
+
function createTransactionReceiptRecorder() {
|
|
13372
|
+
const nodes = createCountBucket();
|
|
13373
|
+
const edges = createCountBucket();
|
|
13374
|
+
let total = 0;
|
|
13375
|
+
return {
|
|
13376
|
+
recordNode(kind, count2) {
|
|
13377
|
+
increment(nodes, kind, count2);
|
|
13378
|
+
total += count2;
|
|
13379
|
+
},
|
|
13380
|
+
recordEdge(kind, count2) {
|
|
13381
|
+
increment(edges, kind, count2);
|
|
13382
|
+
total += count2;
|
|
13383
|
+
},
|
|
13384
|
+
snapshot(recorded) {
|
|
13385
|
+
return Object.freeze({
|
|
13386
|
+
writes: Object.freeze({
|
|
13387
|
+
nodes: Object.freeze(Object.assign(createCountBucket(), nodes)),
|
|
13388
|
+
edges: Object.freeze(Object.assign(createCountBucket(), edges)),
|
|
13389
|
+
total
|
|
13390
|
+
}),
|
|
13391
|
+
...recorded === void 0 ? {} : { recorded }
|
|
13392
|
+
});
|
|
13393
|
+
}
|
|
13394
|
+
};
|
|
13395
|
+
}
|
|
13396
|
+
function isWrappedMethod(value) {
|
|
13397
|
+
return typeof value === "function";
|
|
13398
|
+
}
|
|
13399
|
+
function isObject(value) {
|
|
13400
|
+
return typeof value === "object" && Boolean(value);
|
|
13401
|
+
}
|
|
13402
|
+
var NODE_WRITE_SURFACE = {
|
|
13403
|
+
methodNames: new Set(NODE_WRITE_NAMES),
|
|
13404
|
+
counters: NODE_WRITE_INTENT_COUNTERS
|
|
13405
|
+
};
|
|
13406
|
+
var EDGE_WRITE_SURFACE = {
|
|
13407
|
+
methodNames: new Set(EDGE_WRITE_NAMES),
|
|
13408
|
+
counters: EDGE_WRITE_INTENT_COUNTERS
|
|
13409
|
+
};
|
|
13410
|
+
function memoizedProxyGet(target, shouldWrap, wrap) {
|
|
13411
|
+
const cache = /* @__PURE__ */ new Map();
|
|
13412
|
+
return new Proxy(target, {
|
|
13413
|
+
get(proxyTarget, property, receiver) {
|
|
13414
|
+
if (typeof property !== "string" || !shouldWrap(property)) {
|
|
13415
|
+
return Reflect.get(proxyTarget, property, receiver);
|
|
13416
|
+
}
|
|
13417
|
+
const cached = cache.get(property);
|
|
13418
|
+
if (cached !== void 0) return cached;
|
|
13419
|
+
const value = Reflect.get(proxyTarget, property, receiver);
|
|
13420
|
+
const wrapped = wrap(value, property);
|
|
13421
|
+
cache.set(property, wrapped);
|
|
13422
|
+
return wrapped;
|
|
13423
|
+
}
|
|
13424
|
+
});
|
|
13425
|
+
}
|
|
13426
|
+
function wrapWriteCollection(collection, kind, surface, record) {
|
|
13427
|
+
return memoizedProxyGet(
|
|
13428
|
+
collection,
|
|
13429
|
+
(property) => surface.methodNames.has(property),
|
|
13430
|
+
(value, property) => {
|
|
13431
|
+
if (!isWrappedMethod(value)) return value;
|
|
13432
|
+
const method = property;
|
|
13433
|
+
const wrapped = async (...args) => {
|
|
13434
|
+
const count2 = surface.counters[method](args);
|
|
13435
|
+
const result = await Reflect.apply(value, collection, args);
|
|
13436
|
+
record(kind, count2);
|
|
13437
|
+
return result;
|
|
13438
|
+
};
|
|
13439
|
+
return wrapped;
|
|
13440
|
+
}
|
|
13441
|
+
);
|
|
13442
|
+
}
|
|
13443
|
+
function wrapCollections(collections, wrapOne) {
|
|
13444
|
+
return memoizedProxyGet(
|
|
13445
|
+
collections,
|
|
13446
|
+
() => true,
|
|
13447
|
+
(collection, kind) => isObject(collection) ? wrapOne(collection, kind) : collection
|
|
13448
|
+
);
|
|
13449
|
+
}
|
|
13450
|
+
function wrapTransactionCollections(nodes, edges, recorder) {
|
|
13451
|
+
return {
|
|
13452
|
+
nodes: wrapCollections(
|
|
13453
|
+
nodes,
|
|
13454
|
+
(collection, kind) => wrapWriteCollection(
|
|
13455
|
+
collection,
|
|
13456
|
+
kind,
|
|
13457
|
+
NODE_WRITE_SURFACE,
|
|
13458
|
+
recorder.recordNode
|
|
13459
|
+
)
|
|
13460
|
+
),
|
|
13461
|
+
edges: wrapCollections(
|
|
13462
|
+
edges,
|
|
13463
|
+
(collection, kind) => wrapWriteCollection(
|
|
13464
|
+
collection,
|
|
13465
|
+
kind,
|
|
13466
|
+
EDGE_WRITE_SURFACE,
|
|
13467
|
+
recorder.recordEdge
|
|
13468
|
+
)
|
|
13469
|
+
)
|
|
13470
|
+
};
|
|
13471
|
+
}
|
|
13472
|
+
|
|
13473
|
+
// src/store/types.ts
|
|
13474
|
+
var AUTO_REFRESH_STATISTICS_ROW_THRESHOLD = 1e3;
|
|
13475
|
+
|
|
13576
13476
|
// src/store/store.ts
|
|
13577
13477
|
var UNKNOWN_SCHEMA_METADATA = Object.freeze({
|
|
13578
13478
|
schemaVersion: void 0,
|
|
@@ -13605,6 +13505,15 @@ function rowMapperConfigFor(backend) {
|
|
|
13605
13505
|
function asError(error) {
|
|
13606
13506
|
return error instanceof Error ? error : new Error(String(error));
|
|
13607
13507
|
}
|
|
13508
|
+
function transactionOutcome(result, recorder, recordedByGraph, graphId) {
|
|
13509
|
+
const recorded = recordedByGraph?.get(graphId);
|
|
13510
|
+
return {
|
|
13511
|
+
result,
|
|
13512
|
+
receipt: recorder.snapshot(
|
|
13513
|
+
recorded === void 0 ? void 0 : asRecordedInstant(recorded)
|
|
13514
|
+
)
|
|
13515
|
+
};
|
|
13516
|
+
}
|
|
13608
13517
|
var Store = class _Store {
|
|
13609
13518
|
#graph;
|
|
13610
13519
|
/**
|
|
@@ -13614,6 +13523,8 @@ var Store = class _Store {
|
|
|
13614
13523
|
#baseBackend;
|
|
13615
13524
|
#backend;
|
|
13616
13525
|
#captureEnabled;
|
|
13526
|
+
#revisionTrackingEnabled;
|
|
13527
|
+
#revisionOrigin;
|
|
13617
13528
|
#recordedReadBinding;
|
|
13618
13529
|
#registry;
|
|
13619
13530
|
#hooks;
|
|
@@ -13634,6 +13545,10 @@ var Store = class _Store {
|
|
|
13634
13545
|
this.#graph = graph;
|
|
13635
13546
|
this.#baseBackend = asRawBackend(backend);
|
|
13636
13547
|
this.#captureEnabled = options?.history === true;
|
|
13548
|
+
this.#revisionTrackingEnabled = this.#captureEnabled || options?.revisionTracking === true;
|
|
13549
|
+
if (this.#revisionTrackingEnabled) {
|
|
13550
|
+
assertRevisionTrackableBackend(backend);
|
|
13551
|
+
}
|
|
13637
13552
|
const explicitSchema = options?.schema === void 0 ? void 0 : requireSqlSchema(options.schema, "store schema");
|
|
13638
13553
|
const resolvedSchema = explicitSchema ?? (backend.tableNames ? createSqlSchema(backend.tableNames) : void 0);
|
|
13639
13554
|
const readSchema = resolvedSchema ?? createSqlSchema(backend.tableNames);
|
|
@@ -13691,6 +13606,22 @@ var Store = class _Store {
|
|
|
13691
13606
|
get historyEnabled() {
|
|
13692
13607
|
return this.#captureEnabled;
|
|
13693
13608
|
}
|
|
13609
|
+
/**
|
|
13610
|
+
* Whether this Store advances a durable revision anchor for graph writes.
|
|
13611
|
+
*
|
|
13612
|
+
* @internal
|
|
13613
|
+
*/
|
|
13614
|
+
get revisionTrackingEnabled() {
|
|
13615
|
+
return this.#revisionTrackingEnabled;
|
|
13616
|
+
}
|
|
13617
|
+
/**
|
|
13618
|
+
* SQL schema that owns the durable revision clock relation.
|
|
13619
|
+
*
|
|
13620
|
+
* @internal
|
|
13621
|
+
*/
|
|
13622
|
+
get revisionSchema() {
|
|
13623
|
+
return this.#sqlSchema();
|
|
13624
|
+
}
|
|
13694
13625
|
/**
|
|
13695
13626
|
* Whether this store has a recorded read relation bound for reconstruction.
|
|
13696
13627
|
*
|
|
@@ -13780,6 +13711,8 @@ var Store = class _Store {
|
|
|
13780
13711
|
if (this.#algorithms === void 0) {
|
|
13781
13712
|
this.#algorithms = createGraphAlgorithms({
|
|
13782
13713
|
graphId: this.graphId,
|
|
13714
|
+
graph: this.#graph,
|
|
13715
|
+
registry: this.#registry,
|
|
13783
13716
|
backend: this.#backend,
|
|
13784
13717
|
schema: this.#schema,
|
|
13785
13718
|
recordedReadBinding: this.#recordedReadBinding,
|
|
@@ -13789,6 +13722,19 @@ var Store = class _Store {
|
|
|
13789
13722
|
return this.#algorithms;
|
|
13790
13723
|
}
|
|
13791
13724
|
// === Dynamic Collection Access ===
|
|
13725
|
+
/**
|
|
13726
|
+
* Resolves `kind` against `collections` (either `this.nodes` or a
|
|
13727
|
+
* transaction-scoped — and possibly receipt-wrapped — node collection map),
|
|
13728
|
+
* or `undefined` when the kind is not registered in this graph. Shared by
|
|
13729
|
+
* `getNodeCollection` and both transaction contexts' `getNodeCollection` so
|
|
13730
|
+
* a receipt-wrapped transaction counts writes made through the dynamic
|
|
13731
|
+
* lookup exactly like `this.#graph.nodes` membership is checked everywhere
|
|
13732
|
+
* else.
|
|
13733
|
+
*/
|
|
13734
|
+
#resolveDynamicNodeCollection(collections, kind) {
|
|
13735
|
+
if (!Object.hasOwn(this.#graph.nodes, kind)) return void 0;
|
|
13736
|
+
return collections[kind];
|
|
13737
|
+
}
|
|
13792
13738
|
/**
|
|
13793
13739
|
* Returns the node collection for the given kind, or undefined if the kind
|
|
13794
13740
|
* is not registered in this graph.
|
|
@@ -13800,8 +13746,7 @@ var Store = class _Store {
|
|
|
13800
13746
|
* throws `KindNotFoundError` instead of forcing a null-check.
|
|
13801
13747
|
*/
|
|
13802
13748
|
getNodeCollection(kind) {
|
|
13803
|
-
|
|
13804
|
-
return this.nodes[kind];
|
|
13749
|
+
return this.#resolveDynamicNodeCollection(this.nodes, kind);
|
|
13805
13750
|
}
|
|
13806
13751
|
/**
|
|
13807
13752
|
* Returns the node collection for the given kind. Throws
|
|
@@ -13920,10 +13865,30 @@ var Store = class _Store {
|
|
|
13920
13865
|
get #nodeOperations() {
|
|
13921
13866
|
return this.#buildNodeOperations(this.#createNodeOperationContext());
|
|
13922
13867
|
}
|
|
13868
|
+
/**
|
|
13869
|
+
* Refreshes planner statistics after an autocommit bulk write that
|
|
13870
|
+
* reached the configured row threshold. A refresh failure must never
|
|
13871
|
+
* fail the (already committed) write — it degrades to a warning.
|
|
13872
|
+
*/
|
|
13873
|
+
async #maybeRefreshStatisticsAfterBulk(rowCount) {
|
|
13874
|
+
const configured = this.#options?.autoRefreshStatistics;
|
|
13875
|
+
if (configured === false) return;
|
|
13876
|
+
const threshold = configured ?? AUTO_REFRESH_STATISTICS_ROW_THRESHOLD;
|
|
13877
|
+
if (rowCount < threshold) return;
|
|
13878
|
+
try {
|
|
13879
|
+
await this.refreshStatistics();
|
|
13880
|
+
} catch (error) {
|
|
13881
|
+
console.warn(
|
|
13882
|
+
"typegraph: statistics refresh after bulk write failed; run store.refreshStatistics() to avoid stale planner statistics.",
|
|
13883
|
+
error
|
|
13884
|
+
);
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13923
13887
|
#buildNodeOperations(ctx) {
|
|
13924
13888
|
return {
|
|
13925
13889
|
defaultTemporalMode: this.#graph.defaults.temporalMode,
|
|
13926
13890
|
rowToNode: (row) => rowToNode(row),
|
|
13891
|
+
maybeRefreshStatisticsAfterBulk: (rowCount) => this.#maybeRefreshStatisticsAfterBulk(rowCount),
|
|
13927
13892
|
executeCreate: (input, backend) => executeNodeCreate(ctx, input, backend),
|
|
13928
13893
|
executeCreateBatch: (inputs, backend) => executeNodeCreateBatch(ctx, inputs, backend),
|
|
13929
13894
|
executeCreateNoReturnBatch: (inputs, backend) => executeNodeCreateNoReturnBatch(ctx, inputs, backend),
|
|
@@ -13982,6 +13947,7 @@ var Store = class _Store {
|
|
|
13982
13947
|
return {
|
|
13983
13948
|
defaultTemporalMode: this.#graph.defaults.temporalMode,
|
|
13984
13949
|
rowToEdge: (row) => rowToEdge(row),
|
|
13950
|
+
maybeRefreshStatisticsAfterBulk: (rowCount) => this.#maybeRefreshStatisticsAfterBulk(rowCount),
|
|
13985
13951
|
executeCreate: (input, backend) => executeEdgeCreate(ctx, input, backend),
|
|
13986
13952
|
executeCreateBatch: (inputs, backend) => executeEdgeCreateBatch(ctx, inputs, backend),
|
|
13987
13953
|
executeCreateNoReturnBatch: (inputs, backend) => executeEdgeCreateNoReturnBatch(ctx, inputs, backend),
|
|
@@ -14181,6 +14147,44 @@ var Store = class _Store {
|
|
|
14181
14147
|
);
|
|
14182
14148
|
return recordedAt === void 0 ? void 0 : asRecordedInstant(recordedAt);
|
|
14183
14149
|
}
|
|
14150
|
+
/**
|
|
14151
|
+
* Returns the durable graph revision used by graph branching. It is undefined
|
|
14152
|
+
* until the first successful tracked write, which is itself a stable initial
|
|
14153
|
+
* anchor. Unlike {@link recordedNow}, this is also available on a live Store
|
|
14154
|
+
* created with `{ revisionTracking: true }`.
|
|
14155
|
+
*
|
|
14156
|
+
* @internal
|
|
14157
|
+
*/
|
|
14158
|
+
async revisionNow() {
|
|
14159
|
+
if (!this.#revisionTrackingEnabled) return void 0;
|
|
14160
|
+
return readRecordedClock(this.#backend, this.#sqlSchema(), this.graphId);
|
|
14161
|
+
}
|
|
14162
|
+
/**
|
|
14163
|
+
* Returns the durable random namespace for this graph's revision clock. A
|
|
14164
|
+
* tracked base token combines it with {@link revisionNow}, preventing a
|
|
14165
|
+
* branch from one independent store from matching a coincident timestamp in
|
|
14166
|
+
* another store.
|
|
14167
|
+
*
|
|
14168
|
+
* @internal
|
|
14169
|
+
*/
|
|
14170
|
+
async revisionOriginNow() {
|
|
14171
|
+
if (!this.#revisionTrackingEnabled) {
|
|
14172
|
+
throw new ConfigurationError(
|
|
14173
|
+
"revisionOriginNow() requires revisionTracking: true or history: true.",
|
|
14174
|
+
{ code: "REVISION_ORIGIN_REQUIRES_TRACKING" }
|
|
14175
|
+
);
|
|
14176
|
+
}
|
|
14177
|
+
const pendingOrigin = this.#revisionOrigin ?? ensureRevisionOrigin(this.#baseBackend, this.#sqlSchema(), this.graphId);
|
|
14178
|
+
this.#revisionOrigin = pendingOrigin;
|
|
14179
|
+
try {
|
|
14180
|
+
return await pendingOrigin;
|
|
14181
|
+
} catch (error) {
|
|
14182
|
+
if (this.#revisionOrigin === pendingOrigin) {
|
|
14183
|
+
this.#revisionOrigin = void 0;
|
|
14184
|
+
}
|
|
14185
|
+
throw error;
|
|
14186
|
+
}
|
|
14187
|
+
}
|
|
14184
14188
|
/**
|
|
14185
14189
|
* Returns a read-only {@link StoreView} pinned to an arbitrary public
|
|
14186
14190
|
* temporal mode. Use {@link Store.asOf} for the common valid-time case;
|
|
@@ -14251,7 +14255,8 @@ var Store = class _Store {
|
|
|
14251
14255
|
this.#search = new StoreSearch({
|
|
14252
14256
|
graphId: this.graphId,
|
|
14253
14257
|
backend: this.#backend,
|
|
14254
|
-
registry: this.#registry
|
|
14258
|
+
registry: this.#registry,
|
|
14259
|
+
createQuery: () => this.query()
|
|
14255
14260
|
});
|
|
14256
14261
|
}
|
|
14257
14262
|
return this.#search;
|
|
@@ -14374,6 +14379,8 @@ var Store = class _Store {
|
|
|
14374
14379
|
algorithmsAtCoordinate(coordinate) {
|
|
14375
14380
|
return createGraphAlgorithms({
|
|
14376
14381
|
graphId: this.graphId,
|
|
14382
|
+
graph: this.#graph,
|
|
14383
|
+
registry: this.#registry,
|
|
14377
14384
|
backend: this.#recordedReads.backendForCoordinate(
|
|
14378
14385
|
coordinate,
|
|
14379
14386
|
"recorded-graph-algorithm"
|
|
@@ -14386,12 +14393,40 @@ var Store = class _Store {
|
|
|
14386
14393
|
}
|
|
14387
14394
|
async transaction(fn, options) {
|
|
14388
14395
|
const invoke = fn;
|
|
14396
|
+
const { result } = await this.#runTransaction(invoke, options, void 0);
|
|
14397
|
+
return result;
|
|
14398
|
+
}
|
|
14399
|
+
async transactionWithReceipt(fn, options) {
|
|
14400
|
+
const invoke = fn;
|
|
14401
|
+
const receiptRecorder = createTransactionReceiptRecorder();
|
|
14402
|
+
const { result, recordedByGraph } = await this.#runTransaction(
|
|
14403
|
+
invoke,
|
|
14404
|
+
options,
|
|
14405
|
+
receiptRecorder
|
|
14406
|
+
);
|
|
14407
|
+
return transactionOutcome(
|
|
14408
|
+
result,
|
|
14409
|
+
receiptRecorder,
|
|
14410
|
+
recordedByGraph,
|
|
14411
|
+
this.graphId
|
|
14412
|
+
);
|
|
14413
|
+
}
|
|
14414
|
+
async #runTransaction(invoke, backendOptions, receiptRecorder) {
|
|
14389
14415
|
if (!this.#backend.capabilities.transactions) {
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14416
|
+
let nodes = this.nodes;
|
|
14417
|
+
let edges = this.edges;
|
|
14418
|
+
if (receiptRecorder !== void 0) {
|
|
14419
|
+
({ nodes, edges } = wrapTransactionCollections(
|
|
14420
|
+
nodes,
|
|
14421
|
+
edges,
|
|
14422
|
+
receiptRecorder
|
|
14423
|
+
));
|
|
14424
|
+
}
|
|
14425
|
+
const result = await invoke({
|
|
14426
|
+
nodes,
|
|
14427
|
+
edges,
|
|
14393
14428
|
backend: this.#backend,
|
|
14394
|
-
getNodeCollection: (kind) => this
|
|
14429
|
+
getNodeCollection: (kind) => this.#resolveDynamicNodeCollection(nodes, kind),
|
|
14395
14430
|
// No transaction, no deferral: operations apply as they happen, so
|
|
14396
14431
|
// their hooks fire immediately.
|
|
14397
14432
|
runNodeOperationHooks: (operation, kind, id, hookedFunction) => this.#withOperationHooks(
|
|
@@ -14399,20 +14434,32 @@ var Store = class _Store {
|
|
|
14399
14434
|
hookedFunction
|
|
14400
14435
|
)
|
|
14401
14436
|
});
|
|
14437
|
+
return { result, recordedByGraph: void 0 };
|
|
14402
14438
|
}
|
|
14403
14439
|
const pending = [];
|
|
14404
14440
|
const runHooks = this.#createBufferedHookRunner(pending);
|
|
14441
|
+
let recordedByGraph;
|
|
14442
|
+
const transactionOptions = receiptRecorder !== void 0 && this.#captureEnabled ? withRecordedFlushObserver(backendOptions, (instants) => {
|
|
14443
|
+
recordedByGraph = instants;
|
|
14444
|
+
}) : backendOptions;
|
|
14405
14445
|
try {
|
|
14406
14446
|
const result = await this.#backend.transaction(
|
|
14407
|
-
async (txBackend, sql17) => invoke(
|
|
14408
|
-
|
|
14447
|
+
async (txBackend, sql17) => invoke(
|
|
14448
|
+
this.#buildTransactionContext(
|
|
14449
|
+
txBackend,
|
|
14450
|
+
sql17,
|
|
14451
|
+
runHooks,
|
|
14452
|
+
receiptRecorder
|
|
14453
|
+
)
|
|
14454
|
+
),
|
|
14455
|
+
transactionOptions
|
|
14409
14456
|
);
|
|
14410
14457
|
for (const outcome of pending) {
|
|
14411
14458
|
this.#hooks.onOperationEnd?.(outcome.ctx, {
|
|
14412
14459
|
durationMs: outcome.durationMs
|
|
14413
14460
|
});
|
|
14414
14461
|
}
|
|
14415
|
-
return result;
|
|
14462
|
+
return { result, recordedByGraph };
|
|
14416
14463
|
} catch (error) {
|
|
14417
14464
|
const failure = asError(error);
|
|
14418
14465
|
for (const outcome of pending) {
|
|
@@ -14463,6 +14510,14 @@ var Store = class _Store {
|
|
|
14463
14510
|
* }); // one COMMIT / ROLLBACK across both layers
|
|
14464
14511
|
* ```
|
|
14465
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
|
+
*
|
|
14466
14521
|
* Not available when the store was created with `{ history: true }`: a
|
|
14467
14522
|
* caller-owned transaction context would let writes happen after capture has
|
|
14468
14523
|
* lost its flush point, so this throws `ConfigurationError`
|
|
@@ -14539,34 +14594,40 @@ var Store = class _Store {
|
|
|
14539
14594
|
* (#140); when history capture is enabled it is replaced with a fail-loud
|
|
14540
14595
|
* guard because raw graph writes would bypass recorded-time capture.
|
|
14541
14596
|
*/
|
|
14542
|
-
#buildTransactionContext(txBackend, sql17, runHooks = this.#immediateHookRunner()) {
|
|
14597
|
+
#buildTransactionContext(txBackend, sql17, runHooks = this.#immediateHookRunner(), receiptRecorder) {
|
|
14543
14598
|
const txNodeOperations = {
|
|
14544
14599
|
...this.#buildNodeOperations(this.#createNodeOperationContext(runHooks)),
|
|
14545
|
-
createQuery: () => this.#createQueryForBackend(txBackend)
|
|
14600
|
+
createQuery: () => this.#createQueryForBackend(txBackend),
|
|
14601
|
+
maybeRefreshStatisticsAfterBulk: void 0
|
|
14546
14602
|
};
|
|
14547
14603
|
const txEdgeOperations = {
|
|
14548
14604
|
...this.#buildEdgeOperations(this.#createEdgeOperationContext(runHooks)),
|
|
14549
|
-
createQuery: () => this.#createQueryForBackend(txBackend)
|
|
14605
|
+
createQuery: () => this.#createQueryForBackend(txBackend),
|
|
14606
|
+
maybeRefreshStatisticsAfterBulk: void 0
|
|
14550
14607
|
};
|
|
14551
14608
|
const runNodeOperationHooks = (operation, kind, id, fn) => runHooks(this.#createOperationContext(operation, "node", kind, id), fn);
|
|
14552
|
-
|
|
14609
|
+
let nodes = createNodeCollectionsProxy(
|
|
14553
14610
|
this.#graph,
|
|
14554
14611
|
this.graphId,
|
|
14555
14612
|
this.#registry,
|
|
14556
14613
|
txBackend,
|
|
14557
14614
|
txNodeOperations
|
|
14558
14615
|
);
|
|
14559
|
-
|
|
14616
|
+
let edges = createEdgeCollectionsProxy(
|
|
14560
14617
|
this.#graph,
|
|
14561
14618
|
this.graphId,
|
|
14562
14619
|
this.#registry,
|
|
14563
14620
|
txBackend,
|
|
14564
14621
|
txEdgeOperations
|
|
14565
14622
|
);
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14623
|
+
if (receiptRecorder !== void 0) {
|
|
14624
|
+
({ nodes, edges } = wrapTransactionCollections(
|
|
14625
|
+
nodes,
|
|
14626
|
+
edges,
|
|
14627
|
+
receiptRecorder
|
|
14628
|
+
));
|
|
14629
|
+
}
|
|
14630
|
+
const getNodeCollection = (kind) => this.#resolveDynamicNodeCollection(nodes, kind);
|
|
14570
14631
|
if (sql17 === void 0) {
|
|
14571
14632
|
return {
|
|
14572
14633
|
nodes,
|
|
@@ -14576,7 +14637,7 @@ var Store = class _Store {
|
|
|
14576
14637
|
runNodeOperationHooks
|
|
14577
14638
|
};
|
|
14578
14639
|
}
|
|
14579
|
-
const exposedSql = this.#captureEnabled ? createHistoryUnsafeSqlRef() : sql17;
|
|
14640
|
+
const exposedSql = this.#captureEnabled ? createHistoryUnsafeSqlRef() : this.#revisionTrackingEnabled ? createRevisionTrackingUnsafeSqlRef() : sql17;
|
|
14580
14641
|
return {
|
|
14581
14642
|
nodes,
|
|
14582
14643
|
edges,
|
|
@@ -14598,7 +14659,20 @@ var Store = class _Store {
|
|
|
14598
14659
|
*/
|
|
14599
14660
|
async clear() {
|
|
14600
14661
|
const doClear = async (target) => {
|
|
14662
|
+
if (this.#revisionTrackingEnabled) {
|
|
14663
|
+
await lockRecordedGraphWrite(target, this.graphId);
|
|
14664
|
+
}
|
|
14665
|
+
const previousRevision = this.#revisionTrackingEnabled && !this.#captureEnabled ? await readRecordedClock(target, this.#sqlSchema(), this.graphId) : void 0;
|
|
14601
14666
|
await target.clearGraph(this.graphId);
|
|
14667
|
+
if (this.#revisionTrackingEnabled && !this.#captureEnabled) {
|
|
14668
|
+
await advanceRevisionClock(
|
|
14669
|
+
target,
|
|
14670
|
+
this.#sqlSchema(),
|
|
14671
|
+
this.graphId,
|
|
14672
|
+
this.#baseBackend.capabilities.transactions,
|
|
14673
|
+
previousRevision
|
|
14674
|
+
);
|
|
14675
|
+
}
|
|
14602
14676
|
};
|
|
14603
14677
|
await (this.#baseBackend.capabilities.transactions ? this.#baseBackend.transaction(async (tx) => doClear(tx)) : doClear(this.#baseBackend));
|
|
14604
14678
|
await this.#clearVectorStorage();
|
|
@@ -15253,6 +15327,8 @@ var Store = class _Store {
|
|
|
15253
15327
|
graph: this.#graph,
|
|
15254
15328
|
graphId: this.graphId,
|
|
15255
15329
|
historyEnabled: this.#captureEnabled,
|
|
15330
|
+
revisionTrackingEnabled: this.#revisionTrackingEnabled,
|
|
15331
|
+
revisionSchema: this.#sqlSchema(),
|
|
15256
15332
|
registry: this.#registry,
|
|
15257
15333
|
createOperationContext: (operation, entity, kind, id) => this.#createOperationContext(operation, entity, kind, id),
|
|
15258
15334
|
withOperationHooks: runHooks
|
|
@@ -15263,6 +15339,8 @@ var Store = class _Store {
|
|
|
15263
15339
|
graph: this.#graph,
|
|
15264
15340
|
graphId: this.graphId,
|
|
15265
15341
|
historyEnabled: this.#captureEnabled,
|
|
15342
|
+
revisionTrackingEnabled: this.#revisionTrackingEnabled,
|
|
15343
|
+
revisionSchema: this.#sqlSchema(),
|
|
15266
15344
|
registry: this.#registry,
|
|
15267
15345
|
createOperationContext: (operation, entity, kind, id) => this.#createOperationContext(operation, entity, kind, id),
|
|
15268
15346
|
withOperationHooks: runHooks
|
|
@@ -15434,5 +15512,5 @@ async function createVerifiedStore(graph, backend, options) {
|
|
|
15434
15512
|
}
|
|
15435
15513
|
|
|
15436
15514
|
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 };
|
|
15437
|
-
//# sourceMappingURL=chunk-
|
|
15438
|
-
//# sourceMappingURL=chunk-
|
|
15515
|
+
//# sourceMappingURL=chunk-UES55P7G.js.map
|
|
15516
|
+
//# sourceMappingURL=chunk-UES55P7G.js.map
|