@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4
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 +25 -3
- package/bin/run.ts +18 -0
- package/components/Application.ts +52 -78
- package/components/DESIGN.md +76 -3
- package/components/Scope.ts +10 -4
- package/components/anthropic/index.ts +10 -1
- package/components/bedrock/index.ts +10 -1
- package/components/componentLoader.ts +23 -14
- package/components/mcp/tools/operations.ts +3 -1
- package/components/ollama/index.ts +10 -1
- package/components/openai/index.ts +265 -18
- package/components/operations.js +40 -1
- package/config/configUtils.ts +3 -19
- package/dataLayer/DESIGN.md +29 -5
- package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
- package/dataLayer/restoreMarker.ts +250 -14
- package/dataLayer/schema.ts +2 -7
- package/dataLayer/schemaDescribe.ts +10 -0
- package/dist/bin/cliOperations.js +28 -3
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/run.js +19 -0
- package/dist/bin/run.js.map +1 -1
- package/dist/components/Application.d.ts +8 -2
- package/dist/components/Application.js +43 -69
- package/dist/components/Application.js.map +1 -1
- package/dist/components/Scope.d.ts +1 -1
- package/dist/components/Scope.js +9 -4
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +0 -10
- package/dist/components/anthropic/index.js +9 -1
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +0 -12
- package/dist/components/bedrock/index.js +9 -1
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.js +23 -14
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +1 -1
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/ollama/index.d.ts +0 -13
- package/dist/components/ollama/index.js +9 -1
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +11 -15
- package/dist/components/openai/index.js +224 -12
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +9 -0
- package/dist/components/operations.js +32 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.d.ts +1 -2
- package/dist/config/configUtils.js +7 -22
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +25 -3
- package/dist/dataLayer/restoreMarker.js +213 -13
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/dataLayer/schema.js +1 -4
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +10 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +28 -0
- package/dist/resources/DatabaseTransaction.d.ts +21 -1
- package/dist/resources/DatabaseTransaction.js +147 -8
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +1 -1
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/Table.d.ts +31 -3
- package/dist/resources/Table.js +397 -79
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/auditStore.js +16 -10
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +1 -0
- package/dist/resources/blob.js +14 -5
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.js +3 -3
- package/dist/resources/branchDatabase.js.map +1 -1
- package/dist/resources/databaseDropPreparation.d.ts +20 -0
- package/dist/resources/databaseDropPreparation.js +106 -0
- package/dist/resources/databaseDropPreparation.js.map +1 -0
- package/dist/resources/databases.d.ts +26 -21
- package/dist/resources/databases.js +1040 -204
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +23 -0
- package/dist/resources/derivedIndexRegistry.js +92 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -1
- package/dist/resources/derivedIndexRuntime.d.ts +10 -0
- package/dist/resources/derivedIndexRuntime.js +136 -25
- package/dist/resources/derivedIndexRuntime.js.map +1 -1
- package/dist/resources/derivedIndexes.d.ts +39 -0
- package/dist/resources/derivedIndexes.js +733 -0
- package/dist/resources/derivedIndexes.js.map +1 -0
- package/dist/resources/fullTextSchema.d.ts +8 -0
- package/dist/resources/fullTextSchema.js +42 -0
- package/dist/resources/fullTextSchema.js.map +1 -1
- package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
- package/dist/resources/fullTextSchemaLifecycle.js +0 -9
- package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
- package/dist/resources/graphql.js +101 -9
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
- package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
- package/dist/resources/models/Models.d.ts +26 -3
- package/dist/resources/models/Models.js +256 -13
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/TestBackend.d.ts +3 -1
- package/dist/resources/models/TestBackend.js +35 -1
- package/dist/resources/models/TestBackend.js.map +1 -1
- package/dist/resources/models/analyticsTable.d.ts +2 -2
- package/dist/resources/models/analyticsTable.js +4 -2
- package/dist/resources/models/analyticsTable.js.map +1 -1
- package/dist/resources/models/backendHelpers.d.ts +24 -0
- package/dist/resources/models/backendHelpers.js +18 -1
- package/dist/resources/models/backendHelpers.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +10 -21
- package/dist/resources/models/backendRegistry.js +74 -18
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +7 -0
- package/dist/resources/models/bootstrap.js +36 -4
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/models/decideHook.d.ts +33 -0
- package/dist/resources/models/decideHook.js +105 -0
- package/dist/resources/models/decideHook.js.map +1 -0
- package/dist/resources/models/decision.d.ts +64 -0
- package/dist/resources/models/decision.js +507 -0
- package/dist/resources/models/decision.js.map +1 -0
- package/dist/resources/models/decisionStore.d.ts +134 -0
- package/dist/resources/models/decisionStore.js +350 -0
- package/dist/resources/models/decisionStore.js.map +1 -0
- package/dist/resources/models/embedHook.d.ts +66 -8
- package/dist/resources/models/embedHook.js +228 -61
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/generativeDecision.d.ts +58 -0
- package/dist/resources/models/generativeDecision.js +315 -0
- package/dist/resources/models/generativeDecision.js.map +1 -0
- package/dist/resources/models/types.d.ts +232 -4
- package/dist/security/authn/oidc/tokenExchange.js +3 -33
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
- package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
- package/dist/security/authn/oidc/tokenUseTable.js +28 -0
- package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
- package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
- package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
- package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
- package/dist/security/certificateVerification/crlVerification.js +77 -165
- package/dist/security/certificateVerification/crlVerification.js.map +1 -1
- package/dist/security/certificateVerification/types.d.ts +0 -3
- package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
- package/dist/security/certificateVerification/verificationTables.js +125 -0
- package/dist/security/certificateVerification/verificationTables.js.map +1 -0
- package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
- package/dist/security/certificateVerification/verificationUtils.js +6 -31
- package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
- package/dist/security/jsLoader.d.ts +5 -0
- package/dist/security/jsLoader.js +8 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/Server.d.ts +1 -0
- package/dist/server/Server.js.map +1 -1
- package/dist/server/http.js +2 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +21 -2
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobOwnership.d.ts +25 -0
- package/dist/server/jobs/jobOwnership.js +127 -0
- package/dist/server/jobs/jobOwnership.js.map +1 -0
- package/dist/server/jobs/jobProcess.js +9 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +18 -5
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/loadRootComponents.js +10 -0
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +11 -1
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +12 -2
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/threads/itc.d.ts +4 -1
- package/dist/server/threads/itc.js +29 -6
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +12 -4
- package/dist/server/threads/manageThreads.js +241 -119
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +1 -0
- package/dist/server/threads/threadServer.js +15 -1
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/server/threads/windowsProcessTree.d.ts +91 -0
- package/dist/server/threads/windowsProcessTree.js +361 -0
- package/dist/server/threads/windowsProcessTree.js.map +1 -0
- package/dist/server/throttle.d.ts +1 -1
- package/dist/server/throttle.js +8 -1
- package/dist/server/throttle.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.js +53 -46
- package/dist/upgrade/directives/5-3-0.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +10 -1
- package/dist/utility/errors/hdbError.js +55 -11
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/fsync.d.ts +2 -0
- package/dist/utility/fsync.js +20 -0
- package/dist/utility/fsync.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +3 -0
- package/dist/utility/hdbTerms.js +3 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/signalling.d.ts +7 -1
- package/dist/utility/signalling.js +27 -3
- package/dist/utility/signalling.js.map +1 -1
- package/dist/utility/when.d.ts +1 -0
- package/dist/utility/when.js +21 -0
- package/dist/utility/when.js.map +1 -1
- package/dist/validation/configValidator.js +30 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +13 -0
- package/json/systemSchema.json +28 -0
- package/npm-shrinkwrap.json +151 -55
- package/package.json +14 -13
- package/resources/DESIGN.md +80 -100
- package/resources/DatabaseTransaction.ts +157 -10
- package/resources/PrimaryRocksDatabase.ts +2 -2
- package/resources/Table.ts +494 -101
- package/resources/auditStore.ts +18 -10
- package/resources/blob.ts +13 -6
- package/resources/branchDatabase.ts +3 -3
- package/resources/databaseDropPreparation.ts +120 -0
- package/resources/databases.ts +1155 -205
- package/resources/derivedIndexRegistry.ts +122 -0
- package/resources/derivedIndexRuntime.ts +157 -30
- package/resources/derivedIndexes.ts +856 -0
- package/resources/fullTextSchema.ts +55 -0
- package/resources/fullTextSchemaLifecycle.ts +0 -15
- package/resources/graphql.ts +107 -12
- package/resources/indexes/DESIGN.md +7 -0
- package/resources/indexes/hnswDerivedIndex.ts +0 -260
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
- package/resources/models/DESIGN.md +39 -0
- package/resources/models/Models.ts +361 -18
- package/resources/models/TestBackend.ts +56 -1
- package/resources/models/analyticsTable.ts +6 -4
- package/resources/models/backendHelpers.ts +17 -0
- package/resources/models/backendRegistry.ts +76 -21
- package/resources/models/bootstrap.ts +57 -7
- package/resources/models/decideHook.ts +135 -0
- package/resources/models/decision.ts +505 -0
- package/resources/models/decisionStore.ts +419 -0
- package/resources/models/embedHook.ts +272 -55
- package/resources/models/generativeDecision.ts +459 -0
- package/resources/models/types.ts +231 -4
- package/security/DESIGN.md +22 -2
- package/security/authn/oidc/tokenExchange.ts +6 -34
- package/security/authn/oidc/tokenUseTable.ts +27 -0
- package/security/certificateVerification/certificateVerificationSource.ts +2 -3
- package/security/certificateVerification/crlVerification.ts +96 -174
- package/security/certificateVerification/types.ts +0 -3
- package/security/certificateVerification/verificationTables.ts +124 -0
- package/security/certificateVerification/verificationUtils.ts +8 -32
- package/security/jsLoader.ts +9 -1
- package/server/DESIGN.md +27 -1
- package/server/Server.ts +2 -1
- package/server/http.ts +4 -2
- package/server/itc/serverHandlers.js +29 -1
- package/server/jobs/jobOwnership.ts +89 -0
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +15 -4
- package/server/loadRootComponents.js +11 -0
- package/server/serverHelpers/contentTypes.ts +13 -1
- package/server/serverHelpers/serverHandlers.js +11 -2
- package/server/threads/itc.js +32 -6
- package/server/threads/manageThreads.js +268 -122
- package/server/threads/threadServer.js +13 -1
- package/server/threads/windowsProcessTree.ts +409 -0
- package/server/throttle.ts +11 -1
- package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
- package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
- package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
- package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
- package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
- package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
- package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
- package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
- package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
- package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
- package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
- package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
- package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
- package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
- package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
- package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
- package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
- package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
- package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
- package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
- package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
- package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
- package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
- package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
- package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
- package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +61 -54
- package/utility/DESIGN.md +5 -1
- package/utility/errors/hdbError.ts +56 -8
- package/utility/fsync.ts +16 -0
- package/utility/hdbTerms.ts +3 -0
- package/utility/signalling.ts +33 -4
- package/utility/when.ts +22 -0
- package/validation/configValidator.ts +36 -7
|
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
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
|
+
exports.__setReadOnlyModeForTest = __setReadOnlyModeForTest;
|
|
40
41
|
exports.isReadOnlyMode = isReadOnlyMode;
|
|
41
42
|
exports.getRocksCompression = getRocksCompression;
|
|
42
43
|
exports.resetRocksCompression = resetRocksCompression;
|
|
@@ -58,12 +59,17 @@ exports.retakeBranchIdentity = retakeBranchIdentity;
|
|
|
58
59
|
exports.releaseBranchIdentity = releaseBranchIdentity;
|
|
59
60
|
exports.isBranchIdentity = isBranchIdentity;
|
|
60
61
|
exports.openBranchDatabase = openBranchDatabase;
|
|
62
|
+
exports.branchDatabasesHaveWork = branchDatabasesHaveWork;
|
|
61
63
|
exports.closeBranchDatabases = closeBranchDatabases;
|
|
62
64
|
exports.resetDatabases = resetDatabases;
|
|
63
65
|
exports.resolveDatabaseStorageRoot = resolveDatabaseStorageRoot;
|
|
64
66
|
exports.resolveDatabasePath = resolveDatabasePath;
|
|
65
67
|
exports.database = database;
|
|
66
68
|
exports.dropDatabase = dropDatabase;
|
|
69
|
+
exports.databaseDropRecoveryPending = databaseDropRecoveryPending;
|
|
70
|
+
exports.databaseDropPreparationTargets = databaseDropPreparationTargets;
|
|
71
|
+
exports.prepareDatabaseDrop = prepareDatabaseDrop;
|
|
72
|
+
exports.completeDatabaseDropPreparation = completeDatabaseDropPreparation;
|
|
67
73
|
exports.closeDatabase = closeDatabase;
|
|
68
74
|
exports.closeLoadedDatabases = closeLoadedDatabases;
|
|
69
75
|
exports.table = table;
|
|
@@ -113,8 +119,10 @@ const hnswPlaneBinding_ts_1 = require("./indexes/hnswPlaneBinding.js");
|
|
|
113
119
|
const OpenDBIObject_ts_1 = require("../utility/lmdb/OpenDBIObject.js");
|
|
114
120
|
const rocksdb_js_1 = require("@harperfast/rocksdb-js");
|
|
115
121
|
const PrimaryRocksDatabase_ts_1 = require("./PrimaryRocksDatabase.js");
|
|
122
|
+
const DatabaseTransaction_ts_1 = require("./DatabaseTransaction.js");
|
|
116
123
|
const replayLogs_ts_1 = require("./replayLogs.js");
|
|
117
|
-
const
|
|
124
|
+
const derivedIndexes_ts_1 = require("./derivedIndexes.js");
|
|
125
|
+
const derivedIndexRegistry_ts_1 = require("./derivedIndexRegistry.js");
|
|
118
126
|
const node_os_1 = require("node:os");
|
|
119
127
|
const RocksIndexStore_ts_1 = require("./RocksIndexStore.js");
|
|
120
128
|
const rocksMemoryConfig_ts_1 = require("../utility/rocksMemoryConfig.js");
|
|
@@ -122,7 +130,9 @@ const processManagement_js_1 = require("../utility/processManagement/processMana
|
|
|
122
130
|
const fullTextSchema_ts_1 = require("./fullTextSchema.js");
|
|
123
131
|
const jsonSchemaTypes_ts_1 = require("./jsonSchemaTypes.js");
|
|
124
132
|
const fullTextSchemaLifecycle_ts_1 = require("./fullTextSchemaLifecycle.js");
|
|
133
|
+
const embedHook_ts_1 = require("./models/embedHook.js");
|
|
125
134
|
const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
|
|
135
|
+
const databaseDropPreparation_ts_1 = require("./databaseDropPreparation.js");
|
|
126
136
|
/**
|
|
127
137
|
* Check if Harper is running in read-only mode.
|
|
128
138
|
* Read-only mode can be enabled via:
|
|
@@ -131,6 +141,9 @@ const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
|
|
|
131
141
|
* - storage.readOnly config setting
|
|
132
142
|
*/
|
|
133
143
|
let _isReadOnlyMode;
|
|
144
|
+
function __setReadOnlyModeForTest(value) {
|
|
145
|
+
_isReadOnlyMode = value;
|
|
146
|
+
}
|
|
134
147
|
function isReadOnlyMode() {
|
|
135
148
|
if (_isReadOnlyMode !== undefined)
|
|
136
149
|
return _isReadOnlyMode;
|
|
@@ -401,6 +414,11 @@ function openRocksDatabase(path, options) {
|
|
|
401
414
|
}
|
|
402
415
|
const lmdbDatabaseEnvs = new Map();
|
|
403
416
|
const rocksdbDatabaseEnvs = new Map();
|
|
417
|
+
const incompleteDatabaseCloses = new Map();
|
|
418
|
+
function databaseRootUnavailable(rootPath) {
|
|
419
|
+
return ((0, databaseDropPreparation_ts_1.databaseDropPrepared)(rootPath) ||
|
|
420
|
+
(incompleteDatabaseCloses.size > 0 && incompleteDatabaseCloses.has((0, node_path_1.resolve)(rootPath))));
|
|
421
|
+
}
|
|
404
422
|
// set the following in both global and exports
|
|
405
423
|
(0, globals_js_1._assignPackageExport)('databases', exports.databases);
|
|
406
424
|
(0, globals_js_1._assignPackageExport)('tables', exports.tables);
|
|
@@ -419,6 +437,7 @@ const PEER_REDEFINABLE_FIELDS = [
|
|
|
419
437
|
'elements',
|
|
420
438
|
'properties',
|
|
421
439
|
'embed',
|
|
440
|
+
'decide',
|
|
422
441
|
];
|
|
423
442
|
// `indexNulls` is derived from the durable descriptor, never sent by a peer, so naming it in the
|
|
424
443
|
// discard warn would blame the peer for a field it did not write.
|
|
@@ -433,6 +452,19 @@ function applyDurableDeclaration(attribute, descriptor) {
|
|
|
433
452
|
delete attribute[field];
|
|
434
453
|
}
|
|
435
454
|
}
|
|
455
|
+
function persistedFullTextIndexGenerations(value, definitions) {
|
|
456
|
+
const persisted = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
457
|
+
const generations = Object.create(null);
|
|
458
|
+
for (const { name } of definitions) {
|
|
459
|
+
const generation = persisted[name];
|
|
460
|
+
if (typeof generation === 'string' && generation.length > 0)
|
|
461
|
+
generations[name] = generation;
|
|
462
|
+
}
|
|
463
|
+
return generations;
|
|
464
|
+
}
|
|
465
|
+
function createFullTextIndexGeneration() {
|
|
466
|
+
return (0, node_crypto_1.randomBytes)(16).toString('hex');
|
|
467
|
+
}
|
|
436
468
|
/**
|
|
437
469
|
* True when a descriptor claims an index build no live operation in this process can own. The PID and
|
|
438
470
|
* worker generation cannot answer that alone: a container reuses PID 1 and starts the in-memory
|
|
@@ -593,6 +625,8 @@ function getDatabases() {
|
|
|
593
625
|
// TODO: Load any databases defined with explicit storage paths from the config
|
|
594
626
|
const entries = (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true });
|
|
595
627
|
const blockedByRestore = databasesBlockedByRestore(databasePath);
|
|
628
|
+
const blockedByDrop = databaseRootsBlockedByDrop(databasePath);
|
|
629
|
+
const configuredDropCoversDirectory = Object.entries(schemaConfigs).some(([name, config]) => config?.path && (0, node_path_1.resolve)(config.path) === (0, node_path_1.resolve)(databasePath) && blockedByDrop.databaseNames.has(name));
|
|
596
630
|
for (const databaseEntry of entries) {
|
|
597
631
|
// in-progress migration staging dirs are not databases until atomically renamed into place
|
|
598
632
|
if (databaseEntry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
|
|
@@ -607,6 +641,12 @@ function getDatabases() {
|
|
|
607
641
|
continue;
|
|
608
642
|
const dbName = (0, node_path_1.basename)(databaseEntry.name, '.mdb');
|
|
609
643
|
const dbPath = (0, node_path_1.join)(databasePath, databaseEntry.name);
|
|
644
|
+
if (databaseRootUnavailable(dbPath))
|
|
645
|
+
continue;
|
|
646
|
+
if (configuredDropCoversDirectory ||
|
|
647
|
+
blockedByDrop.rootPaths.has(dbPath) ||
|
|
648
|
+
blockedByDrop.databaseNames.has(dbName))
|
|
649
|
+
continue;
|
|
610
650
|
if (blockedByRestore.has(dbName))
|
|
611
651
|
continue;
|
|
612
652
|
if (isOpenBranchPath(dbPath))
|
|
@@ -641,10 +681,16 @@ function getDatabases() {
|
|
|
641
681
|
if (!schemaEntry.isFile()) {
|
|
642
682
|
const schemaPath = (0, node_path_1.join)(baseSchemaPath, schemaEntry.name);
|
|
643
683
|
const schemaAuditPath = (0, node_path_1.join)((0, initializePaths_js_1.getTransactionAuditStoreBasePath)(), schemaEntry.name);
|
|
684
|
+
const blockedByDrop = databaseRootsBlockedByDrop(schemaPath);
|
|
644
685
|
for (const tableEntry of (0, node_fs_1.readdirSync)(schemaPath, { withFileTypes: true })) {
|
|
645
686
|
if (tableEntry.isFile() && (0, node_path_1.extname)(tableEntry.name).toLowerCase() === '.mdb') {
|
|
687
|
+
const tablePath = (0, node_path_1.join)(schemaPath, tableEntry.name);
|
|
688
|
+
if (databaseRootUnavailable(tablePath))
|
|
689
|
+
continue;
|
|
690
|
+
if (blockedByDrop.rootPaths.has(tablePath) || blockedByDrop.databaseNames.has(schemaEntry.name))
|
|
691
|
+
continue;
|
|
646
692
|
const auditPath = (0, node_path_1.join)(schemaAuditPath, tableEntry.name);
|
|
647
|
-
readMetaDb(
|
|
693
|
+
readMetaDb(tablePath, (0, node_path_1.basename)(tableEntry.name, '.mdb'), schemaEntry.name, auditPath, true);
|
|
648
694
|
}
|
|
649
695
|
}
|
|
650
696
|
}
|
|
@@ -653,10 +699,13 @@ function getDatabases() {
|
|
|
653
699
|
if (schemaConfigs) {
|
|
654
700
|
for (const dbName in schemaConfigs) {
|
|
655
701
|
const schemaConfig = schemaConfigs[dbName];
|
|
702
|
+
if (databaseDropRecoveryPending(dbName))
|
|
703
|
+
continue;
|
|
656
704
|
const databasePath = schemaConfig.path;
|
|
657
705
|
if ((0, node_fs_1.existsSync)(databasePath)) {
|
|
658
706
|
const entries = (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true });
|
|
659
707
|
const blockedByRestore = databasesBlockedByRestore(databasePath);
|
|
708
|
+
const blockedByDrop = databaseRootsBlockedByDrop(databasePath);
|
|
660
709
|
for (const databaseEntry of entries) {
|
|
661
710
|
if (databaseEntry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
|
|
662
711
|
continue; // migration staging dir
|
|
@@ -666,14 +715,18 @@ function getDatabases() {
|
|
|
666
715
|
continue; // reserved branch root
|
|
667
716
|
if (blockedByRestore.has((0, node_path_1.basename)(databaseEntry.name, '.mdb')))
|
|
668
717
|
continue;
|
|
669
|
-
|
|
718
|
+
const dbPath = (0, node_path_1.join)(databasePath, databaseEntry.name);
|
|
719
|
+
if (databaseRootUnavailable(dbPath))
|
|
720
|
+
continue;
|
|
721
|
+
if (blockedByDrop.rootPaths.has(dbPath) || blockedByDrop.databaseNames.has(dbName))
|
|
722
|
+
continue;
|
|
723
|
+
if (isOpenBranchPath(dbPath))
|
|
670
724
|
continue;
|
|
671
725
|
if (databaseEntry.isFile() && (0, node_path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb') {
|
|
672
|
-
readMetaDb(
|
|
726
|
+
readMetaDb(dbPath, (0, node_path_1.basename)(databaseEntry.name, '.mdb'), dbName);
|
|
673
727
|
}
|
|
674
728
|
else {
|
|
675
729
|
try {
|
|
676
|
-
const dbPath = (0, node_path_1.join)(databasePath, databaseEntry.name);
|
|
677
730
|
const files = (0, node_fs_1.readdirSync)(dbPath, { withFileTypes: true });
|
|
678
731
|
if (files.find((file) => file.name === 'CURRENT')?.isFile() &&
|
|
679
732
|
files.some((file) => file.name.startsWith('MANIFEST-'))) {
|
|
@@ -694,7 +747,7 @@ function getDatabases() {
|
|
|
694
747
|
for (const tableName in tableConfigs) {
|
|
695
748
|
const tableConfig = tableConfigs[tableName];
|
|
696
749
|
const tablePath = (0, node_path_1.join)(tableConfig.path, (0, node_path_1.basename)(tableName + '.mdb'));
|
|
697
|
-
if ((0, node_fs_1.existsSync)(tablePath)) {
|
|
750
|
+
if (!databaseRootUnavailable(tablePath) && !(0, restoreMarker_ts_1.databaseDropMarkerPresent)(tablePath) && (0, node_fs_1.existsSync)(tablePath)) {
|
|
698
751
|
readMetaDb(tablePath, tableName, dbName, null, true);
|
|
699
752
|
}
|
|
700
753
|
}
|
|
@@ -704,6 +757,8 @@ function getDatabases() {
|
|
|
704
757
|
}
|
|
705
758
|
// now remove any databases or tables that have been removed
|
|
706
759
|
for (const dbName in exports.databases) {
|
|
760
|
+
if (databaseUsesPreparedRoot(dbName))
|
|
761
|
+
continue;
|
|
707
762
|
const definedTables = definedDatabases.get(dbName);
|
|
708
763
|
if (definedTables) {
|
|
709
764
|
const tables = exports.databases[dbName];
|
|
@@ -912,6 +967,38 @@ function databasesBlockedByRestore(databasePath) {
|
|
|
912
967
|
}
|
|
913
968
|
return blocked;
|
|
914
969
|
}
|
|
970
|
+
const loggedDropMarkersByDirectory = new Map();
|
|
971
|
+
function databaseRootsBlockedByDrop(databasePath) {
|
|
972
|
+
const rootPaths = new Set();
|
|
973
|
+
const databaseNames = new Set();
|
|
974
|
+
const directoryKey = (0, node_path_1.resolve)(databasePath);
|
|
975
|
+
const previouslyLogged = loggedDropMarkersByDirectory.get(directoryKey) ?? new Set();
|
|
976
|
+
const currentMarkers = new Set();
|
|
977
|
+
for (const drop of (0, restoreMarker_ts_1.scanBlockedDatabaseDrops)(databasePath)) {
|
|
978
|
+
currentMarkers.add(drop.markerPath);
|
|
979
|
+
const message = drop.databaseName
|
|
980
|
+
? `Incomplete drop of database '${drop.databaseName}' detected; not loading ${drop.rootPath} — retry drop_database to recover (${drop.markerPath})`
|
|
981
|
+
: `Incomplete database drop with invalid marker metadata detected; not loading ${drop.rootPath} — inspect the root and marker before manual recovery (${drop.markerPath})`;
|
|
982
|
+
if (previouslyLogged.has(drop.markerPath))
|
|
983
|
+
logger.trace(message);
|
|
984
|
+
else
|
|
985
|
+
logger.error(message);
|
|
986
|
+
rootPaths.add(drop.rootPath);
|
|
987
|
+
if (drop.databaseName)
|
|
988
|
+
databaseNames.add(drop.databaseName);
|
|
989
|
+
}
|
|
990
|
+
if (currentMarkers.size > 0)
|
|
991
|
+
loggedDropMarkersByDirectory.set(directoryKey, currentMarkers);
|
|
992
|
+
else
|
|
993
|
+
loggedDropMarkersByDirectory.delete(directoryKey);
|
|
994
|
+
return { rootPaths, databaseNames };
|
|
995
|
+
}
|
|
996
|
+
function configuredDropPendingWithin(databasePath, schemaConfigs) {
|
|
997
|
+
const normalizedPath = (0, node_path_1.resolve)(databasePath);
|
|
998
|
+
return Object.entries(schemaConfigs).some(([name, config]) => config?.path &&
|
|
999
|
+
(0, node_path_1.resolve)(config.path) === normalizedPath &&
|
|
1000
|
+
(0, restoreMarker_ts_1.scanBlockedDatabaseDrops)(config.path).some((drop) => drop.databaseName === name));
|
|
1001
|
+
}
|
|
915
1002
|
/**
|
|
916
1003
|
* This is responsible for reading the internal dbi of a single database file to get a list of all the tables and
|
|
917
1004
|
* their indexed or registered attributes
|
|
@@ -1094,14 +1181,15 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1094
1181
|
}
|
|
1095
1182
|
if (failedAttempts < MAX_INTERRUPTED_DROP_ATTEMPTS) {
|
|
1096
1183
|
try {
|
|
1097
|
-
completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName);
|
|
1184
|
+
const completed = completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName);
|
|
1098
1185
|
// Sweep every generation this worker has ever tracked for this table, not
|
|
1099
1186
|
// just the one just resolved: if a prior generation was exhausted here,
|
|
1100
1187
|
// then resolved+recreated+re-dropped by another worker as this generation
|
|
1101
1188
|
// without this worker ever observing a live row in between (the only other
|
|
1102
1189
|
// place that sweeps), the prior generation's entry would otherwise never
|
|
1103
1190
|
// be cleared.
|
|
1104
|
-
|
|
1191
|
+
if (completed)
|
|
1192
|
+
clearInterruptedDropEntries(path, tableName);
|
|
1105
1193
|
definedTables?.delete(tableName);
|
|
1106
1194
|
}
|
|
1107
1195
|
catch (error) {
|
|
@@ -1188,10 +1276,14 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1188
1276
|
else if (warnings.length === 0)
|
|
1189
1277
|
warnedFullTextStates.delete(warningKey);
|
|
1190
1278
|
}
|
|
1279
|
+
const fullTextIndexGenerations = persistedFullTextIndexGenerations(primaryAttribute.fullTextIndexGenerations, fullTextIndexes);
|
|
1280
|
+
const fullTextIndexRetirements = (0, fullTextSchema_ts_1.persistedFullTextIndexNames)(primaryAttribute.fullTextIndexRetirements);
|
|
1191
1281
|
if (table && !recreateTable) {
|
|
1192
1282
|
if (primaryAttribute.audit === true && table.audit !== true)
|
|
1193
1283
|
table.enableAuditing();
|
|
1194
1284
|
table.fullTextIndexes = fullTextIndexes;
|
|
1285
|
+
table.fullTextIndexGenerations = fullTextIndexGenerations;
|
|
1286
|
+
table.fullTextIndexRetirements = fullTextIndexRetirements;
|
|
1195
1287
|
indices = table.indices;
|
|
1196
1288
|
existingAttributes = table.attributes;
|
|
1197
1289
|
table.schemaVersion++;
|
|
@@ -1338,6 +1430,8 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1338
1430
|
indices,
|
|
1339
1431
|
attributes,
|
|
1340
1432
|
fullTextIndexes,
|
|
1433
|
+
fullTextIndexGenerations,
|
|
1434
|
+
fullTextIndexRetirements,
|
|
1341
1435
|
schemaDefined: primaryAttribute.schemaDefined,
|
|
1342
1436
|
dbisDB: attributesDbi,
|
|
1343
1437
|
}));
|
|
@@ -1345,8 +1439,7 @@ function initStores(path, rootStore, databaseName, { defaultTable, auditPath, is
|
|
|
1345
1439
|
if (!destination)
|
|
1346
1440
|
exports.databaseEventsEmitter.emit('updateTable', table);
|
|
1347
1441
|
}
|
|
1348
|
-
|
|
1349
|
-
table.derivedIndexRuntime = (0, hnswDerivedIndex_ts_1.attachDerivedIndexes)(table);
|
|
1442
|
+
(0, derivedIndexes_ts_1.refreshDerivedIndexes)(table);
|
|
1350
1443
|
if (Array.isArray(primaryAttribute.relationships)) {
|
|
1351
1444
|
relationshipsToHydrate.push({ table, databaseName, tableName, definitions: primaryAttribute.relationships });
|
|
1352
1445
|
}
|
|
@@ -1650,10 +1743,27 @@ function openBranchDatabase(path, databaseName, storeName, blobRoots) {
|
|
|
1650
1743
|
releaseBranchIdentity(storeName);
|
|
1651
1744
|
const stranded = rocksdbDatabaseEnvs.get(path);
|
|
1652
1745
|
rocksdbDatabaseEnvs.delete(path);
|
|
1746
|
+
const auditCleanup = stranded?.auditStore?.stopAuditCleanup?.();
|
|
1747
|
+
auditCleanup?.catch((cleanupError) => logger.warn(`Error retiring audit cleanup for branch database at ${path}`, cleanupError));
|
|
1653
1748
|
closeBranchHandles(path, stranded, openedStores, tables);
|
|
1654
1749
|
throw error;
|
|
1655
1750
|
}
|
|
1656
|
-
let
|
|
1751
|
+
let closing;
|
|
1752
|
+
let handleTeardownStarted = false;
|
|
1753
|
+
const unregisterBranch = () => {
|
|
1754
|
+
if (openBranches.get(path) === branch)
|
|
1755
|
+
openBranches.delete(path);
|
|
1756
|
+
releaseBranchIdentity(storeName);
|
|
1757
|
+
rocksdbDatabaseEnvs.delete(path);
|
|
1758
|
+
manageThreads.markBranchStorePath(path, false);
|
|
1759
|
+
};
|
|
1760
|
+
const closeRemainingHandles = () => {
|
|
1761
|
+
const closeFailures = closeBranchHandles(path, rootStore, openedStores, tables);
|
|
1762
|
+
if (closeFailures.length) {
|
|
1763
|
+
throw new AggregateError(closeFailures, `Could not close branch database '${databaseName}'`);
|
|
1764
|
+
}
|
|
1765
|
+
unregisterBranch();
|
|
1766
|
+
};
|
|
1657
1767
|
const branch = {
|
|
1658
1768
|
tables,
|
|
1659
1769
|
rootStore,
|
|
@@ -1665,14 +1775,59 @@ function openBranchDatabase(path, databaseName, storeName, blobRoots) {
|
|
|
1665
1775
|
close() {
|
|
1666
1776
|
// guard on the handle, not on the registrations: those are keyed by path, and a closed
|
|
1667
1777
|
// branch frees its path, so a stale handle would otherwise tear down its successor
|
|
1668
|
-
if (
|
|
1669
|
-
return;
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1778
|
+
if (closing)
|
|
1779
|
+
return closing;
|
|
1780
|
+
if (handleTeardownStarted) {
|
|
1781
|
+
const operation = Promise.resolve().then(closeRemainingHandles);
|
|
1782
|
+
const retryable = operation.catch((error) => {
|
|
1783
|
+
if (closing === retryable)
|
|
1784
|
+
closing = undefined;
|
|
1785
|
+
throw error;
|
|
1786
|
+
});
|
|
1787
|
+
closing = retryable;
|
|
1788
|
+
closing.catch(() => { });
|
|
1789
|
+
return closing;
|
|
1790
|
+
}
|
|
1791
|
+
const releaseActivation = (0, derivedIndexes_ts_1.suspendDerivedIndexActivation)(rootStore);
|
|
1792
|
+
const commitSuspension = (0, DatabaseTransaction_ts_1.suspendDatabaseCommits)([rootStore]);
|
|
1793
|
+
const deadline = Date.now() + (0, DatabaseTransaction_ts_1.getDatabaseCommitDrainTimeoutMilliseconds)();
|
|
1794
|
+
let closed = false;
|
|
1795
|
+
const operation = commitSuspension
|
|
1796
|
+
.waitForDrain({ deadline, databaseName })
|
|
1797
|
+
.then(() => settleTableMaintenance(tables, deadline))
|
|
1798
|
+
.then(() => settleBranchDerivedIndexes(tables))
|
|
1799
|
+
.then(() => rootStore.auditStore?.stopAuditCleanup?.())
|
|
1800
|
+
.then(() => {
|
|
1801
|
+
handleTeardownStarted = true;
|
|
1802
|
+
closeRemainingHandles();
|
|
1803
|
+
commitSuspension.release();
|
|
1804
|
+
releaseActivation();
|
|
1805
|
+
closed = true;
|
|
1806
|
+
})
|
|
1807
|
+
.finally(() => {
|
|
1808
|
+
if (!closed && !handleTeardownStarted) {
|
|
1809
|
+
commitSuspension.release();
|
|
1810
|
+
releaseActivation();
|
|
1811
|
+
resumeTableMaintenance(tables);
|
|
1812
|
+
for (const table of Object.values(tables))
|
|
1813
|
+
(0, derivedIndexes_ts_1.refreshDerivedIndexes)(table);
|
|
1814
|
+
}
|
|
1815
|
+
else if (!closed) {
|
|
1816
|
+
// Native handle teardown started, so this wrapper graph cannot safely return to service.
|
|
1817
|
+
(0, DatabaseTransaction_ts_1.permanentlySuspendDatabaseCommits)([rootStore]);
|
|
1818
|
+
(0, derivedIndexes_ts_1.permanentlySuspendDerivedIndexActivation)(rootStore);
|
|
1819
|
+
commitSuspension.release();
|
|
1820
|
+
releaseActivation();
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
const retryable = operation.catch((error) => {
|
|
1824
|
+
if (closing === retryable)
|
|
1825
|
+
closing = undefined;
|
|
1826
|
+
throw error;
|
|
1827
|
+
});
|
|
1828
|
+
closing = retryable;
|
|
1829
|
+
closing.catch(() => { });
|
|
1830
|
+
return closing;
|
|
1676
1831
|
},
|
|
1677
1832
|
};
|
|
1678
1833
|
openBranches.set(path, branch);
|
|
@@ -1689,7 +1844,7 @@ function openBranchDatabase(path, databaseName, storeName, blobRoots) {
|
|
|
1689
1844
|
*/
|
|
1690
1845
|
function closeBranchHandles(path, rootStore, openedStores = [], tables = {}) {
|
|
1691
1846
|
const reclamationPaths = new Set([path]);
|
|
1692
|
-
|
|
1847
|
+
const closeFailures = [];
|
|
1693
1848
|
const closeStore = (store, description) => {
|
|
1694
1849
|
if (!store || store.status === 'closed')
|
|
1695
1850
|
return;
|
|
@@ -1700,6 +1855,7 @@ function closeBranchHandles(path, rootStore, openedStores = [], tables = {}) {
|
|
|
1700
1855
|
}
|
|
1701
1856
|
catch (error) {
|
|
1702
1857
|
logger.warn(`Error closing ${description} for branch database at ${path}`, error);
|
|
1858
|
+
closeFailures.push(error);
|
|
1703
1859
|
}
|
|
1704
1860
|
};
|
|
1705
1861
|
// the class, before its stores: an expiration timer or a reclamation handler on a closed store
|
|
@@ -1721,11 +1877,32 @@ function closeBranchHandles(path, rootStore, openedStores = [], tables = {}) {
|
|
|
1721
1877
|
blob_ts_1.databasePaths.delete(rootStore);
|
|
1722
1878
|
for (const reclamationPath of reclamationPaths)
|
|
1723
1879
|
(0, storageReclamation_ts_1.removeStorageReclamation)(reclamationPath);
|
|
1880
|
+
return closeFailures;
|
|
1881
|
+
}
|
|
1882
|
+
async function settleBranchDerivedIndexes(tables) {
|
|
1883
|
+
const attachments = new Set();
|
|
1884
|
+
for (const tableName in tables) {
|
|
1885
|
+
const attachment = tables[tableName]?.derivedIndexRuntime;
|
|
1886
|
+
if (attachment)
|
|
1887
|
+
attachments.add(attachment);
|
|
1888
|
+
}
|
|
1889
|
+
await Promise.all([...attachments].map((attachment) => attachment.close()));
|
|
1724
1890
|
}
|
|
1725
1891
|
/** Branches are process-local, so this is shutdown, not a data operation. */
|
|
1726
|
-
function
|
|
1727
|
-
|
|
1728
|
-
|
|
1892
|
+
function branchDatabasesHaveWork() {
|
|
1893
|
+
return openBranches.size > 0;
|
|
1894
|
+
}
|
|
1895
|
+
async function closeBranchDatabases(requireClosed = false) {
|
|
1896
|
+
const results = await Promise.allSettled([...openBranches.values()].map((branch) => branch?.close()));
|
|
1897
|
+
const failures = [];
|
|
1898
|
+
for (const result of results) {
|
|
1899
|
+
if (result.status === 'rejected') {
|
|
1900
|
+
logger.warn('Error closing branch database during worker teardown', result.reason);
|
|
1901
|
+
failures.push(result.reason);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
if (requireClosed && failures.length > 0)
|
|
1905
|
+
throw new AggregateError(failures, 'Could not close every branch database during worker teardown');
|
|
1729
1906
|
}
|
|
1730
1907
|
function resetDatabases() {
|
|
1731
1908
|
loadedDatabases = false;
|
|
@@ -1819,13 +1996,22 @@ function resolveDatabasePath(databaseName) {
|
|
|
1819
1996
|
* @returns
|
|
1820
1997
|
*/
|
|
1821
1998
|
function database({ database: databaseName, table: tableName }) {
|
|
1999
|
+
return openDatabaseRoot({ database: databaseName, table: tableName });
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Open a database root. Destructive DDL uses the internal bypass only after it owns the database's
|
|
2003
|
+
* drop-preparation fence; public callers must continue to fail while that fence is held.
|
|
2004
|
+
*/
|
|
2005
|
+
function openDatabaseRoot({ database: databaseName, table: tableName }, { allowPreparedDrop = false } = {}) {
|
|
1822
2006
|
if (!databaseName)
|
|
1823
2007
|
databaseName = DEFAULT_DATABASE_NAME;
|
|
1824
2008
|
getDatabases();
|
|
1825
|
-
|
|
1826
|
-
const definedDatabase = definedDatabases.get(databaseName);
|
|
2009
|
+
let definedDatabase = definedDatabases.get(databaseName);
|
|
1827
2010
|
if (definedDatabase?.rootStore) {
|
|
1828
|
-
|
|
2011
|
+
const rootStore = definedDatabase.rootStore;
|
|
2012
|
+
if (!allowPreparedDrop && databaseRootUnavailable(rootStore.path))
|
|
2013
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2014
|
+
return rootStore;
|
|
1829
2015
|
}
|
|
1830
2016
|
const databaseConfig = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
|
|
1831
2017
|
if (process.env.SCHEMAS_DATA_PATH) {
|
|
@@ -1833,10 +2019,36 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
1833
2019
|
}
|
|
1834
2020
|
const tablePath = tableName && databaseConfig[databaseName]?.tables?.[tableName]?.path;
|
|
1835
2021
|
const databasePath = resolveDatabaseStorageRoot(databaseName, tableName);
|
|
2022
|
+
if (!allowPreparedDrop && configuredDropPendingWithin(databasePath, databaseConfig))
|
|
2023
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2024
|
+
// A configured path is a scan root, not an exact database path, so an unresolved alias can
|
|
2025
|
+
// target any child store. Keep first-open fenced until the destructive operation completes.
|
|
2026
|
+
if (!allowPreparedDrop && databaseConfig[databaseName]?.path) {
|
|
2027
|
+
const blockedRestores = (0, restoreMarker_ts_1.scanBlockedRestores)(databasePath);
|
|
2028
|
+
if (blockedRestores.length > 0) {
|
|
2029
|
+
const error = new Error(`Database '${databaseName}' shares a configured path with an ${blockedRestores[0][1]} restore of '${blockedRestores[0][0]}'; complete that restore before reopening it`);
|
|
2030
|
+
error.statusCode = 409;
|
|
2031
|
+
throw error;
|
|
2032
|
+
}
|
|
2033
|
+
if ((0, databaseDropPreparation_ts_1.databaseDropPreparedWithin)(databasePath))
|
|
2034
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2035
|
+
if ((0, restoreMarker_ts_1.scanBlockedDatabaseDrops)(databasePath).length > 0) {
|
|
2036
|
+
const error = new Error(`Database '${databaseName}' shares a configured path with an incomplete drop; retry drop_database to recover it`);
|
|
2037
|
+
error.statusCode = 409;
|
|
2038
|
+
throw error;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1836
2041
|
let rootStore;
|
|
1837
2042
|
const useRocksdb = (process.env.HARPER_STORAGE_ENGINE || (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_ENGINE)) !== 'lmdb';
|
|
2043
|
+
const path = useRocksdb
|
|
2044
|
+
? (0, node_path_1.join)(databasePath, tablePath ? tableName : databaseName)
|
|
2045
|
+
: (0, node_path_1.join)(databasePath, `${tablePath ? tableName : databaseName}.mdb`);
|
|
2046
|
+
if (!allowPreparedDrop && databaseRootUnavailable(path))
|
|
2047
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2048
|
+
throwIfBlockedByRestore(path, databaseName);
|
|
2049
|
+
ensureDB(databaseName);
|
|
2050
|
+
definedDatabase = definedDatabases.get(databaseName);
|
|
1838
2051
|
if (useRocksdb) {
|
|
1839
|
-
const path = (0, node_path_1.join)(databasePath, tablePath ? tableName : databaseName);
|
|
1840
2052
|
// the scan is not the only way to reach a branch's directory: a branch leaves its store in
|
|
1841
2053
|
// `rocksdbDatabaseEnvs`, so without this an on-demand open would staple it onto
|
|
1842
2054
|
// `definedDatabases` and the next `closeDatabase` would close it under the live handle
|
|
@@ -1850,7 +2062,6 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
1850
2062
|
// this on-demand open (create_table/create_database and friends) must not resurrect a
|
|
1851
2063
|
// database that a restore is rewriting (or left half-purged) — the scan-time restore
|
|
1852
2064
|
// checks don't cover this path
|
|
1853
|
-
throwIfBlockedByRestore(path, databaseName);
|
|
1854
2065
|
rootStore = openRocksDatabase(path, {
|
|
1855
2066
|
disableWAL: false,
|
|
1856
2067
|
enableStats: true,
|
|
@@ -1859,7 +2070,6 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
1859
2070
|
}
|
|
1860
2071
|
}
|
|
1861
2072
|
else {
|
|
1862
|
-
const path = (0, node_path_1.join)(databasePath, `${tablePath ? tableName : databaseName}.mdb`);
|
|
1863
2073
|
rootStore = lmdbDatabaseEnvs.get(path);
|
|
1864
2074
|
if (!rootStore || rootStore.status === 'closed') {
|
|
1865
2075
|
// TODO: validate database name
|
|
@@ -1876,6 +2086,11 @@ function database({ database: databaseName, table: tableName }) {
|
|
|
1876
2086
|
return rootStore;
|
|
1877
2087
|
}
|
|
1878
2088
|
function throwIfBlockedByRestore(dbPath, databaseName) {
|
|
2089
|
+
if ((0, restoreMarker_ts_1.databaseDropMarkerPresent)(dbPath)) {
|
|
2090
|
+
const error = new Error(`Database '${databaseName}' has an incomplete drop; retry drop_database to recover it`);
|
|
2091
|
+
error.statusCode = 409;
|
|
2092
|
+
throw error;
|
|
2093
|
+
}
|
|
1879
2094
|
const restoreState = (0, restoreMarker_ts_1.checkRestoreState)(dbPath);
|
|
1880
2095
|
if (restoreState !== 'clear') {
|
|
1881
2096
|
const error = new Error(restoreState === 'in-progress'
|
|
@@ -1885,83 +2100,234 @@ function throwIfBlockedByRestore(dbPath, databaseName) {
|
|
|
1885
2100
|
throw error;
|
|
1886
2101
|
}
|
|
1887
2102
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
* or a crashed restore left a marker (incomplete). Pushes the acquired lock onto `held` so the
|
|
1891
|
-
* caller releases it after the drop. On refusal, releases anything already held and throws.
|
|
1892
|
-
*
|
|
1893
|
-
* The lock is not reentrant within a process, so a path already in `held` must be skipped — every
|
|
1894
|
-
* table in a RocksDB database shares one root store (and one lock path), and re-acquiring it in the
|
|
1895
|
-
* same drop would spuriously 409 on the second table.
|
|
1896
|
-
*/
|
|
1897
|
-
function lockDatabaseForDrop(dbPath, databaseName, held) {
|
|
1898
|
-
if (held.some((h) => h.dbPath === dbPath))
|
|
2103
|
+
function lockDatabaseForDrop(dbPath, databaseName, held, blobDatabaseName = databaseName, blobPaths = (0, blob_ts_1.getBlobPathsForDatabaseName)(blobDatabaseName)) {
|
|
2104
|
+
if (held.some((lock) => lock.dbPath === dbPath))
|
|
1899
2105
|
return;
|
|
1900
|
-
let lock;
|
|
1901
2106
|
try {
|
|
1902
|
-
|
|
2107
|
+
held.push((0, restoreMarker_ts_1.beginDatabaseDrop)(dbPath, databaseName, blobDatabaseName, blobPaths));
|
|
1903
2108
|
}
|
|
1904
2109
|
catch (error) {
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
error.statusCode = 409;
|
|
2110
|
+
const cleanupFailures = [];
|
|
2111
|
+
for (const lock of held.splice(0)) {
|
|
2112
|
+
try {
|
|
2113
|
+
(0, restoreMarker_ts_1.cancelDatabaseDrop)(lock);
|
|
2114
|
+
}
|
|
2115
|
+
catch (cleanupError) {
|
|
2116
|
+
cleanupFailures.push(cleanupError);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (cleanupFailures.length > 0)
|
|
2120
|
+
throw new AggregateError([error, ...cleanupFailures], `Could not cancel database drop '${databaseName}'`);
|
|
1917
2121
|
throw error;
|
|
1918
2122
|
}
|
|
1919
|
-
|
|
2123
|
+
}
|
|
2124
|
+
function releaseDatabaseDropLocks(locks, retainMarkers) {
|
|
2125
|
+
const failures = [];
|
|
2126
|
+
while (locks.length > 0) {
|
|
2127
|
+
try {
|
|
2128
|
+
const lock = locks.shift();
|
|
2129
|
+
if (retainMarkers)
|
|
2130
|
+
(0, restoreMarker_ts_1.abandonDatabaseDrop)(lock);
|
|
2131
|
+
else
|
|
2132
|
+
(0, restoreMarker_ts_1.cancelDatabaseDrop)(lock);
|
|
2133
|
+
}
|
|
2134
|
+
catch (error) {
|
|
2135
|
+
failures.push(error);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (failures.length > 0)
|
|
2139
|
+
throw new AggregateError(failures, 'Could not release database drop locks');
|
|
2140
|
+
}
|
|
2141
|
+
function inferDropBlobDatabaseName(databaseName, rootPath) {
|
|
2142
|
+
const schemaConfigs = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
|
|
2143
|
+
const databaseConfig = schemaConfigs[databaseName];
|
|
2144
|
+
const normalizedRootPath = (0, node_path_1.resolve)(rootPath);
|
|
2145
|
+
for (const [tableName, tableConfig] of Object.entries(databaseConfig?.tables || {})) {
|
|
2146
|
+
if (!tableConfig?.path)
|
|
2147
|
+
continue;
|
|
2148
|
+
if ([(0, node_path_1.resolve)(tableConfig.path, tableName), (0, node_path_1.resolve)(tableConfig.path, `${tableName}.mdb`)].includes(normalizedRootPath))
|
|
2149
|
+
return databaseName;
|
|
2150
|
+
}
|
|
2151
|
+
if ((0, node_path_1.resolve)((0, node_path_1.dirname)(rootPath)) === (0, node_path_1.resolve)((0, initializePaths_js_1.getBaseSchemaPath)(), databaseName))
|
|
2152
|
+
return databaseName;
|
|
2153
|
+
const physicalName = (0, node_path_1.basename)(rootPath).replace(/\.mdb$/, '');
|
|
2154
|
+
const rootDirectory = (0, node_path_1.resolve)((0, node_path_1.dirname)(rootPath));
|
|
2155
|
+
if (databaseConfig?.path && (0, node_path_1.resolve)(databaseConfig.path) === rootDirectory) {
|
|
2156
|
+
if (!schemaConfigs[physicalName]?.path)
|
|
2157
|
+
return physicalName;
|
|
2158
|
+
for (const [configuredName, configured] of Object.entries(schemaConfigs)) {
|
|
2159
|
+
if (configured?.path && (0, node_path_1.resolve)(configured.path) === rootDirectory)
|
|
2160
|
+
return configuredName;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
try {
|
|
2164
|
+
const storageRoot = resolveDatabaseStorageRoot(databaseName);
|
|
2165
|
+
if ([(0, node_path_1.resolve)(storageRoot, databaseName), (0, node_path_1.resolve)(storageRoot, `${databaseName}.mdb`)].includes(normalizedRootPath))
|
|
2166
|
+
return databaseName;
|
|
2167
|
+
}
|
|
2168
|
+
catch { }
|
|
2169
|
+
return physicalName;
|
|
2170
|
+
}
|
|
2171
|
+
const incompleteDatabaseDropStores = new Map();
|
|
2172
|
+
function rememberIncompleteDatabaseClose(databaseNames, rootPaths, retry) {
|
|
2173
|
+
const names = new Set(databaseNames);
|
|
2174
|
+
const normalizedPaths = [...new Set([...rootPaths].map((rootPath) => (0, node_path_1.resolve)(rootPath)))];
|
|
2175
|
+
const incompleteClose = { databaseNames: names, rootPaths: normalizedPaths, retry };
|
|
2176
|
+
for (const rootPath of normalizedPaths)
|
|
2177
|
+
incompleteDatabaseCloses.set(rootPath, incompleteClose);
|
|
2178
|
+
logger.warn(`Database '${[...names].join("', '")}' handles remain partially closed on this worker; the affected roots stay unavailable until drop_database is retried or the worker restarts`, normalizedPaths);
|
|
2179
|
+
}
|
|
2180
|
+
function incompleteDatabaseClosePaths(databaseName) {
|
|
2181
|
+
const rootPaths = new Set();
|
|
2182
|
+
for (const incompleteClose of new Set(incompleteDatabaseCloses.values())) {
|
|
2183
|
+
if (!incompleteClose.databaseNames.has(databaseName))
|
|
2184
|
+
continue;
|
|
2185
|
+
for (const rootPath of incompleteClose.rootPaths)
|
|
2186
|
+
rootPaths.add(rootPath);
|
|
2187
|
+
}
|
|
2188
|
+
return [...rootPaths];
|
|
2189
|
+
}
|
|
2190
|
+
async function retryIncompleteDatabaseClose(rootPath) {
|
|
2191
|
+
const incompleteClose = incompleteDatabaseCloses.get((0, node_path_1.resolve)(rootPath));
|
|
2192
|
+
if (!incompleteClose)
|
|
2193
|
+
return;
|
|
2194
|
+
if (!incompleteClose.task) {
|
|
2195
|
+
const operation = incompleteClose.retry().then(() => {
|
|
2196
|
+
for (const path of incompleteClose.rootPaths) {
|
|
2197
|
+
if (incompleteDatabaseCloses.get(path) === incompleteClose)
|
|
2198
|
+
incompleteDatabaseCloses.delete(path);
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
let retryable;
|
|
2202
|
+
retryable = operation.catch((error) => {
|
|
2203
|
+
if (incompleteClose.task === retryable)
|
|
2204
|
+
incompleteClose.task = undefined;
|
|
2205
|
+
throw error;
|
|
2206
|
+
});
|
|
2207
|
+
incompleteClose.task = retryable;
|
|
2208
|
+
}
|
|
2209
|
+
await incompleteClose.task;
|
|
2210
|
+
}
|
|
2211
|
+
async function destroyIncompleteDatabaseRoot(rootPath) {
|
|
2212
|
+
const rootStore = incompleteDatabaseDropStores.get(rootPath);
|
|
2213
|
+
if (!rootStore || !(0, node_fs_1.existsSync)(rootPath)) {
|
|
2214
|
+
incompleteDatabaseDropStores.delete(rootPath);
|
|
2215
|
+
await (0, promises_1.rm)(rootPath, { recursive: true, force: true });
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
2219
|
+
if (rootStore.status === 'open') {
|
|
2220
|
+
await rootStore.dbisDb?.close?.();
|
|
2221
|
+
await rootStore.close();
|
|
2222
|
+
}
|
|
2223
|
+
await rootStore.destroy();
|
|
2224
|
+
}
|
|
2225
|
+
else {
|
|
2226
|
+
if (rootStore.status === 'open')
|
|
2227
|
+
await rootStore.close();
|
|
2228
|
+
await (0, promises_1.rm)(rootPath, { force: true });
|
|
2229
|
+
}
|
|
2230
|
+
incompleteDatabaseDropStores.delete(rootPath);
|
|
2231
|
+
}
|
|
2232
|
+
async function resumeIncompleteDatabaseDrop(databaseName, rootPaths) {
|
|
2233
|
+
const dropLocks = [];
|
|
2234
|
+
let destructiveWorkStarted = false;
|
|
2235
|
+
try {
|
|
2236
|
+
const paths = new Set(rootPaths);
|
|
2237
|
+
for (const rootPath of paths)
|
|
2238
|
+
await retryIncompleteDatabaseClose(rootPath);
|
|
2239
|
+
if ((0, rocksdb_js_1.registryStatus)().some((entry) => paths.has(entry.path) && entry.refCount > 0 && !incompleteDatabaseDropStores.has(entry.path)))
|
|
2240
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2241
|
+
for (const rootPath of paths)
|
|
2242
|
+
lockDatabaseForDrop(rootPath, databaseName, dropLocks, inferDropBlobDatabaseName(databaseName, rootPath));
|
|
2243
|
+
destructiveWorkStarted = true;
|
|
2244
|
+
for (const lock of dropLocks)
|
|
2245
|
+
await destroyIncompleteDatabaseRoot(lock.dbPath);
|
|
2246
|
+
await (0, blob_ts_1.deleteBlobPaths)(dropLocks.flatMap((lock) => lock.blobPaths));
|
|
2247
|
+
while (dropLocks.length > 0)
|
|
2248
|
+
(0, restoreMarker_ts_1.completeDatabaseDrop)(dropLocks.shift());
|
|
2249
|
+
}
|
|
2250
|
+
finally {
|
|
2251
|
+
releaseDatabaseDropLocks(dropLocks, destructiveWorkStarted);
|
|
2252
|
+
}
|
|
1920
2253
|
}
|
|
1921
2254
|
/**
|
|
1922
2255
|
* Delete the database
|
|
1923
2256
|
* @param databaseName
|
|
1924
2257
|
*/
|
|
1925
|
-
async function dropDatabase(databaseName) {
|
|
1926
|
-
if (!exports.databases[databaseName])
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2258
|
+
async function dropDatabase(databaseName, requestedRootPaths = []) {
|
|
2259
|
+
if (!exports.databases[databaseName]) {
|
|
2260
|
+
const rootPaths = new Set(requestedRootPaths);
|
|
2261
|
+
if (rootPaths.size === 0)
|
|
2262
|
+
throw new Error('Database does not exist');
|
|
2263
|
+
return resumeIncompleteDatabaseDrop(databaseName, rootPaths);
|
|
2264
|
+
}
|
|
2265
|
+
const { databaseNames, dbTables, rootStores } = collectDatabaseGraph(databaseName);
|
|
2266
|
+
const dropLocks = [];
|
|
2267
|
+
let releaseDerivedIndexActivation;
|
|
2268
|
+
let destructiveWorkStarted = false;
|
|
2269
|
+
let dropCompleted = false;
|
|
1936
2270
|
try {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2271
|
+
if (rootStores.size === 0) {
|
|
2272
|
+
rootStores.add(openDatabaseRoot({ database: databaseName }, { allowPreparedDrop: true }));
|
|
2273
|
+
}
|
|
2274
|
+
const blobStorageByRootPath = new Map([...rootStores].map((rootStore) => [
|
|
2275
|
+
rootStore.path,
|
|
2276
|
+
{
|
|
2277
|
+
databaseName: rootStore.databaseName ?? databaseName,
|
|
2278
|
+
paths: (0, blob_ts_1.getRootBlobPathsForDB)(rootStore),
|
|
2279
|
+
},
|
|
2280
|
+
]));
|
|
2281
|
+
for (const rootPath of new Set([...requestedRootPaths, ...blobStorageByRootPath.keys()])) {
|
|
2282
|
+
const blobStorage = blobStorageByRootPath.get(rootPath);
|
|
2283
|
+
const blobDatabaseName = blobStorage?.databaseName ?? inferDropBlobDatabaseName(databaseName, rootPath);
|
|
2284
|
+
lockDatabaseForDrop(rootPath, databaseName, dropLocks, blobDatabaseName, blobStorage?.paths ?? (0, blob_ts_1.getBlobPathsForDatabaseName)(blobDatabaseName));
|
|
2285
|
+
}
|
|
2286
|
+
const openedRootPaths = new Set([...rootStores].map((rootStore) => rootStore.path));
|
|
2287
|
+
const detachedRootPaths = dropLocks.map((lock) => lock.dbPath).filter((rootPath) => !openedRootPaths.has(rootPath));
|
|
2288
|
+
const detachedRootPathSet = new Set(detachedRootPaths);
|
|
2289
|
+
const detachedRootHasForeignHandle = () => (0, rocksdb_js_1.registryStatus)().some((entry) => detachedRootPathSet.has(entry.path) && entry.refCount > 0 && !incompleteDatabaseDropStores.has(entry.path));
|
|
2290
|
+
if (detachedRootHasForeignHandle())
|
|
2291
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2292
|
+
releaseDerivedIndexActivation = await settleDatabaseDerivedIndexes(dbTables, true, rootStores);
|
|
2293
|
+
destructiveWorkStarted = true;
|
|
2294
|
+
for (const rootStore of rootStores) {
|
|
1942
2295
|
lmdbDatabaseEnvs.delete(rootStore.path);
|
|
1943
2296
|
rocksdbDatabaseEnvs.delete(rootStore.path);
|
|
1944
2297
|
}
|
|
1945
|
-
for (const tableName
|
|
1946
|
-
exports.databaseEventsEmitter.emit('dropTable', tableName, databaseName);
|
|
2298
|
+
for (const [tableName, table] of Object.entries(dbTables)) {
|
|
2299
|
+
exports.databaseEventsEmitter.emit('dropTable', table.tableName ?? tableName, table.databaseName);
|
|
1947
2300
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2301
|
+
for (const name of databaseNames) {
|
|
2302
|
+
if (name === 'data') {
|
|
2303
|
+
for (const tableName in exports.tables) {
|
|
2304
|
+
delete exports.tables[tableName];
|
|
2305
|
+
}
|
|
2306
|
+
delete exports.tables[DEFINED_TABLES];
|
|
1951
2307
|
}
|
|
1952
|
-
delete exports.
|
|
2308
|
+
delete exports.databases[name];
|
|
2309
|
+
definedDatabases?.delete(name);
|
|
2310
|
+
exports.databaseEventsEmitter.emit('dropDatabase', name);
|
|
1953
2311
|
}
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2312
|
+
const closedStores = new Set();
|
|
2313
|
+
for (const table of Object.values(dbTables)) {
|
|
2314
|
+
if (!(table?.primaryStore?.rootStore instanceof rocksdb_js_1.RocksDatabase))
|
|
2315
|
+
continue;
|
|
2316
|
+
for (const store of [...Object.values(table.indices || {}), table.primaryStore]) {
|
|
2317
|
+
if (!store || closedStores.has(store))
|
|
2318
|
+
continue;
|
|
2319
|
+
closedStores.add(store);
|
|
2320
|
+
await store.close?.();
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
for (const rootStore of rootStores) {
|
|
1959
2324
|
await rootStore.auditStore?.stopAuditCleanup?.();
|
|
1960
2325
|
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
1961
2326
|
if (rootStore.status === 'open') {
|
|
1962
2327
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
1963
|
-
rootStore.close();
|
|
1964
|
-
rootStore.
|
|
2328
|
+
await rootStore.dbisDb?.close?.();
|
|
2329
|
+
await rootStore.close();
|
|
2330
|
+
await rootStore.destroy();
|
|
1965
2331
|
}
|
|
1966
2332
|
else {
|
|
1967
2333
|
await rootStore.close();
|
|
@@ -1969,116 +2335,367 @@ async function dropDatabase(databaseName) {
|
|
|
1969
2335
|
}
|
|
1970
2336
|
}
|
|
1971
2337
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2338
|
+
if (detachedRootHasForeignHandle())
|
|
2339
|
+
throw new hdbError_ts_1.DatabaseClosingError(databaseName);
|
|
2340
|
+
for (const rootPath of detachedRootPaths)
|
|
2341
|
+
await (0, promises_1.rm)(rootPath, { recursive: true, force: true });
|
|
2342
|
+
await (0, blob_ts_1.deleteBlobPaths)(dropLocks.flatMap((lock) => lock.blobPaths));
|
|
2343
|
+
while (dropLocks.length > 0)
|
|
2344
|
+
(0, restoreMarker_ts_1.completeDatabaseDrop)(dropLocks.shift());
|
|
2345
|
+
dropCompleted = true;
|
|
2346
|
+
}
|
|
2347
|
+
finally {
|
|
2348
|
+
if (destructiveWorkStarted) {
|
|
2349
|
+
for (const rootStore of rootStores)
|
|
2350
|
+
blob_ts_1.databasePaths.delete(rootStore);
|
|
2351
|
+
for (const rootStore of rootStores) {
|
|
2352
|
+
if ((0, restoreMarker_ts_1.databaseDropMarkerPresent)(rootStore.path))
|
|
2353
|
+
incompleteDatabaseDropStores.set(rootStore.path, rootStore);
|
|
1983
2354
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2355
|
+
if (!dropCompleted) {
|
|
2356
|
+
(0, DatabaseTransaction_ts_1.permanentlySuspendDatabaseCommits)(rootStores);
|
|
2357
|
+
for (const rootStore of rootStores)
|
|
2358
|
+
(0, derivedIndexes_ts_1.permanentlySuspendDerivedIndexActivation)(rootStore);
|
|
1987
2359
|
}
|
|
1988
2360
|
}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
finally {
|
|
1992
|
-
for (const lock of restoreLocks)
|
|
1993
|
-
(0, restoreMarker_ts_1.releaseRestoreLock)(lock);
|
|
2361
|
+
releaseDerivedIndexActivation?.();
|
|
2362
|
+
releaseDatabaseDropLocks(dropLocks, destructiveWorkStarted);
|
|
1994
2363
|
}
|
|
1995
2364
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
const closeStore = (store, description) => {
|
|
2365
|
+
const databaseDropPreparationTasks = new Map();
|
|
2366
|
+
function addPhysicalDatabaseRoots(directory, rootPaths) {
|
|
2367
|
+
if (!(0, node_fs_1.existsSync)(directory))
|
|
2368
|
+
return;
|
|
2369
|
+
for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })) {
|
|
2370
|
+
if (entry.name === restoreMarker_ts_1.RESTORE_META_DIR || entry.name === exports.BRANCH_ROOT_DIR || entry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
|
|
2371
|
+
continue;
|
|
2372
|
+
const rootPath = (0, node_path_1.join)(directory, entry.name);
|
|
2373
|
+
if (entry.isFile()) {
|
|
2374
|
+
if ((0, node_path_1.extname)(entry.name).toLowerCase() === '.mdb')
|
|
2375
|
+
rootPaths.add(rootPath);
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
if (!entry.isDirectory())
|
|
2379
|
+
continue;
|
|
2012
2380
|
try {
|
|
2013
|
-
|
|
2381
|
+
const files = (0, node_fs_1.readdirSync)(rootPath, { withFileTypes: true });
|
|
2382
|
+
if (files.find((file) => file.name === 'CURRENT')?.isFile() &&
|
|
2383
|
+
files.some((file) => file.name.startsWith('MANIFEST-')))
|
|
2384
|
+
rootPaths.add(rootPath);
|
|
2014
2385
|
}
|
|
2015
2386
|
catch (error) {
|
|
2016
|
-
|
|
2387
|
+
if (!('code' in error && error.code === 'ENOENT'))
|
|
2388
|
+
throw error;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
function databaseDropRecoveryGraphPaths(databaseName, incompleteRootPaths) {
|
|
2393
|
+
const rootPaths = new Set(incompleteRootPaths);
|
|
2394
|
+
if (incompleteRootPaths.length === 0)
|
|
2395
|
+
return [];
|
|
2396
|
+
const databaseConfig = ((0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {})[databaseName];
|
|
2397
|
+
if (databaseConfig?.path)
|
|
2398
|
+
addPhysicalDatabaseRoots(databaseConfig.path, rootPaths);
|
|
2399
|
+
addPhysicalDatabaseRoots((0, node_path_1.join)((0, initializePaths_js_1.getBaseSchemaPath)(), databaseName), rootPaths);
|
|
2400
|
+
for (const [tableName, tableConfig] of Object.entries(databaseConfig?.tables || {})) {
|
|
2401
|
+
if (!tableConfig?.path)
|
|
2402
|
+
continue;
|
|
2403
|
+
for (const rootPath of [(0, node_path_1.join)(tableConfig.path, tableName), (0, node_path_1.join)(tableConfig.path, `${tableName}.mdb`)]) {
|
|
2404
|
+
if ((0, node_fs_1.existsSync)(rootPath))
|
|
2405
|
+
rootPaths.add(rootPath);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
try {
|
|
2409
|
+
const storageRoot = resolveDatabaseStorageRoot(databaseName);
|
|
2410
|
+
for (const rootPath of [(0, node_path_1.join)(storageRoot, databaseName), (0, node_path_1.join)(storageRoot, `${databaseName}.mdb`)]) {
|
|
2411
|
+
if ((0, node_fs_1.existsSync)(rootPath))
|
|
2412
|
+
rootPaths.add(rootPath);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
catch { }
|
|
2416
|
+
return [...rootPaths];
|
|
2417
|
+
}
|
|
2418
|
+
function incompleteDatabaseDropPaths(databaseName) {
|
|
2419
|
+
const directories = new Set();
|
|
2420
|
+
try {
|
|
2421
|
+
directories.add(resolveDatabaseStorageRoot(databaseName));
|
|
2422
|
+
}
|
|
2423
|
+
catch { }
|
|
2424
|
+
directories.add((0, node_path_1.join)((0, initializePaths_js_1.getBaseSchemaPath)(), databaseName));
|
|
2425
|
+
const databaseConfig = ((0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {})[databaseName];
|
|
2426
|
+
if (databaseConfig?.path)
|
|
2427
|
+
directories.add(databaseConfig.path);
|
|
2428
|
+
for (const tableConfig of Object.values(databaseConfig?.tables || {})) {
|
|
2429
|
+
if (tableConfig?.path)
|
|
2430
|
+
directories.add(tableConfig.path);
|
|
2431
|
+
}
|
|
2432
|
+
const rootPaths = new Set();
|
|
2433
|
+
for (const directory of directories) {
|
|
2434
|
+
for (const drop of (0, restoreMarker_ts_1.scanBlockedDatabaseDrops)(directory)) {
|
|
2435
|
+
if (drop.databaseName === databaseName)
|
|
2436
|
+
rootPaths.add(drop.rootPath);
|
|
2017
2437
|
}
|
|
2438
|
+
}
|
|
2439
|
+
return [...rootPaths];
|
|
2440
|
+
}
|
|
2441
|
+
function databaseDropRecoveryPending(databaseName) {
|
|
2442
|
+
return incompleteDatabaseDropPaths(databaseName).length > 0 || incompleteDatabaseClosePaths(databaseName).length > 0;
|
|
2443
|
+
}
|
|
2444
|
+
function databaseDropPreparationTargets(databaseName) {
|
|
2445
|
+
getDatabases();
|
|
2446
|
+
const incompleteRootPaths = [
|
|
2447
|
+
...new Set([...incompleteDatabaseDropPaths(databaseName), ...incompleteDatabaseClosePaths(databaseName)]),
|
|
2448
|
+
];
|
|
2449
|
+
const recoveryRootPaths = databaseDropRecoveryGraphPaths(databaseName, incompleteRootPaths);
|
|
2450
|
+
if (!exports.databases[databaseName]) {
|
|
2451
|
+
if (recoveryRootPaths.length > 0)
|
|
2452
|
+
return { rootPaths: recoveryRootPaths };
|
|
2453
|
+
throw new Error(`Database '${databaseName}' does not exist`);
|
|
2454
|
+
}
|
|
2455
|
+
const graph = collectDatabaseGraph(databaseName);
|
|
2456
|
+
if (graph.rootStores.size === 0)
|
|
2457
|
+
graph.rootStores.add(openDatabaseRoot({ database: databaseName }));
|
|
2458
|
+
return {
|
|
2459
|
+
rootPaths: [...new Set([...recoveryRootPaths, ...[...graph.rootStores].map((rootStore) => rootStore.path)])],
|
|
2018
2460
|
};
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2461
|
+
}
|
|
2462
|
+
function prepareDatabaseDrop(databaseName, preparationId, ownerThreadId, requestedRootPaths = []) {
|
|
2463
|
+
const existing = databaseDropPreparationTasks.get(databaseName);
|
|
2464
|
+
if (existing?.id === preparationId)
|
|
2465
|
+
return existing.task;
|
|
2466
|
+
const rootPaths = new Set(requestedRootPaths);
|
|
2467
|
+
if (exports.databases[databaseName]) {
|
|
2468
|
+
const graph = collectDatabaseGraph(databaseName);
|
|
2469
|
+
for (const rootStore of graph.rootStores)
|
|
2470
|
+
rootPaths.add(rootStore.path);
|
|
2471
|
+
}
|
|
2472
|
+
const names = [];
|
|
2473
|
+
for (const name of Object.keys(exports.databases)) {
|
|
2474
|
+
const definedRoot = definedDatabases?.get(name)?.rootStore;
|
|
2475
|
+
if (definedRoot && rootPaths.has(definedRoot.path)) {
|
|
2476
|
+
names.push(name);
|
|
2022
2477
|
continue;
|
|
2023
|
-
|
|
2024
|
-
|
|
2478
|
+
}
|
|
2479
|
+
const tables = exports.databases[name];
|
|
2480
|
+
if (Object.values(tables).some((table) => {
|
|
2481
|
+
const rootStore = table?.primaryStore?.rootStore;
|
|
2482
|
+
return rootStore && rootPaths.has(rootStore.path);
|
|
2483
|
+
}))
|
|
2484
|
+
names.push(name);
|
|
2025
2485
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2486
|
+
const paths = [...rootPaths];
|
|
2487
|
+
(0, databaseDropPreparation_ts_1.claimDatabaseDropPreparations)(paths, preparationId, ownerThreadId, databaseName);
|
|
2488
|
+
const task = (async () => {
|
|
2489
|
+
const failures = [];
|
|
2490
|
+
for (const rootPath of paths) {
|
|
2491
|
+
try {
|
|
2492
|
+
await retryIncompleteDatabaseClose(rootPath);
|
|
2493
|
+
}
|
|
2494
|
+
catch (error) {
|
|
2495
|
+
failures.push(error);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
for (const name of names) {
|
|
2499
|
+
try {
|
|
2500
|
+
await closeDatabase(name, { requireClosed: true });
|
|
2501
|
+
}
|
|
2502
|
+
catch (error) {
|
|
2503
|
+
failures.push(error);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
for (const rootPath of paths) {
|
|
2507
|
+
if (!incompleteDatabaseDropStores.has(rootPath))
|
|
2508
|
+
continue;
|
|
2509
|
+
try {
|
|
2510
|
+
await destroyIncompleteDatabaseRoot(rootPath);
|
|
2511
|
+
}
|
|
2512
|
+
catch (error) {
|
|
2513
|
+
failures.push(error);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
if (failures.length > 0)
|
|
2517
|
+
throw new AggregateError(failures, `Could not prepare database '${databaseName}' for drop`);
|
|
2518
|
+
})();
|
|
2519
|
+
databaseDropPreparationTasks.set(databaseName, { id: preparationId, task, rootPaths: paths });
|
|
2520
|
+
for (const rootPath of paths)
|
|
2521
|
+
(0, databaseDropPreparation_ts_1.trackDatabaseDropPreparationTask)(rootPath, preparationId, task);
|
|
2522
|
+
return task;
|
|
2523
|
+
}
|
|
2524
|
+
async function completeDatabaseDropPreparation(databaseName, preparationId, requestedRootPaths = []) {
|
|
2525
|
+
const existing = databaseDropPreparationTasks.get(databaseName);
|
|
2526
|
+
if (existing?.id === preparationId) {
|
|
2527
|
+
try {
|
|
2528
|
+
await existing.task;
|
|
2529
|
+
}
|
|
2530
|
+
catch {
|
|
2531
|
+
// Preparation already reported this failure to the strict broadcaster.
|
|
2532
|
+
}
|
|
2533
|
+
databaseDropPreparationTasks.delete(databaseName);
|
|
2534
|
+
}
|
|
2535
|
+
(0, databaseDropPreparation_ts_1.releaseDatabaseDropPreparations)(existing?.rootPaths ?? requestedRootPaths, preparationId);
|
|
2536
|
+
}
|
|
2537
|
+
async function closeDatabaseStores(databaseName, dbTables, rootStores, requireClosed) {
|
|
2538
|
+
const rootStorePaths = [];
|
|
2539
|
+
const closeFailures = [];
|
|
2540
|
+
const closeStore = async (store, description) => {
|
|
2541
|
+
if (!store || store.status === 'closed')
|
|
2542
|
+
return true;
|
|
2543
|
+
try {
|
|
2544
|
+
await store.close?.();
|
|
2545
|
+
return true;
|
|
2546
|
+
}
|
|
2547
|
+
catch (error) {
|
|
2548
|
+
logger.warn(`Error closing ${description} while closing database ${databaseName}:`, error);
|
|
2549
|
+
closeFailures.push(error);
|
|
2550
|
+
return false;
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2038
2553
|
const lmdbRootStores = new Set([...rootStores].filter((rootStore) => !(rootStore instanceof rocksdb_js_1.RocksDatabase)));
|
|
2039
2554
|
for (const tableName in dbTables) {
|
|
2040
2555
|
const table = dbTables[tableName];
|
|
2041
|
-
// a dbi closed natively after its environment closed (through another alias sharing it) runs
|
|
2042
|
-
// mdb_dbi_close against the freed environment
|
|
2043
2556
|
if (!table?.primaryStore || lmdbRootStores.has(table.primaryStore.rootStore))
|
|
2044
2557
|
continue;
|
|
2558
|
+
const tableIdentity = `${table.databaseName ?? databaseName}.${table.tableName ?? tableName}`;
|
|
2045
2559
|
for (const indexName in table.indices || {}) {
|
|
2046
|
-
closeStore(table.indices[indexName], `index ${
|
|
2560
|
+
await closeStore(table.indices[indexName], `index ${tableIdentity}.${indexName}`);
|
|
2047
2561
|
}
|
|
2048
|
-
closeStore(table.primaryStore, `table ${
|
|
2562
|
+
await closeStore(table.primaryStore, `table ${tableIdentity}`);
|
|
2049
2563
|
}
|
|
2050
2564
|
for (const rootStore of rootStores) {
|
|
2051
2565
|
(0, storageReclamation_ts_1.removeStorageReclamation)(rootStore.path);
|
|
2566
|
+
blob_ts_1.databasePaths.delete(rootStore);
|
|
2567
|
+
let rootClosed = rootStore.status !== 'open';
|
|
2052
2568
|
if (!lmdbRootStores.has(rootStore)) {
|
|
2053
|
-
closeStore(rootStore.dbisDb, 'attributes store');
|
|
2054
|
-
closeStore(rootStore, 'root store');
|
|
2055
|
-
}
|
|
2056
|
-
else if (
|
|
2057
|
-
closeStore(rootStore, 'root store');
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2569
|
+
await closeStore(rootStore.dbisDb, 'attributes store');
|
|
2570
|
+
rootClosed = await closeStore(rootStore, 'root store');
|
|
2571
|
+
}
|
|
2572
|
+
else if (!rootClosed)
|
|
2573
|
+
rootClosed = await closeStore(rootStore, 'root store');
|
|
2574
|
+
if (rootClosed || !requireClosed)
|
|
2575
|
+
rootStorePaths.push(rootStore.path);
|
|
2576
|
+
}
|
|
2577
|
+
for (const path of rootStorePaths) {
|
|
2578
|
+
lmdbDatabaseEnvs.delete(path);
|
|
2579
|
+
rocksdbDatabaseEnvs.delete(path);
|
|
2580
|
+
}
|
|
2581
|
+
return closeFailures;
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* Close a database's store handles on this thread and unregister every alias sharing those roots,
|
|
2585
|
+
* without touching files. Restore uses this before rewriting the closed directory. With
|
|
2586
|
+
* `requireClosed`, any handle failure rejects and leaves the affected wrappers permanently fenced
|
|
2587
|
+
* for an explicit retry. Closing an LMDB environment closes every DBI and alias that shares it.
|
|
2588
|
+
*/
|
|
2589
|
+
async function closeDatabase(databaseName, { requireClosed = false } = {}) {
|
|
2590
|
+
if (!exports.databases[databaseName])
|
|
2591
|
+
return false;
|
|
2592
|
+
const { databaseNames, dbTables, rootStores } = collectDatabaseGraph(databaseName);
|
|
2593
|
+
const releaseDerivedIndexActivation = await settleDatabaseDerivedIndexes(dbTables, false, rootStores);
|
|
2594
|
+
let databaseClosed = false;
|
|
2595
|
+
let handleCloseStarted = false;
|
|
2596
|
+
try {
|
|
2597
|
+
await Promise.all([...rootStores].map((rootStore) => rootStore.auditStore?.stopAuditCleanup?.()));
|
|
2598
|
+
handleCloseStarted = true;
|
|
2599
|
+
const closeFailures = await closeDatabaseStores(databaseName, dbTables, rootStores, requireClosed);
|
|
2600
|
+
if (requireClosed && closeFailures.length > 0) {
|
|
2601
|
+
for (const rootStore of rootStores) {
|
|
2602
|
+
lmdbDatabaseEnvs.delete(rootStore.path);
|
|
2603
|
+
rocksdbDatabaseEnvs.delete(rootStore.path);
|
|
2604
|
+
}
|
|
2605
|
+
for (const name of databaseNames)
|
|
2606
|
+
unregisterDatabase(name);
|
|
2607
|
+
rememberIncompleteDatabaseClose(databaseNames, [...rootStores].map((rootStore) => rootStore.path), async () => {
|
|
2608
|
+
const retryFailures = await closeDatabaseStores(databaseName, dbTables, rootStores, true);
|
|
2609
|
+
if (retryFailures.length > 0)
|
|
2610
|
+
throw new AggregateError(retryFailures, `Could not finish closing database graph '${databaseName}'`);
|
|
2611
|
+
});
|
|
2612
|
+
throw new AggregateError(closeFailures, `Could not close database graph '${[...databaseNames].join("', '")}' for destructive DDL`);
|
|
2613
|
+
}
|
|
2614
|
+
for (const name of databaseNames)
|
|
2615
|
+
unregisterDatabase(name);
|
|
2616
|
+
databaseClosed = true;
|
|
2617
|
+
return true;
|
|
2618
|
+
}
|
|
2619
|
+
finally {
|
|
2620
|
+
if (handleCloseStarted && !databaseClosed) {
|
|
2621
|
+
(0, DatabaseTransaction_ts_1.permanentlySuspendDatabaseCommits)(rootStores);
|
|
2622
|
+
for (const rootStore of rootStores)
|
|
2623
|
+
(0, derivedIndexes_ts_1.permanentlySuspendDerivedIndexActivation)(rootStore);
|
|
2624
|
+
}
|
|
2625
|
+
else if (!databaseClosed && !handleCloseStarted) {
|
|
2626
|
+
resumeTableMaintenance(dbTables);
|
|
2627
|
+
for (const table of Object.values(dbTables))
|
|
2628
|
+
(0, derivedIndexes_ts_1.refreshDerivedIndexes)(table);
|
|
2629
|
+
}
|
|
2630
|
+
releaseDerivedIndexActivation();
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
function collectDatabaseGraph(databaseName) {
|
|
2634
|
+
const databaseNames = new Set([databaseName]);
|
|
2635
|
+
const processedNames = new Set();
|
|
2636
|
+
const rootStores = new Set();
|
|
2637
|
+
const tableSet = new Set();
|
|
2638
|
+
for (;;) {
|
|
2639
|
+
for (const name of databaseNames) {
|
|
2640
|
+
if (processedNames.has(name))
|
|
2641
|
+
continue;
|
|
2642
|
+
processedNames.add(name);
|
|
2643
|
+
const tables = exports.databases[name];
|
|
2644
|
+
for (const tableName in tables) {
|
|
2645
|
+
const table = tables[tableName];
|
|
2646
|
+
tableSet.add(table);
|
|
2647
|
+
if (table?.primaryStore?.rootStore)
|
|
2648
|
+
rootStores.add(table.primaryStore.rootStore);
|
|
2649
|
+
}
|
|
2650
|
+
const definedRoot = definedDatabases?.get(name)?.rootStore;
|
|
2651
|
+
if (definedRoot)
|
|
2652
|
+
rootStores.add(definedRoot);
|
|
2653
|
+
}
|
|
2654
|
+
let foundAlias = false;
|
|
2655
|
+
for (const aliasName of Object.keys(exports.databases)) {
|
|
2656
|
+
if (databaseNames.has(aliasName))
|
|
2657
|
+
continue;
|
|
2658
|
+
for (const rootStore of rootStores) {
|
|
2659
|
+
if (!databaseUsesRootStore(aliasName, rootStore))
|
|
2660
|
+
continue;
|
|
2661
|
+
databaseNames.add(aliasName);
|
|
2662
|
+
foundAlias = true;
|
|
2066
2663
|
break;
|
|
2067
2664
|
}
|
|
2068
2665
|
}
|
|
2666
|
+
if (!foundAlias)
|
|
2667
|
+
break;
|
|
2069
2668
|
}
|
|
2070
|
-
return
|
|
2669
|
+
return {
|
|
2670
|
+
databaseNames,
|
|
2671
|
+
dbTables: Object.fromEntries([...tableSet].map((table, index) => [index, table])),
|
|
2672
|
+
rootStores,
|
|
2673
|
+
};
|
|
2071
2674
|
}
|
|
2072
2675
|
function databaseUsesRootStore(databaseName, rootStore) {
|
|
2073
2676
|
if (definedDatabases?.get(databaseName)?.rootStore === rootStore)
|
|
2074
2677
|
return true;
|
|
2075
2678
|
const dbTables = exports.databases[databaseName];
|
|
2679
|
+
if (!dbTables)
|
|
2680
|
+
return false;
|
|
2076
2681
|
for (const tableName in dbTables) {
|
|
2077
2682
|
if (dbTables[tableName]?.primaryStore?.rootStore === rootStore)
|
|
2078
2683
|
return true;
|
|
2079
2684
|
}
|
|
2080
2685
|
return false;
|
|
2081
2686
|
}
|
|
2687
|
+
function databaseUsesPreparedRoot(databaseName) {
|
|
2688
|
+
const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
|
|
2689
|
+
if (definedRoot && (0, databaseDropPreparation_ts_1.databaseDropPrepared)(definedRoot.path))
|
|
2690
|
+
return true;
|
|
2691
|
+
const dbTables = exports.databases[databaseName];
|
|
2692
|
+
for (const tableName in dbTables) {
|
|
2693
|
+
const rootStore = dbTables[tableName]?.primaryStore?.rootStore;
|
|
2694
|
+
if (rootStore && (0, databaseDropPreparation_ts_1.databaseDropPrepared)(rootStore.path))
|
|
2695
|
+
return true;
|
|
2696
|
+
}
|
|
2697
|
+
return false;
|
|
2698
|
+
}
|
|
2082
2699
|
function unregisterDatabase(databaseName) {
|
|
2083
2700
|
const definedDatabase = definedDatabases?.get(databaseName);
|
|
2084
2701
|
if (definedDatabase)
|
|
@@ -2092,43 +2709,115 @@ function unregisterDatabase(databaseName) {
|
|
|
2092
2709
|
delete exports.databases[databaseName];
|
|
2093
2710
|
}
|
|
2094
2711
|
/**
|
|
2095
|
-
* Close every
|
|
2712
|
+
* Close every user database this thread has open, releasing its native handles.
|
|
2096
2713
|
*
|
|
2097
|
-
*
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2101
|
-
* `getDatabases()` and exits when the job finishes — must close its handles explicitly, or those
|
|
2102
|
-
* handles linger process-wide (and, e.g., block an online `restore_backup` from confirming the
|
|
2103
|
-
* database is closed). The `system` database is intentionally left open: it is non-enumerable here
|
|
2714
|
+
* A job worker opens the database graph and must prove every handle is closed before its exit can
|
|
2715
|
+
* satisfy destructive-DDL preparation. RocksDB handles otherwise leak in the process-global
|
|
2716
|
+
* registry; an open LMDB handle can likewise prevent deletion on Windows. The `system` database is
|
|
2717
|
+
* intentionally left open: it is non-enumerable here
|
|
2104
2718
|
* (skipped by the loop), is never restored online, and the exiting worker may still touch the job
|
|
2105
2719
|
* table during teardown. Best-effort: closing failures are swallowed inside `closeDatabase`.
|
|
2106
2720
|
*
|
|
2107
2721
|
* Branches are invisible to the loop below but hold handles from the same registry, so this — the
|
|
2108
2722
|
* thread's one teardown entry point — closes them too.
|
|
2109
2723
|
*/
|
|
2110
|
-
function closeLoadedDatabases() {
|
|
2111
|
-
|
|
2724
|
+
async function closeLoadedDatabases({ requireClosed = false } = {}) {
|
|
2725
|
+
const closeFailures = [];
|
|
2726
|
+
try {
|
|
2727
|
+
await closeBranchDatabases(requireClosed);
|
|
2728
|
+
}
|
|
2729
|
+
catch (error) {
|
|
2730
|
+
closeFailures.push(error);
|
|
2731
|
+
}
|
|
2112
2732
|
// snapshot the names first: closeDatabase() deletes from `databases` as it goes
|
|
2113
2733
|
for (const databaseName of Object.keys(exports.databases)) {
|
|
2114
2734
|
const dbTables = exports.databases[databaseName];
|
|
2115
2735
|
if (!dbTables)
|
|
2116
2736
|
continue;
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
if (dbTables[tableName]?.primaryStore?.rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
2120
|
-
isRocks = true;
|
|
2121
|
-
break;
|
|
2122
|
-
}
|
|
2737
|
+
try {
|
|
2738
|
+
await closeDatabase(databaseName, { requireClosed });
|
|
2123
2739
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
isRocks = true;
|
|
2740
|
+
catch (error) {
|
|
2741
|
+
logger.warn(`Error closing database ${databaseName} during worker teardown`, error);
|
|
2742
|
+
closeFailures.push(error);
|
|
2128
2743
|
}
|
|
2129
|
-
if (isRocks)
|
|
2130
|
-
closeDatabase(databaseName);
|
|
2131
2744
|
}
|
|
2745
|
+
if (requireClosed && closeFailures.length > 0)
|
|
2746
|
+
throw new AggregateError(closeFailures, 'Could not close every database during worker teardown');
|
|
2747
|
+
}
|
|
2748
|
+
async function settleTableMaintenance(dbTables, deadline) {
|
|
2749
|
+
await Promise.all(Object.values(dbTables).map((table) => table?.closeMaintenance?.(deadline)));
|
|
2750
|
+
}
|
|
2751
|
+
function resumeTableMaintenance(dbTables) {
|
|
2752
|
+
for (const table of Object.values(dbTables))
|
|
2753
|
+
table?.resumeMaintenance?.();
|
|
2754
|
+
}
|
|
2755
|
+
async function settleDatabaseDerivedIndexes(dbTables, dropping, additionalRootStores = []) {
|
|
2756
|
+
const rootStores = new Set(additionalRootStores);
|
|
2757
|
+
for (const tableName in dbTables) {
|
|
2758
|
+
const rootStore = dbTables[tableName]?.primaryStore?.rootStore;
|
|
2759
|
+
if (rootStore)
|
|
2760
|
+
rootStores.add(rootStore);
|
|
2761
|
+
}
|
|
2762
|
+
const activationReleases = [...rootStores].map(derivedIndexes_ts_1.suspendDerivedIndexActivation);
|
|
2763
|
+
const commitSuspension = (0, DatabaseTransaction_ts_1.suspendDatabaseCommits)(rootStores);
|
|
2764
|
+
const deadline = Date.now() + (0, DatabaseTransaction_ts_1.getDatabaseCommitDrainTimeoutMilliseconds)();
|
|
2765
|
+
const databaseName = [...rootStores][0]?.databaseName ?? 'unknown';
|
|
2766
|
+
let lifecycleReleased = false;
|
|
2767
|
+
const releaseLifecycle = () => {
|
|
2768
|
+
if (lifecycleReleased)
|
|
2769
|
+
return;
|
|
2770
|
+
lifecycleReleased = true;
|
|
2771
|
+
commitSuspension.release();
|
|
2772
|
+
for (const release of activationReleases)
|
|
2773
|
+
release();
|
|
2774
|
+
};
|
|
2775
|
+
const attachments = new Map();
|
|
2776
|
+
for (const tableName in dbTables) {
|
|
2777
|
+
const table = dbTables[tableName];
|
|
2778
|
+
const attachment = table?.derivedIndexRuntime;
|
|
2779
|
+
if (!attachment)
|
|
2780
|
+
continue;
|
|
2781
|
+
let tables = attachments.get(attachment);
|
|
2782
|
+
if (!tables)
|
|
2783
|
+
attachments.set(attachment, (tables = []));
|
|
2784
|
+
tables.push(table);
|
|
2785
|
+
}
|
|
2786
|
+
const entries = [...attachments];
|
|
2787
|
+
try {
|
|
2788
|
+
await commitSuspension.waitForDrain({ deadline, databaseName });
|
|
2789
|
+
await settleInterruptedDropRetirements(rootStores);
|
|
2790
|
+
await settleTableMaintenance(dbTables, deadline);
|
|
2791
|
+
if (attachments.size === 0)
|
|
2792
|
+
return releaseLifecycle;
|
|
2793
|
+
const results = await Promise.allSettled(entries.map(async ([attachment]) => attachment.close(dropping)));
|
|
2794
|
+
const failures = results
|
|
2795
|
+
.filter((result) => result.status === 'rejected')
|
|
2796
|
+
.map((result) => result.reason);
|
|
2797
|
+
if (failures.length === 1)
|
|
2798
|
+
throw failures[0];
|
|
2799
|
+
if (failures.length)
|
|
2800
|
+
throw new AggregateError(failures, 'Could not settle derived indexes before closing the database');
|
|
2801
|
+
}
|
|
2802
|
+
catch (error) {
|
|
2803
|
+
resumeTableMaintenance(dbTables);
|
|
2804
|
+
releaseLifecycle();
|
|
2805
|
+
for (const [attachment, tables] of entries) {
|
|
2806
|
+
if (dropping)
|
|
2807
|
+
attachment.completeDrop?.(false);
|
|
2808
|
+
for (const table of tables)
|
|
2809
|
+
(0, derivedIndexes_ts_1.refreshDerivedIndexes)(table);
|
|
2810
|
+
}
|
|
2811
|
+
throw error;
|
|
2812
|
+
}
|
|
2813
|
+
for (const [attachment, tables] of entries) {
|
|
2814
|
+
if (dropping)
|
|
2815
|
+
attachment.completeDrop?.();
|
|
2816
|
+
for (const table of tables)
|
|
2817
|
+
if (table.derivedIndexRuntime === attachment)
|
|
2818
|
+
table.derivedIndexRuntime = undefined;
|
|
2819
|
+
}
|
|
2820
|
+
return releaseLifecycle;
|
|
2132
2821
|
}
|
|
2133
2822
|
// HNSW_NO_AUTOVERSION kill-switch: when set, a NEW index initializes as legacy rather than
|
|
2134
2823
|
// versioned. process.env values are strings, so a bare truthiness check would treat "0"/"false"
|
|
@@ -2557,6 +3246,8 @@ function declareTable(target, tableDefinition) {
|
|
|
2557
3246
|
let fullTextValuesForPersistence;
|
|
2558
3247
|
let fullTextPersistencePending = false;
|
|
2559
3248
|
let activeFullTextIndexes;
|
|
3249
|
+
let fullTextIndexGenerationMap = Object.create(null);
|
|
3250
|
+
let fullTextIndexRetirementNames;
|
|
2560
3251
|
let restoreFullTextLiveState;
|
|
2561
3252
|
let armFullTextLiveStateRestore;
|
|
2562
3253
|
const attributesToIndex = [];
|
|
@@ -2600,7 +3291,9 @@ function declareTable(target, tableDefinition) {
|
|
|
2600
3291
|
let persistedPrimary = persistedPrimaryDescriptor(Table.dbisDB);
|
|
2601
3292
|
let persistedFullTextValues = persistedPrimary.descriptor?.fullTextIndexes;
|
|
2602
3293
|
const incomingFullTextValues = fullTextIndexesExplicit ? fullTextIndexes : [];
|
|
3294
|
+
let fullTextValidationAttributes;
|
|
2603
3295
|
if ((persistedFullTextValues !== undefined && (rootStore instanceof rocksdb_js_1.RocksDatabase || fullTextIndexesExplicit)) ||
|
|
3296
|
+
persistedPrimary.descriptor?.fullTextIndexRetirements !== undefined ||
|
|
2604
3297
|
Table.fullTextIndexes?.length > 0 ||
|
|
2605
3298
|
(fullTextIndexesExplicit && (!Array.isArray(incomingFullTextValues) || incomingFullTextValues.length > 0))) {
|
|
2606
3299
|
if (!(rootStore instanceof rocksdb_js_1.RocksDatabase)) {
|
|
@@ -2609,6 +3302,7 @@ function declareTable(target, tableDefinition) {
|
|
|
2609
3302
|
persistedFullTextValues = persistedPrimary.descriptor?.fullTextIndexes;
|
|
2610
3303
|
}
|
|
2611
3304
|
const originalAttributes = Table.attributes.slice();
|
|
3305
|
+
fullTextIndexRetirementNames = (0, fullTextSchema_ts_1.persistedFullTextIndexNames)(persistedPrimary.descriptor?.fullTextIndexRetirements);
|
|
2612
3306
|
const relationshipAttributes = originalAttributes.filter((attribute) => attribute.relationship);
|
|
2613
3307
|
const originalMetadata = {
|
|
2614
3308
|
description: Table.description,
|
|
@@ -2629,14 +3323,15 @@ function declareTable(target, tableDefinition) {
|
|
|
2629
3323
|
Object.assign(Table, originalMetadata);
|
|
2630
3324
|
Table.properties = (0, jsonSchemaTypes_ts_1.projectAttributesToProperties)(restored);
|
|
2631
3325
|
const restoredPrimary = persistedPrimaryDescriptor(Table.dbisDB).descriptor;
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
3326
|
+
const restoredFullTextIndexes = rootStore instanceof rocksdb_js_1.RocksDatabase && restoredPrimary?.audit === true
|
|
3327
|
+
? (0, fullTextSchemaLifecycle_ts_1.readPersistedFullTextDefinitions)(restoredPrimary?.fullTextIndexes, restored, fullTextWarning)
|
|
3328
|
+
: [];
|
|
3329
|
+
Table.fullTextIndexes = restoredFullTextIndexes;
|
|
3330
|
+
Table.fullTextIndexGenerations = persistedFullTextIndexGenerations(restoredPrimary?.fullTextIndexGenerations, restoredFullTextIndexes);
|
|
3331
|
+
Table.fullTextIndexRetirements = (0, fullTextSchema_ts_1.persistedFullTextIndexNames)(restoredPrimary?.fullTextIndexRetirements);
|
|
2636
3332
|
Table.schemaVersion++;
|
|
2637
3333
|
Table.updatedAttributes();
|
|
2638
|
-
|
|
2639
|
-
Table.derivedIndexRuntime = (0, hnswDerivedIndex_ts_1.attachDerivedIndexes)(Table);
|
|
3334
|
+
(0, derivedIndexes_ts_1.refreshDerivedIndexes)(Table);
|
|
2640
3335
|
};
|
|
2641
3336
|
};
|
|
2642
3337
|
const durableAttributes = catalogAttributes(Table.dbisDB);
|
|
@@ -2656,6 +3351,10 @@ function declareTable(target, tableDefinition) {
|
|
|
2656
3351
|
validationAttributes.unshift(durablePrimary);
|
|
2657
3352
|
}
|
|
2658
3353
|
}
|
|
3354
|
+
fullTextValidationAttributes = validationAttributes;
|
|
3355
|
+
// Before the full-text branches can write an interim descriptor; the check on the merged list
|
|
3356
|
+
// below stays authoritative.
|
|
3357
|
+
(0, embedHook_ts_1.assertDerivedFieldOwnership)(validationAttributes);
|
|
2659
3358
|
const persistedAudit = persistedPrimary.descriptor?.audit;
|
|
2660
3359
|
const durableAudit = persistedAudit === true;
|
|
2661
3360
|
const finalAudit = origin === 'cluster'
|
|
@@ -2683,25 +3382,59 @@ function declareTable(target, tableDefinition) {
|
|
|
2683
3382
|
throw new hdbError_ts_1.ClientError(`Table '${databaseName}.${tableName}' cannot use @fullText with the LMDB storage engine`, 400);
|
|
2684
3383
|
if (compiled.length > 0 && !finalAudit)
|
|
2685
3384
|
throw new hdbError_ts_1.ClientError(`Table '${databaseName}.${tableName}' must enable audit logging before using @fullText because its transaction log is the derived-index recovery source`, 400);
|
|
2686
|
-
const interim = (0, fullTextSchemaLifecycle_ts_1.safeInterimFullTextDefinitions)(persistedFullTextValues, validationAttributes, compiled);
|
|
2687
3385
|
const pinAudit = compiled.length > 0 && persistedPrimary.descriptor?.audit !== true;
|
|
2688
|
-
|
|
3386
|
+
const requestedNames = new Set(compiled.map(({ name }) => name));
|
|
3387
|
+
const durableDefinitions = (0, fullTextSchemaLifecycle_ts_1.readPersistedFullTextDefinitions)(persistedFullTextValues, durableAttributes, fullTextWarning);
|
|
3388
|
+
const removedNames = durableDefinitions.map(({ name }) => name).filter((name) => !requestedNames.has(name));
|
|
3389
|
+
const invalidatedNames = durableDefinitions
|
|
3390
|
+
.filter(({ name }) => requestedNames.has(name))
|
|
3391
|
+
.filter((definition) => {
|
|
3392
|
+
try {
|
|
3393
|
+
(0, fullTextSchema_ts_1.compileFullTextDefinitions)([definition], validationAttributes);
|
|
3394
|
+
return false;
|
|
3395
|
+
}
|
|
3396
|
+
catch (error) {
|
|
3397
|
+
if (error instanceof hdbError_ts_1.ClientError)
|
|
3398
|
+
return true;
|
|
3399
|
+
throw error;
|
|
3400
|
+
}
|
|
3401
|
+
})
|
|
3402
|
+
.map(({ name }) => name);
|
|
3403
|
+
const transitionRetirements = new Set([...removedNames, ...invalidatedNames]);
|
|
3404
|
+
if (pinAudit || transitionRetirements.size > 0) {
|
|
2689
3405
|
const interimPrimary = { ...persistedPrimary.descriptor };
|
|
2690
3406
|
if (pinAudit)
|
|
2691
3407
|
interimPrimary.audit = true;
|
|
2692
|
-
if (
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
3408
|
+
if (transitionRetirements.size > 0) {
|
|
3409
|
+
const interimDefinitions = durableDefinitions.filter(({ name }) => requestedNames.has(name));
|
|
3410
|
+
if (interimDefinitions.length > 0) {
|
|
3411
|
+
interimPrimary.fullTextIndexes = interimDefinitions;
|
|
3412
|
+
interimPrimary.fullTextIndexGenerations = persistedFullTextIndexGenerations(persistedPrimary.descriptor?.fullTextIndexGenerations, interimDefinitions);
|
|
3413
|
+
}
|
|
3414
|
+
else {
|
|
3415
|
+
delete interimPrimary.fullTextIndexes;
|
|
3416
|
+
delete interimPrimary.fullTextIndexGenerations;
|
|
3417
|
+
}
|
|
3418
|
+
const retirements = new Set((0, fullTextSchema_ts_1.persistedFullTextIndexNames)(persistedPrimary.descriptor?.fullTextIndexRetirements));
|
|
3419
|
+
for (const name of transitionRetirements)
|
|
3420
|
+
retirements.add(name);
|
|
3421
|
+
for (const name of requestedNames)
|
|
3422
|
+
if (!transitionRetirements.has(name))
|
|
3423
|
+
retirements.delete(name);
|
|
3424
|
+
fullTextIndexRetirementNames = [...retirements].sort();
|
|
3425
|
+
if (fullTextIndexRetirementNames.length > 0)
|
|
3426
|
+
interimPrimary.fullTextIndexRetirements = fullTextIndexRetirementNames.map((name) => ({ name }));
|
|
3427
|
+
else
|
|
3428
|
+
delete interimPrimary.fullTextIndexRetirements;
|
|
3429
|
+
}
|
|
2696
3430
|
armFullTextLiveStateRestore?.();
|
|
2697
3431
|
Table.dbisDB.put(persistedPrimary.key, interimPrimary);
|
|
2698
3432
|
if (pinAudit && Table.audit !== true)
|
|
2699
3433
|
Table.enableAuditing();
|
|
2700
|
-
Table.fullTextIndexes = rootStore instanceof rocksdb_js_1.RocksDatabase && finalAudit ? interim : [];
|
|
2701
3434
|
hasChanges = true;
|
|
2702
3435
|
}
|
|
2703
3436
|
fullTextValuesForPersistence = compiled;
|
|
2704
|
-
fullTextPersistencePending = !(0, fullTextSchemaLifecycle_ts_1.definitionsEqual)(compiled,
|
|
3437
|
+
fullTextPersistencePending = !(0, fullTextSchemaLifecycle_ts_1.definitionsEqual)(compiled, persistedFullTextValues);
|
|
2705
3438
|
activeFullTextIndexes = compiled;
|
|
2706
3439
|
}
|
|
2707
3440
|
else {
|
|
@@ -2712,6 +3445,16 @@ function declareTable(target, tableDefinition) {
|
|
|
2712
3445
|
activeFullTextIndexes = rootStore instanceof rocksdb_js_1.RocksDatabase && finalAudit ? retained : [];
|
|
2713
3446
|
}
|
|
2714
3447
|
}
|
|
3448
|
+
if (activeFullTextIndexes !== undefined) {
|
|
3449
|
+
const durableGenerationDefinitions = (0, fullTextSchemaLifecycle_ts_1.readPersistedFullTextDefinitions)(persistedFullTextValues, fullTextValidationAttributes ?? Table.attributes, fullTextWarning);
|
|
3450
|
+
const finalGenerationDefinitions = (0, fullTextSchemaLifecycle_ts_1.readPersistedFullTextDefinitions)(fullTextValuesForPersistence ?? persistedFullTextValues, fullTextValidationAttributes ?? Table.attributes, fullTextWarning);
|
|
3451
|
+
fullTextIndexGenerationMap = (0, fullTextSchema_ts_1.reconcileFullTextIndexGenerations)(durableGenerationDefinitions, persistedPrimary.descriptor?.fullTextIndexGenerations, finalGenerationDefinitions, createFullTextIndexGeneration);
|
|
3452
|
+
(0, derivedIndexes_ts_1.assertFullTextActivationSupported)(rootStore, databaseName, tableName, activeFullTextIndexes);
|
|
3453
|
+
if (activeFullTextIndexes.length > 0 &&
|
|
3454
|
+
rootStore instanceof rocksdb_js_1.RocksDatabase &&
|
|
3455
|
+
(0, derivedIndexRegistry_ts_1.fullTextClearInProgress)(rootStore, Table.tableId))
|
|
3456
|
+
throw new hdbError_ts_1.ClientError(`Cannot activate @fullText on '${databaseName}.${tableName}' while Table.clear() is in progress`, 409);
|
|
3457
|
+
}
|
|
2715
3458
|
// it table already exists, get the split segments setting
|
|
2716
3459
|
if (splitSegments == undefined)
|
|
2717
3460
|
splitSegments = Table.splitSegments;
|
|
@@ -2742,6 +3485,9 @@ function declareTable(target, tableDefinition) {
|
|
|
2742
3485
|
if (existingPrimary)
|
|
2743
3486
|
attributes = [existingPrimary, ...attributes];
|
|
2744
3487
|
}
|
|
3488
|
+
// On the complete list (a peer's fields merged, an omitted primary key inherited) and before
|
|
3489
|
+
// it replaces the live one or reaches the catalog, so a refused declaration changes nothing.
|
|
3490
|
+
(0, embedHook_ts_1.assertDerivedFieldOwnership)(attributes);
|
|
2745
3491
|
armFullTextLiveStateRestore?.();
|
|
2746
3492
|
Table.attributes.splice(0, Table.attributes.length, ...attributes);
|
|
2747
3493
|
// Re-assert from the live declaration so a stale value on disk (replicated event,
|
|
@@ -2760,6 +3506,8 @@ function declareTable(target, tableDefinition) {
|
|
|
2760
3506
|
Table.cacheControl = cacheControl;
|
|
2761
3507
|
}
|
|
2762
3508
|
else {
|
|
3509
|
+
if (Array.isArray(attributes))
|
|
3510
|
+
(0, embedHook_ts_1.assertDerivedFieldOwnership)(attributes);
|
|
2763
3511
|
if (fullTextIndexesExplicit) {
|
|
2764
3512
|
if (origin === 'cluster') {
|
|
2765
3513
|
const merged = (0, fullTextSchemaLifecycle_ts_1.mergePeerFullTextDefinitions)([], fullTextIndexes, attributes, fullTextWarning);
|
|
@@ -2789,8 +3537,12 @@ function declareTable(target, tableDefinition) {
|
|
|
2789
3537
|
// Old readers treat every attribute row as live schema, so relationships stay on the ignored primary descriptor.
|
|
2790
3538
|
if (relationshipDefinitions)
|
|
2791
3539
|
primaryKeyAttribute.relationships = relationshipDefinitions;
|
|
2792
|
-
if (Array.isArray(fullTextValuesForPersistence) && fullTextValuesForPersistence.length > 0)
|
|
3540
|
+
if (Array.isArray(fullTextValuesForPersistence) && fullTextValuesForPersistence.length > 0) {
|
|
3541
|
+
const durableFullTextIndexes = (0, fullTextSchemaLifecycle_ts_1.readPersistedFullTextDefinitions)(fullTextValuesForPersistence, attributes, fullTextWarning);
|
|
3542
|
+
fullTextIndexGenerationMap = (0, fullTextSchema_ts_1.reconcileFullTextIndexGenerations)([], undefined, durableFullTextIndexes, createFullTextIndexGeneration);
|
|
2793
3543
|
primaryKeyAttribute.fullTextIndexes = fullTextValuesForPersistence;
|
|
3544
|
+
primaryKeyAttribute.fullTextIndexGenerations = fullTextIndexGenerationMap;
|
|
3545
|
+
}
|
|
2794
3546
|
// can't change compression after the fact (except threshold), so save only when we create the table
|
|
2795
3547
|
primaryKeyAttribute.compression = getDefaultCompression();
|
|
2796
3548
|
if (trackDeletes)
|
|
@@ -2849,6 +3601,8 @@ function declareTable(target, tableDefinition) {
|
|
|
2849
3601
|
target.adopt(attributesDbi);
|
|
2850
3602
|
markInternalDbiNonVersioned(attributesDbi);
|
|
2851
3603
|
exclusiveLock(); // get an exclusive lock on the database so we can verify that we are the only thread creating the table (and assigning the table id)
|
|
3604
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase && (0, derivedIndexRegistry_ts_1.fullTextRetirementInProgress)(rootStore, tableName))
|
|
3605
|
+
throw new hdbError_ts_1.ClientError(`Cannot create '${databaseName}.${tableName}' while its previous full-text storage is being retired`, 409);
|
|
2852
3606
|
const existingTableMeta = attributesDbi.getSync(dbiName);
|
|
2853
3607
|
if (existingTableMeta && !existingTableMeta.dropping) {
|
|
2854
3608
|
// table was created while we were setting up; the lock is not reentrant, so release
|
|
@@ -2864,7 +3618,8 @@ function declareTable(target, tableDefinition) {
|
|
|
2864
3618
|
// create below starts from a clean slate; treating the tombstoned
|
|
2865
3619
|
// entry as an existing table would recurse forever on the stale
|
|
2866
3620
|
// catalog row.
|
|
2867
|
-
completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName)
|
|
3621
|
+
if (!completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName))
|
|
3622
|
+
throw new hdbError_ts_1.ClientError(`Cannot create '${databaseName}.${tableName}' while its interrupted full-text drop is being retired`, 409);
|
|
2868
3623
|
// This resolves the drop without ever going through the schema-load
|
|
2869
3624
|
// reconcile below, which is the only other place that returns a spent
|
|
2870
3625
|
// budget. Without clearing it here too, a table that gets dropped again
|
|
@@ -2921,6 +3676,8 @@ function declareTable(target, tableDefinition) {
|
|
|
2921
3676
|
indices: {},
|
|
2922
3677
|
attributes,
|
|
2923
3678
|
fullTextIndexes: activeFullTextIndexes ?? [],
|
|
3679
|
+
fullTextIndexGenerations: fullTextIndexGenerationMap,
|
|
3680
|
+
fullTextIndexRetirements: fullTextIndexRetirementNames ?? [],
|
|
2924
3681
|
schemaDefined,
|
|
2925
3682
|
dbisDB: attributesDbi,
|
|
2926
3683
|
description,
|
|
@@ -3176,8 +3933,12 @@ function declareTable(target, tableDefinition) {
|
|
|
3176
3933
|
JSON.stringify(attributeDescriptor.elements) !== JSON.stringify(attribute.elements) ||
|
|
3177
3934
|
// Include `embed` so a source/model change refreshes the embed registry.
|
|
3178
3935
|
JSON.stringify(attributeDescriptor.embed) !== JSON.stringify(attribute.embed);
|
|
3179
|
-
// any metadata difference (drives persistence)
|
|
3180
|
-
|
|
3936
|
+
// any metadata difference (drives persistence). `decide` is compared here and not in
|
|
3937
|
+
// `commonChanged`: a changed directive refreshes the decide registry, and a stored decision
|
|
3938
|
+
// does not depend on the model, so an indexed decision attribute must not rebuild its index.
|
|
3939
|
+
const changed = commonChanged ||
|
|
3940
|
+
JSON.stringify(attributeDescriptor?.indexed) !== JSON.stringify(attribute.indexed) ||
|
|
3941
|
+
JSON.stringify(attributeDescriptor?.decide) !== JSON.stringify(attribute.decide);
|
|
3181
3942
|
// structure-affecting difference (drives reindex) — ignores search-only option changes and
|
|
3182
3943
|
// representation-only differences (key order, string-vs-number) via canonicalIndexKey
|
|
3183
3944
|
const indexOptionsStructurallyChanged = canonicalIndexKey(attributeDescriptor?.indexed) !== canonicalIndexKey(attribute.indexed);
|
|
@@ -3337,15 +4098,39 @@ function declareTable(target, tableDefinition) {
|
|
|
3337
4098
|
attributesDbi.put(dbiKey, attribute);
|
|
3338
4099
|
}
|
|
3339
4100
|
}
|
|
3340
|
-
if (
|
|
4101
|
+
if (!deferredPrimaryRow &&
|
|
4102
|
+
(fullTextPersistencePending ||
|
|
4103
|
+
(rootStore instanceof rocksdb_js_1.RocksDatabase &&
|
|
4104
|
+
activeFullTextIndexes !== undefined &&
|
|
4105
|
+
Array.isArray(fullTextValuesForPersistence)))) {
|
|
3341
4106
|
const { key, descriptor } = persistedPrimaryDescriptor(attributesDbi);
|
|
3342
4107
|
if (descriptor && !tableIsDropping(descriptor, key)) {
|
|
3343
4108
|
const updatedPrimary = { ...descriptor };
|
|
3344
|
-
if (Array.isArray(fullTextValuesForPersistence) && fullTextValuesForPersistence.length > 0)
|
|
4109
|
+
if (Array.isArray(fullTextValuesForPersistence) && fullTextValuesForPersistence.length > 0) {
|
|
3345
4110
|
updatedPrimary.fullTextIndexes = fullTextValuesForPersistence;
|
|
3346
|
-
|
|
4111
|
+
updatedPrimary.fullTextIndexGenerations = fullTextIndexGenerationMap;
|
|
4112
|
+
}
|
|
4113
|
+
else {
|
|
3347
4114
|
delete updatedPrimary.fullTextIndexes;
|
|
3348
|
-
|
|
4115
|
+
delete updatedPrimary.fullTextIndexGenerations;
|
|
4116
|
+
}
|
|
4117
|
+
const activeNames = new Set((0, fullTextSchema_ts_1.persistedFullTextIndexNames)(updatedPrimary.fullTextIndexes));
|
|
4118
|
+
const retirements = new Set((0, fullTextSchema_ts_1.persistedFullTextIndexNames)(descriptor.fullTextIndexRetirements));
|
|
4119
|
+
for (const name of (0, fullTextSchema_ts_1.persistedFullTextIndexNames)(descriptor.fullTextIndexes))
|
|
4120
|
+
if (!activeNames.has(name))
|
|
4121
|
+
retirements.add(name);
|
|
4122
|
+
for (const name of activeNames)
|
|
4123
|
+
retirements.delete(name);
|
|
4124
|
+
fullTextIndexRetirementNames = [...retirements].sort();
|
|
4125
|
+
if (fullTextIndexRetirementNames.length > 0)
|
|
4126
|
+
updatedPrimary.fullTextIndexRetirements = fullTextIndexRetirementNames.map((name) => ({ name }));
|
|
4127
|
+
else
|
|
4128
|
+
delete updatedPrimary.fullTextIndexRetirements;
|
|
4129
|
+
if (!(0, fullTextSchemaLifecycle_ts_1.definitionsEqual)(descriptor.fullTextIndexes, updatedPrimary.fullTextIndexes) ||
|
|
4130
|
+
JSON.stringify(descriptor.fullTextIndexGenerations ?? {}) !==
|
|
4131
|
+
JSON.stringify(updatedPrimary.fullTextIndexGenerations ?? {}) ||
|
|
4132
|
+
JSON.stringify(descriptor.fullTextIndexRetirements ?? []) !==
|
|
4133
|
+
JSON.stringify(updatedPrimary.fullTextIndexRetirements ?? [])) {
|
|
3349
4134
|
exclusiveLock();
|
|
3350
4135
|
attributesDbi.put(key, updatedPrimary);
|
|
3351
4136
|
hasChanges = true;
|
|
@@ -3414,8 +4199,11 @@ function declareTable(target, tableDefinition) {
|
|
|
3414
4199
|
finally {
|
|
3415
4200
|
releaseLock();
|
|
3416
4201
|
}
|
|
3417
|
-
if (activeFullTextIndexes !== undefined)
|
|
4202
|
+
if (activeFullTextIndexes !== undefined) {
|
|
3418
4203
|
Table.fullTextIndexes = activeFullTextIndexes;
|
|
4204
|
+
Table.fullTextIndexGenerations = fullTextIndexGenerationMap;
|
|
4205
|
+
Table.fullTextIndexRetirements = fullTextIndexRetirementNames ?? [];
|
|
4206
|
+
}
|
|
3419
4207
|
if (hasChanges || refreshRelationshipAttributes)
|
|
3420
4208
|
Table.schemaVersion++;
|
|
3421
4209
|
if (hasChanges || refreshRelationshipAttributes || refreshedLiveAttributes)
|
|
@@ -3430,8 +4218,7 @@ function declareTable(target, tableDefinition) {
|
|
|
3430
4218
|
}
|
|
3431
4219
|
else if (hasChanges)
|
|
3432
4220
|
signalling.signalSchemaChange(new itc_js_1.SchemaEventMsg(process.pid, 'schema-change', Table.databaseName, Table.tableName, undefined, branchPath));
|
|
3433
|
-
|
|
3434
|
-
Table.derivedIndexRuntime = (0, hnswDerivedIndex_ts_1.attachDerivedIndexes)(Table);
|
|
4221
|
+
(0, derivedIndexes_ts_1.refreshDerivedIndexes)(Table);
|
|
3435
4222
|
Table.origin = origin;
|
|
3436
4223
|
// scope-private: replication and other global subscribers must not learn of a branch class
|
|
3437
4224
|
if ((hasChanges || refreshRelationshipAttributes) && !target.branch) {
|
|
@@ -4273,13 +5060,61 @@ function scheduleGenerationReclaim(rootStore, attributesDbi, databaseName) {
|
|
|
4273
5060
|
state.timer.unref?.();
|
|
4274
5061
|
state.delay = Math.min(state.delay * 2, 60_000);
|
|
4275
5062
|
}
|
|
5063
|
+
const interruptedDropRetirements = new WeakMap();
|
|
5064
|
+
function trackInterruptedDropRetirement(rootStore, task) {
|
|
5065
|
+
let tasks = interruptedDropRetirements.get(rootStore);
|
|
5066
|
+
if (!tasks)
|
|
5067
|
+
interruptedDropRetirements.set(rootStore, (tasks = new Set()));
|
|
5068
|
+
tasks.add(task);
|
|
5069
|
+
const remove = () => {
|
|
5070
|
+
tasks.delete(task);
|
|
5071
|
+
};
|
|
5072
|
+
task.then(remove, remove);
|
|
5073
|
+
}
|
|
5074
|
+
async function settleInterruptedDropRetirements(rootStores) {
|
|
5075
|
+
for (;;) {
|
|
5076
|
+
const tasks = [...rootStores].flatMap((rootStore) => [...(interruptedDropRetirements.get(rootStore) ?? [])]);
|
|
5077
|
+
if (tasks.length === 0)
|
|
5078
|
+
return;
|
|
5079
|
+
await Promise.allSettled(tasks);
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
4276
5082
|
/** Finishes the durable logical state of a drop that stopped after writing its tombstone. */
|
|
4277
|
-
function completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName) {
|
|
5083
|
+
function completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName, fullTextRetired = false) {
|
|
4278
5084
|
logger.debug(`Completing interrupted drop of table ${databaseName}.${tableName}`);
|
|
4279
5085
|
const catalogRows = [...attributesDbi.getRange({ start: tableName + '/', end: tableName + '0' })];
|
|
4280
5086
|
const bareEntry = catalogRows.find(({ key }) => key === tableName + '/');
|
|
4281
5087
|
const primaryEntry = (bareEntry?.value?.isPrimaryKey ? bareEntry : undefined) ?? catalogRows.find(({ value }) => value?.isPrimaryKey);
|
|
4282
5088
|
const tombstoneEntry = bareEntry ?? primaryEntry;
|
|
5089
|
+
if (rootStore instanceof rocksdb_js_1.RocksDatabase && !fullTextRetired) {
|
|
5090
|
+
const names = [
|
|
5091
|
+
...new Set([
|
|
5092
|
+
...(0, fullTextSchema_ts_1.persistedFullTextIndexNames)(primaryEntry?.value?.fullTextIndexes),
|
|
5093
|
+
...(0, fullTextSchema_ts_1.persistedFullTextIndexNames)(primaryEntry?.value?.fullTextIndexRetirements),
|
|
5094
|
+
]),
|
|
5095
|
+
];
|
|
5096
|
+
if (names.length > 0) {
|
|
5097
|
+
const releaseRetirement = (0, derivedIndexRegistry_ts_1.acquireFullTextRetirementFence)(rootStore, tableName);
|
|
5098
|
+
if (releaseRetirement) {
|
|
5099
|
+
const retirement = (async () => {
|
|
5100
|
+
try {
|
|
5101
|
+
const retired = await (0, derivedIndexes_ts_1.retireFullTextIndexes)({ databaseName, tableName, primaryStore: { rootStore } }, names.map((name) => ({ name })), () => rootStore.status === 'open' && !(0, DatabaseTransaction_ts_1.databaseCommitsSuspended)(rootStore));
|
|
5102
|
+
if (!retired || rootStore.status === 'closed' || (0, DatabaseTransaction_ts_1.databaseCommitsSuspended)(rootStore))
|
|
5103
|
+
return;
|
|
5104
|
+
await (0, Table_ts_1.withUpdateAttributesLockNonBlocking)(rootStore, `complete interrupted drop of '${databaseName}.${tableName}'`, () => completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableName, true));
|
|
5105
|
+
}
|
|
5106
|
+
catch (error) {
|
|
5107
|
+
logger.warn(`Could not retire full-text storage for interrupted drop ${databaseName}.${tableName}`, error);
|
|
5108
|
+
}
|
|
5109
|
+
finally {
|
|
5110
|
+
releaseRetirement();
|
|
5111
|
+
}
|
|
5112
|
+
})();
|
|
5113
|
+
trackInterruptedDropRetirement(rootStore, retirement);
|
|
5114
|
+
}
|
|
5115
|
+
return false;
|
|
5116
|
+
}
|
|
5117
|
+
}
|
|
4283
5118
|
if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
|
|
4284
5119
|
const tombstone = tombstoneEntry?.value;
|
|
4285
5120
|
const generation = tombstone?.generation ?? primaryEntry?.value?.generation;
|
|
@@ -4327,6 +5162,7 @@ function completeInterruptedDrop(rootStore, attributesDbi, databaseName, tableNa
|
|
|
4327
5162
|
}
|
|
4328
5163
|
if (tombstoneEntry)
|
|
4329
5164
|
attributesDbi.removeSync(tombstoneEntry.key);
|
|
5165
|
+
return true;
|
|
4330
5166
|
}
|
|
4331
5167
|
function dropTableMeta({ table: tableName, database: databaseName }) {
|
|
4332
5168
|
const rootStore = database({ database: databaseName, table: tableName });
|