@prisma/orm-target-mongo 8.0.0-rc.17-dev.27 → 8.0.0-rc.17-dev.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.mjs +1 -1
- package/dist/adapter.mjs.map +1 -1
- package/dist/adapter__control.mjs +3 -3
- package/dist/adapter__control.mjs.map +1 -1
- package/dist/adapter__runtime.mjs +2 -2
- package/dist/adapter__runtime.mjs.map +1 -1
- package/dist/{codecs-I_SlLNTn-BsshuqIx.mjs → codecs-D6yLMoD2-Bn8-UhA5.mjs} +4 -7
- package/dist/codecs-D6yLMoD2-Bn8-UhA5.mjs.map +1 -0
- package/dist/{control-B0JolOVp.mjs → control-BcqDWXwi.mjs} +648 -107
- package/dist/control-BcqDWXwi.mjs.map +1 -0
- package/dist/{control-B1h4Nci1.d.mts → control-PcpFueeY.d.mts} +24 -7
- package/dist/control-PcpFueeY.d.mts.map +1 -0
- package/dist/{descriptor-meta-BtMd3yW0--oRvmHvL.mjs → descriptor-meta-BtglG_aY-DhbxL8Gw.mjs} +3 -3
- package/dist/{descriptor-meta-BtMd3yW0--oRvmHvL.mjs.map → descriptor-meta-BtglG_aY-DhbxL8Gw.mjs.map} +1 -1
- package/dist/{descriptor-meta-runtime-DpdkUA8--CvurCIDn.mjs → descriptor-meta-runtime-CwIqqZ8J-C3UNM2K8.mjs} +3 -3
- package/dist/{descriptor-meta-runtime-DpdkUA8--CvurCIDn.mjs.map → descriptor-meta-runtime-CwIqqZ8J-C3UNM2K8.mjs.map} +1 -1
- package/dist/driver.d.mts +1 -1
- package/dist/driver.mjs +1 -1
- package/dist/driver__control.d.mts +1 -1
- package/dist/driver__control.mjs +1 -1
- package/dist/driver__control.mjs.map +1 -1
- package/dist/{migration-C9WLWp1C.d.mts → migration-CPNxGxSK.d.mts} +12 -5
- package/dist/migration-CPNxGxSK.d.mts.map +1 -0
- package/dist/{migration-factories-CgyFxFZT-D4wek2HG.mjs → migration-factories-BmDfC5c5-BI-Wusuk.mjs} +139 -4
- package/dist/migration-factories-BmDfC5c5-BI-Wusuk.mjs.map +1 -0
- package/dist/{mongo-adapter-BW-kRaXm-B74CBt9J.mjs → mongo-adapter-DWNaz2UT-HiI8mJ7J.mjs} +10 -8
- package/dist/mongo-adapter-DWNaz2UT-HiI8mJ7J.mjs.map +1 -0
- package/dist/{mongo-driver-BTjYMBKs-CN2niXFQ.d.mts → mongo-driver-CTyvKDUg-bwRH7EDL.d.mts} +4 -3
- package/dist/{mongo-driver-BTjYMBKs-CN2niXFQ.d.mts.map → mongo-driver-CTyvKDUg-bwRH7EDL.d.mts.map} +1 -1
- package/dist/{mongo-driver-DLXugjhP-BQhpUAg7.mjs → mongo-driver-D-ikvsKV-RwsHzWwL.mjs} +11 -4
- package/dist/mongo-driver-D-ikvsKV-RwsHzWwL.mjs.map +1 -0
- package/dist/mongo-target-errors-Btf3ML1w-B5EMYbz1.mjs +9 -0
- package/dist/mongo-target-errors-Btf3ML1w-B5EMYbz1.mjs.map +1 -0
- package/dist/{op-factory-call-DgkZzsyP-j04fJDt5.d.mts → op-factory-call-BoYr7vvM-jFNdzIK6.d.mts} +40 -6
- package/dist/op-factory-call-BoYr7vvM-jFNdzIK6.d.mts.map +1 -0
- package/dist/{prisma6-binding-DoapgPvm.mjs → prisma6-binding-i6ORtPD9.mjs} +2 -2
- package/dist/{prisma6-binding-DoapgPvm.mjs.map → prisma6-binding-i6ORtPD9.mjs.map} +1 -1
- package/dist/target.d.mts +4 -4
- package/dist/target.mjs +5 -5
- package/dist/target__codecs.mjs +1 -1
- package/dist/target__control.d.mts +3 -3
- package/dist/target__control.mjs +2 -2
- package/dist/target__migration.d.mts +2 -2
- package/dist/target__migration.mjs +2 -2
- package/dist/target__pack.mjs +1 -1
- package/dist/target__pack.mjs.map +1 -1
- package/dist/target__prisma6-binding.mjs +1 -1
- package/dist/target__runtime.mjs +1 -1
- package/dist/target__runtime.mjs.map +1 -1
- package/package.json +9 -9
- package/dist/codecs-I_SlLNTn-BsshuqIx.mjs.map +0 -1
- package/dist/control-B0JolOVp.mjs.map +0 -1
- package/dist/control-B1h4Nci1.d.mts.map +0 -1
- package/dist/migration-C9WLWp1C.d.mts.map +0 -1
- package/dist/migration-factories-CgyFxFZT-D4wek2HG.mjs.map +0 -1
- package/dist/mongo-adapter-BW-kRaXm-B74CBt9J.mjs.map +0 -1
- package/dist/mongo-driver-DLXugjhP-BQhpUAg7.mjs.map +0 -1
- package/dist/op-factory-call-DgkZzsyP-j04fJDt5.d.mts.map +0 -1
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as mongoTargetDescriptorMeta } from "./descriptor-meta-
|
|
3
|
-
import { a as dropCollection, n as createCollection, o as dropIndex, r as createIndex, t as collMod } from "./migration-factories-
|
|
1
|
+
import { t as mongoTargetError } from "./mongo-target-errors-Btf3ML1w-B5EMYbz1.mjs";
|
|
2
|
+
import { t as mongoTargetDescriptorMeta } from "./descriptor-meta-BtglG_aY-DhbxL8Gw.mjs";
|
|
3
|
+
import { a as dropCollection, c as renameField, d as unsetField, n as createCollection, o as dropIndex, r as createIndex, s as renameCollection, t as collMod, u as unrewritableFieldNameReason } from "./migration-factories-BmDfC5c5-BI-Wusuk.mjs";
|
|
4
4
|
import { type } from "arktype";
|
|
5
5
|
import { blindCast } from "@prisma/orm-framework/utils/casts";
|
|
6
6
|
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
7
7
|
import { NamespaceBase, UNBOUND_NAMESPACE_ID, elementCoordinates, freezeNode, hydrateNamespaceEntities } from "@prisma/orm-framework/components/ir";
|
|
8
|
-
import { APP_SPACE_ID, VERIFY_CODE_SCHEMA_FAILURE, contractSnapshotJsonSpecifier, contractSnapshotTypesSpecifier, describeMigrationStatement, issueOutcome } from "@prisma/orm-framework/components/control";
|
|
9
|
-
import { CollModCommand, CreateCollectionCommand, CreateIndexCommand, DropCollectionCommand, DropIndexCommand, ListCollectionsCommand, ListIndexesCommand, MongoAndExpr, MongoExistsExpr, MongoFieldFilter, MongoNotExpr, MongoOrExpr } from "@prisma/orm-family-mongo/query-ast/control";
|
|
10
|
-
import {
|
|
8
|
+
import { APP_SPACE_ID, VERIFY_CODE_SCHEMA_FAILURE, contractSnapshotJsonSpecifier, contractSnapshotTypesSpecifier, describeMigrationStatement, issueOutcome, modelDisplayName } from "@prisma/orm-framework/components/control";
|
|
9
|
+
import { CollModCommand, CreateCollectionCommand, CreateIndexCommand, DropCollectionCommand, DropIndexCommand, ListCollectionsCommand, ListIndexesCommand, MongoAndExpr, MongoExistsExpr, MongoFieldFilter, MongoNotExpr, MongoOrExpr, RenameCollectionCommand, isMongoDmlOperation } from "@prisma/orm-family-mongo/query-ast/control";
|
|
10
|
+
import { AggregateCommand, MongoAddFieldsStage, MongoLimitStage, MongoLookupStage, MongoMatchStage, MongoMergeStage, MongoProjectStage, MongoSortStage, RawAggregateCommand, RawDeleteManyCommand, RawDeleteOneCommand, RawFindOneAndDeleteCommand, RawFindOneAndUpdateCommand, RawInsertManyCommand, RawInsertOneCommand, RawUpdateManyCommand, RawUpdateOneCommand } from "@prisma/orm-family-mongo/query-ast/execution";
|
|
11
|
+
import { canonicalizeSchemasForVerification, contractToMongoSchemaIR, diffMongoSchemas, fieldRewriteOf } from "@prisma/orm-family-mongo/family/control";
|
|
11
12
|
import { notOk, ok } from "@prisma/orm-framework/utils/result";
|
|
12
13
|
import { MongoContractSerializerBase, MongoSchemaVerifierBase } from "@prisma/orm-family-mongo/family/ir";
|
|
13
14
|
import { MongoStorage } from "@prisma/orm-family-mongo/contract";
|
|
14
15
|
import { composeMongoEntityKinds } from "@prisma/orm-family-mongo/contract/entity-kinds";
|
|
15
|
-
import { asNamespaceId } from "@prisma/orm-framework/contract/types";
|
|
16
|
-
import { canonicalize, deepEqual } from "@prisma/orm-family-mongo/schema-ir";
|
|
16
|
+
import { asNamespaceId, effectiveControlPolicy } from "@prisma/orm-framework/contract/types";
|
|
17
|
+
import { MongoSchemaCollection, MongoSchemaIR, canonicalize, deepEqual } from "@prisma/orm-family-mongo/schema-ir";
|
|
17
18
|
import { TsExpression, jsonToTsSource, renderImports } from "@prisma/orm-framework/ts-render";
|
|
18
19
|
import { Migration } from "@prisma/orm-toolchain/migration-tools/migration";
|
|
19
20
|
import { resolveRequirementSpecifiers } from "@prisma/orm-framework/components/emission";
|
|
@@ -21,7 +22,6 @@ import { detectScaffoldRuntime, shebangLineFor } from "@prisma/orm-toolchain/mig
|
|
|
21
22
|
import { errorRunnerFailed } from "@prisma/orm-framework/errors/execution";
|
|
22
23
|
import { verifyMongoSchema } from "@prisma/orm-family-mongo/family/schema-verify";
|
|
23
24
|
import { InternalError } from "@prisma/orm-framework/utils/internal-error";
|
|
24
|
-
import { AggregateCommand, MongoAddFieldsStage, MongoLimitStage, MongoLookupStage, MongoMatchStage, MongoMergeStage, MongoProjectStage, MongoSortStage, RawAggregateCommand, RawDeleteManyCommand, RawDeleteOneCommand, RawFindOneAndDeleteCommand, RawFindOneAndUpdateCommand, RawInsertManyCommand, RawInsertOneCommand, RawUpdateManyCommand, RawUpdateOneCommand } from "@prisma/orm-family-mongo/query-ast/execution";
|
|
25
25
|
//#region ../../../3-mongo-target/1-mongo-target/dist/control.mjs
|
|
26
26
|
/**
|
|
27
27
|
* Mongo target `Namespace` concretion. In Mongo the "namespace" concept
|
|
@@ -252,6 +252,75 @@ var DropCollectionCall = class extends OpFactoryCallNode {
|
|
|
252
252
|
return `dropCollection(${jsonToTsSource(this.collection)})`;
|
|
253
253
|
}
|
|
254
254
|
};
|
|
255
|
+
var RenameCollectionCall = class extends OpFactoryCallNode {
|
|
256
|
+
factoryName = "renameCollection";
|
|
257
|
+
operationClass = "widening";
|
|
258
|
+
from;
|
|
259
|
+
to;
|
|
260
|
+
label;
|
|
261
|
+
constructor(from, to) {
|
|
262
|
+
super();
|
|
263
|
+
this.from = from;
|
|
264
|
+
this.to = to;
|
|
265
|
+
this.label = `Rename collection ${from} to ${to}`;
|
|
266
|
+
this.freeze();
|
|
267
|
+
}
|
|
268
|
+
toOp() {
|
|
269
|
+
return renameCollection(this.from, this.to);
|
|
270
|
+
}
|
|
271
|
+
renderTypeScript() {
|
|
272
|
+
return `renameCollection(${jsonToTsSource(this.from)}, ${jsonToTsSource(this.to)})`;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
function renderFilterOption(filter) {
|
|
276
|
+
return filter === void 0 ? "" : `, ${jsonToTsSource({ filter })}`;
|
|
277
|
+
}
|
|
278
|
+
var RenameFieldCall = class extends OpFactoryCallNode {
|
|
279
|
+
factoryName = "renameField";
|
|
280
|
+
operationClass = "widening";
|
|
281
|
+
collection;
|
|
282
|
+
from;
|
|
283
|
+
to;
|
|
284
|
+
filter;
|
|
285
|
+
label;
|
|
286
|
+
constructor(collection, from, to, filter) {
|
|
287
|
+
super();
|
|
288
|
+
this.collection = collection;
|
|
289
|
+
this.from = from;
|
|
290
|
+
this.to = to;
|
|
291
|
+
this.filter = filter;
|
|
292
|
+
this.label = this.toOp().label;
|
|
293
|
+
this.freeze();
|
|
294
|
+
}
|
|
295
|
+
toOp() {
|
|
296
|
+
return renameField(this.collection, this.from, this.to, ifDefined("filter", this.filter));
|
|
297
|
+
}
|
|
298
|
+
renderTypeScript() {
|
|
299
|
+
return `renameField(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.from)}, ${jsonToTsSource(this.to)}${renderFilterOption(this.filter)})`;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var UnsetFieldCall = class extends OpFactoryCallNode {
|
|
303
|
+
factoryName = "unsetField";
|
|
304
|
+
operationClass = "destructive";
|
|
305
|
+
collection;
|
|
306
|
+
field;
|
|
307
|
+
filter;
|
|
308
|
+
label;
|
|
309
|
+
constructor(collection, field, filter) {
|
|
310
|
+
super();
|
|
311
|
+
this.collection = collection;
|
|
312
|
+
this.field = field;
|
|
313
|
+
this.filter = filter;
|
|
314
|
+
this.label = this.toOp().label;
|
|
315
|
+
this.freeze();
|
|
316
|
+
}
|
|
317
|
+
toOp() {
|
|
318
|
+
return unsetField(this.collection, this.field, ifDefined("filter", this.filter));
|
|
319
|
+
}
|
|
320
|
+
renderTypeScript() {
|
|
321
|
+
return `unsetField(${jsonToTsSource(this.collection)}, ${jsonToTsSource(this.field)}${renderFilterOption(this.filter)})`;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
255
324
|
var CollModCall = class extends OpFactoryCallNode {
|
|
256
325
|
factoryName = "collMod";
|
|
257
326
|
collection;
|
|
@@ -309,6 +378,322 @@ function schemaCollectionToCreateCollectionOptions(coll) {
|
|
|
309
378
|
...ifDefined("changeStreamPreAndPostImages", opts?.changeStreamPreAndPostImages)
|
|
310
379
|
};
|
|
311
380
|
}
|
|
381
|
+
/** The collection a model stores its documents in; a model without `@@map` names it verbatim. */
|
|
382
|
+
function collectionOf(contract, coordinate) {
|
|
383
|
+
const collection = contract?.domain.namespaces[coordinate.namespaceId]?.models[coordinate.model]?.storage["collection"];
|
|
384
|
+
return typeof collection === "string" ? collection : coordinate.model;
|
|
385
|
+
}
|
|
386
|
+
function modelAt(contract, coordinate) {
|
|
387
|
+
const namespace = contract.domain.namespaces[coordinate.namespaceId];
|
|
388
|
+
return namespace !== void 0 && Object.hasOwn(namespace.models, coordinate.model) ? namespace.models[coordinate.model] : void 0;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* The condition that selects a variant's documents in the collection it shares with its base: the
|
|
392
|
+
* base's discriminator field holding the variant's value. A model that is not a variant has none.
|
|
393
|
+
*/
|
|
394
|
+
function variantFilter(contract, coordinate) {
|
|
395
|
+
const base = modelAt(contract, coordinate)?.base;
|
|
396
|
+
if (base === void 0) return void 0;
|
|
397
|
+
const baseModel = contract.domain.namespaces[base.namespace]?.models[base.model];
|
|
398
|
+
const field = baseModel?.discriminator?.field;
|
|
399
|
+
const value = baseModel?.variants?.[coordinate.model]?.value;
|
|
400
|
+
return field === void 0 || value === void 0 ? void 0 : { [field]: value };
|
|
401
|
+
}
|
|
402
|
+
function collectionControlPolicy(contract, collection) {
|
|
403
|
+
return effectiveControlPolicy(Object.values(contract.storage.namespaces).map((namespace) => namespace?.entries.collection?.[collection]).find((entry) => entry !== void 0)?.control, contract.defaultControlPolicy);
|
|
404
|
+
}
|
|
405
|
+
/** Why MongoDB would refuse a collection name, if it would. */
|
|
406
|
+
function invalidCollectionNameReason(name) {
|
|
407
|
+
if (name === "") return "it is empty";
|
|
408
|
+
if (name.includes("$")) return "it contains \"$\"";
|
|
409
|
+
if (name.includes("\0")) return "it contains a null character";
|
|
410
|
+
if (name.startsWith("system.")) return "it starts with \"system.\", which MongoDB reserves";
|
|
411
|
+
}
|
|
412
|
+
function isEmbedRelation(relation) {
|
|
413
|
+
return !("on" in relation) && !("through" in relation);
|
|
414
|
+
}
|
|
415
|
+
function storedField(model, field) {
|
|
416
|
+
if (model === void 0) return { kind: "absent" };
|
|
417
|
+
if (Object.hasOwn(model.fields, field)) return { kind: "field" };
|
|
418
|
+
const relation = Object.hasOwn(model.relations, field) ? model.relations[field] : void 0;
|
|
419
|
+
if (relation === void 0) return { kind: "absent" };
|
|
420
|
+
return isEmbedRelation(relation) ? { kind: "embed" } : { kind: "reference" };
|
|
421
|
+
}
|
|
422
|
+
function refused(statement, label, reason, why) {
|
|
423
|
+
return {
|
|
424
|
+
kind: "statementRefused",
|
|
425
|
+
summary: `${label}: ${reason}`,
|
|
426
|
+
why,
|
|
427
|
+
refusedStatement: statement
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const LEAVE_OUT = "Leave out this statement.";
|
|
431
|
+
const TIME_SERIES_UPDATES = "MongoDB updates the documents of a time-series collection only through its metaField";
|
|
432
|
+
/**
|
|
433
|
+
* A removal the plan cannot carry out because its collection is a time-series collection, whose
|
|
434
|
+
* documents MongoDB does not let an update change except through the metaField.
|
|
435
|
+
*/
|
|
436
|
+
function timeSeriesRemovalRefusals(removals, schema) {
|
|
437
|
+
return removals.filter((removal) => schema.collection(removal.call.collection)?.options?.timeseries !== void 0).map((removal) => ({
|
|
438
|
+
kind: "fieldRemovalRefused",
|
|
439
|
+
summary: `Cannot remove field ${JSON.stringify(removal.call.field)} from collection ${JSON.stringify(removal.call.collection)}: it is a time-series collection`,
|
|
440
|
+
why: `${TIME_SERIES_UPDATES}, so the field cannot be removed from the documents that hold it. Keep the field in the contract, as an optional field if new documents leave it out, then run this command again.`
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Plans the rename each statement states, in order: a model statement whose collection changes renames the
|
|
445
|
+
* collection in the schema the diff then starts from, and a field statement whose stored name
|
|
446
|
+
* changes renames the field in the documents that hold it.
|
|
447
|
+
*/
|
|
448
|
+
function planStatements(input) {
|
|
449
|
+
let schema = input.schema;
|
|
450
|
+
const collectionRenames = [];
|
|
451
|
+
const fieldRenames = [];
|
|
452
|
+
const callOfStatement = [];
|
|
453
|
+
const conflicts = [];
|
|
454
|
+
const renamedCollections = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const statement of input.statements) {
|
|
456
|
+
const label = `Cannot ${describeMigrationStatement(statement, input.fromContract, input.contract)}`;
|
|
457
|
+
const outcome = statement.entity === "model" ? planModelStatement(statement, label, schema, input) : planFieldStatement(statement, label, renamedCollections, schema, input);
|
|
458
|
+
if (outcome.kind === "refused") {
|
|
459
|
+
conflicts.push(outcome.conflict);
|
|
460
|
+
callOfStatement.push(void 0);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (outcome.kind === "renameCollection") {
|
|
464
|
+
schema = renameInSchema(schema, outcome.call.from, outcome.call.to);
|
|
465
|
+
renamedCollections.set(outcome.call.from, outcome.call.to);
|
|
466
|
+
collectionRenames.push(outcome.call);
|
|
467
|
+
}
|
|
468
|
+
if (outcome.kind === "renameField") fieldRenames.push(outcome.call);
|
|
469
|
+
callOfStatement.push(outcome.kind === "unchanged" ? void 0 : outcome.call);
|
|
470
|
+
}
|
|
471
|
+
return conflicts.length > 0 ? notOk(conflicts) : ok({
|
|
472
|
+
schema,
|
|
473
|
+
collectionRenames,
|
|
474
|
+
fieldRenames,
|
|
475
|
+
callOfStatement
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function renameInSchema(schema, from, to) {
|
|
479
|
+
return new MongoSchemaIR(schema.collections.map((collection) => collection.name === from ? new MongoSchemaCollection({
|
|
480
|
+
name: to,
|
|
481
|
+
indexes: collection.indexes,
|
|
482
|
+
...collection.validator === void 0 ? {} : { validator: collection.validator },
|
|
483
|
+
...collection.options === void 0 ? {} : { options: collection.options }
|
|
484
|
+
}) : collection));
|
|
485
|
+
}
|
|
486
|
+
function controlPolicyRefusal(statement, label, contract, collection) {
|
|
487
|
+
const policy = collectionControlPolicy(contract, collection);
|
|
488
|
+
if (policy === "managed") return void 0;
|
|
489
|
+
return {
|
|
490
|
+
kind: "refused",
|
|
491
|
+
conflict: refused(statement, label, `collection "${collection}" has control policy "${policy}"`, `Statements rename only collections, and fields of collections, whose control policy is "managed". Make the change in the database yourself and leave out this statement.`)
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The reason and advice when a statement renames a variant whose discriminator value changes: its
|
|
496
|
+
* documents keep the old value, which the new validator does not accept.
|
|
497
|
+
*/
|
|
498
|
+
function discriminatorValueChange(statement, input) {
|
|
499
|
+
const before = variantFilter(input.fromContract, statement.from);
|
|
500
|
+
const after = variantFilter(input.contract, statement.to);
|
|
501
|
+
const base = modelAt(input.contract, statement.to)?.base;
|
|
502
|
+
if (before === void 0 || after === void 0 || base === void 0) return void 0;
|
|
503
|
+
const [field, oldValue] = Object.entries(before)[0] ?? [];
|
|
504
|
+
const newValue = Object.values(after)[0];
|
|
505
|
+
if (field === void 0 || oldValue === newValue) return void 0;
|
|
506
|
+
const fromName = modelDisplayName(statement.from);
|
|
507
|
+
const toName = modelDisplayName(statement.to);
|
|
508
|
+
return [`its discriminator value changes from ${JSON.stringify(oldValue)} to ${JSON.stringify(newValue)}`, `The documents of "${fromName}" hold ${field} ${JSON.stringify(oldValue)}, and this release does not rewrite a discriminator value, so they would no longer match "${toName}" and would fail validation. Keep the value: declare "${toName}" with @@base(${base.model}, ${JSON.stringify(oldValue)}). A later release changes a stored value with --convert.`];
|
|
509
|
+
}
|
|
510
|
+
function planModelStatement(statement, label, schema, input) {
|
|
511
|
+
const discriminatorChange = discriminatorValueChange(statement, input);
|
|
512
|
+
if (discriminatorChange !== void 0) return {
|
|
513
|
+
kind: "refused",
|
|
514
|
+
conflict: refused(statement, label, ...discriminatorChange)
|
|
515
|
+
};
|
|
516
|
+
const from = collectionOf(input.fromContract, statement.from);
|
|
517
|
+
const to = collectionOf(input.contract, statement.to);
|
|
518
|
+
if (from === to) return { kind: "unchanged" };
|
|
519
|
+
const policyRefusal = controlPolicyRefusal(statement, label, input.contract, to);
|
|
520
|
+
if (policyRefusal !== void 0) return policyRefusal;
|
|
521
|
+
const refuse = (reason, why) => ({
|
|
522
|
+
kind: "refused",
|
|
523
|
+
conflict: refused(statement, label, reason, why)
|
|
524
|
+
});
|
|
525
|
+
const source = schema.collection(from);
|
|
526
|
+
if (source === void 0) return refuse(`the schema being planned from has no collection "${from}"`, `The database has no collection "${from}", or "${from}" is a view, which MongoDB cannot rename. Inspect it with prisma db schema, or ${LEAVE_OUT.toLowerCase()}`);
|
|
527
|
+
if (source.options?.timeseries !== void 0) return refuse(`collection "${from}" is a time-series collection`, `MongoDB cannot rename a time-series collection. Leave out this statement. To keep the documents, first create collection "${to}" yourself with the same time-series options and copy them into it; the plan then drops "${from}" once --delete names the model.`);
|
|
528
|
+
const invalid = invalidCollectionNameReason(to);
|
|
529
|
+
if (invalid !== void 0) return refuse(`MongoDB does not accept the collection name "${to}": ${invalid}`, "Give the model a collection name MongoDB accepts with @@map, then run this command again.");
|
|
530
|
+
if (schema.collection(to) !== void 0) return refuse(`the schema being planned from already has a collection "${to}"`, `A rename cannot replace a collection that already exists. Rename or drop collection "${to}" first, or ${LEAVE_OUT.toLowerCase()}`);
|
|
531
|
+
return {
|
|
532
|
+
kind: "renameCollection",
|
|
533
|
+
call: new RenameCollectionCall(from, to)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function planFieldStatement(statement, label, renamedCollections, schema, input) {
|
|
537
|
+
const refuse = (reason, why) => ({
|
|
538
|
+
kind: "refused",
|
|
539
|
+
conflict: refused(statement, label, reason, why)
|
|
540
|
+
});
|
|
541
|
+
const fromField = storedField(modelAt(input.fromContract, statement.from), statement.from.field);
|
|
542
|
+
const toField = storedField(modelAt(input.contract, statement.to), statement.to.field);
|
|
543
|
+
if (fromField.kind === "embed" || toField.kind === "embed") return refuse("the field is an embedded relation", `Renaming an embedded relation is not supported in this release. ${LEAVE_OUT}`);
|
|
544
|
+
if (fromField.kind !== "field" && toField.kind !== "field") return { kind: "unchanged" };
|
|
545
|
+
if (fromField.kind !== "field" || toField.kind !== "field") return refuse("the field is stored in documents on one side of the statement only", `A relation is not stored in documents, so a statement cannot rename a field to or from one. ${LEAVE_OUT}`);
|
|
546
|
+
if (statement.from.field === "_id" || statement.to.field === "_id") return refuse("it renames _id", `MongoDB does not let a document change its _id. ${LEAVE_OUT}`);
|
|
547
|
+
const unsafeFrom = unrewritableFieldNameReason(statement.from.field);
|
|
548
|
+
if (unsafeFrom !== void 0) return refuse(`the stored field name "${statement.from.field}" cannot be rewritten`, `${unsafeFrom}, so a statement cannot move its values. Rename it in the database yourself, or ${LEAVE_OUT.toLowerCase()}`);
|
|
549
|
+
const unsafeTo = unrewritableFieldNameReason(statement.to.field);
|
|
550
|
+
if (unsafeTo !== void 0) return refuse(`the stored field name "${statement.to.field}" cannot be written`, `${unsafeTo}. Give the field a stored name without "." that does not start with "$" (with @map), or ${LEAVE_OUT.toLowerCase()}`);
|
|
551
|
+
const originCollection = collectionOf(input.fromContract, statement.from);
|
|
552
|
+
const collection = renamedCollections.get(originCollection) ?? originCollection;
|
|
553
|
+
const destinationCollection = collectionOf(input.contract, statement.to);
|
|
554
|
+
if (collection !== destinationCollection) return refuse(`the model's collection changes from "${originCollection}" to "${destinationCollection}", and no statement renames it`, collectionChangeAdvice(statement, originCollection, destinationCollection));
|
|
555
|
+
if (statement.from.field === statement.to.field) return { kind: "unchanged" };
|
|
556
|
+
if (schema.collection(collection)?.options?.timeseries !== void 0) return refuse(`collection "${collection}" is a time-series collection`, `${TIME_SERIES_UPDATES}, so a statement cannot move the values of a field in them. Keep the stored name: name the field ${JSON.stringify(statement.from.field)} in the contract (with @map(${JSON.stringify(statement.from.field)}) in PSL), then run this command again without this statement.`);
|
|
557
|
+
const policyRefusal = controlPolicyRefusal(statement, label, input.contract, collection);
|
|
558
|
+
if (policyRefusal !== void 0) return policyRefusal;
|
|
559
|
+
const filter = variantFilter(input.fromContract, statement.from) ?? rootDocumentsLosing(input.contract, statement.to, statement.from.field)?.filter;
|
|
560
|
+
return {
|
|
561
|
+
kind: "renameField",
|
|
562
|
+
call: new RenameFieldCall(collection, statement.from.field, statement.to.field, filter)
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function collectionChangeAdvice(statement, from, to) {
|
|
566
|
+
if (!sameModel(statement.from, statement.to)) return `Add --rename ${modelDisplayName(statement.from)}:${modelDisplayName(statement.to)} before this statement, so the collection is renamed with its documents.`;
|
|
567
|
+
return `A statement cannot rename the collection of a model that keeps its name. Rename the collection in a step of its own first: change only @@map and emit the contract. ${renameCollectionByHandSteps(statement.from, from, to)} Then change the field and run this statement again.`;
|
|
568
|
+
}
|
|
569
|
+
function renameCollectionByHandSteps(model, from, to) {
|
|
570
|
+
return `With db update, rename the collection in the database yourself, for example db.getCollection(${JSON.stringify(from)}).renameCollection(${JSON.stringify(to)}) in mongosh, then run db update. With migration plan, plan it, passing --delete ${modelDisplayName(model)} to answer its question; in its migration.ts make renameCollection(${JSON.stringify(from)}, ${JSON.stringify(to)}) the first operation, imported with the other operations, delete the drop of "${from}", and delete the create of "${to}" if there is one; run node on that migration.ts so it writes ops.json again; then apply it with prisma db migrate.`;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* How to keep the documents of a model that keeps its name while its collection changes from
|
|
574
|
+
* `from` to `to`: no statement can rename that collection, so the user renames it by hand.
|
|
575
|
+
*/
|
|
576
|
+
function keepCollectionByHandAdvice(model, from, to) {
|
|
577
|
+
return `To keep its documents, rename collection "${from}" to "${to}" instead; a statement cannot rename the collection of a model that keeps its name. ${renameCollectionByHandSteps(model, from, to)}`;
|
|
578
|
+
}
|
|
579
|
+
function modelCoordinates(contract) {
|
|
580
|
+
return Object.entries(contract.domain?.namespaces ?? {}).flatMap(([namespaceId, namespace]) => Object.keys(namespace.models).map((model) => ({
|
|
581
|
+
namespaceId: asNamespaceId(namespaceId),
|
|
582
|
+
model
|
|
583
|
+
})));
|
|
584
|
+
}
|
|
585
|
+
function sameModel(a, b) {
|
|
586
|
+
return a.namespaceId === b.namespaceId && a.model === b.model;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* The destination model an origin model became: the one a statement renamed it to, else the one of
|
|
590
|
+
* the same name, else the only one stored the same way (same collection, same variant value).
|
|
591
|
+
*/
|
|
592
|
+
function destinationModelOf(origin, input) {
|
|
593
|
+
const renamed = input.statements.find((statement) => statement.entity === "model" && sameModel(statement.from, origin));
|
|
594
|
+
if (renamed !== void 0) return renamed.to;
|
|
595
|
+
if (modelAt(input.contract, origin) !== void 0) return origin;
|
|
596
|
+
const collection = input.collectionAfterRenames(collectionOf(input.fromContract, origin));
|
|
597
|
+
const filter = JSON.stringify(variantFilter(input.fromContract, origin) ?? null);
|
|
598
|
+
const stored = modelCoordinates(input.contract).filter((candidate) => collectionOf(input.contract, candidate) === collection && JSON.stringify(variantFilter(input.contract, candidate) ?? null) === filter);
|
|
599
|
+
const [only] = stored;
|
|
600
|
+
return stored.length === 1 ? only : void 0;
|
|
601
|
+
}
|
|
602
|
+
function renamedByStatement(input, field) {
|
|
603
|
+
return input.statements.some((statement) => statement.entity === "field" && sameModel(statement.from, field) && statement.from.field === field.field);
|
|
604
|
+
}
|
|
605
|
+
function storedFieldsOf(contract, coordinate) {
|
|
606
|
+
const model = modelAt(contract, coordinate);
|
|
607
|
+
if (model === void 0) return /* @__PURE__ */ new Set();
|
|
608
|
+
const base = model.base === void 0 ? void 0 : modelAt(contract, {
|
|
609
|
+
namespaceId: asNamespaceId(model.base.namespace),
|
|
610
|
+
model: model.base.model
|
|
611
|
+
});
|
|
612
|
+
return /* @__PURE__ */ new Set([...Object.keys(model.fields), ...Object.keys(base?.fields ?? {})]);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* The documents of a root model that lose `field`: all of them, or, when some of its destination
|
|
616
|
+
* variants now store the field themselves, those whose discriminator value is none of theirs.
|
|
617
|
+
* `undefined` when the root model itself still stores it.
|
|
618
|
+
*/
|
|
619
|
+
function rootDocumentsLosing(contract, root, field) {
|
|
620
|
+
const model = modelAt(contract, root);
|
|
621
|
+
if (model === void 0 || Object.hasOwn(model.fields, field)) return void 0;
|
|
622
|
+
const discriminator = model.discriminator?.field;
|
|
623
|
+
const keeping = Object.entries(model.variants ?? {}).filter(([variant]) => Object.hasOwn(modelAt(contract, {
|
|
624
|
+
...root,
|
|
625
|
+
model: variant
|
|
626
|
+
})?.fields ?? {}, field)).map(([, entry]) => entry.value).sort();
|
|
627
|
+
if (discriminator === void 0 || keeping.length === 0) return { filter: void 0 };
|
|
628
|
+
return { filter: { [discriminator]: { $nin: keeping } } };
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Each field an origin model stores that its destination model no longer stores, in a collection
|
|
632
|
+
* the plan keeps: removing it is data loss, carried out by unsetting it from the documents that
|
|
633
|
+
* hold it, in a collection whose control policy is `managed`. A variant's field is unset only from that variant's documents, unless its destination
|
|
634
|
+
* base now stores it. A base field is unset from the documents of every variant that does not now
|
|
635
|
+
* store it itself.
|
|
636
|
+
*/
|
|
637
|
+
function planFieldRemovals(input) {
|
|
638
|
+
return modelCoordinates(input.fromContract).flatMap((origin) => {
|
|
639
|
+
const collection = input.collectionAfterRenames(collectionOf(input.fromContract, origin));
|
|
640
|
+
if (!input.keptCollections.has(collection)) return [];
|
|
641
|
+
if (collectionControlPolicy(input.contract, collection) !== "managed") return [];
|
|
642
|
+
const destination = destinationModelOf(origin, input);
|
|
643
|
+
if (destination === void 0) return [];
|
|
644
|
+
const model = modelAt(input.fromContract, origin);
|
|
645
|
+
if (model === void 0) return [];
|
|
646
|
+
const variant = variantFilter(input.fromContract, origin);
|
|
647
|
+
const losing = (field) => {
|
|
648
|
+
if (variant !== void 0) return storedFieldsOf(input.contract, destination).has(field) ? void 0 : { filter: variant };
|
|
649
|
+
return rootDocumentsLosing(input.contract, destination, field);
|
|
650
|
+
};
|
|
651
|
+
return Object.keys(model.fields).filter((field) => unrewritableFieldNameReason(field) === void 0 && !renamedByStatement(input, {
|
|
652
|
+
...origin,
|
|
653
|
+
field
|
|
654
|
+
})).sort().flatMap((field) => {
|
|
655
|
+
const lost = losing(field);
|
|
656
|
+
return lost === void 0 ? [] : [{
|
|
657
|
+
call: new UnsetFieldCall(collection, field, lost.filter),
|
|
658
|
+
subject: {
|
|
659
|
+
kind: "field",
|
|
660
|
+
...origin,
|
|
661
|
+
field
|
|
662
|
+
}
|
|
663
|
+
}];
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function isRecord$1(value) {
|
|
668
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
669
|
+
}
|
|
670
|
+
function propertyNames(schema) {
|
|
671
|
+
const properties = isRecord$1(schema) ? schema["properties"] : void 0;
|
|
672
|
+
return isRecord$1(properties) ? Object.keys(properties) : [];
|
|
673
|
+
}
|
|
674
|
+
function branchPropertyNames(schema) {
|
|
675
|
+
const branches = isRecord$1(schema) ? schema["oneOf"] : void 0;
|
|
676
|
+
return Array.isArray(branches) ? branches.flatMap(propertyNames) : [];
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Without an origin contract, a field is known removed when the origin validator declares it at the
|
|
680
|
+
* top level and the destination validator declares it nowhere. It is named by its storage name.
|
|
681
|
+
*/
|
|
682
|
+
function planValidatorFieldRemovals(input) {
|
|
683
|
+
return input.origin.collections.flatMap((originCollection) => {
|
|
684
|
+
if (collectionControlPolicy(input.contract, originCollection.name) !== "managed") return [];
|
|
685
|
+
const destination = input.destination.collection(originCollection.name);
|
|
686
|
+
if (destination?.validator === void 0 || originCollection.validator === void 0) return [];
|
|
687
|
+
const kept = /* @__PURE__ */ new Set([...propertyNames(destination.validator?.jsonSchema), ...branchPropertyNames(destination.validator?.jsonSchema)]);
|
|
688
|
+
return propertyNames(originCollection.validator.jsonSchema).filter((field) => unrewritableFieldNameReason(field) === void 0 && !kept.has(field)).sort().map((field) => ({
|
|
689
|
+
call: new UnsetFieldCall(originCollection.name, field),
|
|
690
|
+
subject: {
|
|
691
|
+
kind: "storage",
|
|
692
|
+
name: `${originCollection.name}.${field}`
|
|
693
|
+
}
|
|
694
|
+
}));
|
|
695
|
+
});
|
|
696
|
+
}
|
|
312
697
|
function renderOps(calls) {
|
|
313
698
|
return calls.map((call) => call.toOp());
|
|
314
699
|
}
|
|
@@ -467,6 +852,35 @@ var PlannerProducedMongoMigration = class extends Migration {
|
|
|
467
852
|
});
|
|
468
853
|
}
|
|
469
854
|
};
|
|
855
|
+
const SAME_HASH = "the migration would start and end at the same contract hash";
|
|
856
|
+
function why(collection) {
|
|
857
|
+
return `The contract gives collection ${JSON.stringify(collection)} no validator, so the names of its fields are not part of the contract's storage hash; a contract written in TypeScript has none yet. A migration that only renames or removes a field would start and end at the same hash, and db migrate refuses such a migration. Use prisma db update for this change instead: it carries the change out with the same --rename or --delete and writes no migration. Or write the migration by hand: run prisma migration new --from <hash of the migration the database is at>, and carry the change out in a dataTransform operation of its migration.ts, since db migrate accepts a migration that starts and ends at the same hash when it runs a dataTransform.`;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Refuses, for a migration package, each field rename and field removal of a plan whose contract
|
|
861
|
+
* storage hash does not change: the collection has no validator in the contract, so its field
|
|
862
|
+
* names are not in the hash, and `db migrate` refuses a migration that starts and ends at the same
|
|
863
|
+
* hash. `db update` writes no migration and plans without an origin, so it is not refused.
|
|
864
|
+
*/
|
|
865
|
+
function sameHashFieldChangeRefusals(input) {
|
|
866
|
+
if (input.origin === null || input.origin.storageHash !== input.contract.storage.storageHash) return [];
|
|
867
|
+
const renames = input.statements.flatMap((statement, index) => {
|
|
868
|
+
const call = input.callOfStatement[index];
|
|
869
|
+
if (!(call instanceof RenameFieldCall)) return [];
|
|
870
|
+
return [{
|
|
871
|
+
kind: "statementRefused",
|
|
872
|
+
summary: `Cannot ${describeMigrationStatement(statement, input.fromContract, input.contract)}: ${SAME_HASH}`,
|
|
873
|
+
why: why(call.collection),
|
|
874
|
+
refusedStatement: statement
|
|
875
|
+
}];
|
|
876
|
+
});
|
|
877
|
+
const removals = input.removals.map(({ call }) => ({
|
|
878
|
+
kind: "fieldRemovalRefused",
|
|
879
|
+
summary: `Cannot remove field ${JSON.stringify(call.field)} from collection ${JSON.stringify(call.collection)}: ${SAME_HASH}`,
|
|
880
|
+
why: why(call.collection)
|
|
881
|
+
}));
|
|
882
|
+
return [...renames, ...removals];
|
|
883
|
+
}
|
|
470
884
|
function buildIndexLookupKey(index) {
|
|
471
885
|
const keys = index.keys.map((k) => `${k.field}:${k.direction}`).join(",");
|
|
472
886
|
const opts = [
|
|
@@ -509,20 +923,41 @@ function validatorUpdateLabel(collName, origin, dest) {
|
|
|
509
923
|
];
|
|
510
924
|
return parts.length === 0 ? `Update validator on ${collName}` : `Update validator on ${collName} (${parts.join("; ")})`;
|
|
511
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* The keys of an option MongoDB fills in when a collection is created without them, each with a
|
|
928
|
+
* test for the value it fills in. `listCollections` reports a time-series collection's granularity
|
|
929
|
+
* as "seconds" and its bucketMaxSpanSeconds when they were not given, and bucketRoundingSeconds only
|
|
930
|
+
* for custom bucketing, which a contract cannot state; and a clustered index's v, key and unique, and
|
|
931
|
+
* its name as "_id_".
|
|
932
|
+
*/
|
|
933
|
+
const SERVER_FILLED = {
|
|
934
|
+
timeseries: {
|
|
935
|
+
granularity: (value) => value === "seconds",
|
|
936
|
+
bucketMaxSpanSeconds: () => true,
|
|
937
|
+
bucketRoundingSeconds: () => true
|
|
938
|
+
},
|
|
939
|
+
clusteredIndex: {
|
|
940
|
+
v: () => true,
|
|
941
|
+
key: () => true,
|
|
942
|
+
unique: () => true,
|
|
943
|
+
name: (value) => value === "_id_"
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
/** The origin's value of an option without the keys the server filled in and the destination leaves out. */
|
|
947
|
+
function withoutServerFilled(option, origin, dest) {
|
|
948
|
+
if (!isPlainObject(origin)) return origin;
|
|
949
|
+
const filled = SERVER_FILLED[option];
|
|
950
|
+
return Object.fromEntries(Object.entries(origin).filter(([key, value]) => isPlainObject(dest) && Object.hasOwn(dest, key) || !filled[key]?.(value)));
|
|
951
|
+
}
|
|
512
952
|
function hasImmutableOptionChange(origin, dest) {
|
|
513
953
|
if (canonicalize(origin?.capped) !== canonicalize(dest?.capped)) return "capped";
|
|
514
|
-
if (canonicalize(origin?.timeseries) !== canonicalize(dest?.timeseries)) return "timeseries";
|
|
954
|
+
if (canonicalize(withoutServerFilled("timeseries", origin?.timeseries, dest?.timeseries)) !== canonicalize(dest?.timeseries)) return "timeseries";
|
|
515
955
|
if (canonicalize(origin?.collation) !== canonicalize(dest?.collation)) return "collation";
|
|
516
|
-
if (canonicalize(origin?.clusteredIndex) !== canonicalize(dest?.clusteredIndex)) return "clusteredIndex";
|
|
956
|
+
if (canonicalize(withoutServerFilled("clusteredIndex", origin?.clusteredIndex, dest?.clusteredIndex)) !== canonicalize(dest?.clusteredIndex)) return "clusteredIndex";
|
|
517
957
|
}
|
|
518
958
|
function collectionHasOptions(coll) {
|
|
519
959
|
return !!(coll.options || coll.validator);
|
|
520
960
|
}
|
|
521
|
-
/** The collection a model stores its documents in; a model without `@@map` names it verbatim. */
|
|
522
|
-
function collectionOf(contract, coordinate) {
|
|
523
|
-
const collection = contract?.domain.namespaces[coordinate.namespaceId]?.models[coordinate.model]?.storage["collection"];
|
|
524
|
-
return typeof collection === "string" ? collection : coordinate.model;
|
|
525
|
-
}
|
|
526
961
|
/**
|
|
527
962
|
* The model of `fromContract` that stores its documents in `collection`, if any: the root model
|
|
528
963
|
* when variants share the collection with it.
|
|
@@ -540,67 +975,47 @@ function modelStoredIn(fromContract, collection) {
|
|
|
540
975
|
}));
|
|
541
976
|
return (storing.find(({ isRoot }) => isRoot) ?? storing[0])?.coordinate;
|
|
542
977
|
}
|
|
543
|
-
/**
|
|
544
|
-
|
|
978
|
+
/**
|
|
979
|
+
* Each collection drop of the plan, with the model whose documents it loses. When that model keeps
|
|
980
|
+
* its name and the plan brings its new collection into existence, the drop says how to rename the
|
|
981
|
+
* collection by hand instead.
|
|
982
|
+
*/
|
|
983
|
+
function collectionDrops(calls, fromContract, contract, collections) {
|
|
984
|
+
const created = (collection) => collections.origin.collection(collection) === void 0 && collections.destination.collection(collection) !== void 0;
|
|
545
985
|
return calls.flatMap((call, operationIndex) => {
|
|
546
986
|
if (!(call instanceof DropCollectionCall)) return [];
|
|
547
987
|
const model = fromContract === null ? void 0 : modelStoredIn(fromContract, call.collection);
|
|
548
|
-
return [{
|
|
988
|
+
if (model === void 0) return [{
|
|
549
989
|
operationIndex,
|
|
550
|
-
subject:
|
|
990
|
+
subject: {
|
|
551
991
|
kind: "storage",
|
|
552
992
|
name: call.collection
|
|
553
|
-
} : {
|
|
554
|
-
kind: "model",
|
|
555
|
-
...model
|
|
556
993
|
}
|
|
557
994
|
}];
|
|
995
|
+
const subject = {
|
|
996
|
+
kind: "model",
|
|
997
|
+
...model
|
|
998
|
+
};
|
|
999
|
+
const destinationCollection = modelStoredInDestination(contract, model);
|
|
1000
|
+
return [destinationCollection !== void 0 && created(destinationCollection) ? {
|
|
1001
|
+
operationIndex,
|
|
1002
|
+
subject,
|
|
1003
|
+
keepDataAdvice: keepCollectionByHandAdvice(model, call.collection, destinationCollection)
|
|
1004
|
+
} : {
|
|
1005
|
+
operationIndex,
|
|
1006
|
+
subject
|
|
1007
|
+
}];
|
|
558
1008
|
});
|
|
559
1009
|
}
|
|
560
|
-
|
|
561
|
-
function
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* How to keep the documents of a subject a plan would drop, since the planner carries out no
|
|
566
|
-
* rename: rename the collection by hand before a plan that drops it is applied. Right for both
|
|
567
|
-
* `db update`, which then finds nothing to drop, and `migration plan`, whose written migration
|
|
568
|
-
* still drops it.
|
|
569
|
-
*/
|
|
570
|
-
function keepDataByHand(subject, fromContract) {
|
|
571
|
-
const collection = subject.kind === "storage" ? subject.name : collectionOf(fromContract, subject);
|
|
572
|
-
return `If it was renamed, keep its documents instead: rename collection "${collection}" by hand on each database before a plan that drops it is applied there, for example with db.getCollection(${shellString(collection)}).renameCollection("<new collection>") in mongosh. db update then drops nothing; a migration written by migration plan still drops "${collection}", so remove that operation from its migration.ts, or do not apply it where the collection was renamed.`;
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* What a plan made without the statement does to the data, and how to keep it. Right for both
|
|
576
|
-
* `db update` and `migration plan`, since the planner does not know which command it serves.
|
|
577
|
-
* Mongo contracts key a model's fields by their stored names, so the statement's field names are
|
|
578
|
-
* the names a `$rename` needs.
|
|
579
|
-
*/
|
|
580
|
-
function keepTheData(statement, fromContract, contract) {
|
|
581
|
-
const from = collectionOf(fromContract, statement.from);
|
|
582
|
-
if (statement.entity === "field") {
|
|
583
|
-
const { field } = statement.from;
|
|
584
|
-
const newField = statement.to.field;
|
|
585
|
-
const collection = `db.getCollection(${shellString(from)})`;
|
|
586
|
-
return `${NOT_IN_THIS_RELEASE} Without the statement, the documents in collection "${from}" keep their values under "${field}", and nothing moves them to "${newField}". To move them, run these in mongosh on each database before a plan made without the statement is applied there, using the field names as they are stored. First turn off the collection's validator, which still requires "${field}": db.runCommand({ collMod: ${shellString(from)}, validationLevel: "off" }). Then drop each unique index that includes "${field}", or the move fails once two documents have lost it, for example ${collection}.dropIndex(${shellString(`${field}_1`)}). Then move the values: ${collection}.updateMany({}, { $rename: { ${shellString(field)}: ${shellString(newField)} } }). Applying the plan then creates the indexes on "${newField}" and turns the validator back on.`;
|
|
587
|
-
}
|
|
588
|
-
const to = collectionOf(contract, statement.to);
|
|
589
|
-
if (from === to) return `${NOT_IN_THIS_RELEASE} Both models store their documents in collection "${from}", so a plan made without the statement keeps them.`;
|
|
590
|
-
return `${NOT_IN_THIS_RELEASE} Without the statement, a plan drops collection "${from}" with its documents and creates collection "${to}". To keep the documents, rename the collection by hand on each database before a plan made without the statement is applied there, for example with db.getCollection(${shellString(from)}).renameCollection(${shellString(to)}) in mongosh. A migration written by migration plan without the statement still drops "${from}" wherever it is applied, so check its operations first.`;
|
|
591
|
-
}
|
|
592
|
-
function statementNotApplied(statement, fromContract, contract) {
|
|
593
|
-
return {
|
|
594
|
-
kind: "statementRefused",
|
|
595
|
-
summary: `MongoDB does not apply rename statements in this release, so nothing was planned: ${describeMigrationStatement(statement, fromContract ?? contract, contract)}`,
|
|
596
|
-
why: keepTheData(statement, fromContract, contract),
|
|
597
|
-
refusedStatement: statement
|
|
598
|
-
};
|
|
1010
|
+
/** The collection the destination stores `model` in, when the destination still has that model. */
|
|
1011
|
+
function modelStoredInDestination(contract, model) {
|
|
1012
|
+
const namespace = contract.domain.namespaces[model.namespaceId];
|
|
1013
|
+
return namespace !== void 0 && Object.hasOwn(namespace.models, model.model) ? collectionOf(contract, model) : void 0;
|
|
599
1014
|
}
|
|
600
1015
|
var MongoMigrationPlanner = class {
|
|
601
1016
|
planCalls(options) {
|
|
602
1017
|
const contract = blindCast(options.contract);
|
|
603
|
-
const originIR = blindCast(options.schema);
|
|
1018
|
+
const originIR = options.statementCalls?.schema ?? blindCast(options.schema);
|
|
604
1019
|
const destinationIR = contractToMongoSchemaIR(contract);
|
|
605
1020
|
const collCreates = [];
|
|
606
1021
|
const drops = [];
|
|
@@ -643,8 +1058,10 @@ var MongoMigrationPlanner = class {
|
|
|
643
1058
|
conflicts
|
|
644
1059
|
};
|
|
645
1060
|
const allCalls = [
|
|
1061
|
+
...options.statementCalls?.collectionRenames ?? [],
|
|
646
1062
|
...collCreates,
|
|
647
1063
|
...drops,
|
|
1064
|
+
...options.statementCalls?.rewrites ?? [],
|
|
648
1065
|
...creates,
|
|
649
1066
|
...validatorOps,
|
|
650
1067
|
...mutableOptionOps,
|
|
@@ -667,21 +1084,75 @@ var MongoMigrationPlanner = class {
|
|
|
667
1084
|
}
|
|
668
1085
|
plan(options) {
|
|
669
1086
|
const contract = blindCast(options.contract);
|
|
670
|
-
const
|
|
671
|
-
|
|
1087
|
+
const originIR = blindCast(options.schema);
|
|
1088
|
+
const fromContract = options.fromContract;
|
|
1089
|
+
const statementPlan = planStatements({
|
|
1090
|
+
statements: options.statements,
|
|
1091
|
+
fromContract: fromContract ?? contract,
|
|
1092
|
+
contract,
|
|
1093
|
+
schema: originIR
|
|
1094
|
+
});
|
|
1095
|
+
if (!statementPlan.ok) return {
|
|
1096
|
+
kind: "failure",
|
|
1097
|
+
conflicts: statementPlan.failure
|
|
1098
|
+
};
|
|
1099
|
+
const { schema, collectionRenames, fieldRenames, callOfStatement } = statementPlan.value;
|
|
1100
|
+
const destinationIR = contractToMongoSchemaIR(contract);
|
|
1101
|
+
const renamedTo = new Map(collectionRenames.map((call) => [call.from, call.to]));
|
|
1102
|
+
const removals = fromContract === null ? planValidatorFieldRemovals({
|
|
1103
|
+
origin: schema,
|
|
1104
|
+
destination: destinationIR,
|
|
1105
|
+
contract
|
|
1106
|
+
}) : planFieldRemovals({
|
|
1107
|
+
statements: options.statements,
|
|
1108
|
+
fromContract,
|
|
1109
|
+
contract,
|
|
1110
|
+
collectionAfterRenames: (collection) => renamedTo.get(collection) ?? collection,
|
|
1111
|
+
keptCollections: new Set(schema.collectionNames.filter((name) => destinationIR.collection(name) !== void 0))
|
|
1112
|
+
});
|
|
1113
|
+
const removalRefusals = [...timeSeriesRemovalRefusals(removals, schema), ...sameHashFieldChangeRefusals({
|
|
1114
|
+
origin: options.origin,
|
|
1115
|
+
fromContract: fromContract ?? contract,
|
|
1116
|
+
contract,
|
|
1117
|
+
statements: options.statements,
|
|
1118
|
+
callOfStatement,
|
|
1119
|
+
removals
|
|
1120
|
+
})];
|
|
1121
|
+
if (removalRefusals.length > 0) return {
|
|
672
1122
|
kind: "failure",
|
|
673
|
-
conflicts:
|
|
1123
|
+
conflicts: removalRefusals
|
|
674
1124
|
};
|
|
675
|
-
const result = this.planCalls(
|
|
1125
|
+
const result = this.planCalls({
|
|
1126
|
+
...options,
|
|
1127
|
+
statementCalls: {
|
|
1128
|
+
schema,
|
|
1129
|
+
collectionRenames,
|
|
1130
|
+
rewrites: [...fieldRenames, ...removals.map((removal) => removal.call)]
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
676
1133
|
if (result.kind === "failure") return result;
|
|
1134
|
+
const { calls } = result;
|
|
1135
|
+
const fieldLoss = removals.map((removal) => ({
|
|
1136
|
+
operationIndex: calls.indexOf(removal.call),
|
|
1137
|
+
subject: removal.subject
|
|
1138
|
+
}));
|
|
677
1139
|
return {
|
|
678
1140
|
kind: "success",
|
|
679
|
-
plan: new PlannerProducedMongoMigration(
|
|
1141
|
+
plan: new PlannerProducedMongoMigration(calls, {
|
|
680
1142
|
from: options.origin?.storageHash ?? null,
|
|
681
1143
|
to: contract.storage.storageHash
|
|
682
1144
|
}, options.snapshotsImportPath),
|
|
683
|
-
appliedStatements:
|
|
684
|
-
|
|
1145
|
+
appliedStatements: options.statements.map((statement, index) => {
|
|
1146
|
+
const call = callOfStatement[index];
|
|
1147
|
+
return {
|
|
1148
|
+
statement,
|
|
1149
|
+
operationIndexes: call === void 0 ? [] : [calls.indexOf(call)]
|
|
1150
|
+
};
|
|
1151
|
+
}),
|
|
1152
|
+
dataLoss: [...collectionDrops(calls, fromContract, contract, {
|
|
1153
|
+
origin: schema,
|
|
1154
|
+
destination: destinationIR
|
|
1155
|
+
}), ...fieldLoss].sort((a, b) => a.operationIndex - b.operationIndex),
|
|
685
1156
|
accessWidening: []
|
|
686
1157
|
};
|
|
687
1158
|
}
|
|
@@ -846,6 +1317,11 @@ const DropCollectionJson = type({
|
|
|
846
1317
|
kind: "\"dropCollection\"",
|
|
847
1318
|
collection: "string"
|
|
848
1319
|
});
|
|
1320
|
+
const RenameCollectionJson = type({
|
|
1321
|
+
kind: "\"renameCollection\"",
|
|
1322
|
+
collection: "string",
|
|
1323
|
+
to: "string"
|
|
1324
|
+
});
|
|
849
1325
|
const CollModJson = type({
|
|
850
1326
|
kind: "\"collMod\"",
|
|
851
1327
|
collection: "string",
|
|
@@ -902,7 +1378,8 @@ const RawUpdateManyJson = type({
|
|
|
902
1378
|
kind: "\"rawUpdateMany\"",
|
|
903
1379
|
collection: "string",
|
|
904
1380
|
filter: "Record<string, unknown>",
|
|
905
|
-
update: "Record<string, unknown> | Record<string, unknown>[]"
|
|
1381
|
+
update: "Record<string, unknown> | Record<string, unknown>[]",
|
|
1382
|
+
"bypassDocumentValidation?": "boolean"
|
|
906
1383
|
});
|
|
907
1384
|
const RawDeleteOneJson = type({
|
|
908
1385
|
kind: "\"rawDeleteOne\"",
|
|
@@ -995,16 +1472,16 @@ const DdlOperationJson = type({
|
|
|
995
1472
|
execute: "Record<string, unknown>[]",
|
|
996
1473
|
postcheck: "Record<string, unknown>[]"
|
|
997
1474
|
});
|
|
998
|
-
const
|
|
1475
|
+
const DmlCheckJson = type({
|
|
999
1476
|
description: "string",
|
|
1000
1477
|
source: "Record<string, unknown>",
|
|
1001
1478
|
filter: "Record<string, unknown>",
|
|
1002
1479
|
expect: "\"exists\" | \"notExists\""
|
|
1003
1480
|
});
|
|
1004
|
-
const
|
|
1481
|
+
const DmlOperationJson = type({
|
|
1005
1482
|
id: "string",
|
|
1006
1483
|
label: "string",
|
|
1007
|
-
operationClass: "\"data\"",
|
|
1484
|
+
operationClass: "\"additive\" | \"widening\" | \"destructive\" | \"data\"",
|
|
1008
1485
|
name: "string",
|
|
1009
1486
|
precheck: "Record<string, unknown>[]",
|
|
1010
1487
|
run: "Record<string, unknown>[]",
|
|
@@ -1153,7 +1630,7 @@ function deserializeDmlCommand(json) {
|
|
|
1153
1630
|
}
|
|
1154
1631
|
case "rawUpdateMany": {
|
|
1155
1632
|
const data = validate(RawUpdateManyJson, json, "rawUpdateMany command");
|
|
1156
|
-
return new RawUpdateManyCommand(data.collection, data.filter, data.update);
|
|
1633
|
+
return new RawUpdateManyCommand(data.collection, data.filter, data.update, ifDefined("bypassDocumentValidation", data.bypassDocumentValidation));
|
|
1157
1634
|
}
|
|
1158
1635
|
case "rawDeleteOne": {
|
|
1159
1636
|
const data = validate(RawDeleteOneJson, json, "rawDeleteOne command");
|
|
@@ -1242,6 +1719,10 @@ function deserializeDdlCommand(json) {
|
|
|
1242
1719
|
});
|
|
1243
1720
|
}
|
|
1244
1721
|
case "dropCollection": return new DropCollectionCommand(validate(DropCollectionJson, json, "dropCollection command").collection);
|
|
1722
|
+
case "renameCollection": {
|
|
1723
|
+
const data = validate(RenameCollectionJson, json, "renameCollection command");
|
|
1724
|
+
return new RenameCollectionCommand(data.collection, data.to);
|
|
1725
|
+
}
|
|
1245
1726
|
case "collMod": {
|
|
1246
1727
|
const data = validate(CollModJson, json, "collMod command");
|
|
1247
1728
|
return new CollModCommand(data.collection, {
|
|
@@ -1286,8 +1767,8 @@ function deserializeStep(json) {
|
|
|
1286
1767
|
command: deserializeDdlCommand(data.command)
|
|
1287
1768
|
};
|
|
1288
1769
|
}
|
|
1289
|
-
function
|
|
1290
|
-
return recordValue(json, "
|
|
1770
|
+
function isDmlOperationJson(json) {
|
|
1771
|
+
return Array.isArray(recordValue(json, "run", "migration operation"));
|
|
1291
1772
|
}
|
|
1292
1773
|
function deserializeDdlOp(json) {
|
|
1293
1774
|
const data = validate(DdlOperationJson, json, "migration operation");
|
|
@@ -1300,8 +1781,8 @@ function deserializeDdlOp(json) {
|
|
|
1300
1781
|
postcheck: data.postcheck.map(deserializeCheck)
|
|
1301
1782
|
};
|
|
1302
1783
|
}
|
|
1303
|
-
function
|
|
1304
|
-
const data = validate(
|
|
1784
|
+
function deserializeDmlCheck(json) {
|
|
1785
|
+
const data = validate(DmlCheckJson, json, "DML operation check");
|
|
1305
1786
|
return {
|
|
1306
1787
|
description: data.description,
|
|
1307
1788
|
source: deserializeMongoQueryPlan(data.source),
|
|
@@ -1309,20 +1790,20 @@ function deserializeDataTransformCheck(json) {
|
|
|
1309
1790
|
expect: data.expect
|
|
1310
1791
|
};
|
|
1311
1792
|
}
|
|
1312
|
-
function
|
|
1313
|
-
const data = validate(
|
|
1793
|
+
function deserializeDmlOp(json) {
|
|
1794
|
+
const data = validate(DmlOperationJson, json, "DML operation");
|
|
1314
1795
|
return {
|
|
1315
1796
|
id: data.id,
|
|
1316
1797
|
label: data.label,
|
|
1317
|
-
operationClass:
|
|
1798
|
+
operationClass: data.operationClass,
|
|
1318
1799
|
name: data.name,
|
|
1319
|
-
precheck: data.precheck.map(
|
|
1800
|
+
precheck: data.precheck.map(deserializeDmlCheck),
|
|
1320
1801
|
run: data.run.map(deserializeMongoQueryPlan),
|
|
1321
|
-
postcheck: data.postcheck.map(
|
|
1802
|
+
postcheck: data.postcheck.map(deserializeDmlCheck)
|
|
1322
1803
|
};
|
|
1323
1804
|
}
|
|
1324
1805
|
function deserializeMongoOp(json) {
|
|
1325
|
-
if (
|
|
1806
|
+
if (isDmlOperationJson(json)) return deserializeDmlOp(json);
|
|
1326
1807
|
return deserializeDdlOp(json);
|
|
1327
1808
|
}
|
|
1328
1809
|
function deserializeMongoOps(json) {
|
|
@@ -1339,6 +1820,59 @@ function runnerFailure(code, summary, opts) {
|
|
|
1339
1820
|
...opts
|
|
1340
1821
|
});
|
|
1341
1822
|
}
|
|
1823
|
+
/**
|
|
1824
|
+
* The failure when a field rename's before-check finds documents that already hold the new name,
|
|
1825
|
+
* which `$rename` would overwrite: what to look for and fix, and that the plan resumes.
|
|
1826
|
+
*/
|
|
1827
|
+
function renameOverwriteRefusal(op) {
|
|
1828
|
+
const rewrite = fieldRewriteOf(op);
|
|
1829
|
+
if (rewrite === void 0) return void 0;
|
|
1830
|
+
const renames = Reflect.get(rewrite.command.update, "$rename");
|
|
1831
|
+
const to = typeof renames === "object" && renames !== null ? Reflect.get(renames, rewrite.field) : void 0;
|
|
1832
|
+
if (typeof to !== "string") return void 0;
|
|
1833
|
+
const [check] = op.precheck;
|
|
1834
|
+
const source = check?.source.command;
|
|
1835
|
+
const match = source instanceof RawAggregateCommand ? Reflect.get(source.pipeline[0] ?? {}, "$match") : void 0;
|
|
1836
|
+
const find = `db.getCollection(${JSON.stringify(rewrite.collection)}).find(${JSON.stringify(match ?? {})})`;
|
|
1837
|
+
return runnerFailure("MIGRATION.PRECHECK_FAILED", `Operation ${op.id} stopped: some documents in collection ${rewrite.collection} already hold ${to} as well as ${rewrite.field}`, {
|
|
1838
|
+
why: `Renaming ${rewrite.field} to ${to} would overwrite their ${to}. Find them with ${find} in mongosh, and move or remove one of the two values in each. Nothing was renamed. The operations before this one in the plan already ran, such as dropping an index on ${rewrite.field}; once the documents are fixed, run the command again and the plan finishes from here.`,
|
|
1839
|
+
meta: {
|
|
1840
|
+
operationId: op.id,
|
|
1841
|
+
name: op.name
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* The failure when a collection rename's before-check finds that the new collection already exists.
|
|
1847
|
+
*/
|
|
1848
|
+
function renameTargetExistsRefusal(op, failed) {
|
|
1849
|
+
const rename = op.execute.find((step) => step.command instanceof RenameCollectionCommand)?.command;
|
|
1850
|
+
if (!(rename instanceof RenameCollectionCommand) || failed.expect !== "notExists") return;
|
|
1851
|
+
const { collection: from, to } = rename;
|
|
1852
|
+
return runnerFailure("MIGRATION.PRECHECK_FAILED", `Operation ${op.id} stopped: collection ${to} already exists, so collection ${from} cannot be renamed to it`, {
|
|
1853
|
+
why: `Nothing was renamed. If ${to} holds nothing you need, drop it, then run the command again; operations that already ran are skipped. If an earlier operation of the same migration created ${to}, as a createIndex on ${to} does, make renameCollection(${JSON.stringify(from)}, ${JSON.stringify(to)}) the first operation of its migration.ts, run node on that migration.ts, and then drop ${to} and run the command again.`,
|
|
1854
|
+
meta: { operationId: op.id }
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
const MONGO_UNAUTHORIZED = 13;
|
|
1858
|
+
/**
|
|
1859
|
+
* The failure for a rewrite that skips validation when the database user lacks the
|
|
1860
|
+
* `bypassDocumentValidation` action, as a `readWrite` user on a hosted cluster does.
|
|
1861
|
+
*/
|
|
1862
|
+
function bypassValidationRefusal(op, plan, error) {
|
|
1863
|
+
if (!(plan.command instanceof RawUpdateManyCommand) || !plan.command.bypassDocumentValidation) return;
|
|
1864
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
1865
|
+
if (Reflect.get(error, "code") !== MONGO_UNAUTHORIZED) return void 0;
|
|
1866
|
+
const codeName = Reflect.get(error, "codeName");
|
|
1867
|
+
return runnerFailure("MIGRATION.EXECUTION_FAILED", `Operation ${op.id} failed: the database user may not skip document validation`, {
|
|
1868
|
+
why: `Renaming or removing a field rewrites documents with bypassDocumentValidation, because the old validator requires the old field. MongoDB refused it with error ${MONGO_UNAUTHORIZED} (${String(codeName)}). Grant the user the bypassDocumentValidation action on the database, for example with the dbAdmin or restore role, then run the command again; operations that already ran are skipped.`,
|
|
1869
|
+
meta: {
|
|
1870
|
+
operationId: op.id,
|
|
1871
|
+
mongoCode: MONGO_UNAUTHORIZED,
|
|
1872
|
+
codeName
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1342
1876
|
var MongoMigrationRunner = class {
|
|
1343
1877
|
deps;
|
|
1344
1878
|
constructor(deps) {
|
|
@@ -1362,22 +1896,22 @@ var MongoMigrationRunner = class {
|
|
|
1362
1896
|
for (const operation of operations) {
|
|
1363
1897
|
options.callbacks?.onOperationStart?.(operation);
|
|
1364
1898
|
try {
|
|
1365
|
-
if (operation
|
|
1366
|
-
const result = await this.
|
|
1899
|
+
if (isMongoDmlOperation(operation)) {
|
|
1900
|
+
const result = await this.executeDmlOperation(operation, adapter, driver, filterEvaluator, runIdempotency, runPrechecks, runPostchecks);
|
|
1367
1901
|
if (result.failure) return result.failure;
|
|
1368
1902
|
if (result.executed) operationsExecuted += 1;
|
|
1369
1903
|
continue;
|
|
1370
1904
|
}
|
|
1371
|
-
const ddlOp = blindCast(operation);
|
|
1372
1905
|
if (runPostchecks && runIdempotency) {
|
|
1373
|
-
if (await this.allChecksSatisfied(
|
|
1906
|
+
if (await this.allChecksSatisfied(operation.postcheck, inspectionExecutor, filterEvaluator)) continue;
|
|
1374
1907
|
}
|
|
1375
1908
|
if (runPrechecks) {
|
|
1376
|
-
|
|
1909
|
+
const failed = await this.firstFailingCheck(operation.precheck, inspectionExecutor, filterEvaluator);
|
|
1910
|
+
if (failed !== void 0) return renameTargetExistsRefusal(operation, failed) ?? runnerFailure("MIGRATION.PRECHECK_FAILED", `Operation ${operation.id} failed during precheck`, { meta: { operationId: operation.id } });
|
|
1377
1911
|
}
|
|
1378
|
-
for (const step of
|
|
1912
|
+
for (const step of operation.execute) await executeDdl(step.command);
|
|
1379
1913
|
if (runPostchecks) {
|
|
1380
|
-
if (!await this.evaluateChecks(
|
|
1914
|
+
if (!await this.evaluateChecks(operation.postcheck, inspectionExecutor, filterEvaluator)) return runnerFailure("MIGRATION.POSTCHECK_FAILED", `Operation ${operation.id} failed during postcheck`, { meta: { operationId: operation.id } });
|
|
1381
1915
|
}
|
|
1382
1916
|
operationsExecuted += 1;
|
|
1383
1917
|
} finally {
|
|
@@ -1449,14 +1983,14 @@ var MongoMigrationRunner = class {
|
|
|
1449
1983
|
});
|
|
1450
1984
|
}
|
|
1451
1985
|
}
|
|
1452
|
-
async
|
|
1986
|
+
async executeDmlOperation(op, adapter, driver, filterEvaluator, runIdempotency, runPrechecks, runPostchecks) {
|
|
1453
1987
|
if (runPostchecks && runIdempotency && op.postcheck.length > 0) {
|
|
1454
|
-
if (await this.
|
|
1988
|
+
if (await this.evaluateDmlChecks(op.postcheck, adapter, driver, filterEvaluator)) return { executed: false };
|
|
1455
1989
|
}
|
|
1456
1990
|
if (runPrechecks && op.precheck.length > 0) {
|
|
1457
|
-
if (!await this.
|
|
1991
|
+
if (!await this.evaluateDmlChecks(op.precheck, adapter, driver, filterEvaluator)) return {
|
|
1458
1992
|
executed: false,
|
|
1459
|
-
failure: runnerFailure("MIGRATION.PRECHECK_FAILED", `Operation ${op.id} failed during precheck`, { meta: {
|
|
1993
|
+
failure: renameOverwriteRefusal(op) ?? runnerFailure("MIGRATION.PRECHECK_FAILED", `Operation ${op.id} failed during precheck`, { meta: {
|
|
1460
1994
|
operationId: op.id,
|
|
1461
1995
|
name: op.name
|
|
1462
1996
|
} })
|
|
@@ -1464,10 +1998,19 @@ var MongoMigrationRunner = class {
|
|
|
1464
1998
|
}
|
|
1465
1999
|
for (const plan of op.run) {
|
|
1466
2000
|
const wireCommand = await adapter.lower(plan, {});
|
|
1467
|
-
|
|
2001
|
+
try {
|
|
2002
|
+
for await (const _ of driver.execute(wireCommand));
|
|
2003
|
+
} catch (error) {
|
|
2004
|
+
const refusal = bypassValidationRefusal(op, plan, error);
|
|
2005
|
+
if (refusal === void 0) throw error;
|
|
2006
|
+
return {
|
|
2007
|
+
executed: false,
|
|
2008
|
+
failure: refusal
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
1468
2011
|
}
|
|
1469
2012
|
if (runPostchecks && op.postcheck.length > 0) {
|
|
1470
|
-
if (!await this.
|
|
2013
|
+
if (!await this.evaluateDmlChecks(op.postcheck, adapter, driver, filterEvaluator)) return {
|
|
1471
2014
|
executed: false,
|
|
1472
2015
|
failure: runnerFailure("MIGRATION.POSTCHECK_FAILED", `Operation ${op.id} failed during postcheck`, { meta: {
|
|
1473
2016
|
operationId: op.id,
|
|
@@ -1477,7 +2020,7 @@ var MongoMigrationRunner = class {
|
|
|
1477
2020
|
}
|
|
1478
2021
|
return { executed: true };
|
|
1479
2022
|
}
|
|
1480
|
-
async
|
|
2023
|
+
async evaluateDmlChecks(checks, adapter, driver, filterEvaluator) {
|
|
1481
2024
|
for (const check of checks) {
|
|
1482
2025
|
const commandKind = check.source.command.kind;
|
|
1483
2026
|
if (!READ_ONLY_CHECK_COMMAND_KINDS.has(commandKind)) throw errorRunnerFailed(`Data-transform check rejected: command kind "${commandKind}" is not read-only`, {
|
|
@@ -1500,11 +2043,13 @@ var MongoMigrationRunner = class {
|
|
|
1500
2043
|
return true;
|
|
1501
2044
|
}
|
|
1502
2045
|
async evaluateChecks(checks, inspectionExecutor, filterEvaluator) {
|
|
2046
|
+
return await this.firstFailingCheck(checks, inspectionExecutor, filterEvaluator) === void 0;
|
|
2047
|
+
}
|
|
2048
|
+
async firstFailingCheck(checks, inspectionExecutor, filterEvaluator) {
|
|
1503
2049
|
for (const check of checks) {
|
|
1504
2050
|
const matchFound = (await check.source.accept(inspectionExecutor)).some((doc) => filterEvaluator.evaluate(check.filter, doc));
|
|
1505
|
-
if (!(check.expect === "exists" ? matchFound : !matchFound)) return
|
|
2051
|
+
if (!(check.expect === "exists" ? matchFound : !matchFound)) return check;
|
|
1506
2052
|
}
|
|
1507
|
-
return true;
|
|
1508
2053
|
}
|
|
1509
2054
|
async allChecksSatisfied(checks, inspectionExecutor, filterEvaluator) {
|
|
1510
2055
|
if (checks.length === 0) return false;
|
|
@@ -1628,10 +2173,6 @@ const mongoTargetDescriptor = {
|
|
|
1628
2173
|
contractSerializer: new MongoTargetContractSerializer(),
|
|
1629
2174
|
schemaVerifier: new MongoTargetSchemaVerifier(),
|
|
1630
2175
|
migrations: {
|
|
1631
|
-
renameStatements: {
|
|
1632
|
-
refused: true,
|
|
1633
|
-
keepDataByHand
|
|
1634
|
-
},
|
|
1635
2176
|
createPlanner(_adapter) {
|
|
1636
2177
|
return new MongoMigrationPlanner();
|
|
1637
2178
|
},
|
|
@@ -1681,6 +2222,6 @@ const mongoTargetDescriptor = {
|
|
|
1681
2222
|
}
|
|
1682
2223
|
};
|
|
1683
2224
|
//#endregion
|
|
1684
|
-
export {
|
|
2225
|
+
export { schemaIndexToCreateIndexOptions as C, schemaCollectionToCreateCollectionOptions as S, deserializeMongoOp as _, DropIndexCall as a, renderCallsToTypeScript as b, MongoMigrationRunner as c, MongoTargetSchemaVerifier as d, MongoTargetUnboundDatabase as f, UnsetFieldCall as g, RenameFieldCall as h, DropCollectionCall as i, MongoTargetContractSerializer as l, RenameCollectionCall as m, CreateCollectionCall as n, FilterEvaluator as o, PlannerProducedMongoMigration as p, CreateIndexCall as r, MongoMigrationPlanner as s, CollModCall as t, MongoTargetDatabase as u, deserializeMongoOps as v, serializeMongoOps as w, renderOps as x, mongoTargetDescriptor as y };
|
|
1685
2226
|
|
|
1686
|
-
//# sourceMappingURL=control-
|
|
2227
|
+
//# sourceMappingURL=control-BcqDWXwi.mjs.map
|