123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*
- * Copyright (c) 2015-present, Vitaly Tomilov
- *
- * See the LICENSE file at the top-level directory of this distribution
- * for licensing information.
- *
- * Removal or modification of this copyright notice is prohibited.
- */
- const {Events} = require('./events');
- const {QueryFile} = require('./query-file');
- const {ServerFormatting, PreparedStatement, ParameterizedQuery} = require('./types');
- const {SpecialQuery} = require('./special-query');
- const {queryResult} = require('./query-result');
- const npm = {
- util: require('util'),
- utils: require('./utils'),
- formatting: require('./formatting'),
- errors: require('./errors'),
- stream: require('./stream'),
- text: require('./text')
- };
- const QueryResultError = npm.errors.QueryResultError,
- InternalError = npm.utils.InternalError,
- qrec = npm.errors.queryResultErrorCode;
- const badMask = queryResult.one | queryResult.many; // unsupported combination bit-mask;
- //////////////////////////////
- // Generic query method;
- function $query(ctx, query, values, qrm, config) {
- const special = qrm instanceof SpecialQuery && qrm;
- const $p = config.promise;
- if (special && special.isStream) {
- return npm.stream.call(this, ctx, query, values, config);
- }
- const opt = ctx.options,
- capSQL = opt.capSQL;
- let error, entityType,
- pgFormatting = opt.pgFormatting,
- params = pgFormatting ? values : undefined;
- if (typeof query === 'function') {
- try {
- query = npm.formatting.resolveFunc(query, values);
- } catch (e) {
- error = e;
- params = values;
- query = npm.util.inspect(query);
- }
- }
- if (!error && !query) {
- error = new TypeError(npm.text.invalidQuery);
- }
- if (!error && typeof query === 'object') {
- if (query instanceof QueryFile) {
- query.prepare();
- if (query.error) {
- error = query.error;
- query = query.file;
- } else {
- query = query[QueryFile.$query];
- }
- } else {
- if ('entity' in query) {
- entityType = query.type;
- query = query.entity; // query is a function name;
- } else {
- if (query instanceof ServerFormatting) {
- pgFormatting = true;
- } else {
- if ('name' in query) {
- query = new PreparedStatement(query);
- pgFormatting = true;
- } else {
- if ('text' in query) {
- query = new ParameterizedQuery(query);
- pgFormatting = true;
- }
- }
- }
- if (query instanceof ServerFormatting && !npm.utils.isNull(values)) {
- query.values = values;
- }
- }
- }
- }
- if (!error) {
- if (!pgFormatting && !npm.utils.isText(query)) {
- const errTxt = entityType ? (entityType === 'func' ? npm.text.invalidFunction : npm.text.invalidProc) : npm.text.invalidQuery;
- error = new TypeError(errTxt);
- }
- if (query instanceof ServerFormatting) {
- const qp = query.parse();
- if (qp instanceof Error) {
- error = qp;
- } else {
- query = qp;
- }
- }
- }
- if (!error && !special) {
- if (npm.utils.isNull(qrm)) {
- qrm = queryResult.any; // default query result;
- } else {
- if (qrm !== parseInt(qrm) || (qrm & badMask) === badMask || qrm < 1 || qrm > 6) {
- error = new TypeError(npm.text.invalidMask);
- }
- }
- }
- if (!error && (!pgFormatting || entityType)) {
- try {
- // use 'pg-promise' implementation of values formatting;
- if (entityType) {
- params = undefined;
- query = npm.formatting.formatEntity(query, values, {capSQL, type: entityType});
- } else {
- query = npm.formatting.formatQuery(query, values);
- }
- } catch (e) {
- if (entityType) {
- let prefix = entityType === 'func' ? 'select * from' : 'call';
- if (capSQL) {
- prefix = prefix.toUpperCase();
- }
- query = prefix + ' ' + query + '(...)';
- } else {
- params = values;
- }
- error = e instanceof Error ? e : new npm.utils.InternalError(e);
- }
- }
- return $p((resolve, reject) => {
- if (notifyReject()) {
- return;
- }
- error = Events.query(opt, getContext());
- if (notifyReject()) {
- return;
- }
- try {
- const start = Date.now();
- ctx.db.client.query(query, params, (err, result) => {
- let data, multiResult, lastResult = result;
- if (err) {
- // istanbul ignore if (auto-testing connectivity issues is too problematic)
- if (npm.utils.isConnectivityError(err)) {
- ctx.db.client.$connectionError = err;
- }
- err.query = err.query || query;
- err.params = err.params || params;
- error = err;
- } else {
- multiResult = Array.isArray(result);
- if (multiResult) {
- lastResult = result[result.length - 1];
- for (let i = 0; i < result.length; i++) {
- const r = result[i];
- makeIterable(r);
- error = Events.receive(opt, r.rows, r, getContext());
- if (error) {
- break;
- }
- }
- } else {
- makeIterable(result);
- result.duration = Date.now() - start;
- error = Events.receive(opt, result.rows, result, getContext());
- }
- }
- if (!error) {
- data = lastResult;
- if (special) {
- if (special.isMultiResult) {
- data = multiResult ? result : [result]; // method .multiResult() is called
- }
- // else, method .result() is called
- } else {
- data = data.rows;
- const len = data.length;
- if (len) {
- if (len > 1 && qrm & queryResult.one) {
- // one row was expected, but returned multiple;
- error = new QueryResultError(qrec.multiple, lastResult, query, params);
- } else {
- if (!(qrm & (queryResult.one | queryResult.many))) {
- // no data should have been returned;
- error = new QueryResultError(qrec.notEmpty, lastResult, query, params);
- } else {
- if (!(qrm & queryResult.many)) {
- data = data[0];
- }
- }
- }
- } else {
- // no data returned;
- if (qrm & queryResult.none) {
- if (qrm & queryResult.one) {
- data = null;
- } else {
- data = qrm & queryResult.many ? data : null;
- }
- } else {
- error = new QueryResultError(qrec.noData, lastResult, query, params);
- }
- }
- }
- }
- if (!notifyReject()) {
- resolve(data);
- }
- });
- } catch (e) {
- // this can only happen as a result of an internal failure within node-postgres,
- // like during a sudden loss of communications, which is impossible to reproduce
- // automatically, so removing it from the test coverage:
- // istanbul ignore next
- error = e;
- }
- function getContext() {
- let client;
- if (ctx.db) {
- client = ctx.db.client;
- } else {
- error = new Error(npm.text.looseQuery);
- }
- return {
- client, query, params,
- dc: ctx.dc,
- ctx: ctx.ctx
- };
- }
- notifyReject();
- function notifyReject() {
- const context = getContext();
- if (error) {
- if (error instanceof InternalError) {
- error = error.error;
- }
- Events.error(opt, error, context);
- reject(error);
- return true;
- }
- }
- });
- }
- // Extends Result to provide iterable for the rows;
- //
- // To be removed once the following PR is merged amd released:
- // https://github.com/brianc/node-postgres/pull/2861
- function makeIterable(r) {
- r[Symbol.iterator] = function () {
- return this.rows.values();
- };
- }
- module.exports = config => {
- return function (ctx, query, values, qrm) {
- return $query.call(this, ctx, query, values, qrm, config);
- };
- };
|