query.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*
  2. * Copyright (c) 2015-present, Vitaly Tomilov
  3. *
  4. * See the LICENSE file at the top-level directory of this distribution
  5. * for licensing information.
  6. *
  7. * Removal or modification of this copyright notice is prohibited.
  8. */
  9. const {Events} = require('./events');
  10. const {QueryFile} = require('./query-file');
  11. const {ServerFormatting, PreparedStatement, ParameterizedQuery} = require('./types');
  12. const {SpecialQuery} = require('./special-query');
  13. const {queryResult} = require('./query-result');
  14. const npm = {
  15. util: require('util'),
  16. utils: require('./utils'),
  17. formatting: require('./formatting'),
  18. errors: require('./errors'),
  19. stream: require('./stream'),
  20. text: require('./text')
  21. };
  22. const QueryResultError = npm.errors.QueryResultError,
  23. InternalError = npm.utils.InternalError,
  24. qrec = npm.errors.queryResultErrorCode;
  25. const badMask = queryResult.one | queryResult.many; // unsupported combination bit-mask;
  26. //////////////////////////////
  27. // Generic query method;
  28. function $query(ctx, query, values, qrm, config) {
  29. const special = qrm instanceof SpecialQuery && qrm;
  30. const $p = config.promise;
  31. if (special && special.isStream) {
  32. return npm.stream.call(this, ctx, query, values, config);
  33. }
  34. const opt = ctx.options,
  35. capSQL = opt.capSQL;
  36. let error, entityType,
  37. pgFormatting = opt.pgFormatting,
  38. params = pgFormatting ? values : undefined;
  39. if (typeof query === 'function') {
  40. try {
  41. query = npm.formatting.resolveFunc(query, values);
  42. } catch (e) {
  43. error = e;
  44. params = values;
  45. query = npm.util.inspect(query);
  46. }
  47. }
  48. if (!error && !query) {
  49. error = new TypeError(npm.text.invalidQuery);
  50. }
  51. if (!error && typeof query === 'object') {
  52. if (query instanceof QueryFile) {
  53. query.prepare();
  54. if (query.error) {
  55. error = query.error;
  56. query = query.file;
  57. } else {
  58. query = query[QueryFile.$query];
  59. }
  60. } else {
  61. if ('entity' in query) {
  62. entityType = query.type;
  63. query = query.entity; // query is a function name;
  64. } else {
  65. if (query instanceof ServerFormatting) {
  66. pgFormatting = true;
  67. } else {
  68. if ('name' in query) {
  69. query = new PreparedStatement(query);
  70. pgFormatting = true;
  71. } else {
  72. if ('text' in query) {
  73. query = new ParameterizedQuery(query);
  74. pgFormatting = true;
  75. }
  76. }
  77. }
  78. if (query instanceof ServerFormatting && !npm.utils.isNull(values)) {
  79. query.values = values;
  80. }
  81. }
  82. }
  83. }
  84. if (!error) {
  85. if (!pgFormatting && !npm.utils.isText(query)) {
  86. const errTxt = entityType ? (entityType === 'func' ? npm.text.invalidFunction : npm.text.invalidProc) : npm.text.invalidQuery;
  87. error = new TypeError(errTxt);
  88. }
  89. if (query instanceof ServerFormatting) {
  90. const qp = query.parse();
  91. if (qp instanceof Error) {
  92. error = qp;
  93. } else {
  94. query = qp;
  95. }
  96. }
  97. }
  98. if (!error && !special) {
  99. if (npm.utils.isNull(qrm)) {
  100. qrm = queryResult.any; // default query result;
  101. } else {
  102. if (qrm !== parseInt(qrm) || (qrm & badMask) === badMask || qrm < 1 || qrm > 6) {
  103. error = new TypeError(npm.text.invalidMask);
  104. }
  105. }
  106. }
  107. if (!error && (!pgFormatting || entityType)) {
  108. try {
  109. // use 'pg-promise' implementation of values formatting;
  110. if (entityType) {
  111. params = undefined;
  112. query = npm.formatting.formatEntity(query, values, {capSQL, type: entityType});
  113. } else {
  114. query = npm.formatting.formatQuery(query, values);
  115. }
  116. } catch (e) {
  117. if (entityType) {
  118. let prefix = entityType === 'func' ? 'select * from' : 'call';
  119. if (capSQL) {
  120. prefix = prefix.toUpperCase();
  121. }
  122. query = prefix + ' ' + query + '(...)';
  123. } else {
  124. params = values;
  125. }
  126. error = e instanceof Error ? e : new npm.utils.InternalError(e);
  127. }
  128. }
  129. return $p((resolve, reject) => {
  130. if (notifyReject()) {
  131. return;
  132. }
  133. error = Events.query(opt, getContext());
  134. if (notifyReject()) {
  135. return;
  136. }
  137. try {
  138. const start = Date.now();
  139. ctx.db.client.query(query, params, (err, result) => {
  140. let data, multiResult, lastResult = result;
  141. if (err) {
  142. // istanbul ignore if (auto-testing connectivity issues is too problematic)
  143. if (npm.utils.isConnectivityError(err)) {
  144. ctx.db.client.$connectionError = err;
  145. }
  146. err.query = err.query || query;
  147. err.params = err.params || params;
  148. error = err;
  149. } else {
  150. multiResult = Array.isArray(result);
  151. if (multiResult) {
  152. lastResult = result[result.length - 1];
  153. for (let i = 0; i < result.length; i++) {
  154. const r = result[i];
  155. makeIterable(r);
  156. error = Events.receive(opt, r.rows, r, getContext());
  157. if (error) {
  158. break;
  159. }
  160. }
  161. } else {
  162. makeIterable(result);
  163. result.duration = Date.now() - start;
  164. error = Events.receive(opt, result.rows, result, getContext());
  165. }
  166. }
  167. if (!error) {
  168. data = lastResult;
  169. if (special) {
  170. if (special.isMultiResult) {
  171. data = multiResult ? result : [result]; // method .multiResult() is called
  172. }
  173. // else, method .result() is called
  174. } else {
  175. data = data.rows;
  176. const len = data.length;
  177. if (len) {
  178. if (len > 1 && qrm & queryResult.one) {
  179. // one row was expected, but returned multiple;
  180. error = new QueryResultError(qrec.multiple, lastResult, query, params);
  181. } else {
  182. if (!(qrm & (queryResult.one | queryResult.many))) {
  183. // no data should have been returned;
  184. error = new QueryResultError(qrec.notEmpty, lastResult, query, params);
  185. } else {
  186. if (!(qrm & queryResult.many)) {
  187. data = data[0];
  188. }
  189. }
  190. }
  191. } else {
  192. // no data returned;
  193. if (qrm & queryResult.none) {
  194. if (qrm & queryResult.one) {
  195. data = null;
  196. } else {
  197. data = qrm & queryResult.many ? data : null;
  198. }
  199. } else {
  200. error = new QueryResultError(qrec.noData, lastResult, query, params);
  201. }
  202. }
  203. }
  204. }
  205. if (!notifyReject()) {
  206. resolve(data);
  207. }
  208. });
  209. } catch (e) {
  210. // this can only happen as a result of an internal failure within node-postgres,
  211. // like during a sudden loss of communications, which is impossible to reproduce
  212. // automatically, so removing it from the test coverage:
  213. // istanbul ignore next
  214. error = e;
  215. }
  216. function getContext() {
  217. let client;
  218. if (ctx.db) {
  219. client = ctx.db.client;
  220. } else {
  221. error = new Error(npm.text.looseQuery);
  222. }
  223. return {
  224. client, query, params,
  225. dc: ctx.dc,
  226. ctx: ctx.ctx
  227. };
  228. }
  229. notifyReject();
  230. function notifyReject() {
  231. const context = getContext();
  232. if (error) {
  233. if (error instanceof InternalError) {
  234. error = error.error;
  235. }
  236. Events.error(opt, error, context);
  237. reject(error);
  238. return true;
  239. }
  240. }
  241. });
  242. }
  243. // Extends Result to provide iterable for the rows;
  244. //
  245. // To be removed once the following PR is merged amd released:
  246. // https://github.com/brianc/node-postgres/pull/2861
  247. function makeIterable(r) {
  248. r[Symbol.iterator] = function () {
  249. return this.rows.values();
  250. };
  251. }
  252. module.exports = config => {
  253. return function (ctx, query, values, qrm) {
  254. return $query.call(this, ctx, query, values, qrm, config);
  255. };
  256. };