update.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // ██╗ ██╗██████╗ ██████╗ █████╗ ████████╗███████╗
  2. // ██║ ██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
  3. // ██║ ██║██████╔╝██║ ██║███████║ ██║ █████╗
  4. // ██║ ██║██╔═══╝ ██║ ██║██╔══██║ ██║ ██╔══╝
  5. // ╚██████╔╝██║ ██████╔╝██║ ██║ ██║ ███████╗
  6. // ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝
  7. //
  8. // Modify the record(s) and return the values that were modified if needed.
  9. // If a fetch was performed, first the records need to be searched for with the
  10. // primary key selected.
  11. var _ = require('@sailshq/lodash');
  12. var runQuery = require('./run-query');
  13. var compileStatement = require('./compile-statement');
  14. module.exports = function insertRecord(options, cb) {
  15. // ╦ ╦╔═╗╦ ╦╔╦╗╔═╗╔╦╗╔═╗ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐
  16. // ╚╗╔╝╠═╣║ ║ ║║╠═╣ ║ ║╣ │ │├─┘ │ ││ ││││└─┐
  17. // ╚╝ ╩ ╩╩═╝╩═╩╝╩ ╩ ╩ ╚═╝ └─┘┴ ┴ ┴└─┘┘└┘└─┘
  18. if (_.isUndefined(options) || !_.isPlainObject(options)) {
  19. throw new Error('Invalid options argument. Options must contain: connection, statement, fetch, and primaryKey.');
  20. }
  21. if (!_.has(options, 'connection') || !_.isObject(options.connection)) {
  22. throw new Error('Invalid option used in options argument. Missing or invalid connection.');
  23. }
  24. if (!_.has(options, 'statement') || !_.isPlainObject(options.statement)) {
  25. throw new Error('Invalid option used in options argument. Missing or invalid statement.');
  26. }
  27. if (!_.has(options, 'primaryKey') || !_.isString(options.primaryKey)) {
  28. throw new Error('Invalid option used in options argument. Missing or invalid primaryKey.');
  29. }
  30. if (!_.has(options, 'fetch') || !_.isBoolean(options.fetch)) {
  31. throw new Error('Invalid option used in options argument. Missing or invalid fetch flag.');
  32. }
  33. // ╔═╗╔═╗╔╦╗ ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┐ ┌┐ ┌─┐┬┌┐┌┌─┐ ┬ ┬┌─┐┌┬┐┌─┐┌┬┐┌─┐┌┬┐
  34. // ║ ╦║╣ ║ ├┬┘├┤ │ │ │├┬┘ ││└─┐ ├┴┐├┤ │││││ ┬ │ │├─┘ ││├─┤ │ ├┤ ││
  35. // ╚═╝╚═╝ ╩ ┴└─└─┘└─┘└─┘┴└──┴┘└─┘ └─┘└─┘┴┘└┘└─┘ └─┘┴ ─┴┘┴ ┴ ┴ └─┘─┴┘
  36. // If a fetch is used, the records that will be updated need to be found first.
  37. // This is because in order to (semi) accurately return the records that were
  38. // updated in MySQL first they need to be found, then updated, then found again.
  39. // Why? Because if you have a criteria such as update name to foo where name = bar
  40. // Once the records have been updated there is no way to get them again. So first
  41. // select the primary keys of the records to update, update the records, and then
  42. // search for those records.
  43. (function getRecordsToUpdate(proceed) {
  44. // Only look up the records if fetch was used
  45. if (!options.fetch) {
  46. return proceed();
  47. }
  48. // Otherwise build up a select query
  49. var fetchStatement = {
  50. select: [options.primaryKey],
  51. from: options.statement.using,
  52. where: options.statement.where
  53. };
  54. // ╔═╗╔═╗╔╦╗╔═╗╦╦ ╔═╗ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬
  55. // ║ ║ ║║║║╠═╝║║ ║╣ │─┼┐│ │├┤ ├┬┘└┬┘
  56. // ╚═╝╚═╝╩ ╩╩ ╩╩═╝╚═╝ └─┘└└─┘└─┘┴└─ ┴
  57. // Compile the statement into a native query.
  58. var compiledFetchQuery;
  59. try {
  60. compiledFetchQuery = compileStatement(fetchStatement);
  61. } catch (e) {
  62. // If the statement could not be compiled, return an error.
  63. return proceed(e);
  64. }
  65. // ╦═╗╦ ╦╔╗╔ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬
  66. // ╠╦╝║ ║║║║ │─┼┐│ │├┤ ├┬┘└┬┘
  67. // ╩╚═╚═╝╝╚╝ └─┘└└─┘└─┘┴└─ ┴
  68. // Run the initial find query
  69. runQuery({
  70. connection: options.connection,
  71. nativeQuery: compiledFetchQuery.nativeQuery,
  72. valuesToEscape: compiledFetchQuery.valuesToEscape,
  73. meta: compiledFetchQuery.meta,
  74. disconnectOnError: false,
  75. queryType: 'select'
  76. },
  77. function runQueryCb(err, report) {
  78. if (err) {
  79. return proceed(err);
  80. }
  81. return proceed(undefined, report);
  82. });
  83. })(function afterInitialFetchCb(err, selectReport) {
  84. if (err) {
  85. return cb(err);
  86. }
  87. // ╔═╗╔═╗╔╦╗╔═╗╦╦ ╔═╗ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬
  88. // ║ ║ ║║║║╠═╝║║ ║╣ │─┼┐│ │├┤ ├┬┘└┬┘
  89. // ╚═╝╚═╝╩ ╩╩ ╩╩═╝╚═╝ └─┘└└─┘└─┘┴└─ ┴
  90. // Compile the update statement into a native query.
  91. var compiledUpdateQuery;
  92. try {
  93. compiledUpdateQuery = compileStatement(options.statement);
  94. } catch (e) {
  95. // If the statement could not be compiled, return an error.
  96. return cb(e);
  97. }
  98. // ╦═╗╦ ╦╔╗╔ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬
  99. // ╠╦╝║ ║║║║ │─┼┐│ │├┤ ├┬┘└┬┘
  100. // ╩╚═╚═╝╝╚╝ └─┘└└─┘└─┘┴└─ ┴
  101. // Run the initial query
  102. runQuery({
  103. connection: options.connection,
  104. nativeQuery: compiledUpdateQuery.nativeQuery,
  105. valuesToEscape: compiledUpdateQuery.valuesToEscape,
  106. meta: compiledUpdateQuery.meta,
  107. disconnectOnError: false,
  108. queryType: 'update'
  109. },
  110. function runQueryCb(err, report) {
  111. if (err) {
  112. return cb(err);
  113. }
  114. // If no fetch was used, then nothing else needs to be done.
  115. if (!options.fetch) {
  116. return cb(undefined, report.result);
  117. }
  118. // ╔═╗╔═╗╦═╗╔═╗╔═╗╦═╗╔╦╗ ┌┬┐┬ ┬┌─┐ ┌─┐┌─┐┌┬┐┌─┐┬ ┬
  119. // ╠═╝║╣ ╠╦╝╠╣ ║ ║╠╦╝║║║ │ ├─┤├┤ ├┤ ├┤ │ │ ├─┤
  120. // ╩ ╚═╝╩╚═╚ ╚═╝╩╚═╩ ╩ ┴ ┴ ┴└─┘ └ └─┘ ┴ └─┘┴ ┴
  121. // Otherwise, fetch the newly inserted record
  122. var fetchStatement = {
  123. select: '*',
  124. from: options.statement.using,
  125. where: {}
  126. };
  127. // Build the fetch statement where clause
  128. var selectPks = _.map(selectReport.result, function mapPks(record) {
  129. return record[options.primaryKey];
  130. });
  131. fetchStatement.where[options.primaryKey] = {
  132. in: selectPks
  133. };
  134. // Handle case where pk value was changed:
  135. if (!_.isUndefined(options.statement.update[options.primaryKey])) {
  136. // There should only ever be either zero or one record that were found before.
  137. if (selectPks.length === 0) { /* do nothing */ }
  138. else if (selectPks.length === 1) {
  139. var oldPkValue = selectPks[0];
  140. _.remove(fetchStatement.where[options.primaryKey].in, oldPkValue);
  141. var newPkValue = options.statement.update[options.primaryKey];
  142. fetchStatement.where[options.primaryKey].in.push(newPkValue);
  143. }
  144. else {
  145. return cb(new Error('Consistency violation: Updated multiple records to have the same primary key value. (PK values should be unique!)'));
  146. }
  147. }
  148. // ╔═╗╔═╗╔╦╗╔═╗╦╦ ╔═╗ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬
  149. // ║ ║ ║║║║╠═╝║║ ║╣ │─┼┐│ │├┤ ├┬┘└┬┘
  150. // ╚═╝╚═╝╩ ╩╩ ╩╩═╝╚═╝ └─┘└└─┘└─┘┴└─ ┴
  151. // Compile the statement into a native query.
  152. var compiledFetchQuery;
  153. try {
  154. compiledFetchQuery = compileStatement(fetchStatement);
  155. } catch (err) {
  156. // If the statement could not be compiled, return an error.
  157. return cb(err);
  158. }
  159. // ╦═╗╦ ╦╔╗╔ ┌─┐ ┬ ┬┌─┐┬─┐┬ ┬
  160. // ╠╦╝║ ║║║║ │─┼┐│ │├┤ ├┬┘└┬┘
  161. // ╩╚═╚═╝╝╚╝ └─┘└└─┘└─┘┴└─ ┴
  162. // Run the fetch query.
  163. runQuery({
  164. connection: options.connection,
  165. nativeQuery: compiledFetchQuery.nativeQuery,
  166. valuesToEscape: compiledFetchQuery.valuesToEscape,
  167. meta: compiledFetchQuery.meta,
  168. disconnectOnError: false,
  169. queryType: 'select'
  170. }, function runQueryCb(err, report) {
  171. if (err) {
  172. return cb(err);
  173. }
  174. return cb(undefined, report.result);
  175. });
  176. });
  177. });
  178. };