You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
9.4 KiB
265 lines
9.4 KiB
/*
|
|
* 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;
|
|
}
|
|
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];
|
|
error = Events.receive(opt, r.rows, r, getContext());
|
|
if (error) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
module.exports = config => {
|
|
return function (ctx, query, values, qrm) {
|
|
return $query.call(this, ctx, query, values, qrm, config);
|
|
};
|
|
};
|
|
|