| 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/2861function 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);    };};
 |