UpsertGraphOperation.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. const Promise = require('bluebird');
  2. const UpsertNode = require('../graphUpserter/UpsertNode');
  3. const UpsertGraph = require('../graphUpserter/UpsertGraph');
  4. const HasManyRelation = require('../../relations/hasMany/HasManyRelation');
  5. const InsertGraphOperation = require('./InsertGraphOperation');
  6. const transformOptionsFromPath = require('../../utils/transformOptionsFromPath');
  7. class UpsertGraphOperation extends InsertGraphOperation {
  8. constructor(name, opt) {
  9. super(
  10. name,
  11. Object.assign({}, opt, {
  12. opt: {}
  13. })
  14. );
  15. this.graph = null;
  16. this.upsertOpt = opt.opt || {};
  17. }
  18. onBefore1(builder) {
  19. this.graph = new UpsertGraph(this.models, this.isArray, this.upsertOpt);
  20. return this.graph.build(builder).then(() => this.delete(builder));
  21. }
  22. // This operation inherits from InsertGraphOperation because we use it to perform
  23. // all the inserts. Before the graph inserter instance is created, this method gets called.
  24. // We need to set a `dbRefProp` for each `Relate` node so that the graph inserter takes care
  25. // of most of the relates in an efficient way.
  26. beforeGraphInserterCreated(builder) {
  27. for (let i = 0, l = this.graph.nodes.length; i < l; ++i) {
  28. const node = this.graph.nodes[i];
  29. // Set `dbRefProp` for the upsertModel so that `insertGraph` takes care of relating models.
  30. // `insertGraph` cannot relate `HasManyRelation`s, so we ignore those.
  31. if (node.hasType(UpsertNode.Type.Relate) && !(node.relation instanceof HasManyRelation)) {
  32. node.upsertModel[node.modelClass.dbRefProp] = node.upsertModel.$values(
  33. node.relation.relatedProp.props
  34. );
  35. }
  36. }
  37. }
  38. // This operation inherits from InsertGraphOperation because we use it to perform
  39. // all the inserts. After the graph inserter instance is created, this method gets called
  40. // We need to modify the insert graph so that existing models won't get inserted again.
  41. afterGraphInserterCreated(builder, graphInserter) {
  42. const insertNodes = graphInserter.graph.nodes;
  43. // Go through all insert graph nodes and mark the ones that should not be inserted
  44. // as inserted so that they are not inserted again.
  45. for (let i = 0, l = insertNodes.length; i < l; ++i) {
  46. const insertNode = insertNodes[i];
  47. const upsertNode = this.graph.nodesByUpsert.get(insertNode.model);
  48. if ((!upsertNode || !upsertNode.hasType(UpsertNode.Type.Insert)) && !insertNode.handled) {
  49. // Resolve the nodes that depend on the node's model.
  50. for (let d = 0, ld = insertNode.isNeededBy.length; d < ld; ++d) {
  51. insertNode.isNeededBy[d].resolve(insertNode.model);
  52. }
  53. if (insertNode.parentNode) {
  54. const parent = insertNode.parentNode;
  55. // If this node is in a many-to-many relation we also need to remove it from
  56. // its parent's manyToManyConnections so that the join row is not inserted.
  57. parent.manyToManyConnections = parent.manyToManyConnections.filter(
  58. conn => conn.node !== insertNode
  59. );
  60. }
  61. insertNode.markAsInserted();
  62. }
  63. }
  64. }
  65. onAfter2(builder, result) {
  66. return this.relate(builder)
  67. .then(() => this.update(builder))
  68. .then(() => this.upsertRecursively(builder))
  69. .then(() => super.onAfter2(builder, result));
  70. }
  71. delete(builder) {
  72. return Promise.map(
  73. this.graph.nodes,
  74. node => {
  75. const relNames = Object.keys(node.relations);
  76. return Promise.map(
  77. relNames,
  78. relName => {
  79. const relation = node.modelClass.getRelation(relName);
  80. const nodes = node.relations[relName].filter(it =>
  81. it.hasType(UpsertNode.Type.Delete, UpsertNode.Type.Unrelate)
  82. );
  83. const ids = nodes.map(it => it.currentModel.$id());
  84. if (ids.length) {
  85. const unrelate = nodes[0].hasType(UpsertNode.Type.Unrelate);
  86. const query = node.upsertModel.$relatedQuery(relName).childQueryOf(builder);
  87. if (!relation.isOneToOne()) {
  88. query.whereInComposite(builder.fullIdColumnFor(relation.relatedModelClass), ids);
  89. }
  90. if (unrelate) {
  91. query.unrelate();
  92. } else {
  93. query.delete();
  94. }
  95. return query;
  96. }
  97. },
  98. { concurrency: 1 }
  99. );
  100. },
  101. { concurrency: this.graph.rootModelClass.concurrency }
  102. );
  103. }
  104. relate(builder) {
  105. // We only need to relate `HasManyRelations` (and HasOneRelations that inherit HasManyRelation)
  106. // because the graph inserter took care of the other relates.
  107. const relateNodes = this.graph.nodes.filter(
  108. it => it.hasType(UpsertNode.Type.Relate) && it.relation instanceof HasManyRelation
  109. );
  110. return Promise.map(
  111. relateNodes,
  112. node => {
  113. return node.parentNode.upsertModel
  114. .$relatedQuery(node.relationName)
  115. .childQueryOf(builder, true)
  116. .relate(node.upsertModel.$id());
  117. },
  118. { concurrency: this.graph.rootModelClass.concurrency }
  119. );
  120. }
  121. update(builder) {
  122. const updateNodes = this.graph.nodes.filter(it =>
  123. it.hasType(UpsertNode.Type.Update, UpsertNode.Type.Patch)
  124. );
  125. return Promise.map(
  126. updateNodes,
  127. node => {
  128. let query = null;
  129. const patch = node.hasType(UpsertNode.Type.Patch);
  130. // The models were created by the parent class `InsertGraphOperation` with a
  131. // skipValidation flag. We need to explicitly call $validate here.
  132. node.upsertModel.$validate(node.upsertModel, {
  133. patch: patch,
  134. dataPath: node.dataPath
  135. });
  136. if (node.parentNode) {
  137. // Call patch through parent's $relatedQuery to make things like many-to-many
  138. // extra property updates to work.
  139. query = node.parentNode.upsertModel
  140. .$relatedQuery(node.relationName)
  141. .childQueryOf(builder, true)
  142. .findById(node.upsertModel.$id())
  143. [patch ? 'patch' : 'update'](node.upsertModel);
  144. } else {
  145. query = node.upsertModel
  146. .$query()
  147. .childQueryOf(builder, true)
  148. .patch();
  149. }
  150. return query;
  151. },
  152. { concurrency: this.graph.rootModelClass.concurrency }
  153. );
  154. }
  155. upsertRecursively(builder) {
  156. const upsertRecursivelyNodes = this.graph.nodes.filter(it =>
  157. it.hasType(UpsertNode.Type.UpsertRecursively)
  158. );
  159. return Promise.map(
  160. upsertRecursivelyNodes,
  161. node => {
  162. return node.upsertModel.constructor
  163. .query()
  164. .upsertGraph(node.upsertModel, transformOptionsFromPath(node.opt, node.relPathFromRoot))
  165. .childQueryOf(builder, true);
  166. },
  167. { concurrency: this.graph.rootModelClass.concurrency }
  168. );
  169. }
  170. }
  171. module.exports = UpsertGraphOperation;