@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2
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/bin/cliOperations.ts +12 -0
- package/bin/restart.ts +66 -6
- package/components/Application.ts +1134 -112
- package/components/DESIGN.md +421 -0
- package/components/OptionsWatcher.ts +368 -102
- package/components/Scope.ts +20 -1
- package/components/componentLoader.ts +43 -4
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/DESIGN.md +109 -0
- package/components/mcp/audit.ts +21 -17
- package/components/operations.js +284 -52
- package/components/operationsValidation.js +49 -2
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/config/DESIGN.md +306 -0
- package/config/RootConfigWatcher.ts +191 -37
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +78 -26
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/DESIGN.md +179 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
- package/dataLayer/restoreMarker.ts +92 -25
- package/dist/bin/cliOperations.js +13 -0
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/restart.js +42 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/components/Application.d.ts +104 -9
- package/dist/components/Application.js +954 -102
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -1
- package/dist/components/OptionsWatcher.js +378 -104
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.js +15 -1
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +35 -3
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -1
- package/dist/components/mcp/audit.js +21 -17
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +263 -54
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +48 -2
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +2 -0
- package/dist/config/RootConfigWatcher.js +189 -35
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +10 -9
- package/dist/config/configUtils.js +63 -27
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +21 -8
- package/dist/dataLayer/restoreMarker.js +94 -27
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +3 -0
- package/dist/resources/DatabaseTransaction.d.ts +25 -0
- package/dist/resources/DatabaseTransaction.js +224 -18
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +22 -3
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +22 -6
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +1 -1
- package/dist/resources/RecordEncoder.js +46 -10
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +97 -13
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -0
- package/dist/resources/RocksIndexStore.js +2 -1
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
- package/dist/resources/RocksTransactionLogStore.js +104 -33
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +58 -7
- package/dist/resources/Table.js +1143 -348
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +3 -0
- package/dist/resources/analytics/write.js +49 -16
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +170 -0
- package/dist/resources/auditStore.js +457 -11
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/crdt.d.ts +10 -0
- package/dist/resources/crdt.js +22 -0
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +16 -13
- package/dist/resources/databases.js +625 -177
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.js +3 -2
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
- package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
- package/dist/resources/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +47 -4
- package/dist/resources/recordLock.js +138 -7
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2597 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.js +5 -0
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +160 -40
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +59 -23
- package/dist/security/auth.js.map +1 -1
- package/dist/security/deferredAuthentication.d.ts +11 -0
- package/dist/security/deferredAuthentication.js +25 -3
- package/dist/security/deferredAuthentication.js.map +1 -1
- package/dist/security/jsLoader.js +9 -4
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/REST.js +39 -5
- package/dist/server/REST.js.map +1 -1
- package/dist/server/http.d.ts +5 -1
- package/dist/server/http.js +34 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/mqtt.js +5 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +2 -0
- package/dist/server/serverHelpers/Headers.js +6 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
- package/dist/server/serverHelpers/contentTypes.js +189 -15
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
- package/dist/server/serverHelpers/serverUtilities.js +110 -20
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +4 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
- package/dist/server/storageReclamation.js +1 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- package/dist/server/threads/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +64 -6
- package/dist/server/threads/manageThreads.js +261 -12
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +196 -13
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +30 -7
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +24 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.js +218 -38
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +172 -92
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/npmUtilities.js +6 -4
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/watcherFallback.d.ts +0 -45
- package/dist/utility/watcherFallback.js +1 -125
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.js +6 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +9 -0
- package/json/systemSchema.json +3 -0
- package/npm-shrinkwrap.json +177 -87
- package/package.json +13 -5
- package/resources/DESIGN.md +689 -19
- package/resources/DatabaseTransaction.ts +230 -17
- package/resources/LMDBTransaction.ts +21 -3
- package/resources/PrimaryRocksDatabase.ts +20 -7
- package/resources/RecordEncoder.ts +52 -10
- package/resources/Resource.ts +97 -13
- package/resources/ResourceInterface.ts +8 -0
- package/resources/RocksIndexStore.ts +2 -1
- package/resources/RocksTransactionLogStore.ts +111 -31
- package/resources/Table.ts +1273 -396
- package/resources/analytics/DESIGN.md +38 -0
- package/resources/analytics/write.ts +50 -17
- package/resources/auditStore.ts +460 -11
- package/resources/crdt.ts +22 -0
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +611 -147
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +3 -2
- package/resources/indexes/DESIGN.md +833 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
- package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
- package/resources/indexes/fullTextNativeBinding.ts +146 -0
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/record-locks.md +1407 -0
- package/resources/recordLock.ts +173 -7
- package/resources/recordLockCoordinator.ts +3082 -0
- package/resources/replayLogs.ts +5 -0
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/DESIGN.md +40 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +169 -49
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +4 -4
- package/security/DESIGN.md +175 -0
- package/security/auth.ts +53 -24
- package/security/deferredAuthentication.ts +24 -2
- package/security/jsLoader.ts +9 -4
- package/server/DESIGN.md +275 -0
- package/server/REST.ts +42 -6
- package/server/http.ts +34 -2
- package/server/mqtt.ts +6 -4
- package/server/serverHelpers/Headers.ts +5 -1
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +188 -15
- package/server/serverHelpers/serverUtilities.ts +157 -27
- package/server/serverHelpers/uwsServer.ts +4 -1
- package/server/serverHelpers/webSocketCloseReason.ts +25 -0
- package/server/storageReclamation.ts +2 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +254 -12
- package/server/threads/socketRouter.ts +217 -11
- package/server/threads/threadServer.js +30 -7
- package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
- package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
- package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
- package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
- package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
- package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
- package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
- package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
- package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
- package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
- package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
- package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
- package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
- package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
- package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
- package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
- package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
- package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
- package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
- package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
- package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
- package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
- package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
- package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
- package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
- package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/DESIGN.md +55 -0
- package/utility/errors/hdbError.ts +54 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/logging/harper_logger.ts +210 -30
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +196 -91
- package/utility/npmUtilities.ts +6 -4
- package/utility/watcherFallback.ts +0 -122
- package/validation/configValidator.ts +6 -3
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.BRANCH_REMOVING_SUFFIX = exports.BRANCH_ROOT_DIR = exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
|
|
39
|
+
exports.CHECKPOINT_ALGORITHM = exports.BRANCH_REMOVING_SUFFIX = exports.BRANCH_ROOT_DIR = exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
|
|
40
40
|
exports.isReadOnlyMode = isReadOnlyMode;
|
|
41
41
|
exports.getRocksCompression = getRocksCompression;
|
|
42
42
|
exports.resetRocksCompression = resetRocksCompression;
|
|
@@ -65,6 +65,8 @@ exports.table = table;
|
|
|
65
65
|
exports.scopedTableFactory = scopedTableFactory;
|
|
66
66
|
exports.reloadBranchAt = reloadBranchAt;
|
|
67
67
|
exports.canonicalizeIndexOptions = canonicalizeIndexOptions;
|
|
68
|
+
exports.setIndexingCheckpointPeriod = setIndexingCheckpointPeriod;
|
|
69
|
+
exports.resumeStartKey = resumeStartKey;
|
|
68
70
|
exports.dropTableMeta = dropTableMeta;
|
|
69
71
|
exports.onUpdatedTable = onUpdatedTable;
|
|
70
72
|
exports.onRemovedTable = onRemovedTable;
|
|
@@ -72,10 +74,11 @@ exports.onRemovedDB = onRemovedDB;
|
|
|
72
74
|
exports.getDefaultCompression = getDefaultCompression;
|
|
73
75
|
exports.flushDatabases = flushDatabases;
|
|
74
76
|
const node_events_1 = require("node:events");
|
|
77
|
+
const node_crypto_1 = require("node:crypto");
|
|
75
78
|
const environmentManager_ts_1 = require("../utility/environment/environmentManager.js");
|
|
76
79
|
const terms_ts_1 = require("../utility/lmdb/terms.js");
|
|
77
80
|
const lmdb_1 = require("lmdb");
|
|
78
|
-
const
|
|
81
|
+
const node_path_1 = require("node:path");
|
|
79
82
|
const node_fs_1 = require("node:fs");
|
|
80
83
|
const promises_1 = require("node:fs/promises");
|
|
81
84
|
const initializePaths_js_1 = require("../dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js");
|
|
@@ -98,13 +101,14 @@ const blob_ts_1 = require("./blob.js");
|
|
|
98
101
|
const storageReclamation_ts_1 = require("../server/storageReclamation.js");
|
|
99
102
|
const common_validators_ts_1 = require("../validation/common_validators.js");
|
|
100
103
|
const customIndexes_ts_1 = require("./indexes/customIndexes.js");
|
|
104
|
+
const hnswPlaneBinding_ts_1 = require("./indexes/hnswPlaneBinding.js");
|
|
101
105
|
const OpenDBIObject_ts_1 = require("../utility/lmdb/OpenDBIObject.js");
|
|
102
106
|
const rocksdb_js_1 = require("@harperfast/rocksdb-js");
|
|
103
107
|
const PrimaryRocksDatabase_ts_1 = require("./PrimaryRocksDatabase.js");
|
|
104
108
|
const replayLogs_ts_1 = require("./replayLogs.js");
|
|
109
|
+
const hnswDerivedIndex_ts_1 = require("./indexes/hnswDerivedIndex.js");
|
|
105
110
|
const node_os_1 = require("node:os");
|
|
106
111
|
const RocksIndexStore_ts_1 = require("./RocksIndexStore.js");
|
|
107
|
-
const when_ts_1 = require("../utility/when.js");
|
|
108
112
|
const rocksMemoryConfig_ts_1 = require("../utility/rocksMemoryConfig.js");
|
|
109
113
|
const processManagement_js_1 = require("../utility/processManagement/processManagement.js");
|
|
110
114
|
const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
|
|
@@ -417,6 +421,23 @@ function applyDurableDeclaration(attribute, descriptor) {
|
|
|
417
421
|
delete attribute[field];
|
|
418
422
|
}
|
|
419
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* True when a descriptor claims an index build no live operation in this process can own. The PID and
|
|
426
|
+
* worker generation cannot answer that alone: a container reuses PID 1 and starts the in-memory
|
|
427
|
+
* generation back at 1 while the persisted one is higher. A descriptor with no incarnation was written
|
|
428
|
+
* before the field existed, so it belongs to an earlier process; a thread started without one of its
|
|
429
|
+
* own cannot judge, and falls back rather than declaring a live build dead.
|
|
430
|
+
*/
|
|
431
|
+
function isAbandonedIndexBuild(descriptor, currentRestartGeneration) {
|
|
432
|
+
if (!descriptor)
|
|
433
|
+
return false;
|
|
434
|
+
if (descriptor.indexingPID && descriptor.indexingPID !== process.pid)
|
|
435
|
+
return true;
|
|
436
|
+
if (descriptor.restartNumber < currentRestartGeneration)
|
|
437
|
+
return true;
|
|
438
|
+
const incarnation = manageThreads.processIncarnation;
|
|
439
|
+
return !!descriptor.indexingPID && incarnation != null && descriptor.indexingIncarnation !== incarnation;
|
|
440
|
+
}
|
|
420
441
|
// How many times the schema load will try to finish a tombstoned drop before
|
|
421
442
|
// giving up for the rest of this process's lifetime. A drop that fails once
|
|
422
443
|
// almost always fails identically forever - the usual cause is a RocksDB
|
|
@@ -492,7 +513,7 @@ function getDatabases() {
|
|
|
492
513
|
definedDatabases = new Map();
|
|
493
514
|
relationshipsToHydrate = [];
|
|
494
515
|
const hdbBasePath = (0, environmentManager_ts_1.getHdbBasePath)();
|
|
495
|
-
let databasePath = hdbBasePath && (0,
|
|
516
|
+
let databasePath = hdbBasePath && (0, node_path_1.join)(hdbBasePath, hdbTerms_ts_1.DATABASES_DIR_NAME);
|
|
496
517
|
const schemaConfigs = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
|
|
497
518
|
// not sure why this doesn't work with the environmemt manager
|
|
498
519
|
if (process.env.SCHEMAS_DATA_PATH)
|
|
@@ -500,7 +521,7 @@ function getDatabases() {
|
|
|
500
521
|
databasePath =
|
|
501
522
|
process.env.STORAGE_PATH ||
|
|
502
523
|
(0, configUtils_ts_1.getConfigPath)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_PATH) ||
|
|
503
|
-
(databasePath && ((0, node_fs_1.existsSync)(databasePath) ? databasePath : (0,
|
|
524
|
+
(databasePath && ((0, node_fs_1.existsSync)(databasePath) ? databasePath : (0, node_path_1.join)((0, environmentManager_ts_1.getHdbBasePath)(), hdbTerms_ts_1.LEGACY_DATABASES_DIR_NAME)));
|
|
504
525
|
if (databasePath && (0, node_fs_1.existsSync)(databasePath)) {
|
|
505
526
|
// First load all the databases from our main database folder
|
|
506
527
|
// TODO: Load any databases defined with explicit storage paths from the config
|
|
@@ -518,14 +539,14 @@ function getDatabases() {
|
|
|
518
539
|
// branch directories are process-local derivatives, never databases in their own right
|
|
519
540
|
if (databaseEntry.name === exports.BRANCH_ROOT_DIR)
|
|
520
541
|
continue;
|
|
521
|
-
const dbName = (0,
|
|
522
|
-
const dbPath = (0,
|
|
542
|
+
const dbName = (0, node_path_1.basename)(databaseEntry.name, '.mdb');
|
|
543
|
+
const dbPath = (0, node_path_1.join)(databasePath, databaseEntry.name);
|
|
523
544
|
if (blockedByRestore.has(dbName))
|
|
524
545
|
continue;
|
|
525
546
|
if (isOpenBranchPath(dbPath))
|
|
526
547
|
continue;
|
|
527
548
|
if (databaseEntry.isFile() &&
|
|
528
|
-
(0,
|
|
549
|
+
(0, node_path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb' &&
|
|
529
550
|
!schemaConfigs[dbName]?.path) {
|
|
530
551
|
logger.trace(`loading lmdb database: ${dbPath}`);
|
|
531
552
|
readMetaDb(dbPath, null, dbName);
|
|
@@ -552,12 +573,12 @@ function getDatabases() {
|
|
|
552
573
|
if ((0, node_fs_1.existsSync)(baseSchemaPath)) {
|
|
553
574
|
for (const schemaEntry of (0, node_fs_1.readdirSync)(baseSchemaPath, { withFileTypes: true })) {
|
|
554
575
|
if (!schemaEntry.isFile()) {
|
|
555
|
-
const schemaPath = (0,
|
|
556
|
-
const schemaAuditPath = (0,
|
|
576
|
+
const schemaPath = (0, node_path_1.join)(baseSchemaPath, schemaEntry.name);
|
|
577
|
+
const schemaAuditPath = (0, node_path_1.join)((0, initializePaths_js_1.getTransactionAuditStoreBasePath)(), schemaEntry.name);
|
|
557
578
|
for (const tableEntry of (0, node_fs_1.readdirSync)(schemaPath, { withFileTypes: true })) {
|
|
558
|
-
if (tableEntry.isFile() && (0,
|
|
559
|
-
const auditPath = (0,
|
|
560
|
-
readMetaDb((0,
|
|
579
|
+
if (tableEntry.isFile() && (0, node_path_1.extname)(tableEntry.name).toLowerCase() === '.mdb') {
|
|
580
|
+
const auditPath = (0, node_path_1.join)(schemaAuditPath, tableEntry.name);
|
|
581
|
+
readMetaDb((0, node_path_1.join)(schemaPath, tableEntry.name), (0, node_path_1.basename)(tableEntry.name, '.mdb'), schemaEntry.name, auditPath, true);
|
|
561
582
|
}
|
|
562
583
|
}
|
|
563
584
|
}
|
|
@@ -577,16 +598,16 @@ function getDatabases() {
|
|
|
577
598
|
continue; // reserved restore-metadata dir
|
|
578
599
|
if (databaseEntry.name === exports.BRANCH_ROOT_DIR)
|
|
579
600
|
continue; // reserved branch root
|
|
580
|
-
if (blockedByRestore.has((0,
|
|
601
|
+
if (blockedByRestore.has((0, node_path_1.basename)(databaseEntry.name, '.mdb')))
|
|
581
602
|
continue;
|
|
582
|
-
if (isOpenBranchPath((0,
|
|
603
|
+
if (isOpenBranchPath((0, node_path_1.join)(databasePath, databaseEntry.name)))
|
|
583
604
|
continue;
|
|
584
|
-
if (databaseEntry.isFile() && (0,
|
|
585
|
-
readMetaDb((0,
|
|
605
|
+
if (databaseEntry.isFile() && (0, node_path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb') {
|
|
606
|
+
readMetaDb((0, node_path_1.join)(databasePath, databaseEntry.name), (0, node_path_1.basename)(databaseEntry.name, '.mdb'), dbName);
|
|
586
607
|
}
|
|
587
608
|
else {
|
|
588
609
|
try {
|
|
589
|
-
const dbPath = (0,
|
|
610
|
+
const dbPath = (0, node_path_1.join)(databasePath, databaseEntry.name);
|
|
590
611
|
const files = (0, node_fs_1.readdirSync)(dbPath, { withFileTypes: true });
|
|
591
612
|
if (files.find((file) => file.name === 'CURRENT')?.isFile() &&
|
|
592
613
|
files.some((file) => file.name.startsWith('MANIFEST-'))) {
|
|
@@ -606,7 +627,7 @@ function getDatabases() {
|
|
|
606
627
|
if (tableConfigs) {
|
|
607
628
|
for (const tableName in tableConfigs) {
|
|
608
629
|
const tableConfig = tableConfigs[tableName];
|
|
609
|
-
const tablePath = (0,
|
|
630
|
+
const tablePath = (0, node_path_1.join)(tableConfig.path, (0, node_path_1.basename)(tableName + '.mdb'));
|
|
610
631
|
if ((0, node_fs_1.existsSync)(tablePath)) {
|
|
611
632
|
readMetaDb(tablePath, tableName, dbName, null, true);
|
|
612
633
|
}
|
|
@@ -843,6 +864,7 @@ function readMetaDb(path, defaultTable, databaseName = DEFAULT_DATABASE_NAME, au
|
|
|
843
864
|
rootStore = (0, lmdb_1.open)(envInit);
|
|
844
865
|
lmdbDatabaseEnvs.set(path, rootStore);
|
|
845
866
|
}
|
|
867
|
+
rootStore.dbisDb?.resetReadTxn();
|
|
846
868
|
return initStores(path, rootStore, databaseName, { defaultTable, auditPath, isLegacy });
|
|
847
869
|
}
|
|
848
870
|
catch (error) {
|
|
@@ -885,7 +907,7 @@ function readRocksMetaDb(path, defaultTable, databaseName = DEFAULT_DATABASE_NAM
|
|
|
885
907
|
}
|
|
886
908
|
function initStores(path, rootStore, databaseName, { defaultTable, auditPath, isLegacy, destination, storeName, openedStores } = {}) {
|
|
887
909
|
// a store with no tables never reaches the per-table loop below, and blob roots resolve from this
|
|
888
|
-
rootStore.databaseName
|
|
910
|
+
rootStore.databaseName ??= storeName ?? databaseName;
|
|
889
911
|
const envInit = new OpenEnvironmentObject_ts_1.default(path, isReadOnlyMode());
|
|
890
912
|
const internalDbiInit = createOpenDBIObject(false);
|
|
891
913
|
let attributesDbi = rootStore.dbisDb;
|
|
@@ -921,6 +943,9 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
921
943
|
});
|
|
922
944
|
}
|
|
923
945
|
auditStore.isLegacy = true;
|
|
946
|
+
// A legacy standalone audit root skips openAuditStore, so give it a floor here or it
|
|
947
|
+
// reports its retention horizon as permanently unknown.
|
|
948
|
+
(0, auditStore_ts_1.establishAuditFloor)(auditStore);
|
|
924
949
|
}
|
|
925
950
|
}
|
|
926
951
|
else {
|
|
@@ -1046,6 +1071,8 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1046
1071
|
let table = tables[tableName];
|
|
1047
1072
|
// unless its store was migrated to a different engine (e.g. LMDB to RocksDB on startup)
|
|
1048
1073
|
const recreateForEngineChange = !!table && table.primaryStore?.rootStore instanceof rocksdb_js_1.RocksDatabase !== rootStore instanceof rocksdb_js_1.RocksDatabase;
|
|
1074
|
+
const recreateForTableIdChange = !!table && primaryAttribute.tableId != null && table.tableId !== primaryAttribute.tableId;
|
|
1075
|
+
const recreateTable = recreateForEngineChange || recreateForTableIdChange;
|
|
1049
1076
|
let indices = {}, existingAttributes = [];
|
|
1050
1077
|
let tableId;
|
|
1051
1078
|
let primaryStore;
|
|
@@ -1057,7 +1084,9 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1057
1084
|
const cacheControl = primaryAttribute.cacheControl;
|
|
1058
1085
|
const splitSegments = primaryAttribute.splitSegments;
|
|
1059
1086
|
const replicate = primaryAttribute.replicate;
|
|
1060
|
-
if (table && !
|
|
1087
|
+
if (table && !recreateTable) {
|
|
1088
|
+
if (primaryAttribute.audit === true && table.audit !== true)
|
|
1089
|
+
table.enableAuditing();
|
|
1061
1090
|
indices = table.indices;
|
|
1062
1091
|
existingAttributes = table.attributes;
|
|
1063
1092
|
table.schemaVersion++;
|
|
@@ -1108,6 +1137,8 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1108
1137
|
indices[attribute.name] = dbi;
|
|
1109
1138
|
indices[attribute.name].indexNulls = attribute.indexNulls;
|
|
1110
1139
|
}
|
|
1140
|
+
// the only way a thread that never declares the schema reaches Table.indices
|
|
1141
|
+
indices[attribute.name].isIndexing = !!attribute.indexingPID;
|
|
1111
1142
|
const existingAttribute = existingAttributes.find((existingAttribute) => existingAttribute.name === attribute.name);
|
|
1112
1143
|
if (existingAttribute)
|
|
1113
1144
|
existingAttributes.splice(existingAttributes.indexOf(existingAttribute), 1, attribute);
|
|
@@ -1167,13 +1198,15 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1167
1198
|
existingAttributes.splice(existingAttributes.indexOf(existingAttribute), 1);
|
|
1168
1199
|
attributesUpdated = true;
|
|
1169
1200
|
}
|
|
1170
|
-
if (table && !
|
|
1201
|
+
if (table && !recreateTable) {
|
|
1171
1202
|
if (attributesUpdated) {
|
|
1172
1203
|
table.schemaVersion++;
|
|
1173
1204
|
table.updatedAttributes();
|
|
1174
1205
|
}
|
|
1175
1206
|
}
|
|
1176
1207
|
else {
|
|
1208
|
+
if (recreateForTableIdChange)
|
|
1209
|
+
table.cleanup();
|
|
1177
1210
|
table = setTable(tables, tableName, (0, Table_ts_1.makeTable)({
|
|
1178
1211
|
// A branch builds into a caller-owned destination; its tables must refuse DDL.
|
|
1179
1212
|
isBranch: Boolean(destination),
|
|
@@ -1201,6 +1234,8 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1201
1234
|
if (!destination)
|
|
1202
1235
|
exports.databaseEventsEmitter.emit('updateTable', table);
|
|
1203
1236
|
}
|
|
1237
|
+
void table.derivedIndexRuntime?.close();
|
|
1238
|
+
table.derivedIndexRuntime = (0, hnswDerivedIndex_ts_1.attachDerivedIndexes)(table);
|
|
1204
1239
|
if (Array.isArray(primaryAttribute.relationships)) {
|
|
1205
1240
|
relationshipsToHydrate.push({ table, databaseName, tableName, definitions: primaryAttribute.relationships });
|
|
1206
1241
|
}
|
|
@@ -1239,7 +1274,7 @@ function resolveBranchPath(baseName, appName) {
|
|
|
1239
1274
|
throw new Error(`Invalid ${label} name for a branch path: ${JSON.stringify(segment)}`);
|
|
1240
1275
|
}
|
|
1241
1276
|
}
|
|
1242
|
-
return (0,
|
|
1277
|
+
return (0, node_path_1.join)(resolveDatabaseStorageRoot(baseName), exports.BRANCH_ROOT_DIR, appName, baseName);
|
|
1243
1278
|
}
|
|
1244
1279
|
/** `undefined` marks a path reserved by an open still in flight, which owns it just as firmly. */
|
|
1245
1280
|
const openBranches = new Map();
|
|
@@ -1487,6 +1522,8 @@ function openBranchDatabase(path, databaseName, storeName, blobRoots) {
|
|
|
1487
1522
|
openBranches.set(path, undefined);
|
|
1488
1523
|
retakeBranchIdentity(storeName);
|
|
1489
1524
|
try {
|
|
1525
|
+
// before the open: table load schedules TTL, eviction and audit cleanup, which ask who owns this store
|
|
1526
|
+
manageThreads.markBranchStorePath(path);
|
|
1490
1527
|
rootStore = readRocksMetaDb(path, null, databaseName, { destination: tables, storeName, openedStores });
|
|
1491
1528
|
// Pin the handle to the roots the caller proved this branch was published with, before it is
|
|
1492
1529
|
// handed out. A row's `storageIndex` is a position in that list, so resolving through current
|
|
@@ -1498,6 +1535,7 @@ function openBranchDatabase(path, databaseName, storeName, blobRoots) {
|
|
|
1498
1535
|
}
|
|
1499
1536
|
catch (error) {
|
|
1500
1537
|
openBranches.delete(path);
|
|
1538
|
+
manageThreads.markBranchStorePath(path, false);
|
|
1501
1539
|
releaseBranchIdentity(storeName);
|
|
1502
1540
|
const stranded = rocksdbDatabaseEnvs.get(path);
|
|
1503
1541
|
rocksdbDatabaseEnvs.delete(path);
|
|
@@ -1522,6 +1560,7 @@ function openBranchDatabase(path, databaseName, storeName, blobRoots) {
|
|
|
1522
1560
|
openBranches.delete(path);
|
|
1523
1561
|
releaseBranchIdentity(storeName);
|
|
1524
1562
|
rocksdbDatabaseEnvs.delete(path);
|
|
1563
|
+
manageThreads.markBranchStorePath(path, false);
|
|
1525
1564
|
closeBranchHandles(path, rootStore, openedStores, tables);
|
|
1526
1565
|
},
|
|
1527
1566
|
};
|
|
@@ -1647,10 +1686,10 @@ function resolveDatabaseStorageRoot(databaseName, tableName) {
|
|
|
1647
1686
|
databaseConfig[databaseName]?.path ||
|
|
1648
1687
|
process.env.STORAGE_PATH ||
|
|
1649
1688
|
(0, configUtils_ts_1.getConfigPath)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_PATH) ||
|
|
1650
|
-
(hdbBasePath && (0, node_fs_1.existsSync)((0,
|
|
1651
|
-
? (0,
|
|
1689
|
+
(hdbBasePath && (0, node_fs_1.existsSync)((0, node_path_1.join)(hdbBasePath, hdbTerms_ts_1.DATABASES_DIR_NAME))
|
|
1690
|
+
? (0, node_path_1.join)(hdbBasePath, hdbTerms_ts_1.DATABASES_DIR_NAME)
|
|
1652
1691
|
: hdbBasePath
|
|
1653
|
-
? (0,
|
|
1692
|
+
? (0, node_path_1.join)(hdbBasePath, hdbTerms_ts_1.LEGACY_DATABASES_DIR_NAME)
|
|
1654
1693
|
: undefined);
|
|
1655
1694
|
if (!databasePath) {
|
|
1656
1695
|
throw new Error(`Unable to determine database storage path. Ensure STORAGE_PATH, HDB_ROOT, or a valid config path is set.`);
|
|
@@ -1661,7 +1700,7 @@ function resolveDatabaseStorageRoot(databaseName, tableName) {
|
|
|
1661
1700
|
* Resolve the directory path of a RocksDB database (whether or not it exists or is loaded).
|
|
1662
1701
|
*/
|
|
1663
1702
|
function resolveDatabasePath(databaseName) {
|
|
1664
|
-
return (0,
|
|
1703
|
+
return (0, node_path_1.join)(resolveDatabaseStorageRoot(databaseName), databaseName);
|
|
1665
1704
|
}
|
|
1666
1705
|
/**
|
|
1667
1706
|
* Get root store for a database
|
|
@@ -1686,7 +1725,7 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
1686
1725
|
let rootStore;
|
|
1687
1726
|
const useRocksdb = (process.env.HARPER_STORAGE_ENGINE || (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_ENGINE)) !== 'lmdb';
|
|
1688
1727
|
if (useRocksdb) {
|
|
1689
|
-
const path = (0,
|
|
1728
|
+
const path = (0, node_path_1.join)(databasePath, tablePath ? tableName : databaseName);
|
|
1690
1729
|
// the scan is not the only way to reach a branch's directory: a branch leaves its store in
|
|
1691
1730
|
// `rocksdbDatabaseEnvs`, so without this an on-demand open would staple it onto
|
|
1692
1731
|
// `definedDatabases` and the next `closeDatabase` would close it under the live handle
|
|
@@ -1709,7 +1748,7 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
1709
1748
|
}
|
|
1710
1749
|
}
|
|
1711
1750
|
else {
|
|
1712
|
-
const path = (0,
|
|
1751
|
+
const path = (0, node_path_1.join)(databasePath, `${tablePath ? tableName : databaseName}.mdb`);
|
|
1713
1752
|
rootStore = lmdbDatabaseEnvs.get(path);
|
|
1714
1753
|
if (!rootStore || rootStore.status === 'closed') {
|
|
1715
1754
|
// TODO: validate database name
|
|
@@ -2062,6 +2101,9 @@ function openIndex(dbiKey, rootStore, attribute) {
|
|
|
2062
2101
|
const CustomIndex = customIndexes_ts_1.CUSTOM_INDEXES[attribute.indexed.type];
|
|
2063
2102
|
if (CustomIndex) {
|
|
2064
2103
|
indexStore.customIndex = new CustomIndex(indexStore, attribute.indexed);
|
|
2104
|
+
// derived state whose maintaining option is now off must not linger to be adopted
|
|
2105
|
+
// stale on a later re-enable
|
|
2106
|
+
indexStore.customIndex.cleanupDisabledPlane?.();
|
|
2065
2107
|
}
|
|
2066
2108
|
else {
|
|
2067
2109
|
logger.error(`The indexing type '${attribute.indexed.type}' is unknown`);
|
|
@@ -2096,14 +2138,17 @@ const GLOBAL_TARGET = {
|
|
|
2096
2138
|
/**
|
|
2097
2139
|
* The factory a branched application declares tables through: each declaration goes to the branch
|
|
2098
2140
|
* of the database it names, or to `table()` itself for a database the application did not branch.
|
|
2099
|
-
* An unbranched application gets `table` by identity
|
|
2141
|
+
* An unbranched, shared application gets `table` by identity. An isolated application still gets a
|
|
2142
|
+
* wrapper so its own declarations can claim their single-threaded maintenance work.
|
|
2100
2143
|
*/
|
|
2101
|
-
function scopedTableFactory(branches) {
|
|
2102
|
-
if (!branches?.size)
|
|
2144
|
+
function scopedTableFactory(branches, isolatedApplicationOwner = false) {
|
|
2145
|
+
if (!branches?.size && !isolatedApplicationOwner)
|
|
2103
2146
|
return table;
|
|
2104
2147
|
return function scopedTable(tableDefinition) {
|
|
2148
|
+
if (isolatedApplicationOwner)
|
|
2149
|
+
tableDefinition = { ...tableDefinition, isolatedApplicationOwner: true };
|
|
2105
2150
|
// `||`, not `??`: `table()` resolves every falsy name to the default database
|
|
2106
|
-
const branch = branches
|
|
2151
|
+
const branch = branches?.get(tableDefinition.database || DEFAULT_DATABASE_NAME);
|
|
2107
2152
|
return branch ? declareTable(branchTarget(branch), tableDefinition) : table(tableDefinition);
|
|
2108
2153
|
};
|
|
2109
2154
|
}
|
|
@@ -2160,7 +2205,9 @@ function reloadBranchAt(path) {
|
|
|
2160
2205
|
return branch.tables;
|
|
2161
2206
|
}
|
|
2162
2207
|
function declareTable(target, tableDefinition) {
|
|
2163
|
-
let { table: tableName, database: databaseName, expiration, eviction, scanInterval, attributes, audit, sealed, splitSegments, replicate, randomAccessFields, trackDeletes, schemaDefined, schemaRelationshipsDefined, origin, description, properties, hidden, cacheControl, } = tableDefinition;
|
|
2208
|
+
let { table: tableName, database: databaseName, expiration, eviction, scanInterval, attributes, audit, sealed, splitSegments, replicate, randomAccessFields, trackDeletes, schemaDefined, schemaRelationshipsDefined, origin, description, properties, hidden, cacheControl, isolatedApplicationOwner, } = tableDefinition;
|
|
2209
|
+
const auditExplicitlyEnabled = audit === true;
|
|
2210
|
+
const auditExplicitlyDisabled = audit === false;
|
|
2164
2211
|
if (!databaseName)
|
|
2165
2212
|
databaseName = DEFAULT_DATABASE_NAME;
|
|
2166
2213
|
// Reject reserved names here too, not only at the operations API: a database
|
|
@@ -2197,27 +2244,137 @@ function declareTable(target, tableDefinition) {
|
|
|
2197
2244
|
schemaDefined = true;
|
|
2198
2245
|
const relationshipDefinitions = schemaRelationshipsDefined ? normalizeRelationships(attributes) : undefined;
|
|
2199
2246
|
const internalDbiInit = createOpenDBIObject(false);
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2247
|
+
let releaseExclusiveLock;
|
|
2248
|
+
const hasHnswAtEntry = attributes.some((attribute) => attribute.indexed?.type === 'HNSW');
|
|
2249
|
+
const persistedPrimaryDescriptor = (catalog) => {
|
|
2250
|
+
const declaredPrimaryKey = attributes.find((attribute) => attribute.isPrimaryKey)?.name ?? Table?.primaryKey;
|
|
2251
|
+
if (declaredPrimaryKey) {
|
|
2252
|
+
const key = `${tableName}/${declaredPrimaryKey}`;
|
|
2253
|
+
const descriptor = catalog?.getSync(key);
|
|
2254
|
+
if (descriptor?.isPrimaryKey)
|
|
2255
|
+
return { key, descriptor };
|
|
2256
|
+
}
|
|
2257
|
+
const key = `${tableName}/`;
|
|
2258
|
+
return { key, descriptor: catalog?.getSync(key) };
|
|
2259
|
+
};
|
|
2260
|
+
const hasLegacyHnswStateAtEntry = Table &&
|
|
2261
|
+
origin !== 'cluster' &&
|
|
2262
|
+
attributes.some((attribute) => {
|
|
2263
|
+
if (attribute.indexed?.type !== 'HNSW')
|
|
2264
|
+
return false;
|
|
2265
|
+
const persisted = Table.dbisDB?.getSync(`${tableName}/${attribute.name || attribute.attribute || ''}`)?.indexed;
|
|
2266
|
+
if (persisted?.type !== 'HNSW')
|
|
2267
|
+
return false;
|
|
2268
|
+
if (Object.hasOwn(persisted, 'nativePlane') && typeof persisted.nativePlane !== 'boolean')
|
|
2269
|
+
return true;
|
|
2270
|
+
for (const name of customIndexes_ts_1.CUSTOM_INDEXES.HNSW.numericOptions)
|
|
2271
|
+
if (Object.hasOwn(persisted, name) && typeof persisted[name] !== 'number')
|
|
2272
|
+
return true;
|
|
2273
|
+
return false;
|
|
2274
|
+
});
|
|
2275
|
+
try {
|
|
2276
|
+
if (Table &&
|
|
2277
|
+
hasHnswAtEntry &&
|
|
2278
|
+
origin !== 'cluster' &&
|
|
2279
|
+
(rootStore instanceof rocksdb_js_1.RocksDatabase || hasLegacyHnswStateAtEntry))
|
|
2280
|
+
exclusiveLock();
|
|
2281
|
+
const persistedAuditAtEntry = hasHnswAtEntry || (Table && Table.audit !== true)
|
|
2282
|
+
? persistedPrimaryDescriptor(Table?.dbisDB).descriptor?.audit
|
|
2283
|
+
: undefined;
|
|
2284
|
+
if (!auditExplicitlyDisabled && persistedAuditAtEntry === true && Table?.audit !== true)
|
|
2285
|
+
Table.enableAuditing();
|
|
2286
|
+
for (const attribute of attributes) {
|
|
2287
|
+
if (attribute.attribute && !attribute.name) {
|
|
2288
|
+
// there is some legacy code that calls the attribute's name the attribute's attribute
|
|
2289
|
+
attribute.name = attribute.attribute;
|
|
2290
|
+
attribute.indexed = true;
|
|
2291
|
+
}
|
|
2292
|
+
else
|
|
2293
|
+
attribute.attribute = attribute.name;
|
|
2294
|
+
if (attribute.expiresAt)
|
|
2295
|
+
attribute.indexed = true;
|
|
2296
|
+
if (attribute.indexed?.type === 'HNSW' && origin !== 'cluster') {
|
|
2297
|
+
const existingAttribute = Table?.attributes.find((existing) => existing.name === attribute.name);
|
|
2298
|
+
const persistedIndexed = Table?.dbisDB?.getSync(`${tableName}/${attribute.name || ''}`)?.indexed ?? existingAttribute?.indexed;
|
|
2299
|
+
customIndexes_ts_1.CUSTOM_INDEXES.HNSW.normalizeDeclarationOptions(attribute.indexed, persistedIndexed);
|
|
2300
|
+
if (attribute.indexed.nativePlane != null) {
|
|
2301
|
+
const persistedNativePlane = persistedIndexed?.nativePlane;
|
|
2302
|
+
const matchesPersistedLegacySpelling = persistedIndexed?.type === 'HNSW' &&
|
|
2303
|
+
Object.hasOwn(persistedIndexed, 'nativePlane') &&
|
|
2304
|
+
typeof persistedNativePlane !== 'boolean' &&
|
|
2305
|
+
(Object.is(persistedNativePlane, attribute.indexed.nativePlane) ||
|
|
2306
|
+
(typeof persistedNativePlane === 'string' &&
|
|
2307
|
+
typeof attribute.indexed.nativePlane === 'number' &&
|
|
2308
|
+
persistedNativePlane.trim() !== '' &&
|
|
2309
|
+
Number(persistedNativePlane) === attribute.indexed.nativePlane));
|
|
2310
|
+
if (matchesPersistedLegacySpelling) {
|
|
2311
|
+
attribute.indexed.nativePlane = persistedNativePlane;
|
|
2312
|
+
}
|
|
2313
|
+
else {
|
|
2314
|
+
attribute.indexed.nativePlane = customIndexes_ts_1.CUSTOM_INDEXES.HNSW.normalizeNativePlaneDeclaration(attribute.indexed.nativePlane);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
const auditEnabledAtEntry = auditExplicitlyEnabled ||
|
|
2320
|
+
(!auditExplicitlyDisabled &&
|
|
2321
|
+
(persistedAuditAtEntry === true || (persistedAuditAtEntry == null && Table?.audit === true)));
|
|
2322
|
+
if (origin !== 'cluster' &&
|
|
2323
|
+
attributes.some((attribute) => {
|
|
2324
|
+
if (attribute.indexed?.type !== 'HNSW')
|
|
2325
|
+
return false;
|
|
2326
|
+
if (attribute.indexed.nativePlane != null)
|
|
2327
|
+
return Boolean(attribute.indexed.nativePlane);
|
|
2328
|
+
const existingAttribute = Table?.attributes.find((existing) => existing.name === attribute.name && existing.indexed?.type === 'HNSW');
|
|
2329
|
+
return Boolean(existingAttribute?.indexed.nativePlane);
|
|
2330
|
+
}) &&
|
|
2331
|
+
!auditEnabledAtEntry) {
|
|
2332
|
+
throw new hdbError_ts_1.ClientError(`Table '${databaseName}.${tableName}' must enable audit logging before using nativePlane because its transaction log is the derived-index recovery source; set nativePlane: false to use the JS index`);
|
|
2205
2333
|
}
|
|
2206
|
-
else
|
|
2207
|
-
attribute.attribute = attribute.name;
|
|
2208
|
-
if (attribute.expiresAt)
|
|
2209
|
-
attribute.indexed = true;
|
|
2210
2334
|
}
|
|
2335
|
+
catch (error) {
|
|
2336
|
+
releaseLock();
|
|
2337
|
+
throw error;
|
|
2338
|
+
}
|
|
2339
|
+
const validateHnswOptions = (catalog, auditQualifiesDefault) => {
|
|
2340
|
+
for (const attribute of attributes) {
|
|
2341
|
+
const indexed = attribute.indexed;
|
|
2342
|
+
if (indexed?.type !== 'HNSW')
|
|
2343
|
+
continue;
|
|
2344
|
+
const persistedIndexed = catalog?.getSync(`${tableName}/${attribute.name || ''}`)?.indexed;
|
|
2345
|
+
if (indexed.nativePlane) {
|
|
2346
|
+
customIndexes_ts_1.CUSTOM_INDEXES.HNSW.validateNativePlaneOptions(rootStore, indexed);
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
if (indexed.nativePlane != null)
|
|
2350
|
+
continue;
|
|
2351
|
+
if (persistedIndexed?.type === 'HNSW' && Object.hasOwn(persistedIndexed, 'nativePlane')) {
|
|
2352
|
+
if (persistedIndexed.nativePlane)
|
|
2353
|
+
customIndexes_ts_1.CUSTOM_INDEXES.HNSW.validateNativePlaneOptions(rootStore, {
|
|
2354
|
+
...indexed,
|
|
2355
|
+
nativePlane: persistedIndexed.nativePlane,
|
|
2356
|
+
});
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
if (!auditQualifiesDefault)
|
|
2360
|
+
continue;
|
|
2361
|
+
if (persistedIndexed?.type !== 'HNSW')
|
|
2362
|
+
customIndexes_ts_1.CUSTOM_INDEXES.HNSW.canDefaultToNativePlane(rootStore, indexed);
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
if (!Table && origin !== 'cluster')
|
|
2366
|
+
validateHnswOptions(undefined, auditExplicitlyEnabled);
|
|
2211
2367
|
let hasChanges;
|
|
2212
2368
|
let refreshRelationshipAttributes = false;
|
|
2369
|
+
let refreshedLiveAttributes = false;
|
|
2213
2370
|
let deferredPrimaryRow;
|
|
2214
2371
|
let unpublishedPrimaryStore;
|
|
2215
2372
|
let published = false;
|
|
2216
|
-
let releaseExclusiveLock;
|
|
2217
2373
|
const attributesToIndex = [];
|
|
2218
2374
|
const indicesToRemove = [];
|
|
2219
2375
|
try {
|
|
2220
2376
|
if (Table) {
|
|
2377
|
+
refreshedLiveAttributes = true;
|
|
2221
2378
|
primaryKey = Table.primaryKey;
|
|
2222
2379
|
if (Table.primaryStore.rootStore.status === 'closed') {
|
|
2223
2380
|
throw new Error(`Can not use a closed data store from ${tableName} class`);
|
|
@@ -2243,11 +2400,15 @@ function declareTable(target, tableDefinition) {
|
|
|
2243
2400
|
}
|
|
2244
2401
|
}
|
|
2245
2402
|
// Acquire before the first mutation of the live Table below, so a lost race leaves no
|
|
2246
|
-
// attributes this worker describes but never persisted. Only the RocksDB acquire is bounded
|
|
2247
|
-
//
|
|
2248
|
-
// environment-wide write transaction that cannot time out, so it stays lazy.
|
|
2403
|
+
// attributes this worker describes but never persisted. Only the RocksDB acquire is bounded;
|
|
2404
|
+
// ordinary LMDB declarations stay lazy, while legacy HNSW normalization locks at entry.
|
|
2249
2405
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase)
|
|
2250
2406
|
exclusiveLock();
|
|
2407
|
+
if (origin !== 'cluster') {
|
|
2408
|
+
const lockedAttributesDbi = Table.dbisDB;
|
|
2409
|
+
const persistedAuditUnderLock = persistedPrimaryDescriptor(lockedAttributesDbi).descriptor?.audit;
|
|
2410
|
+
validateHnswOptions(lockedAttributesDbi, auditExplicitlyEnabled || (!auditExplicitlyDisabled && persistedAuditUnderLock === true));
|
|
2411
|
+
}
|
|
2251
2412
|
// it table already exists, get the split segments setting
|
|
2252
2413
|
if (splitSegments == undefined)
|
|
2253
2414
|
splitSegments = Table.splitSegments;
|
|
@@ -2271,6 +2432,13 @@ function declareTable(target, tableDefinition) {
|
|
|
2271
2432
|
}
|
|
2272
2433
|
attributes = merged;
|
|
2273
2434
|
}
|
|
2435
|
+
else if (!attributes.some((attribute) => attribute.isPrimaryKey)) {
|
|
2436
|
+
const existingPrimary = Table.attributes.find((attribute) => attribute.isPrimaryKey);
|
|
2437
|
+
if (existingPrimary && attributes.some((attribute) => attribute.name === existingPrimary.name))
|
|
2438
|
+
throw new hdbError_ts_1.ClientError(`Cannot remove the primary key designation from '${databaseName}.${tableName}.${existingPrimary.name}'`);
|
|
2439
|
+
if (existingPrimary)
|
|
2440
|
+
attributes = [existingPrimary, ...attributes];
|
|
2441
|
+
}
|
|
2274
2442
|
Table.attributes.splice(0, Table.attributes.length, ...attributes);
|
|
2275
2443
|
// Re-assert from the live declaration so a stale value on disk (replicated event,
|
|
2276
2444
|
// v4-era backfill) is corrected on every reload. Gated on `schemaDefinedExplicit` so
|
|
@@ -2470,7 +2638,7 @@ function declareTable(target, tableDefinition) {
|
|
|
2470
2638
|
const attribute = attributes.find((attribute) => attribute.name === attribute_name);
|
|
2471
2639
|
const removeIndex = !attribute?.indexed && value.indexed && !value.isPrimaryKey;
|
|
2472
2640
|
// rows already present under a create are aborted state
|
|
2473
|
-
const staleRow = !attribute || Boolean(deferredPrimaryRow);
|
|
2641
|
+
const staleRow = (!attribute && !value.isPrimaryKey) || Boolean(deferredPrimaryRow);
|
|
2474
2642
|
if (staleRow || removeIndex) {
|
|
2475
2643
|
exclusiveLock();
|
|
2476
2644
|
hasChanges = true;
|
|
@@ -2483,9 +2651,75 @@ function declareTable(target, tableDefinition) {
|
|
|
2483
2651
|
}
|
|
2484
2652
|
}
|
|
2485
2653
|
}
|
|
2654
|
+
const hasHnswDeclaration = attributes.some((attribute) => attribute.indexed?.type === 'HNSW');
|
|
2655
|
+
const persistedAudit = hasHnswDeclaration ? persistedPrimaryDescriptor(attributesDbi).descriptor?.audit : undefined;
|
|
2656
|
+
// A cluster declaration can apply audit on a create, but deliberately cannot rewrite an existing
|
|
2657
|
+
// table's primary row. Do not let an incoming audit value qualify a replicated native descriptor
|
|
2658
|
+
// that this node would then persist beside its durable audit:false row.
|
|
2659
|
+
const explicitAuditCanBeApplied = origin !== 'cluster' || Boolean(deferredPrimaryRow);
|
|
2660
|
+
const auditEnabledForNativeDefault = (auditExplicitlyEnabled && explicitAuditCanBeApplied) || (!auditExplicitlyDisabled && persistedAudit === true);
|
|
2661
|
+
const auditEnabledForNativePlane = !auditExplicitlyDisabled && (auditEnabledForNativeDefault || (persistedAudit == null && Table.audit === true));
|
|
2662
|
+
for (const attribute of attributes) {
|
|
2663
|
+
const indexed = attribute.indexed;
|
|
2664
|
+
if (!indexed || typeof indexed !== 'object' || indexed.type !== 'HNSW')
|
|
2665
|
+
continue;
|
|
2666
|
+
const descriptor = attributesDbi.getSync(tableName + '/' + (attribute.name || ''));
|
|
2667
|
+
const existingHnsw = descriptor?.indexed?.type === 'HNSW';
|
|
2668
|
+
if (indexed.nativePlane == null) {
|
|
2669
|
+
if (existingHnsw) {
|
|
2670
|
+
if (Object.hasOwn(descriptor.indexed, 'nativePlane')) {
|
|
2671
|
+
indexed.nativePlane = descriptor.indexed.nativePlane;
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
else if (origin !== 'cluster' &&
|
|
2675
|
+
auditEnabledForNativeDefault &&
|
|
2676
|
+
customIndexes_ts_1.CUSTOM_INDEXES.HNSW.canDefaultToNativePlane(rootStore, indexed)) {
|
|
2677
|
+
indexed.nativePlane = true;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
else if (origin === 'cluster' && !existingHnsw && indexed.nativePlane) {
|
|
2681
|
+
let canRunNative = false;
|
|
2682
|
+
try {
|
|
2683
|
+
canRunNative = auditEnabledForNativeDefault && customIndexes_ts_1.CUSTOM_INDEXES.HNSW.canRunNativePlane(rootStore, indexed);
|
|
2684
|
+
}
|
|
2685
|
+
catch { }
|
|
2686
|
+
if (!canRunNative) {
|
|
2687
|
+
logger.warn(`Using the JS HNSW index for replicated attribute ${databaseName}.${tableName}.${attribute.name} because this node does not satisfy the nativePlane requirements`);
|
|
2688
|
+
indexed.nativePlane = false;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
const nativePlaneEnabled = origin !== 'cluster' &&
|
|
2693
|
+
attributes.some((attribute) => attribute.indexed?.type === 'HNSW' && attribute.indexed.nativePlane);
|
|
2694
|
+
if (nativePlaneEnabled && !auditEnabledForNativePlane) {
|
|
2695
|
+
throw new hdbError_ts_1.ClientError(`Table '${databaseName}.${tableName}' must enable audit logging before using nativePlane because its transaction log is the derived-index recovery source; set nativePlane: false to use the JS index`);
|
|
2696
|
+
}
|
|
2697
|
+
if (nativePlaneEnabled && persistedAudit !== true)
|
|
2698
|
+
audit = true;
|
|
2699
|
+
if (nativePlaneEnabled && persistedAudit !== true && !attributes.some((attribute) => attribute.isPrimaryKey)) {
|
|
2700
|
+
exclusiveLock();
|
|
2701
|
+
const primaryKey = primaryDescriptorKey();
|
|
2702
|
+
const primaryDescriptor = attributesDbi.getSync(primaryKey);
|
|
2703
|
+
if (primaryDescriptor && !tableIsDropping(primaryDescriptor, primaryKey)) {
|
|
2704
|
+
Table.enableAuditing();
|
|
2705
|
+
attributesDbi.put(primaryKey, { ...primaryDescriptor, audit: true });
|
|
2706
|
+
hasChanges = true;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2486
2709
|
// TODO: If we have attributes and the schemaDefined flag is not set, turn it on
|
|
2487
2710
|
// iterate through the attributes to ensure that we have all the dbis created and indexed
|
|
2488
|
-
|
|
2711
|
+
const attributesInPersistenceOrder = nativePlaneEnabled
|
|
2712
|
+
? [
|
|
2713
|
+
...attributes.filter((attribute) => attribute.isPrimaryKey),
|
|
2714
|
+
...attributes.filter((attribute) => !attribute.isPrimaryKey),
|
|
2715
|
+
]
|
|
2716
|
+
: auditExplicitlyDisabled
|
|
2717
|
+
? [
|
|
2718
|
+
...attributes.filter((attribute) => !attribute.isPrimaryKey),
|
|
2719
|
+
...attributes.filter((attribute) => attribute.isPrimaryKey),
|
|
2720
|
+
]
|
|
2721
|
+
: attributes;
|
|
2722
|
+
for (const attribute of attributesInPersistenceOrder) {
|
|
2489
2723
|
if (attribute.relationship) {
|
|
2490
2724
|
refreshRelationshipAttributes = true;
|
|
2491
2725
|
continue;
|
|
@@ -2509,7 +2743,7 @@ function declareTable(target, tableDefinition) {
|
|
|
2509
2743
|
// primary key can't change indexing, but settings can change
|
|
2510
2744
|
if (origin !== 'cluster' &&
|
|
2511
2745
|
(schemaDefinedMismatch ||
|
|
2512
|
-
(audit
|
|
2746
|
+
(typeof audit === 'boolean' && audit !== attributeDescriptor.audit) ||
|
|
2513
2747
|
(sealed !== undefined && sealed !== Table.sealed) ||
|
|
2514
2748
|
(replicate !== undefined && replicate !== Table.replicate) ||
|
|
2515
2749
|
(+expiration || undefined) !== (+attributeDescriptor.expiration || undefined) ||
|
|
@@ -2550,8 +2784,7 @@ function declareTable(target, tableDefinition) {
|
|
|
2550
2784
|
applyDurableDeclaration(attribute, attributeDescriptor);
|
|
2551
2785
|
const abandonedIndexBuild = attribute.indexed &&
|
|
2552
2786
|
(attributeDescriptor.indexingFailed ||
|
|
2553
|
-
(attributeDescriptor
|
|
2554
|
-
attributeDescriptor.restartNumber < (worker_threads_1.workerData?.restartNumber ?? manageThreads.restartNumber));
|
|
2787
|
+
isAbandonedIndexBuild(attributeDescriptor, worker_threads_1.workerData?.restartNumber ?? manageThreads.restartNumber));
|
|
2555
2788
|
if (abandonedIndexBuild) {
|
|
2556
2789
|
// Recovery is the exception to skipping the handling below, because without it `isIndexing`
|
|
2557
2790
|
// stays pinned on with nothing left to clear it and every query on the attribute fails with
|
|
@@ -2640,15 +2873,13 @@ function declareTable(target, tableDefinition) {
|
|
|
2640
2873
|
if (changed ||
|
|
2641
2874
|
indexFormatNeedsPersist ||
|
|
2642
2875
|
attributeDescriptor?.indexingFailed ||
|
|
2643
|
-
(attributeDescriptor
|
|
2644
|
-
attributeDescriptor?.restartNumber < currentRestartGeneration) {
|
|
2876
|
+
isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
|
|
2645
2877
|
hasChanges = true;
|
|
2646
2878
|
exclusiveLock();
|
|
2647
2879
|
attributeDescriptor = attributesDbi.getSync(dbiKey);
|
|
2648
2880
|
if (structurallyChanged ||
|
|
2649
2881
|
attributeDescriptor?.indexingFailed ||
|
|
2650
|
-
(attributeDescriptor
|
|
2651
|
-
attributeDescriptor?.restartNumber < currentRestartGeneration) {
|
|
2882
|
+
isAbandonedIndexBuild(attributeDescriptor, currentRestartGeneration)) {
|
|
2652
2883
|
hasChanges = true;
|
|
2653
2884
|
if (attribute.indexNulls === undefined)
|
|
2654
2885
|
attribute.indexNulls = true;
|
|
@@ -2667,9 +2898,20 @@ function declareTable(target, tableDefinition) {
|
|
|
2667
2898
|
// representation-only option difference — preserve the checkpoint so the backfill
|
|
2668
2899
|
// resumes rather than restarts. Canonicalized to match structurallyChanged above.
|
|
2669
2900
|
const indexOptionsChanged = canonicalIndexKey(attributeDescriptor?.indexed) !== canonicalIndexKey(attribute.indexed);
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2901
|
+
// Only a checkpoint runIndexing stamped with its own key resumes: earlier releases advanced
|
|
2902
|
+
// lastIndexedKey past failed and unflushed index writes, so any other is a full rebuild.
|
|
2903
|
+
const uncertifiedCheckpoint = attributeDescriptor?.lastIndexedKey !== undefined &&
|
|
2904
|
+
(attributeDescriptor.checkpointCertified === undefined ||
|
|
2905
|
+
attributeDescriptor.checkpointAlgorithm !== exports.CHECKPOINT_ALGORITHM ||
|
|
2906
|
+
(0, lmdb_1.compareKeys)(attributeDescriptor.checkpointCertified, attributeDescriptor.lastIndexedKey) !== 0);
|
|
2907
|
+
attribute.lastIndexedKey =
|
|
2908
|
+
indexOptionsChanged || uncertifiedCheckpoint
|
|
2909
|
+
? undefined
|
|
2910
|
+
: (attributeDescriptor?.lastIndexedKey ?? undefined);
|
|
2911
|
+
if (attribute.lastIndexedKey !== undefined) {
|
|
2912
|
+
attribute.checkpointCertified = attribute.lastIndexedKey;
|
|
2913
|
+
attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
|
|
2914
|
+
}
|
|
2673
2915
|
// Explicit reindex is the upgrade path from a legacy (un-versioned) custom-index
|
|
2674
2916
|
// object store to the versioned, VT-cacheable format. A full rebuild from scratch
|
|
2675
2917
|
// (lastIndexedKey === undefined) clears the store and rewrites every node, so the
|
|
@@ -2691,6 +2933,9 @@ function declareTable(target, tableDefinition) {
|
|
|
2691
2933
|
// the new process reuses the old PID. Cleared on clean completion; left in place
|
|
2692
2934
|
// on failure/crash so the next, higher-numbered restart re-triggers the backfill.
|
|
2693
2935
|
attribute.restartNumber = currentRestartGeneration;
|
|
2936
|
+
if (manageThreads.processIncarnation != null)
|
|
2937
|
+
attribute.indexingIncarnation = manageThreads.processIncarnation;
|
|
2938
|
+
attribute.indexingBuildId = (0, node_crypto_1.randomBytes)(8).toString('hex');
|
|
2694
2939
|
delete attribute.indexingFailed; // clear failure flag for the new run
|
|
2695
2940
|
dbi.isIndexing = true;
|
|
2696
2941
|
Object.defineProperty(attribute, 'dbi', { value: dbi, configurable: true, enumerable: false });
|
|
@@ -2706,6 +2951,12 @@ function declareTable(target, tableDefinition) {
|
|
|
2706
2951
|
reindexReasons.push(`crash-recovery(pid=${attributeDescriptor.indexingPID})`);
|
|
2707
2952
|
if (attributeDescriptor?.restartNumber < currentRestartGeneration)
|
|
2708
2953
|
reindexReasons.push('restart-number');
|
|
2954
|
+
if (uncertifiedCheckpoint)
|
|
2955
|
+
reindexReasons.push('uncertified-checkpoint');
|
|
2956
|
+
if (attributeDescriptor?.indexingPID === process.pid &&
|
|
2957
|
+
manageThreads.processIncarnation != null &&
|
|
2958
|
+
attributeDescriptor.indexingIncarnation !== manageThreads.processIncarnation)
|
|
2959
|
+
reindexReasons.push('abandoned-build(previous process incarnation)');
|
|
2709
2960
|
logger.info(`reindex ${databaseName}.${tableName}.${attribute.name}: reason=${reindexReasons.join(',') || 'unknown'}`);
|
|
2710
2961
|
// we only set indexing nulls to true if new or reindexing, we can't have partial indexing of null
|
|
2711
2962
|
attributesToIndex.push(attribute);
|
|
@@ -2718,12 +2969,22 @@ function declareTable(target, tableDefinition) {
|
|
|
2718
2969
|
// workers / a reload would treat the still-partial index as ready and return incomplete results.
|
|
2719
2970
|
attribute.indexingPID = attributeDescriptor.indexingPID;
|
|
2720
2971
|
attribute.lastIndexedKey = attributeDescriptor.lastIndexedKey;
|
|
2972
|
+
if (attributeDescriptor.checkpointCertified !== undefined) {
|
|
2973
|
+
attribute.checkpointCertified = attributeDescriptor.checkpointCertified;
|
|
2974
|
+
attribute.checkpointAlgorithm = attributeDescriptor.checkpointAlgorithm;
|
|
2975
|
+
}
|
|
2721
2976
|
// Carry the in-progress restart generation too, so persisting this metadata-only
|
|
2722
2977
|
// change doesn't drop it and break the crash-recovery trigger for the running backfill.
|
|
2723
2978
|
attribute.restartNumber = attributeDescriptor.restartNumber;
|
|
2979
|
+
attribute.indexingIncarnation = attributeDescriptor.indexingIncarnation;
|
|
2980
|
+
attribute.indexingBuildId = attributeDescriptor.indexingBuildId;
|
|
2724
2981
|
if (attributeDescriptor.indexingFailed)
|
|
2725
2982
|
attribute.indexingFailed = attributeDescriptor.indexingFailed;
|
|
2726
2983
|
}
|
|
2984
|
+
// The declared attribute never carries the stamp, so any rewrite of a descriptor that has
|
|
2985
|
+
// one would drop it and make a completed index look like a pre-stamp build.
|
|
2986
|
+
if (attribute.checkpointAlgorithm === undefined && attributeDescriptor?.checkpointAlgorithm !== undefined)
|
|
2987
|
+
attribute.checkpointAlgorithm = attributeDescriptor.checkpointAlgorithm;
|
|
2727
2988
|
attributesDbi.put(dbiKey, attribute);
|
|
2728
2989
|
}
|
|
2729
2990
|
// If a migration is in progress (indexingPID set), any newly opened dbi must also
|
|
@@ -2784,32 +3045,39 @@ function declareTable(target, tableDefinition) {
|
|
|
2784
3045
|
finally {
|
|
2785
3046
|
releaseLock();
|
|
2786
3047
|
}
|
|
2787
|
-
if (hasChanges || refreshRelationshipAttributes)
|
|
3048
|
+
if (hasChanges || refreshRelationshipAttributes)
|
|
2788
3049
|
Table.schemaVersion++;
|
|
3050
|
+
if (hasChanges || refreshRelationshipAttributes || refreshedLiveAttributes)
|
|
2789
3051
|
Table.updatedAttributes();
|
|
2790
|
-
}
|
|
2791
3052
|
logger.trace(`${tableName} table loading, running index`);
|
|
2792
3053
|
const branchPath = target.branch?.path;
|
|
2793
3054
|
if (attributesToIndex.length > 0 || indicesToRemove.length > 0) {
|
|
2794
|
-
|
|
3055
|
+
// captured before the backfill can rewrite the attributes
|
|
3056
|
+
const buildIds = new Map(attributesToIndex.map((attribute) => [attribute, attribute.indexingBuildId]));
|
|
3057
|
+
const markSettled = () => markAbandonedIndexBuild(Table, rootStore, buildIds);
|
|
3058
|
+
Table.indexingOperation = runIndexing(Table, attributesToIndex, indicesToRemove, branchPath).then(markSettled, markSettled);
|
|
2795
3059
|
}
|
|
2796
3060
|
else if (hasChanges)
|
|
2797
3061
|
signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName, undefined, branchPath));
|
|
3062
|
+
void Table.derivedIndexRuntime?.close();
|
|
3063
|
+
Table.derivedIndexRuntime = (0, hnswDerivedIndex_ts_1.attachDerivedIndexes)(Table);
|
|
2798
3064
|
Table.origin = origin;
|
|
2799
3065
|
// scope-private: replication and other global subscribers must not learn of a branch class
|
|
2800
3066
|
if ((hasChanges || refreshRelationshipAttributes) && !target.branch) {
|
|
2801
3067
|
exports.databaseEventsEmitter.emit('updateTable', Table, origin !== 'cluster');
|
|
2802
3068
|
}
|
|
2803
|
-
if (expiration || eviction || scanInterval)
|
|
3069
|
+
if (expiration || eviction || scanInterval || attributes.some((attribute) => attribute.expiresAt))
|
|
2804
3070
|
Table.setTTLExpiration({
|
|
2805
3071
|
expiration,
|
|
2806
3072
|
eviction,
|
|
2807
3073
|
scanInterval,
|
|
3074
|
+
fromSchema: true,
|
|
3075
|
+
isolatedApplicationOwner,
|
|
2808
3076
|
});
|
|
2809
3077
|
logger.trace(`${tableName} table loaded`);
|
|
2810
3078
|
return Table;
|
|
2811
|
-
//
|
|
2812
|
-
//
|
|
3079
|
+
// A migrated catalog can retain a named primary descriptor beside a bare table tombstone, so a
|
|
3080
|
+
// drop in flight has to be checked on both representations.
|
|
2813
3081
|
function tableIsDropping(descriptor, descriptorKey) {
|
|
2814
3082
|
if (descriptor?.dropping)
|
|
2815
3083
|
return true;
|
|
@@ -2818,13 +3086,7 @@ function declareTable(target, tableDefinition) {
|
|
|
2818
3086
|
// The catalog row initStores() reads a table's settings from: the primary key's own row when it
|
|
2819
3087
|
// has one, and the bare table row otherwise.
|
|
2820
3088
|
function primaryDescriptorKey() {
|
|
2821
|
-
|
|
2822
|
-
if (declaredPrimaryKey) {
|
|
2823
|
-
const attributeKey = tableName + '/' + declaredPrimaryKey;
|
|
2824
|
-
if (attributesDbi.getSync(attributeKey))
|
|
2825
|
-
return attributeKey;
|
|
2826
|
-
}
|
|
2827
|
-
return tableName + '/';
|
|
3089
|
+
return persistedPrimaryDescriptor(attributesDbi).key;
|
|
2828
3090
|
}
|
|
2829
3091
|
// The catalog of a published table stays, but a class the registration never accepted is
|
|
2830
3092
|
// unreachable, so release what makeTable() registered process-wide instead of leaving its timers
|
|
@@ -2892,73 +3154,238 @@ function declareTable(target, tableDefinition) {
|
|
|
2892
3154
|
}
|
|
2893
3155
|
}
|
|
2894
3156
|
/**
|
|
2895
|
-
*
|
|
2896
|
-
*
|
|
2897
|
-
* and config objects can supply them reordered or as numbers; without canonicalizing, such a
|
|
2898
|
-
* representation-only difference flips the structural comparison and forces a needless full rebuild
|
|
2899
|
-
* (clearing + rebuilding the index, 503-ing the attribute throughout) for a semantically identical
|
|
2900
|
-
* index. Sorts object keys and coerces numeric-looking (non-zero) string scalars to numbers.
|
|
2901
|
-
* Conservative by design: boolean-vs-object, absent-vs-present, and string-"0"-vs-number-0
|
|
2902
|
-
* differences are all preserved, so a genuine change (`true` vs `{ type: 'HNSW' }`, an added/removed
|
|
2903
|
-
* option, a changed value) still triggers a rebuild. Persistence keys off the raw form, so the stored
|
|
2904
|
-
* descriptor self-heals toward this shape over time. harper#1357
|
|
3157
|
+
* Stable structural form for deciding whether an index must be rebuilt. `coerceZero` extends numeric
|
|
3158
|
+
* coercion to zero; a truthiness-sensitive numeric option must normalize its value before using it.
|
|
2905
3159
|
*/
|
|
2906
|
-
function canonicalizeIndexOptions(value) {
|
|
3160
|
+
function canonicalizeIndexOptions(value, coerceZero = false) {
|
|
2907
3161
|
if (Array.isArray(value))
|
|
2908
|
-
return value.map(canonicalizeIndexOptions);
|
|
3162
|
+
return value.map((item) => canonicalizeIndexOptions(item, coerceZero));
|
|
2909
3163
|
if (value && typeof value === 'object') {
|
|
2910
3164
|
const canonical = {};
|
|
2911
|
-
|
|
2912
|
-
|
|
3165
|
+
const customIndex = value.type && customIndexes_ts_1.CUSTOM_INDEXES[value.type];
|
|
3166
|
+
for (const key of Object.keys(value).sort()) {
|
|
3167
|
+
if (customIndex?.truthyStructuralOptions?.has(key)) {
|
|
3168
|
+
if (value[key])
|
|
3169
|
+
canonical[key] = true;
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
const optionValue = customIndex?.normalizeOptionValue
|
|
3173
|
+
? customIndex.normalizeOptionValue(key, value[key])
|
|
3174
|
+
: value[key];
|
|
3175
|
+
canonical[key] = canonicalizeIndexOptions(optionValue, coerceZero || Boolean(customIndex?.numericOptions?.has(key)));
|
|
3176
|
+
}
|
|
2913
3177
|
return canonical;
|
|
2914
3178
|
}
|
|
2915
|
-
// Coerce numeric-looking strings ("16" -> 16) so string-vs-number representations of the same
|
|
2916
|
-
// option compare equal — EXCEPT zero: the string "0" is truthy while the number 0 is falsy, and
|
|
2917
|
-
// index code may branch on truthiness (e.g. HNSW `if (this.optimizeRouting)` doubles maxConnections),
|
|
2918
|
-
// so "0" and 0 build structurally different indexes and must still trigger a rebuild. Zero is the
|
|
2919
|
-
// only finite number whose string and numeric forms diverge in truthiness, so excluding it fully
|
|
2920
|
-
// closes that gap. Leave non-numeric strings, booleans, null, etc. intact.
|
|
2921
3179
|
if (typeof value === 'string' && value.trim() !== '') {
|
|
2922
3180
|
const numeric = Number(value);
|
|
2923
|
-
if (numeric !== 0 && Number.isFinite(numeric))
|
|
3181
|
+
if ((numeric !== 0 || coerceZero) && Number.isFinite(numeric))
|
|
2924
3182
|
return numeric;
|
|
2925
3183
|
}
|
|
2926
3184
|
return value;
|
|
2927
3185
|
}
|
|
3186
|
+
// Bumped when a change alters which keys a checkpoint may certify. A descriptor stamped by any other
|
|
3187
|
+
// version resumes as uncertified (full rebuild) rather than being trusted, and a completed index keeps
|
|
3188
|
+
// the stamp of the build that wrote it.
|
|
3189
|
+
exports.CHECKPOINT_ALGORITHM = 2;
|
|
2928
3190
|
const MAX_OUTSTANDING_INDEXING = 1000;
|
|
2929
3191
|
const MIN_OUTSTANDING_INDEXING = 10;
|
|
3192
|
+
const INDEXING_YIELD_INTERVAL = 100;
|
|
3193
|
+
// A resumable checkpoint is written only after a flush (see flushIndexStores), at most once per period
|
|
3194
|
+
// and never before this many more records: the flush seals every column family in the database, so a
|
|
3195
|
+
// slow backfill must not impose the period's flush rate on unrelated tables.
|
|
3196
|
+
let indexingCheckpointPeriodMs = 5000;
|
|
3197
|
+
let indexingCheckpointMinRecords = 10000;
|
|
3198
|
+
function setIndexingCheckpointPeriod(ms, minRecords = indexingCheckpointMinRecords) {
|
|
3199
|
+
const previous = { ms: indexingCheckpointPeriodMs, minRecords: indexingCheckpointMinRecords };
|
|
3200
|
+
indexingCheckpointPeriodMs = ms;
|
|
3201
|
+
indexingCheckpointMinRecords = minRecords;
|
|
3202
|
+
return previous;
|
|
3203
|
+
}
|
|
3204
|
+
const yieldEventTurn = () => new Promise((resolve) => setImmediate(resolve));
|
|
3205
|
+
// RocksDB index stores have no WAL (openRocksDatabase defaults disableWAL), so a flush is what makes the
|
|
3206
|
+
// entries a checkpoint certifies durable. A flush only covers writes issued before it started, so a caller
|
|
3207
|
+
// never joins one in flight: it joins the next one, which every backfill on that database asking meanwhile
|
|
3208
|
+
// shares — at most one in flight and one queued.
|
|
3209
|
+
const indexingFlushes = new WeakMap();
|
|
3210
|
+
function flushIndexStores(rootStore) {
|
|
3211
|
+
if (!(rootStore instanceof rocksdb_js_1.RocksDatabase))
|
|
3212
|
+
return;
|
|
3213
|
+
let flushes = indexingFlushes.get(rootStore);
|
|
3214
|
+
if (!flushes)
|
|
3215
|
+
indexingFlushes.set(rootStore, (flushes = {}));
|
|
3216
|
+
if (flushes.queued)
|
|
3217
|
+
return flushes.queued;
|
|
3218
|
+
const start = () => {
|
|
3219
|
+
flushes.queued = undefined;
|
|
3220
|
+
const flush = rootStore.flush().finally(() => {
|
|
3221
|
+
if (flushes.inFlight === flush)
|
|
3222
|
+
flushes.inFlight = undefined;
|
|
3223
|
+
});
|
|
3224
|
+
flushes.inFlight = flush;
|
|
3225
|
+
return flush;
|
|
3226
|
+
};
|
|
3227
|
+
if (!flushes.inFlight)
|
|
3228
|
+
return start();
|
|
3229
|
+
return (flushes.queued = flushes.inFlight.then(start, start));
|
|
3230
|
+
}
|
|
3231
|
+
function resumeStartKey(attributes) {
|
|
3232
|
+
let start;
|
|
3233
|
+
for (const attribute of attributes) {
|
|
3234
|
+
if (attribute.lastIndexedKey == undefined)
|
|
3235
|
+
return undefined;
|
|
3236
|
+
if (start === undefined || (0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
|
|
3237
|
+
start = attribute.lastIndexedKey;
|
|
3238
|
+
}
|
|
3239
|
+
return start;
|
|
3240
|
+
}
|
|
3241
|
+
/**
|
|
3242
|
+
* Persists the failure marker for a build that ended without running one of runIndexing's own exit
|
|
3243
|
+
* paths, so something re-triggers it. Fenced on `indexingBuildId` inside the storage engine's catalog
|
|
3244
|
+
* serialization boundary, because a replacement generation (or another thread declaring different index
|
|
3245
|
+
* options) can claim the attribute before an outgoing build's promise settles, and marking that would fail
|
|
3246
|
+
* a live build. The fence read and write stay synchronous, and nothing here may throw because
|
|
3247
|
+
* `Table.indexingOperation` reaches operations-API callers.
|
|
3248
|
+
*/
|
|
3249
|
+
async function markAbandonedIndexBuild(Table, rootStore, buildIds) {
|
|
3250
|
+
for (const [attribute, buildId] of buildIds) {
|
|
3251
|
+
try {
|
|
3252
|
+
let marked;
|
|
3253
|
+
if (buildId == null || Table.dbisDB.getSync(attribute.key)?.indexingBuildId !== buildId)
|
|
3254
|
+
continue;
|
|
3255
|
+
const markIfOwned = () => {
|
|
3256
|
+
const descriptor = Table.dbisDB.getSync(attribute.key);
|
|
3257
|
+
if (descriptor?.indexingBuildId === buildId && !descriptor.indexingFailed) {
|
|
3258
|
+
Table.dbisDB.putSync(attribute.key, { ...descriptor, indexingFailed: true });
|
|
3259
|
+
marked = true;
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
3263
|
+
(0, Table_ts_1.acquireUpdateAttributesLock)(rootStore, `abandoned index build '${Table.tableName}.${attribute.name}'`);
|
|
3264
|
+
try {
|
|
3265
|
+
markIfOwned();
|
|
3266
|
+
}
|
|
3267
|
+
finally {
|
|
3268
|
+
(0, Table_ts_1.releaseUpdateAttributesLock)(rootStore);
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
else {
|
|
3272
|
+
rootStore.transactionSync(markIfOwned);
|
|
3273
|
+
}
|
|
3274
|
+
if (marked)
|
|
3275
|
+
logger.warn(`Indexing of ${Table.databaseName}.${Table.tableName}.${attribute.name} ended without completing. ` +
|
|
3276
|
+
`The index stays incomplete and every query on the attribute reports it as not indexed yet; ` +
|
|
3277
|
+
`the next load of the table retries the backfill from the last checkpoint (indexingFailed=true).`);
|
|
3278
|
+
}
|
|
3279
|
+
catch (error) {
|
|
3280
|
+
// A store closed by shutdown is the common case, and it cannot be written to at all.
|
|
3281
|
+
try {
|
|
3282
|
+
logger.debug(`Could not mark the abandoned index build of ${Table.tableName}.${attribute.name}`, error);
|
|
3283
|
+
}
|
|
3284
|
+
catch { }
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
2930
3288
|
async function runIndexing(Table, attributes, indicesToRemove, branchPath) {
|
|
3289
|
+
let checkpointing;
|
|
3290
|
+
let hadIndexingErrors = false;
|
|
3291
|
+
const attributeErrorReported = {};
|
|
3292
|
+
const onIndexPutRejected = (property, error) => {
|
|
3293
|
+
hadIndexingErrors = true;
|
|
3294
|
+
if (attributeErrorReported[property])
|
|
3295
|
+
return;
|
|
3296
|
+
attributeErrorReported[property] = true;
|
|
3297
|
+
logger.error(`Error indexing attribute ${property}`, error);
|
|
3298
|
+
};
|
|
3299
|
+
const putRejectionHandlers = attributes.map((attribute) => (error) => onIndexPutRejected(attribute.name, error));
|
|
2931
3300
|
try {
|
|
2932
3301
|
logger.info(`Indexing ${Table.tableName} attributes`, attributes);
|
|
2933
3302
|
await signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName, undefined, branchPath));
|
|
2934
3303
|
let lastResolution;
|
|
3304
|
+
// The checkpoint and completion barriers have to cover every mutation still in flight: any of them
|
|
3305
|
+
// may reject after those barriers read hadIndexingErrors.
|
|
3306
|
+
const pendingMutations = new Set();
|
|
3307
|
+
let settleWaiter;
|
|
3308
|
+
const track = (result, onRejected) => {
|
|
3309
|
+
if (!result?.then)
|
|
3310
|
+
return result;
|
|
3311
|
+
const tracked = result.then(() => {
|
|
3312
|
+
pendingMutations.delete(tracked);
|
|
3313
|
+
settleWaiter?.();
|
|
3314
|
+
return false;
|
|
3315
|
+
}, (error) => {
|
|
3316
|
+
pendingMutations.delete(tracked);
|
|
3317
|
+
settleWaiter?.();
|
|
3318
|
+
onRejected(error);
|
|
3319
|
+
return true;
|
|
3320
|
+
});
|
|
3321
|
+
pendingMutations.add(tracked);
|
|
3322
|
+
return result;
|
|
3323
|
+
};
|
|
3324
|
+
// The tracked promises absorb their own rejections, so one failure never abandons its siblings.
|
|
3325
|
+
const drainMutations = async () => {
|
|
3326
|
+
if (!pendingMutations.size)
|
|
3327
|
+
return false;
|
|
3328
|
+
return (await Promise.all([...pendingMutations])).some(Boolean);
|
|
3329
|
+
};
|
|
3330
|
+
// Waiting on a chosen entry would stall behind a slow one the others have already overtaken.
|
|
3331
|
+
const nextSettlement = () => new Promise((resolve) => {
|
|
3332
|
+
settleWaiter = () => {
|
|
3333
|
+
settleWaiter = undefined;
|
|
3334
|
+
resolve(undefined);
|
|
3335
|
+
};
|
|
3336
|
+
});
|
|
2935
3337
|
for (const index of indicesToRemove) {
|
|
2936
|
-
|
|
3338
|
+
index.customIndex?.resetDerivedStorage?.();
|
|
3339
|
+
track(index.drop(), (error) => onIndexPutRejected(index.name, error));
|
|
2937
3340
|
}
|
|
2938
3341
|
let interrupted;
|
|
2939
|
-
let hadIndexingErrors = false;
|
|
2940
|
-
const attributeErrorReported = {};
|
|
2941
3342
|
let indexed = 0;
|
|
2942
3343
|
const attributesLength = attributes.length;
|
|
2943
3344
|
await new Promise((resolve) => setImmediate(resolve)); // yield event turn, indexing should consistently take at least one event turn
|
|
2944
3345
|
if (attributesLength > 0) {
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
if ((0, lmdb_1.compareKeys)(attribute.lastIndexedKey, start) < 0)
|
|
2949
|
-
start = attribute.lastIndexedKey;
|
|
2950
|
-
if (attribute.lastIndexedKey == undefined) {
|
|
3346
|
+
const start = resumeStartKey(attributes);
|
|
3347
|
+
if (start === undefined) {
|
|
3348
|
+
for (const attribute of attributes) {
|
|
2951
3349
|
// if we are starting from the beginning, clear out any previous index entries since we are rewriting
|
|
3350
|
+
attribute.dbi.customIndex?.resetDerivedStorage?.();
|
|
2952
3351
|
if (attribute.dbi.clearAsync) {
|
|
2953
|
-
// LMDB
|
|
2954
|
-
|
|
3352
|
+
// LMDB enqueues this ahead of the index writes, so the scan need not wait for it — but the
|
|
3353
|
+
// barriers must, or a rejected clear certifies a checkpoint over stale entries.
|
|
3354
|
+
track(attribute.dbi.clearAsync(), (error) => onIndexPutRejected(attribute.name, error));
|
|
2955
3355
|
}
|
|
2956
3356
|
else {
|
|
2957
3357
|
await attribute.dbi.clear();
|
|
2958
3358
|
}
|
|
2959
3359
|
}
|
|
2960
3360
|
}
|
|
2961
|
-
|
|
3361
|
+
// A resumed scan starts at the checkpoint, so it must only name a key whose every predecessor is
|
|
3362
|
+
// durably indexed: persisted once the writes it covers have settled and flushed, frozen after any
|
|
3363
|
+
// record fails so the retry re-covers it, and stamped with its own key (see the trigger in table()).
|
|
3364
|
+
const persistCheckpoint = async (key) => {
|
|
3365
|
+
if (hadIndexingErrors)
|
|
3366
|
+
return;
|
|
3367
|
+
try {
|
|
3368
|
+
// Everything still in flight was issued for a key at or before this one: the scan has not
|
|
3369
|
+
// moved past it yet. So a failure among them is a failure this checkpoint would cover.
|
|
3370
|
+
const failed = await drainMutations();
|
|
3371
|
+
if (failed)
|
|
3372
|
+
return;
|
|
3373
|
+
await flushIndexStores(Table.primaryStore.rootStore);
|
|
3374
|
+
const puts = [];
|
|
3375
|
+
for (const attribute of attributes) {
|
|
3376
|
+
attribute.lastIndexedKey = key;
|
|
3377
|
+
attribute.checkpointCertified = key;
|
|
3378
|
+
attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
|
|
3379
|
+
puts.push(Table.dbisDB.put(attribute.key, attribute));
|
|
3380
|
+
}
|
|
3381
|
+
await Promise.all(puts);
|
|
3382
|
+
}
|
|
3383
|
+
catch (error) {
|
|
3384
|
+
logger.warn(`Could not persist the indexing checkpoint for ${Table.tableName}`, error);
|
|
3385
|
+
}
|
|
3386
|
+
};
|
|
3387
|
+
let nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
|
|
3388
|
+
let nextCheckpointRecord = indexingCheckpointMinRecords;
|
|
2962
3389
|
// this means that a new attribute has been introduced that needs to be indexed
|
|
2963
3390
|
for (const { key, value: record } of Table.primaryStore.getRange({
|
|
2964
3391
|
start,
|
|
@@ -2966,96 +3393,90 @@ async function runIndexing(Table, attributes, indicesToRemove, branchPath) {
|
|
|
2966
3393
|
versions: true,
|
|
2967
3394
|
snapshot: false, // don't hold a read transaction this whole time
|
|
2968
3395
|
})) {
|
|
2969
|
-
|
|
2970
|
-
continue; // deletion entry
|
|
3396
|
+
const atInterval = ++indexed % INDEXING_YIELD_INTERVAL === 0;
|
|
2971
3397
|
// TODO: Do we ever need to interrupt due to a schema change that was not a restart?
|
|
2972
3398
|
//if (Table.schemaVersion !== schemaVersion) return; // break out if there are any schema changes and let someone else pick it up
|
|
2973
|
-
|
|
2974
|
-
//
|
|
2975
|
-
// outstanding-based yield below never fires for them. Track that this row did synchronous
|
|
3399
|
+
// Custom indexes (e.g. HNSW) index synchronously and leave pendingMutations empty, so the
|
|
3400
|
+
// backpressure yield below never fires for them. Track that this row did synchronous
|
|
2976
3401
|
// indexing work so we can still yield the event loop after it.
|
|
2977
3402
|
let didSynchronousIndexing = false;
|
|
2978
3403
|
// every index operation needs to be guarded by the version still be the same. If it has already changed before
|
|
2979
3404
|
// we index, that's fine because indexing is idempotent, we can just put the same values again. If it changes
|
|
2980
3405
|
// during the indexing, the indexing here will fail. This is also fine because it means the other thread will have
|
|
2981
3406
|
// performed indexing and we don't need to do anything further
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3407
|
+
if (record) {
|
|
3408
|
+
for (let i = 0; i < attributesLength; i++) {
|
|
3409
|
+
const attribute = attributes[i];
|
|
3410
|
+
const property = attribute.name;
|
|
3411
|
+
const index = attribute.dbi;
|
|
3412
|
+
const onPutRejected = putRejectionHandlers[i];
|
|
3413
|
+
try {
|
|
3414
|
+
const resolver = attribute.resolve;
|
|
3415
|
+
const value = record && (resolver ? resolver(record) : record[property]);
|
|
3416
|
+
if (index.customIndex) {
|
|
3417
|
+
index.customIndex.index(key, value);
|
|
3418
|
+
didSynchronousIndexing = true;
|
|
3419
|
+
continue;
|
|
3420
|
+
}
|
|
3421
|
+
const values = (0, commonUtility_ts_1.getIndexedValues)(value, index.indexNulls);
|
|
3422
|
+
if (values) {
|
|
3423
|
+
for (let i = 0, l = values.length; i < l; i++) {
|
|
3424
|
+
track(index.put(values[i], key), onPutRejected);
|
|
3425
|
+
}
|
|
2998
3426
|
}
|
|
2999
3427
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3428
|
+
catch (error) {
|
|
3429
|
+
hadIndexingErrors = true;
|
|
3430
|
+
if (!attributeErrorReported[property]) {
|
|
3431
|
+
// just report an indexing error once per attribute so we don't spam the logs.
|
|
3432
|
+
// A store closed by worker shutdown surfaces here as "Database not open"; that is
|
|
3433
|
+
// a benign interruption (the next generation re-runs the backfill), so don't log
|
|
3434
|
+
// it as an error — the outer catch returns quietly once the iterator also throws.
|
|
3435
|
+
attributeErrorReported[property] = true;
|
|
3436
|
+
if (Table.primaryStore?.rootStore?.status === 'closed')
|
|
3437
|
+
logger.debug(`Indexing attribute ${property} interrupted by store shutdown`, error);
|
|
3438
|
+
else
|
|
3439
|
+
logger.error(`Error indexing attribute ${property}`, error);
|
|
3440
|
+
}
|
|
3013
3441
|
}
|
|
3014
3442
|
}
|
|
3015
3443
|
}
|
|
3016
|
-
(0, when_ts_1.when)(lastResolution, () => outstanding--, (error) => {
|
|
3017
|
-
outstanding--;
|
|
3018
|
-
hadIndexingErrors = true;
|
|
3019
|
-
logger.error(error);
|
|
3020
|
-
});
|
|
3021
3444
|
if (worker_threads_1.workerData && worker_threads_1.workerData.restartNumber !== manageThreads.restartNumber) {
|
|
3022
3445
|
interrupted = true;
|
|
3023
3446
|
}
|
|
3024
|
-
if (
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
}
|
|
3030
|
-
if (interrupted)
|
|
3031
|
-
return;
|
|
3447
|
+
if (interrupted) {
|
|
3448
|
+
await drainMutations();
|
|
3449
|
+
await checkpointing;
|
|
3450
|
+
await persistCheckpoint(key);
|
|
3451
|
+
return;
|
|
3032
3452
|
}
|
|
3033
|
-
if (
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
await
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
//
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3453
|
+
if (atInterval && indexed >= nextCheckpointRecord && performance.now() >= nextCheckpointAt) {
|
|
3454
|
+
nextCheckpointAt = performance.now() + indexingCheckpointPeriodMs;
|
|
3455
|
+
nextCheckpointRecord = indexed + indexingCheckpointMinRecords;
|
|
3456
|
+
await checkpointing;
|
|
3457
|
+
checkpointing = persistCheckpoint(key);
|
|
3458
|
+
}
|
|
3459
|
+
// Checked once per record, so a record's own fan-out can overshoot before the bound applies.
|
|
3460
|
+
while (pendingMutations.size > MAX_OUTSTANDING_INDEXING)
|
|
3461
|
+
await nextSettlement();
|
|
3462
|
+
if (atInterval || didSynchronousIndexing || pendingMutations.size > MIN_OUTSTANDING_INDEXING)
|
|
3463
|
+
await yieldEventTurn();
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
await checkpointing;
|
|
3467
|
+
// A mutation that rejects after completion is declared has no build left to park.
|
|
3468
|
+
await drainMutations();
|
|
3469
|
+
// the tail since the last checkpoint is not durable until flushed; announcing the index complete
|
|
3470
|
+
// before that would outlive a crash that loses it
|
|
3471
|
+
if (!hadIndexingErrors) {
|
|
3472
|
+
try {
|
|
3473
|
+
await flushIndexStores(Table.primaryStore.rootStore);
|
|
3474
|
+
}
|
|
3475
|
+
catch (error) {
|
|
3476
|
+
hadIndexingErrors = true;
|
|
3477
|
+
logger.error(`Could not flush the indexes of ${Table.tableName} before marking them complete`, error);
|
|
3478
|
+
}
|
|
3054
3479
|
}
|
|
3055
|
-
// Yield one more event turn so any queued when() error callbacks (which fire as
|
|
3056
|
-
// microtasks when their tracked promise settles) have a chance to set hadIndexingErrors
|
|
3057
|
-
// before we decide whether to mark indexing as complete.
|
|
3058
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
3059
3480
|
if (hadIndexingErrors) {
|
|
3060
3481
|
// Some records failed to index. Persist the failure marker in the descriptor so
|
|
3061
3482
|
// the next call to table() (including after a restart with a fresh PID) re-triggers
|
|
@@ -3084,9 +3505,15 @@ async function runIndexing(Table, attributes, indicesToRemove, branchPath) {
|
|
|
3084
3505
|
// update the attributes to indicate that we are finished
|
|
3085
3506
|
for (const attribute of attributes) {
|
|
3086
3507
|
delete attribute.lastIndexedKey;
|
|
3508
|
+
delete attribute.checkpointCertified;
|
|
3509
|
+
// Survives completion, unlike the checkpoint fields: without it an index built here is
|
|
3510
|
+
// indistinguishable from one a release that could skip a failed record declared complete.
|
|
3511
|
+
attribute.checkpointAlgorithm = exports.CHECKPOINT_ALGORITHM;
|
|
3087
3512
|
delete attribute.indexingPID;
|
|
3088
3513
|
delete attribute.indexingFailed;
|
|
3089
3514
|
delete attribute.restartNumber;
|
|
3515
|
+
delete attribute.indexingIncarnation;
|
|
3516
|
+
delete attribute.indexingBuildId;
|
|
3090
3517
|
attribute.dbi.isIndexing = false;
|
|
3091
3518
|
// Also clear isIndexing on the currently-active dbi in Table.indices, which may
|
|
3092
3519
|
// differ from attribute.dbi if a resetDatabases() call during this migration
|
|
@@ -3103,6 +3530,7 @@ async function runIndexing(Table, attributes, indicesToRemove, branchPath) {
|
|
|
3103
3530
|
}
|
|
3104
3531
|
}
|
|
3105
3532
|
catch (error) {
|
|
3533
|
+
await checkpointing;
|
|
3106
3534
|
// A worker shutting down closes its stores mid-backfill, so the range iterator or a
|
|
3107
3535
|
// put throws (e.g. "Database not open" / "Iterator not initialized"). This is an
|
|
3108
3536
|
// interruption, not a data error: the next worker generation re-runs the backfill via
|
|
@@ -3166,6 +3594,26 @@ function completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableNa
|
|
|
3166
3594
|
finally {
|
|
3167
3595
|
columnStore.close();
|
|
3168
3596
|
}
|
|
3597
|
+
// derived HNSW plane files live next to the store; the normal drop path removes
|
|
3598
|
+
// them through the custom index, but this recovery path drops raw column stores,
|
|
3599
|
+
// and a same-name recreate must never open a stale plane over a fresh CF
|
|
3600
|
+
try {
|
|
3601
|
+
(0, node_fs_1.unlinkSync)((0, hnswPlaneBinding_ts_1.planeFilePathFor)(rootStore.path, columnName));
|
|
3602
|
+
}
|
|
3603
|
+
catch (error) {
|
|
3604
|
+
// a stale plane left behind (e.g. Windows EBUSY while still mapped) would be
|
|
3605
|
+
// opened over a fresh same-name CF, resolving another graph's node ids
|
|
3606
|
+
// against it — tombstone it so no attach ever adopts it
|
|
3607
|
+
if (error?.code !== 'ENOENT') {
|
|
3608
|
+
logger.warn(`could not delete the HNSW plane file for ${columnName}; tombstoning it as stale`, error);
|
|
3609
|
+
try {
|
|
3610
|
+
(0, node_fs_1.closeSync)((0, node_fs_1.openSync)((0, hnswPlaneBinding_ts_1.planeStalePathFor)((0, hnswPlaneBinding_ts_1.planeFilePathFor)(rootStore.path, columnName)), 'w'));
|
|
3611
|
+
}
|
|
3612
|
+
catch (tombstoneError) {
|
|
3613
|
+
logger.warn(`could not tombstone the stale HNSW plane file for ${columnName}`, tombstoneError);
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3169
3617
|
}
|
|
3170
3618
|
}
|
|
3171
3619
|
}
|