123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- const { difference } = require('../../utils/objectUtils');
- const { isTempColumn } = require('../../utils/tmpColumnUtils');
- const BelongsToOneRelation = require('../../relations/belongsToOne/BelongsToOneRelation');
- const UpsertNodeType = Object.freeze({
- Insert: 'Insert',
- Delete: 'Delete',
- Update: 'Update',
- Patch: 'Patch',
- Relate: 'Relate',
- Unrelate: 'Unrelate',
- UpsertRecursively: 'UpsertRecursively',
- None: 'None'
- });
- const ChangeType = Object.freeze({
- HasOwnChanges: 'HasOwnChanges',
- HasRelationalChanges: 'HasRelationalChanges',
- NoChanges: 'NoChanges'
- });
- const OptionType = Object.freeze({
- Relate: 'relate',
- Unrelate: 'unrelate',
- InsertMissing: 'insertMissing',
- Update: 'update',
- NoInsert: 'noInsert',
- NoUpdate: 'noUpdate',
- NoDelete: 'noDelete',
- NoRelate: 'noRelate',
- NoUnrelate: 'noUnrelate'
- });
- class UpsertNode {
- constructor({ parentNode, relExpr, upsertModel, currentModel, dataPath, opt }) {
- this.parentNode = parentNode || null;
- this.relExpr = relExpr;
- this.relPathFromRoot = getRelationPathFromRoot(this);
- this.upsertModel = upsertModel || null;
- this.currentModel = currentModel || null;
- this.relations = Object.create(null);
- this.dataPath = dataPath;
- this.opt = opt || {};
- const { types, omitFromUpdate } = getTypes(this);
- this.types = types;
- if (upsertModel && currentModel) {
- copyCurrentToUpsert(currentModel, upsertModel);
- }
- if (omitFromUpdate) {
- this.upsertModel.$omitFromDatabaseJson(omitFromUpdate);
- }
- }
- static get Type() {
- return UpsertNodeType;
- }
- static get OptionType() {
- return OptionType;
- }
- get someModel() {
- return this.upsertModel || this.currentModel;
- }
- get modelClass() {
- return this.someModel.constructor;
- }
- get relationName() {
- if (this.parentNode !== null) {
- return this.relExpr.$relation;
- } else {
- return null;
- }
- }
- get relation() {
- if (this.parentNode !== null) {
- return this.parentNode.modelClass.getRelations()[this.relationName];
- } else {
- return null;
- }
- }
- hasType() {
- for (let i = 0, l = arguments.length; i < l; ++i) {
- if (this.types.indexOf(arguments[i]) !== -1) {
- return true;
- }
- }
- return false;
- }
- }
- function copyCurrentToUpsert(currentModel, upsertModel) {
- const props = Object.keys(currentModel);
- for (let i = 0, l = props.length; i < l; ++i) {
- const prop = props[i];
- // Temp columns are created by some queries and they are never meant to
- // be seen by the outside world. Skip those in addition to undefineds.
- if (!isTempColumn(prop) && upsertModel[prop] === undefined) {
- upsertModel[prop] = currentModel[prop];
- }
- }
- }
- function getTypes(node) {
- if (isInsertWithId(node)) {
- return getTypesInsertWithId(node);
- } else if (isInsert(node)) {
- return getTypesInsert(node);
- } else if (isDeleteOrUnrelate(node)) {
- return getTypesDeleteUnrelate(node);
- } else {
- return getTypesUpdate(node);
- }
- }
- function isInsertWithId(node) {
- // Database doesn't have the model, but the upsert graph does and the model
- // in the upsert graph has an id. Depending on other options this might end
- // up being either an insert or a relate.
- return isInsert(node) && node.upsertModel.$hasId();
- }
- function getTypesInsertWithId(node) {
- if (hasOption(node, OptionType.Relate) && node.relation !== null) {
- return getTypesRelate(node);
- } else if (hasOption(node, OptionType.InsertMissing)) {
- // If insertMissing option is set for the node, we insert the model
- // even though it has the id set.
- return getTypesInsert(node);
- } else {
- const parent = node.parentNode;
- throw new Error(
- [
- parent
- ? `model (id=${node.upsertModel.$id()}) is not a child of model (id=${parent.upsertModel.$id()}). `
- : `root model (id=${node.upsertModel.$id()}) does not exist. `,
- parent ? `If you want to relate it, use the relate option. ` : '',
- `If you want to insert it with an id, use the insertMissing option`
- ].join('')
- );
- }
- }
- function getTypesRelate(node) {
- const props = Object.keys(node.upsertModel);
- const rel = node.parentNode.modelClass.getRelations()[node.relationName];
- if (difference(props, rel.relatedProp.props).length !== 0) {
- const relateType = decideType(node, UpsertNodeType.Relate, OptionType.NoRelate);
- // If the relate model contains any other properties besides the foreign
- // keys needed to make the relation, we may also need to update it.
- const possibleUpdateType = decideType(
- node,
- UpsertNodeType.Patch,
- OptionType.Update,
- UpsertNodeType.Update
- );
- const updateType = decideType(node, possibleUpdateType, OptionType.NoUpdate);
- if (relateType === UpsertNodeType.None) {
- return {
- types: [UpsertNodeType.None]
- };
- } else if (updateType === UpsertNodeType.None) {
- return {
- types: [relateType]
- };
- } else if (
- relateType === UpsertNodeType.Relate &&
- hasRelationsInUpsertModel(node.upsertModel)
- ) {
- return {
- types: [
- decideType(node, UpsertNodeType.Relate, OptionType.NoRelate),
- decideType(node, UpsertNodeType.UpsertRecursively, OptionType.NoRelate)
- ]
- };
- } else {
- return {
- types: [relateType, updateType],
- // If we update, we don't want to update the relation props.
- omitFromUpdate: rel.relatedProp.props
- };
- }
- } else {
- return {
- types: [decideType(node, UpsertNodeType.Relate, OptionType.NoRelate)]
- };
- }
- }
- function isInsert(node) {
- // Database doesn't have the model, but the upsert graph does.
- return node.upsertModel !== null && node.currentModel === null;
- }
- function getTypesInsert(node) {
- return {
- types: [decideType(node, UpsertNodeType.Insert, OptionType.NoInsert)]
- };
- }
- function isDeleteOrUnrelate(node) {
- // Database has the model, but the upsert graph doesn't.
- return node.upsertModel === null && node.currentModel !== null;
- }
- function getTypesDeleteUnrelate(node) {
- const ciblingNodes = node.parentNode.relations[node.relation.name];
- const type = hasOption(node, OptionType.Unrelate)
- ? decideType(node, UpsertNodeType.Unrelate, OptionType.NoUnrelate)
- : decideType(node, UpsertNodeType.Delete, OptionType.NoDelete);
- // Optimization: If the relation is a BelongsToOneRelation and we are
- // going to relate a new model to it, we don't need to unrelate since
- // we would end up with useless update operation.
- if (
- type === UpsertNodeType.Unrelate &&
- node.relation instanceof BelongsToOneRelation &&
- ciblingNodes &&
- ciblingNodes.some(it => it.hasType(UpsertNodeType.Relate, UpsertNodeType.Insert))
- ) {
- return {
- types: [UpsertNodeType.None]
- };
- } else {
- return {
- types: [type]
- };
- }
- }
- function getTypesUpdate(node) {
- const { changeType, unchangedProps } = hasChanges(node.currentModel, node.upsertModel);
- if (changeType == ChangeType.NoChanges) {
- return {
- types: [UpsertNodeType.None],
- omitFromUpdate: unchangedProps
- };
- } else if (changeType == ChangeType.HasOwnChanges) {
- const possibleUpdateType = decideType(
- node,
- UpsertNodeType.Patch,
- OptionType.Update,
- UpsertNodeType.Update
- );
- const updateType = decideType(node, possibleUpdateType, OptionType.NoUpdate);
- return {
- types: [updateType],
- omitFromUpdate: unchangedProps
- };
- } else if (changeType == ChangeType.HasRelationalChanges) {
- // Always create a patch node for relational changes even if `noUpdate`
- // option is true.
- return {
- types: [UpsertNodeType.Patch],
- omitFromUpdate: unchangedProps
- };
- }
- }
- function hasOption(node, optName) {
- const opt = node.opt[optName];
- if (Array.isArray(opt)) {
- return opt.indexOf(node.relPathFromRoot) !== -1;
- } else {
- return !!opt;
- }
- }
- function decideType(node, defaultType, option, optionType = UpsertNodeType.None) {
- return hasOption(node, option) ? optionType : defaultType;
- }
- function getRelationPathFromRoot(node) {
- const path = [];
- while (node) {
- if (node.relExpr.$relation) {
- path.unshift(node.relExpr.$relation);
- }
- node = node.parentNode;
- }
- return path.join('.');
- }
- function hasChanges(currentModel, upsertModel) {
- let changeType = ChangeType.NoChanges;
- const changingRelProps = findChangingRelProps(currentModel, upsertModel);
- if (changingRelProps.length) {
- changeType = ChangeType.HasRelationalChanges;
- }
- if (changeType === ChangeType.NoChanges) {
- // If the upsert model has query properties, we cannot know if they will change
- // the value. We need to return HasOwnChanges just in case.
- if (upsertModel.$$queryProps && Object.keys(upsertModel.$$queryProps).length > 0) {
- changeType = ChangeType.HasOwnChanges;
- }
- }
- const keys = Object.keys(upsertModel);
- const relations = upsertModel.constructor.getRelations();
- const unchangedProps = [];
- for (let i = 0, l = keys.length; i < l; ++i) {
- const key = keys[i];
- if (key[0] === '$' || relations[key]) {
- continue;
- }
- // Use non-strict inequality here on purpose. See issue #732.
- if (currentModel[key] === undefined || currentModel[key] != upsertModel[key]) {
- if (changeType === ChangeType.NoChanges) {
- changeType = ChangeType.HasOwnChanges;
- }
- } else if (!changingRelProps.includes(key)) {
- unchangedProps.push(key);
- }
- }
- return {
- changeType: changeType,
- unchangedProps
- };
- }
- function hasRelationsInUpsertModel(upsertModel) {
- const relationArray = upsertModel.constructor.getRelationArray();
- for (let i = 0, l = relationArray.length; i < l; ++i) {
- const relation = relationArray[i];
- const upsertRelated = upsertModel[relation.name];
- if (upsertRelated) {
- return true;
- }
- }
- return false;
- }
- function findChangingRelProps(currentModel, upsertModel) {
- const relationArray = upsertModel.constructor.getRelationArray();
- const changingProps = [];
- for (let i = 0, l = relationArray.length; i < l; ++i) {
- const relation = relationArray[i];
- const upsertRelated = upsertModel[relation.name];
- if (upsertRelated && relation instanceof BelongsToOneRelation) {
- // If the the property is a `BelongsToOneRelation` me may need to update
- // this model if the related model changes causing this model's relation
- // property to need updating.
- const relatedProp = relation.relatedProp;
- const ownerProp = relation.ownerProp;
- const currentRelated = currentModel[relation.name];
- for (let j = 0, lr = relatedProp.size; j < lr; ++j) {
- const currentProp = currentRelated && relatedProp.getProp(currentRelated, j);
- const upsertProp = upsertRelated && relatedProp.getProp(upsertRelated, j);
- if (currentProp !== upsertProp) {
- changingProps.push(ownerProp.props[j]);
- }
- }
- }
- }
- return changingProps;
- }
- module.exports = UpsertNode;
|