compiler.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. 'use strict';
  2. var _ = require('lodash');
  3. var inherits = require('inherits');
  4. var Oracle_Compiler = require('../../oracle/query/compiler');
  5. var ReturningHelper = require('../utils').ReturningHelper;
  6. var BlobHelper = require('../utils').BlobHelper;
  7. function Oracledb_Compiler(client, builder) {
  8. Oracle_Compiler.call(this, client, builder);
  9. }
  10. inherits(Oracledb_Compiler, Oracle_Compiler);
  11. _.assign(Oracledb_Compiler.prototype, {
  12. // Compiles an "insert" query, allowing for multiple
  13. // inserts using a single query statement.
  14. insert: function insert() {
  15. var self = this;
  16. var outBindPrep = this._prepOutbindings(this.single.insert, this.single.returning);
  17. var outBinding = outBindPrep.outBinding;
  18. var returning = outBindPrep.returning;
  19. var insertValues = outBindPrep.values;
  20. if (Array.isArray(insertValues) && insertValues.length === 1 && _.isEmpty(insertValues[0])) {
  21. return this._addReturningToSqlAndConvert('insert into ' + this.tableName + ' (' + this.formatter.wrap(this.single.returning) + ') values (default)', outBinding[0], this.tableName, returning);
  22. }
  23. if (_.isEmpty(this.single.insert) && typeof this.single.insert !== 'function') {
  24. return '';
  25. }
  26. var insertData = this._prepInsert(insertValues);
  27. var sql = {};
  28. if (_.isString(insertData)) {
  29. return this._addReturningToSqlAndConvert('insert into ' + this.tableName + ' ' + insertData, outBinding[0], this.tableName, returning);
  30. }
  31. if (insertData.values.length === 1) {
  32. return this._addReturningToSqlAndConvert('insert into ' + this.tableName + ' (' + this.formatter.columnize(insertData.columns) + ') values (' + this.formatter.parameterize(insertData.values[0]) + ')', outBinding[0], this.tableName, returning);
  33. }
  34. var insertDefaultsOnly = insertData.columns.length === 0;
  35. sql.returning = returning;
  36. sql.sql = 'begin ' + _.map(insertData.values, function (value, index) {
  37. var parameterizedValues = !insertDefaultsOnly ? self.formatter.parameterize(value, self.client.valueForUndefined) : '';
  38. var subSql = 'insert into ' + self.tableName;
  39. if (insertDefaultsOnly) {
  40. // No columns given so only the default value
  41. subSql += ' (' + self.formatter.wrap(self.single.returning) + ') values (default)';
  42. } else {
  43. subSql += ' (' + self.formatter.columnize(insertData.columns) + ') values (' + parameterizedValues + ')';
  44. }
  45. var returningClause = '';
  46. var intoClause = '';
  47. var usingClause = '';
  48. var outClause = '';
  49. _.each(value, function (val) {
  50. if (!(val instanceof BlobHelper)) {
  51. usingClause += ' ?,';
  52. }
  53. });
  54. usingClause = usingClause.slice(0, -1);
  55. // Build returning and into clauses
  56. _.each(outBinding[index], function (ret) {
  57. var columnName = ret.columnName || ret;
  58. returningClause += '"' + columnName + '",';
  59. intoClause += ' ?,';
  60. outClause += ' out ?,';
  61. // Add Helpers to bindings
  62. if (ret instanceof BlobHelper) {
  63. return self.formatter.bindings.push(ret);
  64. }
  65. self.formatter.bindings.push(new ReturningHelper(columnName));
  66. });
  67. // Strip last comma
  68. returningClause = returningClause.slice(0, -1);
  69. intoClause = intoClause.slice(0, -1);
  70. outClause = outClause.slice(0, -1);
  71. if (returningClause && intoClause) {
  72. subSql += ' returning ' + returningClause + ' into' + intoClause;
  73. }
  74. // Pre bind position because subSql is an execute immediate parameter
  75. // later position binding will only convert the ? params
  76. subSql = self.formatter.client.positionBindings(subSql);
  77. var parameterizedValuesWithoutDefaultAndBlob = parameterizedValues.replace('DEFAULT, ', '').replace(', DEFAULT', '').replace('EMPTY_BLOB(), ', '').replace(', EMPTY_BLOB()', '');
  78. return 'execute immediate \'' + subSql.replace(/'/g, "''") + (parameterizedValuesWithoutDefaultAndBlob || value ? '\' using ' : '') + parameterizedValuesWithoutDefaultAndBlob + (parameterizedValuesWithoutDefaultAndBlob && outClause ? ',' : '') + outClause + ';';
  79. }).join(' ') + 'end;';
  80. sql.outBinding = outBinding;
  81. if (returning[0] === '*') {
  82. returning = returning.slice(0, -1);
  83. // Generate select statement with special order by
  84. // to keep the order because 'in (..)' may change the order
  85. sql.returningSql = function () {
  86. return 'select * from ' + self.tableName + ' where ROWID in (' + this.outBinding.map(function (v, i) {
  87. return ':' + (i + 1);
  88. }).join(', ') + ')' + ' order by case ROWID ' + this.outBinding.map(function (v, i) {
  89. return 'when CHARTOROWID(:' + (i + 1) + ') then ' + i;
  90. }).join(' ') + ' end';
  91. };
  92. }
  93. return sql;
  94. },
  95. _addReturningToSqlAndConvert: function _addReturningToSqlAndConvert(sql, outBinding, tableName, returning) {
  96. var self = this;
  97. var res = {
  98. sql: sql
  99. };
  100. if (!outBinding) {
  101. return res;
  102. }
  103. var returningValues = Array.isArray(outBinding) ? outBinding : [outBinding];
  104. var returningClause = '';
  105. var intoClause = '';
  106. // Build returning and into clauses
  107. _.each(returningValues, function (ret) {
  108. var columnName = ret.columnName || ret;
  109. returningClause += '"' + columnName + '",';
  110. intoClause += '?,';
  111. // Add Helpers to bindings
  112. if (ret instanceof BlobHelper) {
  113. return self.formatter.bindings.push(ret);
  114. }
  115. self.formatter.bindings.push(new ReturningHelper(columnName));
  116. });
  117. res.sql = sql;
  118. // Strip last comma
  119. returningClause = returningClause.slice(0, -1);
  120. intoClause = intoClause.slice(0, -1);
  121. if (returningClause && intoClause) {
  122. res.sql += ' returning ' + returningClause + ' into ' + intoClause;
  123. }
  124. res.outBinding = [outBinding];
  125. if (returning[0] === '*') {
  126. res.returningSql = function () {
  127. return 'select * from ' + self.tableName + ' where ROWID = :1';
  128. };
  129. }
  130. res.returning = returning;
  131. return res;
  132. },
  133. _prepOutbindings: function _prepOutbindings(paramValues, paramReturning) {
  134. var result = {};
  135. var params = paramValues || [];
  136. var returning = paramReturning || [];
  137. if (!Array.isArray(params) && _.isPlainObject(paramValues)) {
  138. params = [params];
  139. }
  140. // Always wrap returning argument in array
  141. if (returning && !Array.isArray(returning)) {
  142. returning = [returning];
  143. }
  144. var outBinding = [];
  145. // Handle Buffer value as Blob
  146. _.each(params, function (values, index) {
  147. if (returning[0] === '*') {
  148. outBinding[index] = ['ROWID'];
  149. } else {
  150. outBinding[index] = _.clone(returning);
  151. }
  152. _.each(values, function (value, key) {
  153. if (value instanceof Buffer) {
  154. values[key] = new BlobHelper(key, value);
  155. // Delete blob duplicate in returning
  156. var blobIndex = outBinding[index].indexOf(key);
  157. if (blobIndex >= 0) {
  158. outBinding[index].splice(blobIndex, 1);
  159. values[key].returning = true;
  160. }
  161. outBinding[index].push(values[key]);
  162. }
  163. if (_.isUndefined(value)) {
  164. delete params[index][key];
  165. }
  166. });
  167. });
  168. result.returning = returning;
  169. result.outBinding = outBinding;
  170. result.values = params;
  171. return result;
  172. },
  173. update: function update() {
  174. var self = this;
  175. var sql = {};
  176. var outBindPrep = this._prepOutbindings(this.single.update, this.single.returning);
  177. var outBinding = outBindPrep.outBinding;
  178. var returning = outBindPrep.returning;
  179. var updates = this._prepUpdate(this.single.update);
  180. var where = this.where();
  181. var returningClause = '';
  182. var intoClause = '';
  183. if (_.isEmpty(this.single.update) && typeof this.single.update !== 'function') {
  184. return '';
  185. }
  186. // Build returning and into clauses
  187. _.each(outBinding, function (out) {
  188. _.each(out, function (ret) {
  189. var columnName = ret.columnName || ret;
  190. returningClause += '"' + columnName + '",';
  191. intoClause += ' ?,';
  192. // Add Helpers to bindings
  193. if (ret instanceof BlobHelper) {
  194. return self.formatter.bindings.push(ret);
  195. }
  196. self.formatter.bindings.push(new ReturningHelper(columnName));
  197. });
  198. });
  199. // Strip last comma
  200. returningClause = returningClause.slice(0, -1);
  201. intoClause = intoClause.slice(0, -1);
  202. sql.outBinding = outBinding;
  203. sql.returning = returning;
  204. sql.sql = 'update ' + this.tableName + ' set ' + updates.join(', ') + (where ? ' ' + where : '');
  205. if (outBinding.length && !_.isEmpty(outBinding[0])) {
  206. sql.sql += ' returning ' + returningClause + ' into' + intoClause;
  207. }
  208. if (returning[0] === '*') {
  209. sql.returningSql = function () {
  210. var sql = 'select * from ' + self.tableName;
  211. var modifiedRowsCount = this.rowsAffected.length || this.rowsAffected;
  212. var returningSqlIn = ' where ROWID in (';
  213. var returningSqlOrderBy = ') order by case ROWID ';
  214. // Needs special order by because in(...) change result order
  215. for (var i = 0; i < modifiedRowsCount; i++) {
  216. if (this.returning[0] === '*') {
  217. returningSqlIn += ':' + (i + 1) + ', ';
  218. returningSqlOrderBy += 'when CHARTOROWID(:' + (i + 1) + ') then ' + i + ' ';
  219. }
  220. }
  221. if (this.returning[0] === '*') {
  222. this.returning = this.returning.slice(0, -1);
  223. returningSqlIn = returningSqlIn.slice(0, -2);
  224. returningSqlOrderBy = returningSqlOrderBy.slice(0, -1);
  225. }
  226. return sql += returningSqlIn + returningSqlOrderBy + ' end';
  227. };
  228. }
  229. return sql;
  230. }
  231. });
  232. module.exports = Oracledb_Compiler;