Update to NPM version

This commit is contained in:
Lucas Patenaude
2024-04-11 04:23:19 -06:00
parent 886d197fa7
commit 6d6ef4f257
8225 changed files with 863748 additions and 1 deletions

View File

@@ -0,0 +1,10 @@
const {assertOptions} = require(`assert-options`);
// this to allow override options-related errors globally (for pg-promise)
global.pgPromiseAssert = assertOptions;
module.exports = {
assert() {
return global.pgPromiseAssert.apply(null, [...arguments]);
}
};

View File

@@ -0,0 +1,178 @@
/*
* 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 {ColorConsole} = require(`./utils/color`);
const npm = {
utils: require(`./utils`),
text: require(`./text`),
formatting: require(`./formatting`)
};
function poolConnect(ctx, db, config) {
return config.promise((resolve, reject) => {
const p = db.$pool;
if (p.ending) {
db.$destroy();
const err = new Error(npm.text.poolDestroyed);
Events.error(ctx.options, err, {
dc: ctx.dc
});
reject(err);
return;
}
p.connect((err, client) => {
if (err) {
Events.error(ctx.options, err, {
cn: npm.utils.getSafeConnection(ctx.cn),
dc: ctx.dc
});
reject(err);
} else {
if (`$useCount` in client) {
// Make sure useCount drops to 1, if it ever reaches maximum integer number;
// We do not drop it to zero, to avoid rerun of initialization queries that
// usually check for useCount === 0;
// istanbul ignore if
if (client.$useCount >= Number.MAX_SAFE_INTEGER) {
client.$useCount = 1; // resetting; cannot auto-test this
} else {
client.$useCount = ++client.$useCount;
}
} else {
Object.defineProperty(client, `$useCount`, {
value: 0,
configurable: false,
enumerable: false,
writable: true
});
setSchema(client, ctx);
}
setCtx(client, ctx);
const end = lockClientEnd(client);
client.on(`error`, onError);
resolve({
client,
useCount: client.$useCount,
release(kill) {
client.end = end;
client.release(kill || client.$connectionError);
Events.disconnect(ctx, client);
client.removeListener(`error`, onError);
}
});
Events.connect(ctx, client, client.$useCount);
}
});
});
}
function directConnect(ctx, config) {
return config.promise((resolve, reject) => {
const client = new config.pgp.pg.Client(ctx.cn);
client.connect(err => {
if (err) {
Events.error(ctx.options, err, {
cn: npm.utils.getSafeConnection(ctx.cn),
dc: ctx.dc
});
reject(err);
} else {
setSchema(client, ctx);
setCtx(client, ctx);
const end = lockClientEnd(client);
client.on(`error`, onError);
resolve({
client,
useCount: 0,
release() {
client.end = end;
const p = config.promise((res, rej) => client.end().then(res).catch(rej));
Events.disconnect(ctx, client);
client.removeListener(`error`, onError);
return p;
}
});
Events.connect(ctx, client, 0);
}
});
});
}
// this event only happens when the connection is lost physically,
// which cannot be tested automatically; removing from coverage:
// istanbul ignore next
function onError(err) {
const ctx = this.$ctx;
const cn = npm.utils.getSafeConnection(ctx.cn);
Events.error(ctx.options, err, {cn, dc: ctx.dc});
if (ctx.cnOptions && typeof ctx.cnOptions.onLost === `function` && !ctx.notified) {
try {
ctx.cnOptions.onLost.call(this, err, {
cn,
dc: ctx.dc,
start: ctx.start,
client: this
});
} catch (e) {
ColorConsole.error(e && e.stack || e);
}
ctx.notified = true;
}
}
function lockClientEnd(client) {
const end = client.end;
client.end = doNotCall => {
// This call can happen only in the following two cases:
// 1. the client made the call directly, against the library's documentation (invalid code)
// 2. connection with the server broke, and the pool is terminating all clients forcefully.
ColorConsole.error(`${npm.text.clientEnd}\n${npm.utils.getLocalStack(1, 3)}\n`);
if (!doNotCall) {
end.call(client);
}
};
return end;
}
function setCtx(client, ctx) {
Object.defineProperty(client, `$ctx`, {
value: ctx,
writable: true
});
}
function setSchema(client, ctx) {
let s = ctx.options.schema;
if (!s) {
return;
}
if (typeof s === `function`) {
s = s.call(ctx.dc, ctx.dc);
}
if (Array.isArray(s)) {
s = s.filter(a => a && typeof a === `string`);
}
if (typeof s === `string` || (Array.isArray(s) && s.length)) {
client.query(npm.formatting.as.format(`SET search_path TO $1:name`, [s]), err => {
// istanbul ignore if;
if (err) {
// This is unlikely to ever happen, unless the connection is created faulty,
// and fails on the very first query, which is impossible to test automatically.
throw err;
}
});
}
}
module.exports = config => ({
pool: (ctx, db) => poolConnect(ctx, db, config),
direct: ctx => directConnect(ctx, config)
});

View File

@@ -0,0 +1,93 @@
/*
* 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.
*/
/**
* @class ConnectionContext
* @private
* @summary Internal connection context.
*
* @param {object} cc
* Connection Context.
*
* @param {object} cc.cn
* Connection details
*
* @param {*} cc.dc
* Database Context
*
* @param {object} cc.options
* Library's Initialization Options
*
* @param {object} cc.db
* Database Session we're attached to, if any.
*
* @param {number} cc.level
* Task Level
*
* @param {number} cc.txLevel
* Transaction Level
*
* @param {object} cc.parentCtx
* Connection Context of the parent operation, if any.
*
*/
class ConnectionContext {
constructor(cc) {
this.cn = cc.cn; // connection details;
this.dc = cc.dc; // database context;
this.options = cc.options; // library options;
this.db = cc.db; // database session;
this.level = cc.level; // task level;
this.txLevel = cc.txLevel; // transaction level;
this.parentCtx = null; // parent context
this.taskCtx = null; // task context
this.start = null; // Date/Time when connected
this.txCount = 0;
}
connect(db) {
this.db = db;
this.start = new Date();
}
disconnect(kill) {
if (this.db) {
const p = this.db.release(kill);
this.db = null;
return p;
}
}
clone() {
const obj = new ConnectionContext(this);
obj.parent = this;
obj.parentCtx = this.taskCtx;
return obj;
}
get nextTxCount() {
let txCurrent = this, txTop = this;
while (txCurrent.parent) {
txCurrent = txCurrent.parent;
if (txCurrent.taskCtx && txCurrent.taskCtx.isTX) {
txTop = txCurrent;
}
}
return txTop.txCount++;
}
}
/**
* Connection Context
* @module context
* @author Vitaly Tomilov
* @private
*/
module.exports = {ConnectionContext};

View File

@@ -0,0 +1,115 @@
/*
* 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 {ColorConsole} = require(`./utils/color`);
const npm = {
utils: require(`./utils`)
};
/**
* @class DatabasePool
* @static
* @private
*/
class DatabasePool {
/**
* Global instance of the database pool repository.
*
* @returns {{dbMap: {}, dbs: Array}}
*/
static get instance() {
const s = Symbol.for(`pgPromiseDatabasePool`);
let scope = global[s];
if (!scope) {
scope = {
dbMap: {}, // map of used database context keys (connection + dc)
dbs: [] // all database objects
};
global[s] = scope;
}
return scope;
}
/**
* @method DatabasePool.register
* @static
* @description
* - Registers each database object, to make sure no duplicates connections are used,
* and if they are, produce a warning;
* - Registers each Pool object, to be able to release them all when requested.
*
* @param {Database} db - The new Database object being registered.
*/
static register(db) {
const cnKey = DatabasePool.createContextKey(db);
npm.utils.addReadProp(db, `$cnKey`, cnKey, true);
const {dbMap, dbs} = DatabasePool.instance;
if (cnKey in dbMap) {
dbMap[cnKey]++;
/* istanbul ignore if */
if (!db.$config.options.noWarnings) {
ColorConsole.warn(`WARNING: Creating a duplicate database object for the same connection.\n${npm.utils.getLocalStack(4, 3)}\n`);
}
} else {
dbMap[cnKey] = 1;
}
dbs.push(db);
}
/**
* @method DatabasePool.unregister
* @static
* @param db
*/
static unregister(db) {
const cnKey = db.$cnKey;
const {dbMap} = DatabasePool.instance;
if (!--dbMap[cnKey]) {
delete dbMap[cnKey];
}
}
/**
* @method DatabasePool.shutDown
* @static
*/
static shutDown() {
const {instance} = DatabasePool;
instance.dbs.forEach(db => {
db.$destroy();
});
instance.dbs.length = 0;
instance.dbMap = {};
}
/**
* @method DatabasePool.createContextKey
* @static
* @description
* For connections that are objects it reorders the keys alphabetically,
* and then serializes the result into a JSON string.
*
* @param {Database} db - Database instance.
*/
static createContextKey(db) {
let cn = db.$cn;
if (typeof cn === `object`) {
const obj = {}, keys = Object.keys(cn).sort();
keys.forEach(name => {
obj[name] = cn[name];
});
cn = obj;
}
return npm.utils.toJson(npm.utils.getSafeConnection(cn)) + npm.utils.toJson(db.$dc);
}
}
module.exports = {DatabasePool};

1691
ProjectSourceCode/node_modules/pg-promise/lib/database.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
### `errors` namespace
This folder contains everything that's available via the [errors] namespace, before and after initialization:
```js
const pgpLib = require('pg-promise');
const pgp = pgpLib(/*initialization options*/);
pgpLib.errors; // `errors` namespace
pgp.errors; // `errors` namespace
```
[errors]:http://vitaly-t.github.io/pg-promise/errors.html

View File

@@ -0,0 +1,51 @@
/*
* 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 {QueryResultError, queryResultErrorCode} = require(`./query-result-error`);
const {PreparedStatementError} = require(`./prepared-statement-error`);
const {ParameterizedQueryError} = require(`./parameterized-query-error`);
const {QueryFileError} = require(`./query-file-error`);
/**
* @namespace errors
* @description
* Error types namespace, available as `pgp.errors`, before and after initializing the library.
*
* @property {function} PreparedStatementError
* {@link errors.PreparedStatementError PreparedStatementError} class constructor.
*
* Represents all errors that can be reported by class {@link PreparedStatement}.
*
* @property {function} ParameterizedQueryError
* {@link errors.ParameterizedQueryError ParameterizedQueryError} class constructor.
*
* Represents all errors that can be reported by class {@link ParameterizedQuery}.
*
* @property {function} QueryFileError
* {@link errors.QueryFileError QueryFileError} class constructor.
*
* Represents all errors that can be reported by class {@link QueryFile}.
*
* @property {function} QueryResultError
* {@link errors.QueryResultError QueryResultError} class constructor.
*
* Represents all result-specific errors from query methods.
*
* @property {errors.queryResultErrorCode} queryResultErrorCode
* Error codes `enum` used by class {@link errors.QueryResultError QueryResultError}.
*
*/
module.exports = {
QueryResultError,
queryResultErrorCode,
PreparedStatementError,
ParameterizedQueryError,
QueryFileError
};

View File

@@ -0,0 +1,95 @@
/*
* 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 {QueryFileError} = require(`./query-file-error`);
const npm = {
os: require(`os`),
utils: require(`../utils`)
};
/**
* @class errors.ParameterizedQueryError
* @augments external:Error
* @description
* {@link errors.ParameterizedQueryError ParameterizedQueryError} class, available from the {@link errors} namespace.
*
* This type represents all errors that can be reported by class {@link ParameterizedQuery}, whether it is used
* explicitly or implicitly (via a simple `{text, values}` object).
*
* @property {string} name
* Standard {@link external:Error Error} property - error type name = `ParameterizedQueryError`.
*
* @property {string} message
* Standard {@link external:Error Error} property - the error message.
*
* @property {string} stack
* Standard {@link external:Error Error} property - the stack trace.
*
* @property {errors.QueryFileError} error
* Internal {@link errors.QueryFileError} object.
*
* It is set only when the source {@link ParameterizedQuery} used a {@link QueryFile} which threw the error.
*
* @property {object} result
* Resulting Parameterized Query object.
*
* @see ParameterizedQuery
*/
class ParameterizedQueryError extends Error {
constructor(error, pq) {
const isQueryFileError = error instanceof QueryFileError;
const message = isQueryFileError ? `Failed to initialize 'text' from a QueryFile.` : error;
super(message);
this.name = this.constructor.name;
if (isQueryFileError) {
this.error = error;
}
this.result = pq;
Error.captureStackTrace(this, this.constructor);
}
}
/**
* @method errors.ParameterizedQueryError#toString
* @description
* Creates a well-formatted multi-line string that represents the error.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
ParameterizedQueryError.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap0 = npm.utils.messageGap(level),
gap1 = npm.utils.messageGap(level + 1),
gap2 = npm.utils.messageGap(level + 2),
lines = [
`ParameterizedQueryError {`,
gap1 + `message: "` + this.message + `"`,
gap1 + `result: {`,
gap2 + `text: ` + npm.utils.toJson(this.result.text),
gap2 + `values: ` + npm.utils.toJson(this.result.values),
gap1 + `}`
];
if (this.error) {
lines.push(gap1 + `error: ` + this.error.toString(level + 1));
}
lines.push(gap0 + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(ParameterizedQueryError, function () {
return this.toString();
});
module.exports = {ParameterizedQueryError};

View File

@@ -0,0 +1,96 @@
/*
* 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 {QueryFileError} = require(`./query-file-error`);
const npm = {
os: require(`os`),
utils: require(`../utils`)
};
/**
* @class errors.PreparedStatementError
* @augments external:Error
* @description
* {@link errors.PreparedStatementError PreparedStatementError} class, available from the {@link errors} namespace.
*
* This type represents all errors that can be reported by class {@link PreparedStatement}, whether it is used
* explicitly or implicitly (via a simple `{name, text, values}` object).
*
* @property {string} name
* Standard {@link external:Error Error} property - error type name = `PreparedStatementError`.
*
* @property {string} message
* Standard {@link external:Error Error} property - the error message.
*
* @property {string} stack
* Standard {@link external:Error Error} property - the stack trace.
*
* @property {errors.QueryFileError} error
* Internal {@link errors.QueryFileError} object.
*
* It is set only when the source {@link PreparedStatement} used a {@link QueryFile} which threw the error.
*
* @property {object} result
* Resulting Prepared Statement object.
*
* @see PreparedStatement
*/
class PreparedStatementError extends Error {
constructor(error, ps) {
const isQueryFileError = error instanceof QueryFileError;
const message = isQueryFileError ? `Failed to initialize 'text' from a QueryFile.` : error;
super(message);
this.name = this.constructor.name;
if (isQueryFileError) {
this.error = error;
}
this.result = ps;
Error.captureStackTrace(this, this.constructor);
}
}
/**
* @method errors.PreparedStatementError#toString
* @description
* Creates a well-formatted multi-line string that represents the error.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
PreparedStatementError.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap0 = npm.utils.messageGap(level),
gap1 = npm.utils.messageGap(level + 1),
gap2 = npm.utils.messageGap(level + 2),
lines = [
`PreparedStatementError {`,
gap1 + `message: "` + this.message + `"`,
gap1 + `result: {`,
gap2 + `name: ` + npm.utils.toJson(this.result.name),
gap2 + `text: ` + npm.utils.toJson(this.result.text),
gap2 + `values: ` + npm.utils.toJson(this.result.values),
gap1 + `}`
];
if (this.error) {
lines.push(gap1 + `error: ` + this.error.toString(level + 1));
}
lines.push(gap0 + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(PreparedStatementError, function () {
return this.toString();
});
module.exports = {PreparedStatementError};

View File

@@ -0,0 +1,95 @@
/*
* 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 npm = {
os: require(`os`),
utils: require(`../utils`),
minify: require(`pg-minify`)
};
/**
* @class errors.QueryFileError
* @augments external:Error
* @description
* {@link errors.QueryFileError QueryFileError} class, available from the {@link errors} namespace.
*
* This type represents all errors related to {@link QueryFile}.
*
* @property {string} name
* Standard {@link external:Error Error} property - error type name = `QueryFileError`.
*
* @property {string} message
* Standard {@link external:Error Error} property - the error message.
*
* @property {string} stack
* Standard {@link external:Error Error} property - the stack trace.
*
* @property {string} file
* File path/name that was passed into the {@link QueryFile} constructor.
*
* @property {object} options
* Set of options that was used by the {@link QueryFile} object.
*
* @property {SQLParsingError} error
* Internal $[SQLParsingError] object.
*
* It is set only when the error was thrown by $[pg-minify] while parsing the SQL file.
*
* @see QueryFile
*
*/
class QueryFileError extends Error {
constructor(error, qf) {
const isSqlError = error instanceof npm.minify.SQLParsingError;
const message = isSqlError ? `Failed to parse the SQL.` : error.message;
super(message);
this.name = this.constructor.name;
if (isSqlError) {
this.error = error;
}
this.file = qf.file;
this.options = qf.options;
Error.captureStackTrace(this, this.constructor);
}
}
/**
* @method errors.QueryFileError#toString
* @description
* Creates a well-formatted multi-line string that represents the error.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
QueryFileError.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap0 = npm.utils.messageGap(level),
gap1 = npm.utils.messageGap(level + 1),
lines = [
`QueryFileError {`,
gap1 + `message: "` + this.message + `"`,
gap1 + `options: ` + npm.utils.toJson(this.options),
gap1 + `file: "` + this.file + `"`
];
if (this.error) {
lines.push(gap1 + `error: ` + this.error.toString(level + 1));
}
lines.push(gap0 + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(QueryFileError, function () {
return this.toString();
});
module.exports = {QueryFileError};

View File

@@ -0,0 +1,177 @@
/*
* 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 npm = {
os: require(`os`),
utils: require(`../utils`),
text: require(`../text`)
};
/**
* @enum {number}
* @alias errors.queryResultErrorCode
* @readonly
* @description
* `queryResultErrorCode` enumerator, available from the {@link errors} namespace.
*
* Represents an integer code for each type of error supported by type {@link errors.QueryResultError}.
*
* @see {@link errors.QueryResultError}
*/
const queryResultErrorCode = {
/** No data returned from the query. */
noData: 0,
/** No return data was expected. */
notEmpty: 1,
/** Multiple rows were not expected. */
multiple: 2
};
const errorMessages = [
{name: `noData`, message: npm.text.noData},
{name: `notEmpty`, message: npm.text.notEmpty},
{name: `multiple`, message: npm.text.multiple}
];
/**
* @class errors.QueryResultError
* @augments external:Error
* @description
*
* This error is specified as the rejection reason for all result-specific methods when the result doesn't match
* the expectation, i.e. when a query result doesn't match its Query Result Mask - the value of {@link queryResult}.
*
* The error applies to the result from the following methods: {@link Database#none none},
* {@link Database#one one}, {@link Database#oneOrNone oneOrNone} and {@link Database#many many}.
*
* Supported errors:
*
* - `No return data was expected.`, method {@link Database#none none}
* - `No data returned from the query.`, methods {@link Database#one one} and {@link Database#many many}
* - `Multiple rows were not expected.`, methods {@link Database#one one} and {@link Database#oneOrNone oneOrNone}
*
* Like any other error, this one is notified with through the global event {@link event:error error}.
*
* The type is available from the {@link errors} namespace.
*
* @property {string} name
* Standard {@link external:Error Error} property - error type name = `QueryResultError`.
*
* @property {string} message
* Standard {@link external:Error Error} property - the error message.
*
* @property {string} stack
* Standard {@link external:Error Error} property - the stack trace.
*
* @property {object} result
* The original $[Result] object that was received.
*
* @property {number} received
* Total number of rows received. It is simply the value of `result.rows.length`.
*
* @property {number} code
* Error code - {@link errors.queryResultErrorCode queryResultErrorCode} value.
*
* @property {string} query
* Query that was executed.
*
* Normally, it is the query already formatted with values, if there were any.
* But if you are using initialization option `pgFormatting`, then the query string is before formatting.
*
* @property {*} values
* Values passed in as query parameters. Available only when initialization option `pgFormatting` is used.
* Otherwise, the values are within the pre-formatted `query` string.
*
* @example
*
* const QueryResultError = pgp.errors.QueryResultError;
* const qrec = pgp.errors.queryResultErrorCode;
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* error: (err, e) => {
* if (err instanceof QueryResultError) {
* // A query returned unexpected number of records, and thus rejected;
*
* // we can check the error code, if we want specifics:
* if(err.code === qrec.noData) {
* // expected some data, but received none;
* }
*
* // If you write QueryResultError into the console,
* // you will get a nicely formatted output.
*
* console.log(err);
*
* // See also: err, e.query, e.params, etc.
* }
* }
* };
*
* @see
* {@link queryResult}, {@link Database#none none}, {@link Database#one one},
* {@link Database#oneOrNone oneOrNone}, {@link Database#many many}
*
*/
class QueryResultError extends Error {
constructor(code, result, query, values) {
const message = errorMessages[code].message;
super(message);
this.name = this.constructor.name;
this.code = code;
this.result = result;
this.query = query;
this.values = values;
this.received = result.rows.length;
Error.captureStackTrace(this, this.constructor);
}
}
/**
* @method errors.QueryResultError#toString
* @description
* Creates a well-formatted multi-line string that represents the error.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
QueryResultError.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap0 = npm.utils.messageGap(level),
gap1 = npm.utils.messageGap(level + 1),
lines = [
`QueryResultError {`,
gap1 + `code: queryResultErrorCode.` + errorMessages[this.code].name,
gap1 + `message: "` + this.message + `"`,
gap1 + `received: ` + this.received,
gap1 + `query: ` + (typeof this.query === `string` ? `"` + this.query + `"` : npm.utils.toJson(this.query))
];
if (this.values !== undefined) {
lines.push(gap1 + `values: ` + npm.utils.toJson(this.values));
}
lines.push(gap0 + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(QueryResultError, function () {
return this.toString();
});
module.exports = {
QueryResultError,
queryResultErrorCode
};

531
ProjectSourceCode/node_modules/pg-promise/lib/events.js generated vendored Normal file
View File

@@ -0,0 +1,531 @@
/*
* 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 {ColorConsole} = require(`./utils/color`);
const npm = {
main: require(`./`),
utils: require(`./utils`)
};
/////////////////////////////////
// Client notification helpers;
class Events {
/**
* @event connect
* @description
* Global notification of acquiring a new database connection from the connection pool, i.e. a virtual connection.
*
* However, for direct calls to method {@link Database#connect Database.connect} with parameter `{direct: true}`,
* this event represents a physical connection.
*
* The library will suppress any error thrown by the handler and write it into the console.
*
* @param {external:Client} client
* $[pg.Client] object that represents the connection.
*
* @param {*} dc
* Database Context that was used when creating the database object (see {@link Database}).
*
* @param {number} useCount
* Number of times the connection has been previously used, starting with 0 for a freshly
* allocated physical connection.
*
* This parameter is always 0 for direct connections (created by calling {@link Database#connect Database.connect}
* with parameter `{direct: true}`).
*
* @example
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* connect(client, dc, useCount) {
* const cp = client.connectionParameters;
* console.log('Connected to database:', cp.database);
* }
*
* };
*/
static connect(ctx, client, useCount) {
if (typeof ctx.options.connect === `function`) {
try {
ctx.options.connect(client, ctx.dc, useCount);
} catch (e) {
// have to silence errors here;
// cannot allow unhandled errors while connecting to the database,
// as it will break the connection logic;
Events.unexpected(`connect`, e);
}
}
}
/**
* @event disconnect
* @description
* Global notification of releasing a database connection back to the connection pool, i.e. releasing the virtual connection.
*
* However, when releasing a direct connection (created by calling {@link Database#connect Database.connect} with parameter
* `{direct: true}`), this event represents a physical disconnection.
*
* The library will suppress any error thrown by the handler and write it into the console.
*
* @param {external:Client} client - $[pg.Client] object that represents connection with the database.
*
* @param {*} dc - Database Context that was used when creating the database object (see {@link Database}).
*
* @example
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* disconnect(client, dc) {
* const cp = client.connectionParameters;
* console.log('Disconnecting from database:', cp.database);
* }
*
* };
*/
static disconnect(ctx, client) {
if (typeof ctx.options.disconnect === `function`) {
try {
ctx.options.disconnect(client, ctx.dc);
} catch (e) {
// have to silence errors here;
// cannot allow unhandled errors while disconnecting from the database,
// as it will break the disconnection logic;
Events.unexpected(`disconnect`, e);
}
}
}
/**
* @event query
* @description
*
* Global notification of a query that's about to execute.
*
* Notification happens just before the query execution. And if the handler throws an error, the query execution
* will be rejected with that error.
*
* @param {EventContext} e
* Event Context Object.
*
* @example
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* query(e) {
* console.log('QUERY:', e.query);
* }
* };
*/
static query(options, context) {
if (typeof options.query === `function`) {
try {
options.query(context);
} catch (e) {
// throwing an error during event 'query'
// will result in a reject for the request.
return e instanceof Error ? e : new npm.utils.InternalError(e);
}
}
}
/**
* @event receive
* @description
* Global notification of any data received from the database, coming from a regular query or from a stream.
*
* The event is fired before the data reaches the client, and it serves two purposes:
* - Providing selective data logging for debugging;
* - Pre-processing data before it reaches the client.
*
* **NOTES:**
* - If you alter the size of `data` directly or through the `result` object, it may affect `QueryResultMask`
* validation for regular queries, which is executed right after.
* - Any data pre-processing needs to be fast here, to avoid performance penalties.
* - If the event handler throws an error, the original request will be rejected with that error.
*
* For methods {@link Database#multi Database.multi} and {@link Database#multiResult Database.multiResult},
* this event is called for every result that's returned. And for method {@link Database#stream Database.stream},
* the event occurs for every record.
*
* @param {Array<Object>} data
* Array of received objects/rows.
*
* If any of those objects are modified during notification, the client will receive the modified data.
*
* @param {external:Result} result
* - Original $[Result] object, if the data is from a non-stream query, in which case `data = result.rows`.
* For single-query requests, $[Result] object is extended with property `duration` - number of milliseconds
* it took to send the query, execute it and get the result back.
* - It is `undefined` when the data comes from a stream (method {@link Database#stream Database.stream}).
*
* @param {EventContext} e
* Event Context Object.
*
* @example
*
* // Example below shows the fastest way to camelize all column names.
* // NOTE: The example does not do processing for nested JSON objects.
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* receive(data, result, e) {
* camelizeColumns(data);
* }
* };
*
* function camelizeColumns(data) {
* const tmp = data[0];
* for (const prop in tmp) {
* const camel = pgp.utils.camelize(prop);
* if (!(camel in tmp)) {
* for (let i = 0; i < data.length; i++) {
* const d = data[i];
* d[camel] = d[prop];
* delete d[prop];
* }
* }
* }
* }
*/
static receive(options, data, result, context) {
if (typeof options.receive === `function`) {
try {
options.receive(data, result, context);
} catch (e) {
// throwing an error during event 'receive'
// will result in a reject for the request.
return e instanceof Error ? e : new npm.utils.InternalError(e);
}
}
}
/**
* @event task
* @description
* Global notification of a task start / finish events, as executed via
* {@link Database#task Database.task} or {@link Database#taskIf Database.taskIf}.
*
* The library will suppress any error thrown by the handler and write it into the console.
*
* @param {EventContext} e
* Event Context Object.
*
* @example
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* task(e) {
* if (e.ctx.finish) {
* // this is a task->finish event;
* console.log('Duration:', e.ctx.duration);
* if (e.ctx.success) {
* // e.ctx.result = resolved data;
* } else {
* // e.ctx.result = error/rejection reason;
* }
* } else {
* // this is a task->start event;
* console.log('Start Time:', e.ctx.start);
* }
* }
* };
*
*/
static task(options, context) {
if (typeof options.task === `function`) {
try {
options.task(context);
} catch (e) {
// silencing the error, to avoid breaking the task;
Events.unexpected(`task`, e);
}
}
}
/**
* @event transact
* @description
* Global notification of a transaction start / finish events, as executed via {@link Database#tx Database.tx}
* or {@link Database#txIf Database.txIf}.
*
* The library will suppress any error thrown by the handler and write it into the console.
*
* @param {EventContext} e
* Event Context Object.
*
* @example
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* transact(e) {
* if (e.ctx.finish) {
* // this is a transaction->finish event;
* console.log('Duration:', e.ctx.duration);
* if (e.ctx.success) {
* // e.ctx.result = resolved data;
* } else {
* // e.ctx.result = error/rejection reason;
* }
* } else {
* // this is a transaction->start event;
* console.log('Start Time:', e.ctx.start);
* }
* }
* };
*
*/
static transact(options, context) {
if (typeof options.transact === `function`) {
try {
options.transact(context);
} catch (e) {
// silencing the error, to avoid breaking the transaction;
Events.unexpected(`transact`, e);
}
}
}
/**
* @event error
* @description
* Global notification of every error encountered by this library.
*
* The library will suppress any error thrown by the handler and write it into the console.
*
* @param {*} err
* The error encountered, of the same value and type as it was reported.
*
* @param {EventContext} e
* Event Context Object.
*
* @example
* const initOptions = {
*
* // pg-promise initialization options...
*
* error(err, e) {
*
* if (e.cn) {
* // this is a connection-related error
* // cn = safe connection details passed into the library:
* // if password is present, it is masked by #
* }
*
* if (e.query) {
* // query string is available
* if (e.params) {
* // query parameters are available
* }
* }
*
* if (e.ctx) {
* // occurred inside a task or transaction
* }
* }
* };
*
*/
static error(options, err, context) {
if (typeof options.error === `function`) {
try {
options.error(err, context);
} catch (e) {
// have to silence errors here;
// throwing unhandled errors while handling an error
// notification is simply not acceptable.
Events.unexpected(`error`, e);
}
}
}
/**
* @event extend
* @description
* Extends {@link Database} protocol with custom methods and properties.
*
* Override this event to extend the existing access layer with your own functions and
* properties best suited for your application.
*
* The extension thus becomes available across all access layers:
*
* - Within the root/default database protocol;
* - Inside transactions, including nested ones;
* - Inside tasks, including nested ones.
*
* All pre-defined methods and properties are read-only, so you will get an error,
* if you try overriding them.
*
* The library will suppress any error thrown by the handler and write it into the console.
*
* @param {object} obj - Protocol object to be extended.
*
* @param {*} dc - Database Context that was used when creating the {@link Database} object.
*
* @see $[pg-promise-demo]
*
* @example
*
* // In the example below we extend the protocol with function `addImage`
* // that will insert one binary image and resolve with the new record id.
*
* const initOptions = {
*
* // pg-promise initialization options...
*
* extend(obj, dc) {
* // dc = database context;
* obj.addImage = data => {
* // adds a new image and resolves with its record id:
* return obj.one('INSERT INTO images(data) VALUES($1) RETURNING id', data, a => a.id);
* }
* }
* };
*
* @example
*
* // It is best to extend the protocol by adding whole entity repositories to it as shown in the following example.
* // For a comprehensive example see https://github.com/vitaly-t/pg-promise-demo
*
* class UsersRepository {
* constructor(rep, pgp) {
* this.rep = rep;
* this.pgp = pgp;
* }
*
* add(name) {
* return this.rep.one('INSERT INTO users(name) VALUES($1) RETURNING id', name, a => a.id);
* }
*
* remove(id) {
* return this.rep.none('DELETE FROM users WHERE id = $1', id);
* }
* }
*
* // Overriding 'extend' event;
* const initOptions = {
*
* // pg-promise initialization options...
*
* extend(obj, dc) {
* // dc = database context;
* obj.users = new UsersRepository(obj, pgp);
* // You can set different repositories based on `dc`
* }
* };
*
* // Usage example:
* db.users.add('John', true)
* .then(id => {
* // user added successfully, id = new user's id
* })
* .catch(error => {
* // failed to add the user;
* });
*
*/
static extend(options, obj, dc) {
if (typeof options.extend === `function`) {
try {
options.extend.call(obj, obj, dc);
} catch (e) {
// have to silence errors here;
// the result of throwing unhandled errors while
// extending the protocol would be unpredictable.
Events.unexpected(`extend`, e);
}
}
}
/**
* @event unexpected
* @param {string} event - unhandled event name.
* @param {string|Error} e - unhandled error.
* @private
*/
static unexpected(event, e) {
// If you should ever get here, your app is definitely broken, and you need to fix
// your event handler to prevent unhandled errors during event notifications.
//
// Console output is suppressed when running tests, to avoid polluting test output
// with error messages that are intentional and of no value to the test.
/* istanbul ignore if */
if (!npm.main.suppressErrors) {
const stack = e instanceof Error ? e.stack : new Error().stack;
ColorConsole.error(`Unexpected error in '${event}' event handler.\n${stack}\n`);
}
}
}
module.exports = {Events};
/**
* @typedef EventContext
* @description
* This common type is used for the following events: {@link event:query query}, {@link event:receive receive},
* {@link event:error error}, {@link event:task task} and {@link event:transact transact}.
*
* @property {string|object} cn
*
* Set only for event {@link event:error error}, and only when the error is connection-related.
*
* It is a safe copy of the connection string/object that was used when initializing `db` - the database instance.
*
* If the original connection contains a password, the safe copy contains it masked with symbol `#`, so the connection
* can be logged safely, without exposing the password.
*
* @property {*} dc
* Database Context that was used when creating the database object (see {@link Database}). It is set for all events.
*
* @property {string|object} query
*
* Query string/object that was passed into the query method. This property is only set during events {@link event:query query},
* {@link event:receive receive} and {@link event:error error} (only when the error is query-related).
*
* @property {external:Client} client
*
* $[pg.Client] object that represents the connection. It is set for all events, except for event {@link event:error error}
* when it is connection-related. Note that sometimes the value may be unset when the connection is lost.
*
* @property {*} params - Formatting parameters for the query.
*
* It is set only for events {@link event:query query}, {@link event:receive receive} and {@link event:error error}, and only
* when it is needed for logging. This library takes an extra step in figuring out when formatting parameters are of any value
* to the event logging:
* - when an error occurs related to the query formatting, event {@link event:error error} is sent with the property set.
* - when initialization parameter `pgFormat` is used, and all query formatting is done within the $[PG] library, events
* {@link event:query query} and {@link event:receive receive} will have this property set also, since this library no longer
* handles the query formatting.
*
* When this parameter is not set, it means one of the two things:
* - there were no parameters passed into the query method;
* - property `query` of this object already contains all the formatting values in it, so logging only the query is sufficient.
*
* @property {TaskContext} ctx
* _Task/Transaction Context_ object.
*
* This property is always set for events {@link event:task task} and {@link event:transact transact}, while for events
* {@link event:query query}, {@link event:receive receive} and {@link event:error error} it is only set when they occur
* inside a task or transaction.
*
*/

View File

@@ -0,0 +1,931 @@
/*
* 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 {assert} = require(`./assert`);
const npm = {
pgUtils: require(`pg/lib/utils`),
patterns: require(`./patterns`),
utils: require(`./utils`)
};
// Format Modification Flags;
const fmFlags = {
raw: 1, // Raw-Text variable
alias: 2, // SQL Alias
name: 4, // SQL Name/Identifier
json: 8, // JSON modifier
csv: 16, // CSV modifier
value: 32 // escaped, but without ''
};
// Format Modification Map;
const fmMap = {
'^': fmFlags.raw,
':raw': fmFlags.raw,
':alias': fmFlags.alias,
'~': fmFlags.name,
':name': fmFlags.name,
':json': fmFlags.json,
':csv': fmFlags.csv,
':list': fmFlags.csv,
':value': fmFlags.value,
'#': fmFlags.value
};
// Global symbols for Custom Type Formatting:
const ctfSymbols = {
toPostgres: Symbol.for(`ctf.toPostgres`),
rawType: Symbol.for(`ctf.rawType`)
};
const maxVariable = 100000; // maximum supported variable is '$100000'
////////////////////////////////////////////////////
// Converts a single value into its Postgres format.
function formatValue({value, fm, cc, options}) {
if (typeof value === `function`) {
return formatValue({value: resolveFunc(value, cc), fm, cc});
}
const ctf = getCTF(value); // Custom Type Formatting
if (ctf) {
fm |= ctf.rawType ? fmFlags.raw : 0;
return formatValue({value: resolveFunc(ctf.toPostgres, value), fm, cc});
}
const isRaw = !!(fm & fmFlags.raw);
fm &= ~fmFlags.raw;
switch (fm) {
case fmFlags.alias:
return $as.alias(value);
case fmFlags.name:
return $as.name(value);
case fmFlags.json:
return $as.json(value, isRaw);
case fmFlags.csv:
return $to.csv(value, options);
case fmFlags.value:
return $as.value(value);
default:
break;
}
if (isNull(value)) {
throwIfRaw(isRaw);
return `null`;
}
switch (typeof value) {
case `string`:
return $to.text(value, isRaw);
case `boolean`:
return $to.bool(value);
case `number`:
case `bigint`:
return $to.number(value);
case `symbol`:
throw new TypeError(`Type Symbol has no meaning for PostgreSQL: ${value.toString()}`);
default:
if (value instanceof Date) {
return $to.date(value, isRaw);
}
if (Array.isArray(value)) {
return $to.array(value, options);
}
if (Buffer.isBuffer(value)) {
return $to.buffer(value, isRaw);
}
return $to.json(value, isRaw);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Converts array of values into PostgreSQL Array Constructor: array[...], as per PostgreSQL documentation:
// http://www.postgresql.org/docs/9.6/static/arrays.html
//
// Arrays of any depth/dimension are supported.
//
// Top-level empty arrays are formatted as literal '{}' to avoid the necessity of explicit type casting,
// as the server cannot automatically infer the type of empty non-literal array.
function formatArray(array, options) {
const loop = a => `[` + a.map(value => Array.isArray(value) ? loop(value) : formatValue({
value,
options
})).join() + `]`;
const prefix = options && options.capSQL ? `ARRAY` : `array`;
return array.length ? (prefix + loop(array)) : `'{}'`;
}
///////////////////////////////////////////////////////////////////
// Formats array/object/value as a list of comma-separated values.
function formatCSV(values, options) {
if (Array.isArray(values)) {
return values.map(value => formatValue({value, options})).join();
}
if (typeof values === `object` && values !== null) {
return Object.keys(values).map(v => formatValue({value: values[v], options})).join();
}
return values === undefined ? `` : formatValue({value: values, options});
}
///////////////////////////////
// Query formatting helpers;
const formatAs = {
object({query, obj, raw, options}) {
options = options && typeof options === `object` ? options : {};
return query.replace(npm.patterns.namedParameters, name => {
const v = formatAs.stripName(name.replace(/^\$[{(<[/]|[\s})>\]/]/g, ``), raw),
c = npm.utils.getIfHas(obj, v.name);
if (!c.valid) {
throw new Error(`Invalid property name '${v.name}'.`);
}
if (c.has) {
return formatValue({value: c.value, fm: v.fm, cc: c.target, options});
}
if (v.name === `this`) {
return formatValue({value: obj, fm: v.fm, options});
}
if (`def` in options) {
const d = options.def, value = typeof d === `function` ? d.call(obj, v.name, obj) : d;
return formatValue({value, fm: v.fm, cc: obj, options});
}
if (options.partial) {
return name;
}
// property must exist as the object's own or inherited;
throw new Error(`Property '${v.name}' doesn't exist.`);
});
},
array({query, array, raw, options}) {
options = options && typeof options === `object` ? options : {};
return query.replace(npm.patterns.multipleValues, name => {
const v = formatAs.stripName(name.substr(1), raw);
const idx = v.name - 1;
if (idx >= maxVariable) {
throw new RangeError(`Variable $${v.name} exceeds supported maximum of $${maxVariable}`);
}
if (idx < array.length) {
return formatValue({value: array[idx], fm: v.fm, options});
}
if (`def` in options) {
const d = options.def, value = typeof d === `function` ? d.call(array, idx, array) : d;
return formatValue({value, fm: v.fm, options});
}
if (options.partial) {
return name;
}
throw new RangeError(`Variable $${v.name} out of range. Parameters array length: ${array.length}`);
});
},
value({query, value, raw, options}) {
return query.replace(npm.patterns.singleValue, name => {
const v = formatAs.stripName(name, raw);
return formatValue({value, fm: v.fm, options});
});
},
stripName(name, raw) {
const mod = name.match(npm.patterns.hasValidModifier);
if (mod) {
return {
name: name.substr(0, mod.index),
fm: fmMap[mod[0]] | (raw ? fmFlags.raw : 0)
};
}
return {
name,
fm: raw ? fmFlags.raw : null
};
}
};
////////////////////////////////////////////
// Simpler check for null/undefined;
function isNull(value) {
return value === undefined || value === null;
}
//////////////////////////////////////////////////////////////////
// Checks if the value supports Custom Type Formatting,
// to return {toPostgres, rawType}, if it does, or null otherwise.
function getCTF(value) {
if (!isNull(value)) {
let toPostgres = value[ctfSymbols.toPostgres], rawType = !!value[ctfSymbols.rawType];
if (typeof toPostgres !== `function`) {
toPostgres = value.toPostgres;
rawType = !!value.rawType;
}
if (typeof toPostgres === `function`) {
if (toPostgres.constructor.name !== `Function`) {
throw new Error(`CTF does not support asynchronous toPostgres functions.`);
}
return {toPostgres, rawType};
}
}
return null;
}
/////////////////////////////////////////
// Wraps a text string in single quotes;
function wrapText(text) {
return `'${text}'`;
}
////////////////////////////////////////////////
// Replaces each single-quote symbol ' with two,
// for compliance with PostgreSQL strings.
function safeText(text) {
return text.replace(/'/g, `''`);
}
/////////////////////////////////////////////
// Throws an exception, if flag 'raw' is set.
function throwIfRaw(raw) {
if (raw) {
throw new TypeError(`Values null/undefined cannot be used as raw text.`);
}
}
/////////////////////////////////////////////////////////////////////////////
// Recursively resolves parameter-function, with an optional Calling Context.
function resolveFunc(value, cc) {
while (typeof value === `function`) {
if (value.constructor.name !== `Function`) {
// Constructor name for asynchronous functions have different names:
// - 'GeneratorFunction' for ES6 generators
// - 'AsyncFunction' for ES7 async functions
throw new Error(`Cannot use asynchronous functions with query formatting.`);
}
value = value.call(cc, cc);
}
return value;
}
///////////////////////////////////////////////////////////////////////////////////
// It implements two types of formatting, depending on the 'values' passed:
//
// 1. format '$1, $2, etc', when 'values' is of type string, boolean, number, date,
// function or null (or an array of the same types, plus undefined values);
// 2. format $*propName*, when 'values' is an object (not null and not Date),
// and where * is any of the supported open-close pairs: {}, (), [], <>, //
//
function formatQuery(query, values, raw, options) {
if (typeof query !== `string`) {
throw new TypeError(`Parameter 'query' must be a text string.`);
}
const ctf = getCTF(values);
if (ctf) {
// Custom Type Formatting
return formatQuery(query, resolveFunc(ctf.toPostgres, values), raw || ctf.rawType, options);
}
if (typeof values === `object` && values !== null) {
if (Array.isArray(values)) {
// $1, $2,... formatting to be applied;
return formatAs.array({query, array: values, raw, options});
}
if (!(values instanceof Date || values instanceof Buffer)) {
// $*propName* formatting to be applied;
return formatAs.object({query, obj: values, raw, options});
}
}
// $1 formatting to be applied, if values != undefined;
return values === undefined ? query : formatAs.value({query, value: values, raw, options});
}
//////////////////////////////////////////////////////
// Formats a function or stored procedure call query;
function formatEntity(entity, values, {capSQL, type}) {
let prefix = type === `func` ? `select * from` : `call`;
if (capSQL) {
prefix = prefix.toUpperCase();
}
return `${prefix} ${$as.alias(entity)}(${formatCSV(values, {capSQL})})`;
}
function formatSqlName(name) {
return `"${name.replace(/"/g, `""`)}"`;
}
/**
* @namespace formatting
* @description
* Namespace for all query-formatting functions, available from `pgp.as` before and after initializing the library.
*
* @property {formatting.ctf} ctf
* Namespace for symbols used by $[Custom Type Formatting].
*
* @property {function} alias
* {@link formatting.alias alias} - formats an SQL alias.
*
* @property {function} name
* {@link formatting.name name} - formats an SQL Name/Identifier.
*
* @property {function} text
* {@link formatting.text text} - formats a text string.
*
* @property {function} number
* {@link formatting.number number} - formats a number.
*
* @property {function} buffer
* {@link formatting.buffer buffer} - formats a `Buffer` object.
*
* @property {function} value
* {@link formatting.value value} - formats text as an open value.
*
* @property {function} json
* {@link formatting.json json} - formats any value as JSON.
*
* @property {function} array
* {@link formatting.array array} - formats an array of any depth.
*
* @property {function} csv
* {@link formatting.csv csv} - formats an array as a list of comma-separated values.
*
* @property {function} func
* {@link formatting.func func} - formats the value returned from a function.
*
* @property {function} format
* {@link formatting.format format} - formats a query, according to parameters.
*
*/
const $as = {
/**
* @namespace formatting.ctf
* @description
* Namespace for ES6 symbols used by $[Custom Type Formatting], available from `pgp.as.ctf` before and after initializing the library.
*
* It was added to avoid explicit/enumerable extension of types that need to be used as formatting parameters, to keep their type signature intact.
*
* @property {external:Symbol} toPostgres
* Property name for the $[Custom Type Formatting] callback function `toPostgres`.
*
* @property {external:Symbol} rawType
* Property name for the $[Custom Type Formatting] flag `rawType`.
*
* @example
* const ctf = pgp.as.ctf; // Custom Type Formatting symbols
*
* class MyType {
* constructor() {
* this[ctf.rawType] = true; // set it only when toPostgres returns a pre-formatted result
* }
*
* [ctf.toPostgres](self) {
* // self = this
*
* // return the custom/actual value here
* }
* }
*
* const a = new MyType();
*
* const s = pgp.as.format('$1', a); // will be custom-formatted
*/
ctf: ctfSymbols,
/**
* @method formatting.text
* @description
* Converts a value into PostgreSQL text presentation, escaped as required.
*
* Escaping the result means:
* 1. Every single-quote (apostrophe) is replaced with two
* 2. The resulting text is wrapped in apostrophes
*
* @param {value|function} value
* Value to be converted, or a function that returns the value.
*
* If the `value` resolves as `null` or `undefined`, while `raw`=`true`,
* it will throw {@link external:TypeError TypeError} = `Values null/undefined cannot be used as raw text.`
*
* @param {boolean} [raw=false]
* Indicates when not to escape the resulting text.
*
* @returns {string}
*
* - `null` string, if the `value` resolves as `null` or `undefined`
* - escaped result of `value.toString()`, if the `value` isn't a string
* - escaped string version, if `value` is a string.
*
* The result is not escaped, if `raw` was passed in as `true`.
*/
text(value, raw) {
value = resolveFunc(value);
if (isNull(value)) {
throwIfRaw(raw);
return `null`;
}
if (typeof value !== `string`) {
value = value.toString();
}
return $to.text(value, raw);
},
/**
* @method formatting.name
* @description
* Properly escapes an sql name or identifier, fixing double-quote symbols and wrapping the result in double quotes.
*
* Implements a safe way to format $[SQL Names] that neutralizes SQL Injection.
*
* When formatting a query, a variable makes use of this method via modifier `:name` or `~`. See method {@link formatting.format format}.
*
* @param {string|function|array|object} name
* SQL name or identifier, or a function that returns it.
*
* The name must be at least 1 character long.
*
* If `name` doesn't resolve into a non-empty string, it throws {@link external:TypeError TypeError} = `Invalid sql name: ...`
*
* If the `name` contains only a single `*` (trailing spaces are ignored), then `name` is returned exactly as is (unescaped).
*
* - If `name` is an Array, it is formatted as a comma-separated list of $[SQL Names]
* - If `name` is a non-Array object, its keys are formatted as a comma-separated list of $[SQL Names]
*
* Passing in an empty array/object will throw {@link external:Error Error} = `Cannot retrieve sql names from an empty array/object.`
*
* @returns {string}
* The SQL Name/Identifier, properly escaped for compliance with the PostgreSQL standard for $[SQL Names] and identifiers.
*
* @see
* {@link formatting.alias alias},
* {@link formatting.format format}
*
* @example
*
* // automatically list object properties as sql names:
* format('INSERT INTO table(${this~}) VALUES(${one}, ${two})', {
* one: 1,
* two: 2
* });
* //=> INSERT INTO table("one","two") VALUES(1, 2)
*
*/
name(name) {
name = resolveFunc(name);
if (name) {
if (typeof name === `string`) {
return /^\s*\*(\s*)$/.test(name) ? name : formatSqlName(name);
}
if (typeof name === `object`) {
const keys = Array.isArray(name) ? name : Object.keys(name);
if (!keys.length) {
throw new Error(`Cannot retrieve sql names from an empty array/object.`);
}
return keys.map(value => {
if (!value || typeof value !== `string`) {
throw new Error(`Invalid sql name: ${npm.utils.toJson(value)}`);
}
return formatSqlName(value);
}).join();
}
}
throw new TypeError(`Invalid sql name: ${npm.utils.toJson(name)}`);
},
/**
* @method formatting.alias
* @description
* Simpler (non-verbose) version of method {@link formatting.name name}, to handle only a regular string-identifier
* that's mostly used as an SQL alias, i.e. it doesn't support `*` or an array/object of names, which in the context of
* an SQL alias would be incorrect. However, it supports `.` as name-separator, for simpler escaping of composite names.
*
* The surrounding double quotes are not added when the alias uses a simple syntax:
* - it is a same-case single word, without spaces
* - it can contain underscores, and can even start with them
* - it can contain digits and `$`, but cannot start with those
*
* The method will automatically split the string with `.`, to support composite SQL names.
*
* When formatting a query, a variable makes use of this method via modifier `:alias`. See method {@link formatting.format format}.
*
* @param {string|function} name
* SQL alias name, or a function that returns it.
*
* The name must be at least 1 character long. And it can contain `.`, to split into multiple SQL names.
*
* If `name` doesn't resolve into a non-empty string, it throws {@link external:TypeError TypeError} = `Invalid sql alias: ...`
*
* @returns {string}
* The SQL alias, properly escaped for compliance with the PostgreSQL standard for $[SQL Names] and identifiers.
*
* @see
* {@link formatting.name name},
* {@link formatting.format format}
*
*/
alias(name) {
name = resolveFunc(name);
if (name && typeof name === `string`) {
return name.split(`.`)
.filter(f => f)
.map(a => {
const m = a.match(/^([a-z_][a-z0-9_$]*|[A-Z_][A-Z0-9_$]*)$/);
if (m && m[0] === a) {
return a;
}
return `"${a.replace(/"/g, `""`)}"`;
}).join(`.`);
}
throw new TypeError(`Invalid sql alias: ${npm.utils.toJson(name)}`);
},
/**
* @method formatting.value
* @description
* Represents an open value, one to be formatted according to its type, properly escaped, but without surrounding quotes for text types.
*
* When formatting a query, a variable makes use of this method via modifier `:value` or `#`. See method {@link formatting.format format}.
*
* @param {value|function} value
* Value to be converted, or a function that returns the value.
*
* If `value` resolves as `null` or `undefined`, it will throw {@link external:TypeError TypeError} = `Open values cannot be null or undefined.`
*
* @returns {string}
* Formatted and properly escaped string, but without surrounding quotes for text types.
*
* @see {@link formatting.format format}
*
*/
value(value) {
value = resolveFunc(value);
if (isNull(value)) {
throw new TypeError(`Open values cannot be null or undefined.`);
}
return safeText(formatValue({value, fm: fmFlags.raw}));
},
/**
* @method formatting.buffer
* @description
* Converts an object of type `Buffer` into a hex string compatible with PostgreSQL type `bytea`.
*
* @param {Buffer|function} obj
* Object to be converted, or a function that returns one.
*
* @param {boolean} [raw=false]
* Indicates when not to wrap the resulting string in quotes.
*
* The generated hex string doesn't need to be escaped.
*
* @returns {string}
*/
buffer(obj, raw) {
obj = resolveFunc(obj);
if (isNull(obj)) {
throwIfRaw(raw);
return `null`;
}
if (obj instanceof Buffer) {
return $to.buffer(obj, raw);
}
throw new TypeError(`${wrapText(obj)} is not a Buffer object.`);
},
/**
* @method formatting.bool
* @description
* Converts a truthy value into PostgreSQL boolean presentation.
*
* @param {boolean|function} value
* Value to be converted, or a function that returns the value.
*
* @returns {string}
*/
bool(value) {
value = resolveFunc(value);
if (isNull(value)) {
return `null`;
}
return $to.bool(value);
},
/**
* @method formatting.date
* @description
* Converts a `Date`-type value into PostgreSQL date/time presentation,
* wrapped in quotes (unless flag `raw` is set).
*
* @param {Date|function} d
* Date object to be converted, or a function that returns one.
*
* @param {boolean} [raw=false]
* Indicates when not to escape the value.
*
* @returns {string}
*/
date(d, raw) {
d = resolveFunc(d);
if (isNull(d)) {
throwIfRaw(raw);
return `null`;
}
if (d instanceof Date) {
return $to.date(d, raw);
}
throw new TypeError(`${wrapText(d)} is not a Date object.`);
},
/**
* @method formatting.number
* @description
* Converts a numeric value into its PostgreSQL number presentation, with support
* for special values of `NaN`, `+Infinity` and `-Infinity`.
*
* @param {number|bigint|function} num
* Number to be converted, or a function that returns one.
*
* @returns {string}
*/
number(num) {
num = resolveFunc(num);
if (isNull(num)) {
return `null`;
}
const t = typeof num;
if (t !== `number` && t !== `bigint`) {
throw new TypeError(`${wrapText(num)} is not a number.`);
}
return $to.number(num);
},
/**
* @method formatting.array
* @description
* Converts an array of values into its PostgreSQL presentation as an Array-Type constructor string: `array[]`.
*
* Top-level empty arrays are formatted as literal `{}`, to avoid the necessity of explicit type casting,
* as the server cannot automatically infer type of an empty non-literal array.
*
* @param {Array|function} arr
* Array to be converted, or a function that returns one.
*
* @param {{}} [options]
* Array-Formatting Options.
*
* @param {boolean} [options.capSQL=false]
* When `true`, outputs `ARRAY` instead of `array`.
*
* @returns {string}
*/
array(arr, options) {
options = assert(options, [`capSQL`]);
arr = resolveFunc(arr);
if (isNull(arr)) {
return `null`;
}
if (Array.isArray(arr)) {
return $to.array(arr, options);
}
throw new TypeError(`${wrapText(arr)} is not an Array object.`);
},
/**
* @method formatting.csv
* @description
* Converts a single value or an array of values into a CSV (comma-separated values) string, with all values formatted
* according to their JavaScript type.
*
* When formatting a query, a variable makes use of this method via modifier `:csv` or its alias `:list`.
*
* When `values` is an object that's not `null` or `Array`, its properties are enumerated for the actual values.
*
* @param {Array|Object|value|function} values
* Value(s) to be converted, or a function that returns it.
*
* @returns {string}
*
* @see {@link formatting.format format}
*/
csv(values) {
return $to.csv(values);
},
/**
* @method formatting.json
* @description
* Converts any value into JSON (includes `BigInt` support), and returns it as a valid string,
* with single-quote symbols fixed, unless flag `raw` is set.
*
* When formatting a query, a variable makes use of this method via modifier `:json`. See method {@link formatting.format format}.
*
* @param {*} data
* Object/value to be converted, or a function that returns it.
*
* @param {boolean} [raw=false]
* Indicates when not to escape the result.
*
* @returns {string}
*
* @see {@link formatting.format format}
*/
json(data, raw) {
data = resolveFunc(data);
if (isNull(data)) {
throwIfRaw(raw);
return `null`;
}
return $to.json(data, raw);
},
/**
* @method formatting.func
* @description
* Calls the function to get the actual value, and then formats the result according to its type + `raw` flag.
*
* @param {function} func
* Function to be called, with support for nesting.
*
* @param {boolean} [raw=false]
* Indicates when not to escape the result.
*
* @param {*} [cc]
* Calling Context: `this` + the only value to be passed into the function on all nested levels.
*
* @returns {string}
*/
func(func, raw, cc) {
if (isNull(func)) {
throwIfRaw(raw);
return `null`;
}
if (typeof func !== `function`) {
throw new TypeError(`${wrapText(func)} is not a function.`);
}
const fm = raw ? fmFlags.raw : null;
return formatValue({value: resolveFunc(func, cc), fm, cc});
},
/**
* @method formatting.format
* @description
* Replaces variables in a string according to the type of `values`:
*
* - Replaces `$1` occurrences when `values` is of type `string`, `boolean`, `number`, `bigint`, `Date`, `Buffer` or when it is `null`.
*
* - Replaces variables `$1`, `$2`, ...`$100000` when `values` is an array of parameters. It throws a {@link external:RangeError RangeError}
* when the values or variables are out of range.
*
* - Replaces `$*propName*`, where `*` is any of `{}`, `()`, `[]`, `<>`, `//`, when `values` is an object that's not a
* `Date`, `Buffer`, {@link QueryFile} or `null`. Special property name `this` refers to the formatting object itself,
* to be injected as a JSON string. When referencing a property that doesn't exist in the formatting object, it throws
* {@link external:Error Error} = `Property 'PropName' doesn't exist`, unless option `partial` is used.
*
* - Supports $[Nested Named Parameters] of any depth.
*
* By default, each variable is automatically formatted according to its type, unless it is a special variable:
*
* - Raw-text variables end with `:raw` or symbol `^`, and prevent escaping the text. Such variables are not
* allowed to be `null` or `undefined`, or the method will throw {@link external:TypeError TypeError} = `Values null/undefined cannot be used as raw text.`
* - `$1:raw`, `$2:raw`,..., and `$*propName:raw*` (see `*` above)
* - `$1^`, `$2^`,..., and `$*propName^*` (see `*` above)
*
* - Open-value variables end with `:value` or symbol `#`, to be escaped, but not wrapped in quotes. Such variables are
* not allowed to be `null` or `undefined`, or the method will throw {@link external:TypeError TypeError} = `Open values cannot be null or undefined.`
* - `$1:value`, `$2:value`,..., and `$*propName:value*` (see `*` above)
* - `$1#`, `$2#`,..., and `$*propName#*` (see `*` above)
*
* - SQL name variables end with `:name` or symbol `~` (tilde), and provide proper escaping for SQL names/identifiers:
* - `$1:name`, `$2:name`,..., and `$*propName:name*` (see `*` above)
* - `$1~`, `$2~`,..., and `$*propName~*` (see `*` above)
*
* - Modifier `:alias` - non-verbose $[SQL Names] escaping.
*
* - JSON override ends with `:json` to format the value of any type as a JSON string
*
* - CSV override ends with `:csv` or `:list` to format an array as a properly escaped comma-separated list of values.
*
* @param {string|QueryFile|object} query
* A query string, a {@link QueryFile} or any object that implements $[Custom Type Formatting], to be formatted according to `values`.
*
* @param {array|object|value} [values]
* Formatting parameter(s) / variable value(s).
*
* @param {{}} [options]
* Formatting Options.
*
* @param {boolean} [options.capSQL=false]
* Formats reserved SQL words capitalized. Presently, this only concerns arrays, to output `ARRAY` when required.
*
* @param {boolean} [options.partial=false]
* Indicates that we intend to do only a partial replacement, i.e. throw no error when encountering a variable or
* property name that's missing within the formatting parameters.
*
* **NOTE:** This option has no meaning when option `def` is used.
*
* @param {*} [options.def]
* Sets default value for every variable that's missing, consequently preventing errors when encountering a variable
* or property name that's missing within the formatting parameters.
*
* It can also be set to a function, to be called with two parameters that depend on the type of formatting being used,
* and to return the actual default value:
*
* - For $[Named Parameters] formatting:
* - `name` - name of the property missing in the formatting object
* - `obj` - the formatting object, and is the same as `this` context
*
* - For $[Index Variables] formatting:
* - `index` - element's index (starts with 1) that's outside of the input array
* - `arr` - the formatting/input array, and is the same as `this` context
*
* You can tell which type of call it is by checking the type of the first parameter.
*
* @returns {string}
* Formatted query string.
*
* The function will throw an error, if any occurs during formatting.
*/
format(query, values, options) {
options = assert(options, [`capSQL`, `partial`, `def`]);
const ctf = getCTF(query);
if (ctf) {
query = ctf.toPostgres.call(query, query);
}
return formatQuery(query, values, false, options);
}
};
/* Pre-parsed type formatting */
const $to = {
array(arr, options) {
return formatArray(arr, options);
},
csv(values, options) {
return formatCSV(resolveFunc(values), options);
},
bool(value) {
return value ? `true` : `false`;
},
buffer(obj, raw) {
const s = `\\x${obj.toString(`hex`)}`;
return raw ? s : wrapText(s);
},
date(d, raw) {
const s = npm.pgUtils.prepareValue(d);
return raw ? s : wrapText(s);
},
json(data, raw) {
const s = npm.utils.toJson(data);
return raw ? s : wrapText(safeText(s));
},
number(num) {
if (typeof num === `bigint` || Number.isFinite(num)) {
return num.toString();
}
// Converting NaN/+Infinity/-Infinity according to Postgres documentation:
// http://www.postgresql.org/docs/9.6/static/datatype-numeric.html#DATATYPE-FLOAT
//
// NOTE: strings for 'NaN'/'+Infinity'/'-Infinity' are not case-sensitive.
if (num === Number.POSITIVE_INFINITY) {
return wrapText(`+Infinity`);
}
if (num === Number.NEGATIVE_INFINITY) {
return wrapText(`-Infinity`);
}
return wrapText(`NaN`);
},
text(value, raw) {
return raw ? value : wrapText(safeText(value));
}
};
module.exports = {
formatQuery,
formatEntity,
resolveFunc,
as: $as
};
/**
* @external Error
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
*/
/**
* @external TypeError
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
*/
/**
* @external RangeError
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
*/
/**
* @external Symbol
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
*/

View File

@@ -0,0 +1,10 @@
### `helpers` namespace
This folder contains everything that's available via the [helpers] namespace, after initializing the library:
```js
const pgp = require('pg-promise')(/*initialization options*/);
const helpers = pgp.helpers; // `helpers` namespace
```
[helpers]:http://vitaly-t.github.io/pg-promise/helpers.html

View File

@@ -0,0 +1,647 @@
/*
* 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 {InnerState} = require(`../inner-state`);
const {assert} = require(`../assert`);
const {TableName} = require(`./table-name`);
const {Column} = require(`./column`);
const npm = {
os: require(`os`),
utils: require(`../utils`),
formatting: require(`../formatting`)
};
/**
* @class helpers.ColumnSet
* @description
* Performance-optimized, read-only structure with query-formatting columns.
*
* In order to avail from performance optimization provided by this class, it should be created
* only once, statically, and then reused.
*
* @param {object|helpers.Column|array} columns
* Columns information object, depending on the type:
*
* - When it is a simple object, its properties are enumerated to represent both column names and property names
* within the source objects. See also option `inherit` that's applicable in this case.
*
* - When it is a single {@link helpers.Column Column} object, property {@link helpers.ColumnSet#columns columns} is initialized with
* just a single column. It is not a unique situation when only a single column is required for an update operation.
*
* - When it is an array, each element is assumed to represent details for a column. If the element is already of type {@link helpers.Column Column},
* it is used directly; otherwise the element is passed into {@link helpers.Column Column} constructor for initialization.
* On any duplicate column name (case-sensitive) it will throw {@link external:Error Error} = `Duplicate column name "name".`
*
* - When it is none of the above, it will throw {@link external:TypeError TypeError} = `Invalid parameter 'columns' specified.`
*
* @param {object} [options]
*
* @param {helpers.TableName|string|{table,schema}} [options.table]
* Table details.
*
* When it is a non-null value, and not a {@link helpers.TableName TableName} object, a new {@link helpers.TableName TableName} is constructed from the value.
*
* It can be used as the default for methods {@link helpers.insert insert} and {@link helpers.update update} when their parameter
* `table` is omitted, and for logging purposes.
*
* @param {boolean} [options.inherit = false]
* Use inherited properties in addition to the object's own properties.
*
* By default, only the object's own properties are enumerated for column names.
*
* @returns {helpers.ColumnSet}
*
* @see
*
* {@link helpers.ColumnSet#columns columns},
* {@link helpers.ColumnSet#names names},
* {@link helpers.ColumnSet#table table},
* {@link helpers.ColumnSet#variables variables} |
* {@link helpers.ColumnSet#assign assign},
* {@link helpers.ColumnSet#assignColumns assignColumns},
* {@link helpers.ColumnSet#extend extend},
* {@link helpers.ColumnSet#merge merge},
* {@link helpers.ColumnSet#prepare prepare}
*
* @example
*
* // A complex insert/update object scenario for table 'purchases' in schema 'fiscal'.
* // For a good performance, you should declare such objects once and then reuse them.
* //
* // Column Requirements:
* //
* // 1. Property 'id' is only to be used for a WHERE condition in updates
* // 2. Property 'list' needs to be formatted as a csv
* // 3. Property 'code' is to be used as raw text, and to be defaulted to 0 when the
* // property is missing in the source object
* // 4. Property 'log' is a JSON object with 'log-entry' for the column name
* // 5. Property 'data' requires SQL type casting '::int[]'
* // 6. Property 'amount' needs to be set to 100, if it is 0
* // 7. Property 'total' must be skipped during updates, if 'amount' was 0, plus its
* // column name is 'total-val'
*
* const cs = new pgp.helpers.ColumnSet([
* '?id', // ColumnConfig equivalent: {name: 'id', cnd: true}
* 'list:csv', // ColumnConfig equivalent: {name: 'list', mod: ':csv'}
* {
* name: 'code',
* mod: '^', // format as raw text
* def: 0 // default to 0 when the property doesn't exist
* },
* {
* name: 'log-entry',
* prop: 'log',
* mod: ':json' // format as JSON
* },
* {
* name: 'data',
* cast: 'int[]' // use SQL type casting '::int[]'
* },
* {
* name: 'amount',
* init(col) {
* // set to 100, if the value is 0:
* return col.value === 0 ? 100 : col.value;
* }
* },
* {
* name: 'total-val',
* prop: 'total',
* skip(col) {
* // skip from updates, if 'amount' is 0:
* return col.source.amount === 0;
* }
* }
* ], {table: {table: 'purchases', schema: 'fiscal'}});
*
* // Alternatively, you could take the table declaration out:
* // const table = new pgp.helpers.TableName('purchases', 'fiscal');
*
* console.log(cs); // console output for the object:
* //=>
* // ColumnSet {
* // table: "fiscal"."purchases"
* // columns: [
* // Column {
* // name: "id"
* // cnd: true
* // }
* // Column {
* // name: "list"
* // mod: ":csv"
* // }
* // Column {
* // name: "code"
* // mod: "^"
* // def: 0
* // }
* // Column {
* // name: "log-entry"
* // prop: "log"
* // mod: ":json"
* // }
* // Column {
* // name: "data"
* // cast: "int[]"
* // }
* // Column {
* // name: "amount"
* // init: [Function]
* // }
* // Column {
* // name: "total-val"
* // prop: "total"
* // skip: [Function]
* // }
* // ]
* // }
*/
class ColumnSet extends InnerState {
constructor(columns, opt) {
super();
if (!columns || typeof columns !== `object`) {
throw new TypeError(`Invalid parameter 'columns' specified.`);
}
opt = assert(opt, [`table`, `inherit`]);
if (!npm.utils.isNull(opt.table)) {
this.table = (opt.table instanceof TableName) ? opt.table : new TableName(opt.table);
}
/**
* @name helpers.ColumnSet#table
* @type {helpers.TableName}
* @readonly
* @description
* Destination table. It can be specified for two purposes:
*
* - **primary:** to be used as the default table when it is omitted during a call into methods {@link helpers.insert insert} and {@link helpers.update update}
* - **secondary:** to be automatically written into the console (for logging purposes).
*/
/**
* @name helpers.ColumnSet#columns
* @type helpers.Column[]
* @readonly
* @description
* Array of {@link helpers.Column Column} objects.
*/
if (Array.isArray(columns)) {
const colNames = {};
this.columns = columns.map(c => {
const col = (c instanceof Column) ? c : new Column(c);
if (col.name in colNames) {
throw new Error(`Duplicate column name "${col.name}".`);
}
colNames[col.name] = true;
return col;
});
} else {
if (columns instanceof Column) {
this.columns = [columns];
} else {
this.columns = [];
for (const name in columns) {
if (opt.inherit || Object.prototype.hasOwnProperty.call(columns, name)) {
this.columns.push(new Column(name));
}
}
}
}
Object.freeze(this.columns);
Object.freeze(this);
this.extendState({
names: undefined,
variables: undefined,
updates: undefined,
isSimple: true
});
for (let i = 0; i < this.columns.length; i++) {
const c = this.columns[i];
// ColumnSet is simple when the source objects require no preparation,
// and should be used directly:
if (c.prop || c.init || `def` in c) {
this._inner.isSimple = false;
break;
}
}
}
/**
* @name helpers.ColumnSet#names
* @type string
* @readonly
* @description
* Returns a string - comma-separated list of all column names, properly escaped.
*
* @example
* const cs = new ColumnSet(['id^', {name: 'cells', cast: 'int[]'}, 'doc:json']);
* console.log(cs.names);
* //=> "id","cells","doc"
*/
get names() {
const _i = this._inner;
if (!_i.names) {
_i.names = this.columns.map(c => c.escapedName).join();
}
return _i.names;
}
/**
* @name helpers.ColumnSet#variables
* @type string
* @readonly
* @description
* Returns a string - formatting template for all column values.
*
* @see {@link helpers.ColumnSet#assign assign}
*
* @example
* const cs = new ColumnSet(['id^', {name: 'cells', cast: 'int[]'}, 'doc:json']);
* console.log(cs.variables);
* //=> ${id^},${cells}::int[],${doc:json}
*/
get variables() {
const _i = this._inner;
if (!_i.variables) {
_i.variables = this.columns.map(c => c.variable + c.castText).join();
}
return _i.variables;
}
}
/**
* @method helpers.ColumnSet#assign
* @description
* Returns a formatting template of SET assignments, either generic or for a single object.
*
* The method is optimized to cache the output string when there are no columns that can be skipped dynamically.
*
* This method is primarily for internal use, that's why it does not validate the input.
*
* @param {object} [options]
* Assignment/formatting options.
*
* @param {object} [options.source]
* Source - a single object that contains values for columns.
*
* The object is only necessary to correctly apply the logic of skipping columns dynamically, based on the source data
* and the rules defined in the {@link helpers.ColumnSet ColumnSet}. If, however, you do not care about that, then you do not need to specify any object.
*
* Note that even if you do not specify the object, the columns marked as conditional (`cnd: true`) will always be skipped.
*
* @param {string} [options.prefix]
* In cases where needed, an alias prefix to be added before each column.
*
* @returns {string}
* Comma-separated list of variable-to-column assignments.
*
* @see {@link helpers.ColumnSet#variables variables}
*
* @example
*
* const cs = new pgp.helpers.ColumnSet([
* '?first', // = {name: 'first', cnd: true}
* 'second:json',
* {name: 'third', mod: ':raw', cast: 'text'}
* ]);
*
* cs.assign();
* //=> "second"=${second:json},"third"=${third:raw}::text
*
* cs.assign({prefix: 'a b c'});
* //=> "a b c"."second"=${second:json},"a b c"."third"=${third:raw}::text
*/
ColumnSet.prototype.assign = function (options) {
const _i = this._inner;
const hasPrefix = options && options.prefix && typeof options.prefix === `string`;
if (_i.updates && !hasPrefix) {
return _i.updates;
}
let dynamic = hasPrefix;
const hasSource = options && options.source && typeof options.source === `object`;
let list = this.columns.filter(c => {
if (c.cnd) {
return false;
}
if (c.skip) {
dynamic = true;
if (hasSource) {
const a = colDesc(c, options.source);
if (c.skip.call(options.source, a)) {
return false;
}
}
}
return true;
});
const prefix = hasPrefix ? npm.formatting.as.alias(options.prefix) + `.` : ``;
list = list.map(c => prefix + c.escapedName + `=` + c.variable + c.castText).join();
if (!dynamic) {
_i.updates = list;
}
return list;
};
/**
* @method helpers.ColumnSet#assignColumns
* @description
* Generates assignments for all columns in the set, with support for aliases and column-skipping logic.
* Aliases are set by using method {@link formatting.alias as.alias}.
*
* @param {{}} [options]
* Optional Parameters.
*
* @param {string} [options.from]
* Alias for the source columns.
*
* @param {string} [options.to]
* Alias for the destination columns.
*
* @param {string | Array<string> | function} [options.skip]
* Name(s) of the column(s) to be skipped (case-sensitive). It can be either a single string or an array of strings.
*
* It can also be a function - iterator, to be called for every column, passing in {@link helpers.Column Column} as
* `this` context, and plus as a single parameter. The function would return a truthy value for every column that needs to be skipped.
*
* @returns {string}
* A string of comma-separated column assignments.
*
* @example
*
* const cs = new pgp.helpers.ColumnSet(['id', 'city', 'street']);
*
* cs.assignColumns({from: 'EXCLUDED', skip: 'id'})
* //=> "city"=EXCLUDED."city","street"=EXCLUDED."street"
*
* @example
*
* const cs = new pgp.helpers.ColumnSet(['?id', 'city', 'street']);
*
* cs.assignColumns({from: 'source', to: 'target', skip: c => c.cnd})
* //=> target."city"=source."city",target."street"=source."street"
*
*/
ColumnSet.prototype.assignColumns = function (options) {
options = assert(options, [`from`, `to`, `skip`]);
const skip = (typeof options.skip === `string` && [options.skip]) || ((Array.isArray(options.skip) || typeof options.skip === `function`) && options.skip);
const from = (typeof options.from === `string` && options.from && (npm.formatting.as.alias(options.from) + `.`)) || ``;
const to = (typeof options.to === `string` && options.to && (npm.formatting.as.alias(options.to) + `.`)) || ``;
const iterator = typeof skip === `function` ? c => !skip.call(c, c) : c => skip.indexOf(c.name) === -1;
const cols = skip ? this.columns.filter(iterator) : this.columns;
return cols.map(c => to + c.escapedName + `=` + from + c.escapedName).join();
};
/**
* @method helpers.ColumnSet#extend
* @description
* Creates a new {@link helpers.ColumnSet ColumnSet}, by joining the two sets of columns.
*
* If the two sets contain a column with the same `name` (case-sensitive), an error is thrown.
*
* @param {helpers.Column|helpers.ColumnSet|array} columns
* Columns to be appended, of the same type as parameter `columns` during {@link helpers.ColumnSet ColumnSet} construction, except:
* - it can also be of type {@link helpers.ColumnSet ColumnSet}
* - it cannot be a simple object (properties enumeration is not supported here)
*
* @returns {helpers.ColumnSet}
* New {@link helpers.ColumnSet ColumnSet} object with the extended/concatenated list of columns.
*
* @see
* {@link helpers.Column Column},
* {@link helpers.ColumnSet#merge merge}
*
* @example
*
* const pgp = require('pg-promise')();
*
* const cs = new pgp.helpers.ColumnSet(['one', 'two'], {table: 'my-table'});
* console.log(cs);
* //=>
* // ColumnSet {
* // table: "my-table"
* // columns: [
* // Column {
* // name: "one"
* // }
* // Column {
* // name: "two"
* // }
* // ]
* // }
* const csExtended = cs.extend(['three']);
* console.log(csExtended);
* //=>
* // ColumnSet {
* // table: "my-table"
* // columns: [
* // Column {
* // name: "one"
* // }
* // Column {
* // name: "two"
* // }
* // Column {
* // name: "three"
* // }
* // ]
* // }
*/
ColumnSet.prototype.extend = function (columns) {
let cs = columns;
if (!(cs instanceof ColumnSet)) {
cs = new ColumnSet(columns);
}
// Any duplicate column will throw Error = 'Duplicate column name "name".',
return new ColumnSet(this.columns.concat(cs.columns), {table: this.table});
};
/**
* @method helpers.ColumnSet#merge
* @description
* Creates a new {@link helpers.ColumnSet ColumnSet}, by joining the two sets of columns.
*
* Items in `columns` with the same `name` (case-sensitive) override the original columns.
*
* @param {helpers.Column|helpers.ColumnSet|array} columns
* Columns to be appended, of the same type as parameter `columns` during {@link helpers.ColumnSet ColumnSet} construction, except:
* - it can also be of type {@link helpers.ColumnSet ColumnSet}
* - it cannot be a simple object (properties enumeration is not supported here)
*
* @see
* {@link helpers.Column Column},
* {@link helpers.ColumnSet#extend extend}
*
* @returns {helpers.ColumnSet}
* New {@link helpers.ColumnSet ColumnSet} object with the merged list of columns.
*
* @example
*
* const pgp = require('pg-promise')();
*
* const cs = new pgp.helpers.ColumnSet(['?one', 'two:json'], {table: 'my-table'});
* console.log(cs);
* //=>
* // ColumnSet {
* // table: "my-table"
* // columns: [
* // Column {
* // name: "one"
* // cnd: true
* // }
* // Column {
* // name: "two"
* // mod: ":json"
* // }
* // ]
* // }
* const csMerged = cs.merge(['two', 'three^']);
* console.log(csMerged);
* //=>
* // ColumnSet {
* // table: "my-table"
* // columns: [
* // Column {
* // name: "one"
* // cnd: true
* // }
* // Column {
* // name: "two"
* // }
* // Column {
* // name: "three"
* // mod: "^"
* // }
* // ]
* // }
*
*/
ColumnSet.prototype.merge = function (columns) {
let cs = columns;
if (!(cs instanceof ColumnSet)) {
cs = new ColumnSet(columns);
}
const colNames = {}, cols = [];
this.columns.forEach((c, idx) => {
cols.push(c);
colNames[c.name] = idx;
});
cs.columns.forEach(c => {
if (c.name in colNames) {
cols[colNames[c.name]] = c;
} else {
cols.push(c);
}
});
return new ColumnSet(cols, {table: this.table});
};
/**
* @method helpers.ColumnSet#prepare
* @description
* Prepares a source object to be formatted, by cloning it and applying the rules as set by the
* columns configuration.
*
* This method is primarily for internal use, that's why it does not validate the input parameters.
*
* @param {object} source
* The source object to be prepared, if required.
*
* It must be a non-`null` object, which the method does not validate, as it is
* intended primarily for internal use by the library.
*
* @returns {object}
* When the object needs to be prepared, the method returns a clone of the source object,
* with all properties and values set according to the columns configuration.
*
* When the object does not need to be prepared, the original object is returned.
*/
ColumnSet.prototype.prepare = function (source) {
if (this._inner.isSimple) {
return source; // a simple ColumnSet requires no object preparation;
}
const target = {};
this.columns.forEach(c => {
const a = colDesc(c, source);
if (c.init) {
target[a.name] = c.init.call(source, a);
} else {
if (a.exists || `def` in c) {
target[a.name] = a.value;
}
}
});
return target;
};
function colDesc(column, source) {
const a = {
source,
name: column.prop || column.name
};
a.exists = a.name in source;
if (a.exists) {
a.value = source[a.name];
} else {
a.value = `def` in column ? column.def : undefined;
}
return a;
}
/**
* @method helpers.ColumnSet#toString
* @description
* Creates a well-formatted multi-line string that represents the object.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
ColumnSet.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap0 = npm.utils.messageGap(level),
gap1 = npm.utils.messageGap(level + 1),
lines = [
`ColumnSet {`
];
if (this.table) {
lines.push(gap1 + `table: ` + this.table);
}
if (this.columns.length) {
lines.push(gap1 + `columns: [`);
this.columns.forEach(c => {
lines.push(c.toString(2));
});
lines.push(gap1 + `]`);
} else {
lines.push(gap1 + `columns: []`);
}
lines.push(gap0 + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(ColumnSet, function () {
return this.toString();
});
module.exports = {ColumnSet};

View File

@@ -0,0 +1,455 @@
/*
* 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 {InnerState} = require(`../inner-state`);
const {assert} = require(`../assert`);
const npm = {
os: require(`os`),
utils: require(`../utils`),
formatting: require(`../formatting`),
patterns: require(`../patterns`)
};
/**
*
* @class helpers.Column
* @description
*
* Read-only structure with details for a single column. Used primarily by {@link helpers.ColumnSet ColumnSet}.
*
* The class parses details into a template, to be used for query generation.
*
* @param {string|helpers.ColumnConfig} col
* Column details, depending on the type.
*
* When it is a string, it is expected to contain a name for both the column and the source property, assuming that the two are the same.
* The name must adhere to JavaScript syntax for variable names. The name can be appended with any format modifier as supported by
* {@link formatting.format as.format} (`^`, `~`, `#`, `:csv`, `:list`, `:json`, `:alias`, `:name`, `:raw`, `:value`), which is then removed from the name and put
* into property `mod`. If the name starts with `?`, it is removed, while setting flag `cnd` = `true`.
*
* If the string doesn't adhere to the above requirements, the method will throw {@link external:TypeError TypeError} = `Invalid column syntax`.
*
* When `col` is a simple {@link helpers.ColumnConfig ColumnConfig}-like object, it is used as an input configurator to set all the properties
* of the class.
*
* @property {string} name
* Destination column name + source property name (if `prop` is skipped). The name must adhere to JavaScript syntax for variables,
* unless `prop` is specified, in which case `name` represents only the column name, and therefore can be any non-empty string.
*
* @property {string} [prop]
* Source property name, if different from the column's name. It must adhere to JavaScript syntax for variables.
*
* It is ignored when it is the same as `name`.
*
* @property {string} [mod]
* Formatting modifier, as supported by method {@link formatting.format as.format}: `^`, `~`, `#`, `:csv`, `:list`, `:json`, `:alias`, `:name`, `:raw`, `:value`.
*
* @property {string} [cast]
* Server-side type casting, without `::` in front.
*
* @property {boolean} [cnd]
* Conditional column flag.
*
* Used by methods {@link helpers.update update} and {@link helpers.sets sets}, ignored by methods {@link helpers.insert insert} and
* {@link helpers.values values}. It indicates that the column is reserved for a `WHERE` condition, not to be set or updated.
*
* It can be set from a string initialization, by adding `?` in front of the name.
*
* @property {*} [def]
* Default value for the property, to be used only when the source object doesn't have the property.
* It is ignored when property `init` is set.
*
* @property {helpers.initCB} [init]
* Override callback for the value.
*
* @property {helpers.skipCB} [skip]
* An override for skipping columns dynamically.
*
* Used by methods {@link helpers.update update} (for a single object) and {@link helpers.sets sets}, ignored by methods
* {@link helpers.insert insert} and {@link helpers.values values}.
*
* It is also ignored when conditional flag `cnd` is set.
*
* @returns {helpers.Column}
*
* @see
* {@link helpers.ColumnConfig ColumnConfig},
* {@link helpers.Column#castText castText},
* {@link helpers.Column#escapedName escapedName},
* {@link helpers.Column#variable variable}
*
* @example
*
* const pgp = require('pg-promise')({
* capSQL: true // if you want all generated SQL capitalized
* });
*
* const Column = pgp.helpers.Column;
*
* // creating a column from just a name:
* const col1 = new Column('colName');
* console.log(col1);
* //=>
* // Column {
* // name: "colName"
* // }
*
* // creating a column from a name + modifier:
* const col2 = new Column('colName:csv');
* console.log(col2);
* //=>
* // Column {
* // name: "colName"
* // mod: ":csv"
* // }
*
* // creating a column from a configurator:
* const col3 = new Column({
* name: 'colName', // required
* prop: 'propName', // optional
* mod: '^', // optional
* def: 123 // optional
* });
* console.log(col3);
* //=>
* // Column {
* // name: "colName"
* // prop: "propName"
* // mod: "^"
* // def: 123
* // }
*
*/
class Column extends InnerState {
constructor(col) {
super();
if (typeof col === `string`) {
const info = parseColumn(col);
this.name = info.name;
if (`mod` in info) {
this.mod = info.mod;
}
if (`cnd` in info) {
this.cnd = info.cnd;
}
} else {
col = assert(col, [`name`, `prop`, `mod`, `cast`, `cnd`, `def`, `init`, `skip`]);
if (`name` in col) {
if (!npm.utils.isText(col.name)) {
throw new TypeError(`Invalid 'name' value: ${npm.utils.toJson(col.name)}. A non-empty string was expected.`);
}
if (npm.utils.isNull(col.prop) && !isValidVariable(col.name)) {
throw new TypeError(`Invalid 'name' syntax: ${npm.utils.toJson(col.name)}.`);
}
this.name = col.name; // column name + property name (if 'prop' isn't specified)
if (!npm.utils.isNull(col.prop)) {
if (!npm.utils.isText(col.prop)) {
throw new TypeError(`Invalid 'prop' value: ${npm.utils.toJson(col.prop)}. A non-empty string was expected.`);
}
if (!isValidVariable(col.prop)) {
throw new TypeError(`Invalid 'prop' syntax: ${npm.utils.toJson(col.prop)}.`);
}
if (col.prop !== col.name) {
// optional property name, if different from the column's name;
this.prop = col.prop;
}
}
if (!npm.utils.isNull(col.mod)) {
if (typeof col.mod !== `string` || !isValidMod(col.mod)) {
throw new TypeError(`Invalid 'mod' value: ${npm.utils.toJson(col.mod)}.`);
}
this.mod = col.mod; // optional format modifier;
}
if (!npm.utils.isNull(col.cast)) {
this.cast = parseCast(col.cast); // optional SQL type casting
}
if (`cnd` in col) {
this.cnd = !!col.cnd;
}
if (`def` in col) {
this.def = col.def; // optional default
}
if (typeof col.init === `function`) {
this.init = col.init; // optional value override (overrides 'def' also)
}
if (typeof col.skip === `function`) {
this.skip = col.skip;
}
} else {
throw new TypeError(`Invalid column details.`);
}
}
const variable = `\${` + (this.prop || this.name) + (this.mod || ``) + `}`;
const castText = this.cast ? (`::` + this.cast) : ``;
const escapedName = npm.formatting.as.name(this.name);
this.extendState({variable, castText, escapedName});
Object.freeze(this);
}
/**
* @name helpers.Column#variable
* @type string
* @readonly
* @description
* Full-syntax formatting variable, ready for direct use in query templates.
*
* @example
*
* const cs = new pgp.helpers.ColumnSet([
* 'id',
* 'coordinate:json',
* {
* name: 'places',
* mod: ':csv',
* cast: 'int[]'
* }
* ]);
*
* // cs.columns[0].variable = ${id}
* // cs.columns[1].variable = ${coordinate:json}
* // cs.columns[2].variable = ${places:csv}::int[]
*/
get variable() {
return this._inner.variable;
}
/**
* @name helpers.Column#castText
* @type string
* @readonly
* @description
* Full-syntax sql type casting, if there is any, or else an empty string.
*/
get castText() {
return this._inner.castText;
}
/**
* @name helpers.Column#escapedName
* @type string
* @readonly
* @description
* Escaped name of the column, ready to be injected into queries directly.
*
*/
get escapedName() {
return this._inner.escapedName;
}
}
function parseCast(name) {
if (typeof name === `string`) {
const s = name.replace(/^[:\s]*|\s*$/g, ``);
if (s) {
return s;
}
}
throw new TypeError(`Invalid 'cast' value: ${npm.utils.toJson(name)}.`);
}
function parseColumn(name) {
const m = name.match(npm.patterns.validColumn);
if (m && m[0] === name) {
const res = {};
if (name[0] === `?`) {
res.cnd = true;
name = name.substr(1);
}
const mod = name.match(npm.patterns.hasValidModifier);
if (mod) {
res.name = name.substr(0, mod.index);
res.mod = mod[0];
} else {
res.name = name;
}
return res;
}
throw new TypeError(`Invalid column syntax: ${npm.utils.toJson(name)}.`);
}
function isValidMod(mod) {
return npm.patterns.validModifiers.indexOf(mod) !== -1;
}
function isValidVariable(name) {
const m = name.match(npm.patterns.validVariable);
return !!m && m[0] === name;
}
/**
* @method helpers.Column#toString
* @description
* Creates a well-formatted multi-line string that represents the object.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
Column.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap0 = npm.utils.messageGap(level),
gap1 = npm.utils.messageGap(level + 1),
lines = [
gap0 + `Column {`,
gap1 + `name: ` + npm.utils.toJson(this.name)
];
if (`prop` in this) {
lines.push(gap1 + `prop: ` + npm.utils.toJson(this.prop));
}
if (`mod` in this) {
lines.push(gap1 + `mod: ` + npm.utils.toJson(this.mod));
}
if (`cast` in this) {
lines.push(gap1 + `cast: ` + npm.utils.toJson(this.cast));
}
if (`cnd` in this) {
lines.push(gap1 + `cnd: ` + npm.utils.toJson(this.cnd));
}
if (`def` in this) {
lines.push(gap1 + `def: ` + npm.utils.toJson(this.def));
}
if (`init` in this) {
lines.push(gap1 + `init: [Function]`);
}
if (`skip` in this) {
lines.push(gap1 + `skip: [Function]`);
}
lines.push(gap0 + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(Column, function () {
return this.toString();
});
/**
* @typedef helpers.ColumnConfig
* @description
* A simple structure with column details, to be passed into the {@link helpers.Column Column} constructor for initialization.
*
* @property {string} name
* Destination column name + source property name (if `prop` is skipped). The name must adhere to JavaScript syntax for variables,
* unless `prop` is specified, in which case `name` represents only the column name, and therefore can be any non-empty string.
*
* @property {string} [prop]
* Source property name, if different from the column's name. It must adhere to JavaScript syntax for variables.
*
* It is ignored when it is the same as `name`.
*
* @property {string} [mod]
* Formatting modifier, as supported by method {@link formatting.format as.format}: `^`, `~`, `#`, `:csv`, `:list`, `:json`, `:alias`, `:name`, `:raw`, `:value`.
*
* @property {string} [cast]
* Server-side type casting. Leading `::` is allowed, but not needed (automatically removed when specified).
*
* @property {boolean} [cnd]
* Conditional column flag.
*
* Used by methods {@link helpers.update update} and {@link helpers.sets sets}, ignored by methods {@link helpers.insert insert} and
* {@link helpers.values values}. It indicates that the column is reserved for a `WHERE` condition, not to be set or updated.
*
* It can be set from a string initialization, by adding `?` in front of the name.
*
* @property {*} [def]
* Default value for the property, to be used only when the source object doesn't have the property.
* It is ignored when property `init` is set.
*
* @property {helpers.initCB} [init]
* Override callback for the value.
*
* @property {helpers.skipCB} [skip]
* An override for skipping columns dynamically.
*
* Used by methods {@link helpers.update update} (for a single object) and {@link helpers.sets sets}, ignored by methods
* {@link helpers.insert insert} and {@link helpers.values values}.
*
* It is also ignored when conditional flag `cnd` is set.
*
*/
/**
* @callback helpers.initCB
* @description
* A callback function type used by parameter `init` within {@link helpers.ColumnConfig ColumnConfig}.
*
* It works as an override for the corresponding property value in the `source` object.
*
* The function is called with `this` set to the `source` object.
*
* @param {*} col
* Column-to-property descriptor.
*
* @param {object} col.source
* The source object, equals to `this` that's passed into the function.
*
* @param {string} col.name
* Resolved name of the property within the `source` object, i.e. the value of `name` when `prop` is not used
* for the column, or the value of `prop` when it is specified.
*
* @param {*} col.value
*
* Property value, set to one of the following:
*
* - Value of the property within the `source` object (`value` = `source[name]`), if the property exists
* - If the property doesn't exist and `def` is set in the column, then `value` is set to the value of `def`
* - If the property doesn't exist and `def` is not set in the column, then `value` is set to `undefined`
*
* @param {boolean} col.exists
* Indicates whether the property exists in the `source` object (`exists = name in source`).
*
* @returns {*}
* The new value to be used for the corresponding column.
*/
/**
* @callback helpers.skipCB
* @description
* A callback function type used by parameter `skip` within {@link helpers.ColumnConfig ColumnConfig}.
*
* It is to dynamically determine when the property with specified `name` in the `source` object is to be skipped.
*
* The function is called with `this` set to the `source` object.
*
* @param {*} col
* Column-to-property descriptor.
*
* @param {object} col.source
* The source object, equals to `this` that's passed into the function.
*
* @param {string} col.name
* Resolved name of the property within the `source` object, i.e. the value of `name` when `prop` is not used
* for the column, or the value of `prop` when it is specified.
*
* @param {*} col.value
*
* Property value, set to one of the following:
*
* - Value of the property within the `source` object (`value` = `source[name]`), if the property exists
* - If the property doesn't exist and `def` is set in the column, then `value` is set to the value of `def`
* - If the property doesn't exist and `def` is not set in the column, then `value` is set to `undefined`
*
* @param {boolean} col.exists
* Indicates whether the property exists in the `source` object (`exists = name in source`).
*
* @returns {boolean}
* A truthy value that indicates whether the column is to be skipped.
*
*/
module.exports = {Column};

View File

@@ -0,0 +1,73 @@
/*
* 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 {Column} = require(`./column`);
const {ColumnSet} = require(`./column-set`);
const {TableName} = require(`./table-name`);
const method = require(`./methods`);
const utils = require(`../utils`);
/**
* @namespace helpers
* @description
* Namespace for query-formatting generators, available as {@link module:pg-promise~helpers pgp.helpers}, after initializing the library.
*
* It unifies the approach to generating multi-row `INSERT` / `UPDATE` queries with the single-row ones.
*
* See also: $[Performance Boost].
*
* @property {function} TableName
* {@link helpers.TableName TableName} class constructor.
*
* @property {function} ColumnSet
* {@link helpers.ColumnSet ColumnSet} class constructor.
*
* @property {function} Column
* {@link helpers.Column Column} class constructor.
*
* @property {function} insert
* {@link helpers.insert insert} static method.
*
* @property {function} update
* {@link helpers.update update} static method.
*
* @property {function} values
* {@link helpers.values values} static method.
*
* @property {function} sets
* {@link helpers.sets sets} static method.
*
* @property {function} concat
* {@link helpers.concat concat} static method.
*/
module.exports = config => {
const capSQL = () => config.options && config.options.capSQL;
const res = {
insert(data, columns, table) {
return method.insert(data, columns, table, capSQL());
},
update(data, columns, table, options) {
return method.update(data, columns, table, options, capSQL());
},
concat(queries) {
return method.concat(queries, capSQL());
},
values(data, columns) {
return method.values(data, columns, capSQL());
},
sets(data, columns) {
return method.sets(data, columns, capSQL());
},
TableName,
ColumnSet,
Column
};
utils.lock(res, true, config.options);
return res;
};

View File

@@ -0,0 +1,103 @@
/*
* 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 {QueryFile} = require(`../../query-file`);
const npm = {
formatting: require(`../../formatting`)
};
/**
* @method helpers.concat
* @description
* Formats and concatenates multiple queries into a single query string.
*
* Before joining the queries, the method does the following:
* - Formats each query, if `values` are provided;
* - Removes all leading and trailing spaces, tabs and semi-colons;
* - Automatically skips all empty queries.
*
* @param {array<string|helpers.QueryFormat|QueryFile>} queries
* Array of mixed-type elements:
* - a simple query string, to be used as is
* - a {@link helpers.QueryFormat QueryFormat}-like object = `{query, [values], [options]}`
* - a {@link QueryFile} object
*
* @returns {string}
* Concatenated string with all queries.
*
* @example
*
* const pgp = require('pg-promise')();
*
* const qf1 = new pgp.QueryFile('./query1.sql', {minify: true});
* const qf2 = new pgp.QueryFile('./query2.sql', {minify: true});
*
* const query = pgp.helpers.concat([
* {query: 'INSERT INTO Users(name, age) VALUES($1, $2)', values: ['John', 23]}, // QueryFormat-like object
* {query: qf1, values: [1, 'Name']}, // QueryFile with formatting parameters
* 'SELECT count(*) FROM Users', // a simple-string query,
* qf2 // direct QueryFile object
* ]);
*
* // query = concatenated string with all the queries
*/
function concat(queries, capSQL) {
if (!Array.isArray(queries)) {
throw new TypeError(`Parameter 'queries' must be an array.`);
}
const fmOptions = {capSQL};
const all = queries.map((q, index) => {
if (typeof q === `string`) {
// a simple query string without parameters:
return clean(q);
}
if (q && typeof q === `object`) {
if (q instanceof QueryFile) {
// QueryFile object:
return clean(q[npm.formatting.as.ctf.toPostgres]());
}
if (`query` in q) {
// object {query, values, options}:
let opt = q.options && typeof q.options === `object` ? q.options : {};
opt = opt.capSQL === undefined ? Object.assign(opt, fmOptions) : opt;
return clean(npm.formatting.as.format(q.query, q.values, opt));
}
}
throw new Error(`Invalid query element at index ${index}.`);
});
return all.filter(q => q).join(`;`);
}
function clean(q) {
// removes from the query all leading and trailing symbols ' ', '\t' and ';'
return q.replace(/^[\s;]*|[\s;]*$/g, ``);
}
module.exports = {concat};
/**
* @typedef helpers.QueryFormat
* @description
* A simple structure of parameters to be passed into method {@link formatting.format as.format} exactly as they are,
* used by {@link helpers.concat}.
*
* @property {string|value|object} query
* A query string or a value/object that implements $[Custom Type Formatting], to be formatted according to `values`.
*
* @property {array|object|value} [values]
* Query-formatting values.
*
* @property {object} [options]
* Query-formatting options, as supported by method {@link formatting.format as.format}.
*
* @see
* {@link formatting.format as.format}
*/

View File

@@ -0,0 +1,13 @@
const {concat} = require(`./concat`);
const {insert} = require(`./insert`);
const {update} = require(`./update`);
const {values} = require(`./values`);
const {sets} = require(`./sets`);
module.exports = {
concat,
insert,
update,
values,
sets
};

View File

@@ -0,0 +1,148 @@
/*
* 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 {TableName} = require(`../table-name`);
const {ColumnSet} = require(`../column-set`);
const npm = {
formatting: require(`../../formatting`),
utils: require(`../../utils`)
};
/**
* @method helpers.insert
* @description
* Generates an `INSERT` query for either one object or an array of objects.
*
* @param {object|object[]} data
* An insert object with properties for insert values, or an array of such objects.
*
* When `data` is not a non-null object and not an array, it will throw {@link external:TypeError TypeError} = `Invalid parameter 'data' specified.`
*
* When `data` is an empty array, it will throw {@link external:TypeError TypeError} = `Cannot generate an INSERT from an empty array.`
*
* When `data` is an array that contains a non-object value, the method will throw {@link external:Error Error} =
* `Invalid insert object at index N.`
*
* @param {array|helpers.Column|helpers.ColumnSet} [columns]
* Set of columns to be inserted.
*
* It is optional when `data` is a single object, and required when `data` is an array of objects. If not specified for an array
* of objects, the method will throw {@link external:TypeError TypeError} = `Parameter 'columns' is required when inserting multiple records.`
*
* When `columns` is not a {@link helpers.ColumnSet ColumnSet} object, a temporary {@link helpers.ColumnSet ColumnSet}
* is created - from the value of `columns` (if it was specified), or from the value of `data` (if it is not an array).
*
* When the final {@link helpers.ColumnSet ColumnSet} is empty (no columns in it), the method will throw
* {@link external:Error Error} = `Cannot generate an INSERT without any columns.`
*
* @param {helpers.TableName|string|{table,schema}} [table]
* Destination table.
*
* It is normally a required parameter. But when `columns` is passed in as a {@link helpers.ColumnSet ColumnSet} object
* with `table` set in it, that will be used when this parameter isn't specified. When neither is available, the method
* will throw {@link external:Error Error} = `Table name is unknown.`
*
* @returns {string}
* An `INSERT` query string.
*
* @see
* {@link helpers.Column Column},
* {@link helpers.ColumnSet ColumnSet},
* {@link helpers.TableName TableName}
*
* @example
*
* const pgp = require('pg-promise')({
* capSQL: true // if you want all generated SQL capitalized
* });
*
* const dataSingle = {val: 123, msg: 'hello'};
* const dataMulti = [{val: 123, msg: 'hello'}, {val: 456, msg: 'world!'}];
*
* // Column details can be taken from the data object:
*
* pgp.helpers.insert(dataSingle, null, 'my-table');
* //=> INSERT INTO "my-table"("val","msg") VALUES(123,'hello')
*
* @example
*
* // Column details are required for a multi-row `INSERT`:
*
* pgp.helpers.insert(dataMulti, ['val', 'msg'], 'my-table');
* //=> INSERT INTO "my-table"("val","msg") VALUES(123,'hello'),(456,'world!')
*
* @example
*
* // Column details from a reusable ColumnSet (recommended for performance):
*
* const cs = new pgp.helpers.ColumnSet(['val', 'msg'], {table: 'my-table'});
*
* pgp.helpers.insert(dataMulti, cs);
* //=> INSERT INTO "my-table"("val","msg") VALUES(123,'hello'),(456,'world!')
*
*/
function insert(data, columns, table, capSQL) {
if (!data || typeof data !== `object`) {
throw new TypeError(`Invalid parameter 'data' specified.`);
}
const isArray = Array.isArray(data);
if (isArray && !data.length) {
throw new TypeError(`Cannot generate an INSERT from an empty array.`);
}
if (columns instanceof ColumnSet) {
if (npm.utils.isNull(table)) {
table = columns.table;
}
} else {
if (isArray && npm.utils.isNull(columns)) {
throw new TypeError(`Parameter 'columns' is required when inserting multiple records.`);
}
columns = new ColumnSet(columns || data);
}
if (!columns.columns.length) {
throw new Error(`Cannot generate an INSERT without any columns.`);
}
if (!table) {
throw new Error(`Table name is unknown.`);
}
if (!(table instanceof TableName)) {
table = new TableName(table);
}
let query = capSQL ? sql.capCase : sql.lowCase;
const fmOptions = {capSQL};
const format = npm.formatting.as.format;
query = format(query, [table.name, columns.names], fmOptions);
if (isArray) {
return query + data.map((d, index) => {
if (!d || typeof d !== `object`) {
throw new Error(`Invalid insert object at index ${index}.`);
}
return `(` + format(columns.variables, columns.prepare(d), fmOptions) + `)`;
}).join();
}
return query + `(` + format(columns.variables, columns.prepare(data), fmOptions) + `)`;
}
const sql = {
lowCase: `insert into $1^($2^) values`,
capCase: `INSERT INTO $1^($2^) VALUES`
};
module.exports = {insert};

View File

@@ -0,0 +1,80 @@
/*
* 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 {ColumnSet} = require(`../column-set`);
const npm = {
format: require(`../../formatting`).as.format,
utils: require(`../../utils`)
};
/**
* @method helpers.sets
* @description
* Generates a string of comma-separated value-set statements from a single object: `col1=val1, col2=val2, ...`,
* to be used as part of a query.
*
* Since it is to be used as part of `UPDATE` queries, {@link helpers.Column Column} properties `cnd` and `skip` apply.
*
* @param {object} data
* A simple, non-null and non-array source object.
*
* If it is anything else, the method will throw {@link external:TypeError TypeError} = `Invalid parameter 'data' specified.`
*
* @param {array|helpers.Column|helpers.ColumnSet} [columns]
* Columns for which to set values.
*
* When not specified, properties of the `data` object are used.
*
* When no effective columns are found, an empty string is returned.
*
* @returns {string}
* - comma-separated value-set statements for the `data` object
* - an empty string, if no effective columns found
*
* @see
* {@link helpers.Column Column},
* {@link helpers.ColumnSet ColumnSet}
*
* @example
*
* const pgp = require('pg-promise')();
*
* const data = {id: 1, val: 123, msg: 'hello'};
*
* // Properties can be pulled automatically from the object:
*
* pgp.helpers.sets(data);
* //=> "id"=1,"val"=123,"msg"='hello'
*
* @example
*
* // Column details from a reusable ColumnSet (recommended for performance);
* // NOTE: Conditional columns (start with '?') are skipped:
*
* const cs = new pgp.helpers.ColumnSet(['?id','val', 'msg']);
*
* pgp.helpers.sets(data, cs);
* //=> "val"=123,"msg"='hello'
*
*/
function sets(data, columns, capSQL) {
if (!data || typeof data !== `object` || Array.isArray(data)) {
throw new TypeError(`Invalid parameter 'data' specified.`);
}
if (!(columns instanceof ColumnSet)) {
columns = new ColumnSet(columns || data);
}
return npm.format(columns.assign({source: data}), columns.prepare(data), {capSQL});
}
module.exports = {sets};

View File

@@ -0,0 +1,245 @@
/*
* 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 {assert} = require(`../../assert`);
const {TableName} = require(`../table-name`);
const {ColumnSet} = require(`../column-set`);
const npm = {
formatting: require(`../../formatting`),
utils: require(`../../utils`)
};
/**
* @method helpers.update
* @description
* Generates a simplified `UPDATE` query for either one object or an array of objects.
*
* The resulting query needs a `WHERE` clause to be appended to it, to specify the update logic.
* This is to allow for update conditions of any complexity that are easy to add.
*
* @param {object|object[]} data
* An update object with properties for update values, or an array of such objects.
*
* When `data` is not a non-null object and not an array, it will throw {@link external:TypeError TypeError} = `Invalid parameter 'data' specified.`
*
* When `data` is an empty array, it will throw {@link external:TypeError TypeError} = `Cannot generate an UPDATE from an empty array.`
*
* When `data` is an array that contains a non-object value, the method will throw {@link external:Error Error} =
* `Invalid update object at index N.`
*
* @param {array|helpers.Column|helpers.ColumnSet} [columns]
* Set of columns to be updated.
*
* It is optional when `data` is a single object, and required when `data` is an array of objects. If not specified for an array
* of objects, the method will throw {@link external:TypeError TypeError} = `Parameter 'columns' is required when updating multiple records.`
*
* When `columns` is not a {@link helpers.ColumnSet ColumnSet} object, a temporary {@link helpers.ColumnSet ColumnSet}
* is created - from the value of `columns` (if it was specified), or from the value of `data` (if it is not an array).
*
* When the final {@link helpers.ColumnSet ColumnSet} is empty (no columns in it), the method will throw
* {@link external:Error Error} = `Cannot generate an UPDATE without any columns.`, unless option `emptyUpdate` was specified.
*
* @param {helpers.TableName|string|{table,schema}} [table]
* Table to be updated.
*
* It is normally a required parameter. But when `columns` is passed in as a {@link helpers.ColumnSet ColumnSet} object
* with `table` set in it, that will be used when this parameter isn't specified. When neither is available, the method
* will throw {@link external:Error Error} = `Table name is unknown.`
*
* @param {{}} [options]
* An object with formatting options for multi-row `UPDATE` queries.
*
* @param {string} [options.tableAlias=t]
* Name of the SQL variable that represents the destination table.
*
* @param {string} [options.valueAlias=v]
* Name of the SQL variable that represents the values.
*
* @param {*} [options.emptyUpdate]
* This is a convenience option, to avoid throwing an error when generating a conditional update results in no columns.
*
* When present, regardless of the value, this option overrides the method's behavior when applying `skip` logic results in no columns,
* i.e. when every column is being skipped.
*
* By default, in that situation the method throws {@link external:Error Error} = `Cannot generate an UPDATE without any columns.`
* But when this option is present, the method will instead return whatever value the option was passed.
*
* @returns {*}
* An `UPDATE` query string that needs a `WHERE` condition appended.
*
* If it results in an empty update, and option `emptyUpdate` was passed in, then the method returns the value
* to which the option was set.
*
* @see
* {@link helpers.Column Column},
* {@link helpers.ColumnSet ColumnSet},
* {@link helpers.TableName TableName}
*
* @example
*
* const pgp = require('pg-promise')({
* capSQL: true // if you want all generated SQL capitalized
* });
*
* const dataSingle = {id: 1, val: 123, msg: 'hello'};
* const dataMulti = [{id: 1, val: 123, msg: 'hello'}, {id: 2, val: 456, msg: 'world!'}];
*
* // Although column details can be taken from the data object, it is not
* // a likely scenario for an update, unless updating the whole table:
*
* pgp.helpers.update(dataSingle, null, 'my-table');
* //=> UPDATE "my-table" SET "id"=1,"val"=123,"msg"='hello'
*
* @example
*
* // A typical single-object update:
*
* // Dynamic conditions must be escaped/formatted properly:
* const condition = pgp.as.format(' WHERE id = ${id}', dataSingle);
*
* pgp.helpers.update(dataSingle, ['val', 'msg'], 'my-table') + condition;
* //=> UPDATE "my-table" SET "val"=123,"msg"='hello' WHERE id = 1
*
* @example
*
* // Column details are required for a multi-row `UPDATE`;
* // Adding '?' in front of a column name means it is only for a WHERE condition:
*
* pgp.helpers.update(dataMulti, ['?id', 'val', 'msg'], 'my-table') + ' WHERE v.id = t.id';
* //=> UPDATE "my-table" AS t SET "val"=v."val","msg"=v."msg" FROM (VALUES(1,123,'hello'),(2,456,'world!'))
* // AS v("id","val","msg") WHERE v.id = t.id
*
* @example
*
* // Column details from a reusable ColumnSet (recommended for performance):
*
* const cs = new pgp.helpers.ColumnSet(['?id', 'val', 'msg'], {table: 'my-table'});
*
* pgp.helpers.update(dataMulti, cs) + ' WHERE v.id = t.id';
* //=> UPDATE "my-table" AS t SET "val"=v."val","msg"=v."msg" FROM (VALUES(1,123,'hello'),(2,456,'world!'))
* // AS v("id","val","msg") WHERE v.id = t.id
*
* @example
*
* // Using parameter `options` to change the default alias names:
*
* pgp.helpers.update(dataMulti, cs, null, {tableAlias: 'X', valueAlias: 'Y'}) + ' WHERE Y.id = X.id';
* //=> UPDATE "my-table" AS X SET "val"=Y."val","msg"=Y."msg" FROM (VALUES(1,123,'hello'),(2,456,'world!'))
* // AS Y("id","val","msg") WHERE Y.id = X.id
*
* @example
*
* // Handling an empty update
*
* const cs = new pgp.helpers.ColumnSet(['?id', '?name'], {table: 'tt'}); // no actual update-able columns
* const result = pgp.helpers.update(dataMulti, cs, null, {emptyUpdate: 123});
* if(result === 123) {
* // We know the update is empty, i.e. no columns that can be updated;
* // And it didn't throw because we specified `emptyUpdate` option.
* }
*/
function update(data, columns, table, options, capSQL) {
if (!data || typeof data !== `object`) {
throw new TypeError(`Invalid parameter 'data' specified.`);
}
const isArray = Array.isArray(data);
if (isArray && !data.length) {
throw new TypeError(`Cannot generate an UPDATE from an empty array.`);
}
if (columns instanceof ColumnSet) {
if (npm.utils.isNull(table)) {
table = columns.table;
}
} else {
if (isArray && npm.utils.isNull(columns)) {
throw new TypeError(`Parameter 'columns' is required when updating multiple records.`);
}
columns = new ColumnSet(columns || data);
}
options = assert(options, [`tableAlias`, `valueAlias`, `emptyUpdate`]);
const format = npm.formatting.as.format,
useEmptyUpdate = `emptyUpdate` in options,
fmOptions = {capSQL};
if (isArray) {
const tableAlias = npm.formatting.as.alias(npm.utils.isNull(options.tableAlias) ? `t` : options.tableAlias);
const valueAlias = npm.formatting.as.alias(npm.utils.isNull(options.valueAlias) ? `v` : options.valueAlias);
const q = capSQL ? sql.multi.capCase : sql.multi.lowCase;
const actualColumns = columns.columns.filter(c => !c.cnd);
if (checkColumns(actualColumns)) {
return options.emptyUpdate;
}
checkTable();
const targetCols = actualColumns.map(c => c.escapedName + `=` + valueAlias + `.` + c.escapedName).join();
const values = data.map((d, index) => {
if (!d || typeof d !== `object`) {
throw new Error(`Invalid update object at index ${index}.`);
}
return `(` + format(columns.variables, columns.prepare(d), fmOptions) + `)`;
}).join();
return format(q, [table.name, tableAlias, targetCols, values, valueAlias, columns.names], fmOptions);
}
const updates = columns.assign({source: data});
if (checkColumns(updates)) {
return options.emptyUpdate;
}
checkTable();
const query = capSQL ? sql.single.capCase : sql.single.lowCase;
return format(query, table.name) + format(updates, columns.prepare(data), fmOptions);
function checkTable() {
if (table && !(table instanceof TableName)) {
table = new TableName(table);
}
if (!table) {
throw new Error(`Table name is unknown.`);
}
}
function checkColumns(cols) {
if (!cols.length) {
if (useEmptyUpdate) {
return true;
}
throw new Error(`Cannot generate an UPDATE without any columns.`);
}
}
}
const sql = {
single: {
lowCase: `update $1^ set `,
capCase: `UPDATE $1^ SET `
},
multi: {
lowCase: `update $1^ as $2^ set $3^ from (values$4^) as $5^($6^)`,
capCase: `UPDATE $1^ AS $2^ SET $3^ FROM (VALUES$4^) AS $5^($6^)`
}
};
module.exports = {update};

View File

@@ -0,0 +1,115 @@
/*
* 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 {ColumnSet} = require(`../column-set`);
const npm = {
formatting: require(`../../formatting`),
utils: require(`../../utils`)
};
/**
* @method helpers.values
* @description
* Generates a string of comma-separated value groups from either one object or an array of objects,
* to be used as part of a query:
*
* - from a single object: `(val_1, val_2, ...)`
* - from an array of objects: `(val_11, val_12, ...), (val_21, val_22, ...)`
*
* @param {object|object[]} data
* A source object with properties as values, or an array of such objects.
*
* If it is anything else, the method will throw {@link external:TypeError TypeError} = `Invalid parameter 'data' specified.`
*
* When `data` is an array that contains a non-object value, the method will throw {@link external:Error Error} =
* `Invalid object at index N.`
*
* When `data` is an empty array, an empty string is returned.
*
* @param {array|helpers.Column|helpers.ColumnSet} [columns]
* Columns for which to return values.
*
* It is optional when `data` is a single object, and required when `data` is an array of objects. If not specified for an array
* of objects, the method will throw {@link external:TypeError TypeError} = `Parameter 'columns' is required when generating multi-row values.`
*
* When the final {@link helpers.ColumnSet ColumnSet} is empty (no columns in it), the method will throw
* {@link external:Error Error} = `Cannot generate values without any columns.`
*
* @returns {string}
* - comma-separated value groups, according to `data`
* - an empty string, if `data` is an empty array
*
* @see
* {@link helpers.Column Column},
* {@link helpers.ColumnSet ColumnSet}
*
* @example
*
* const pgp = require('pg-promise')();
*
* const dataSingle = {val: 123, msg: 'hello'};
* const dataMulti = [{val: 123, msg: 'hello'}, {val: 456, msg: 'world!'}];
*
* // Properties can be pulled automatically from a single object:
*
* pgp.helpers.values(dataSingle);
* //=> (123,'hello')
*
* @example
*
* // Column details are required when using an array of objects:
*
* pgp.helpers.values(dataMulti, ['val', 'msg']);
* //=> (123,'hello'),(456,'world!')
*
* @example
*
* // Column details from a reusable ColumnSet (recommended for performance):
*
* const cs = new pgp.helpers.ColumnSet(['val', 'msg']);
*
* pgp.helpers.values(dataMulti, cs);
* //=> (123,'hello'),(456,'world!')
*
*/
function values(data, columns, capSQL) {
if (!data || typeof data !== `object`) {
throw new TypeError(`Invalid parameter 'data' specified.`);
}
const isArray = Array.isArray(data);
if (!(columns instanceof ColumnSet)) {
if (isArray && npm.utils.isNull(columns)) {
throw new TypeError(`Parameter 'columns' is required when generating multi-row values.`);
}
columns = new ColumnSet(columns || data);
}
if (!columns.columns.length) {
throw new Error(`Cannot generate values without any columns.`);
}
const format = npm.formatting.as.format,
fmOptions = {capSQL};
if (isArray) {
return data.map((d, index) => {
if (!d || typeof d !== `object`) {
throw new Error(`Invalid object at index ${index}.`);
}
return `(` + format(columns.variables, columns.prepare(d), fmOptions) + `)`;
}).join();
}
return `(` + format(columns.variables, columns.prepare(data), fmOptions) + `)`;
}
module.exports = {values};

View File

@@ -0,0 +1,128 @@
/*
* 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 {assert} = require(`../assert`);
const npm = {
utils: require(`../utils`),
format: require(`../formatting`).as // formatting namespace
};
/**
* @class helpers.TableName
* @description
* Represents a full table name that can be injected into queries directly.
*
* This is a read-only type that can be used wherever parameter `table` is supported.
*
* It supports $[Custom Type Formatting], which means you can use the type directly as a formatting
* parameter, without specifying any escaping.
*
* Filter `:alias` is an alternative approach to splitting an SQL name into multiple ones.
*
* @param {string|object} table
* Table name details, depending on the type:
*
* - table name, if `table` is a string
* - object `{table, [schema]}`
*
* @property {string} name
* Formatted/escaped full table name, combining `schema` + `table`.
*
* @property {string} table
* Table name.
*
* @property {string} schema
* Database schema name.
*
* It is `undefined` when no valid schema was specified.
*
* @returns {helpers.TableName}
*
* @see
* {@link helpers.TableName#toPostgres toPostgres}
*
* @example
*
* const table = new pgp.helpers.TableName({table: 'my-table', schema: 'my-schema'});
* console.log(table);
* //=> "my-schema"."my-table"
*
* // Formatting the type directly:
* pgp.as.format('SELECT * FROM $1', table);
* //=> SELECT * FROM "my-schema"."my-table"
*
*/
class TableName {
constructor(table) {
if (typeof table === `string`) {
this.table = table;
} else {
const config = assert(table, [`table`, `schema`]);
this.table = config.table;
if (npm.utils.isText(config.schema)) {
this.schema = config.schema;
}
}
if (!npm.utils.isText(this.table)) {
throw new TypeError(`Table name must be a non-empty text string.`);
}
this.name = npm.format.name(this.table);
if (this.schema) {
this.name = npm.format.name(this.schema) + `.` + this.name;
}
Object.freeze(this);
}
}
/**
* @method helpers.TableName#toPostgres
* @description
* $[Custom Type Formatting], based on $[Symbolic CTF], i.e. the actual method is available only via {@link external:Symbol Symbol}:
*
* ```js
* const ctf = pgp.as.ctf; // Custom Type Formatting symbols namespace
* const fullName = tn[ctf.toPostgres]; // tn = an object of type TableName
* ```
*
* This is a raw formatting type (`rawType = true`), i.e. when used as a query-formatting parameter, type `TableName`
* injects full table name as raw text.
*
* @param {helpers.TableName} [self]
* Optional self-reference, for ES6 arrow functions.
*
* @returns {string}
* Escaped full table name that includes optional schema name, if specified.
*/
TableName.prototype[npm.format.ctf.toPostgres] = function (self) {
self = this instanceof TableName && this || self;
return self.name;
};
TableName.prototype[npm.format.ctf.rawType] = true; // use as pre-formatted
/**
* @method helpers.TableName#toString
* @description
* Creates a well-formatted string that represents the object.
*
* It is called automatically when writing the object into the console.
*
* @returns {string}
*/
TableName.prototype.toString = function () {
return this.name;
};
npm.utils.addInspection(TableName, function () {
return this.toString();
});
module.exports = {TableName};

27
ProjectSourceCode/node_modules/pg-promise/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
/* eslint no-var: off */
var v = process.versions.node.split(`.`),
highVer = +v[0];
// istanbul ignore next
if (highVer < 12) {
// From pg-promise v10.15.0, the oldest supported Node.js is v12.0.0
// Node.js v8.0.0 was supported up to pg-promise v10.14.2
// Node.js v7.6.0 was supported up to pg-promise v10.3.5
// Node.js v4.5.0 was supported up to pg-promise v8.7.5
// Node.js v0.10 was supported up to pg-promise v5.5.8
throw new Error(`Minimum Node.js version supported by pg-promise is 12.0.0`);
}
module.exports = require(`./main`);

View File

@@ -0,0 +1,39 @@
const {addReadProp} = require(`./utils`);
/**
* @private
* @class InnerState
* @description
* Implements support for private/inner state object inside the class,
* which can be accessed by a derived class via hidden read-only property _inner.
*/
class InnerState {
constructor(initialState) {
addReadProp(this, `_inner`, {}, true);
if (initialState && typeof initialState === `object`) {
this.extendState(initialState);
}
}
/**
* Extends or overrides inner state with the specified properties.
*
* Only own properties are used, i.e. inherited ones are skipped.
*/
extendState(state) {
for (const a in state) {
// istanbul ignore else
if (Object.prototype.hasOwnProperty.call(state, a)) {
this._inner[a] = state[a];
}
}
}
}
/**
* @member InnerState#_inner
* Private/Inner object state.
*/
module.exports = {InnerState};

443
ProjectSourceCode/node_modules/pg-promise/lib/main.js generated vendored Normal file
View File

@@ -0,0 +1,443 @@
/*
* 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 {PromiseAdapter} = require(`./promise-adapter`);
const {DatabasePool} = require(`./database-pool`);
const {PreparedStatement, ParameterizedQuery} = require(`./types`);
const {QueryFile} = require(`./query-file`);
const {queryResult} = require(`./query-result`);
const {parsePromise} = require(`./promise-parser`);
const {assert} = require(`./assert`);
const npm = {
path: require(`path`),
pg: require(`pg`),
minify: require(`pg-minify`),
formatting: require(`./formatting`),
helpers: require(`./helpers`),
errors: require(`./errors`),
utils: require(`./utils`),
pubUtils: require(`./utils/public`),
mode: require(`./tx-mode`),
package: require(`../package.json`),
text: require(`./text`)
};
let originalClientConnect;
/**
* @author Vitaly Tomilov
* @module pg-promise
*
* @description
* ## pg-promise v10.15
* All documentation here is for the latest official release only.
*
* ### Initialization Options
*
* Below is the complete list of _Initialization Options_ for the library that can be passed in during
* the library's initialization:
*
* ```js
* const initOptions = {&#47;* options as documented below *&#47;};
*
* const pgp = require('pg-promise')(initOptions);
* ```
*
* @param {{}} [options]
* Library Initialization Options.
*
* @param {boolean} [options.pgFormatting=false]
* Redirects all query formatting to the $[pg] driver.
*
* By default (`false`), the library uses its own advanced query-formatting engine.
* If you set this option to a truthy value, query formatting will be done entirely by the
* $[pg] driver, which means you won't be able to use any of the feature-rich query formatting
* that this library implements, restricting yourself to the very basic `$1, $2,...` syntax.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {boolean} [options.pgNative=false]
* Use $[Native Bindings]. Library $[pg-native] must be included and installed independently, or else there will
* be an error thrown: {@link external:Error Error} = `Failed to initialize Native Bindings.`
*
* This is a static option (can only be set prior to initialization).
*
* @param {object|function} [options.promiseLib=Promise]
* Overrides the default (ES6 Promise) promise library for its internal use.
*
* Example below sets to use $[Bluebird] - the best and recommended promise library. It is the fastest one,
* and supports $[Long Stack Traces], essential for debugging promises.
*
* ```js
* const Promise = require('bluebird');
* const initOptions = {
* promiseLib: Promise
* };
* const pgp = require('pg-promise')(initOptions);
* ```
*
* All existing promise libraries are supported. The ones with recognizable signature are used automatically,
* while the rest can be configured via the $[Promise Adapter].
*
* This is a static option (can only be set prior to initialization).
*
* @param {boolean} [options.noLocking=false]
* Prevents protocol locking.
*
* By default, the library locks much of its protocol to read-only access, as a fool-proof mechanism.
* Specifically for the {@link event:extend extend} event this serves as a protection against overriding existing
* properties or trying to set them at the wrong time.
*
* If this provision gets in the way of using a mock-up framework for your tests, you can force
* the library to deactivate most of the locks by setting `noLocking` = `true` within the options.
*
* This option is dynamic (can be set before or after initialization). However, changing it after the library's
* initialization will not affect {@link Database} objects that have already been created.
*
* @param {boolean} [options.capSQL=false]
* Capitalizes any SQL generated by the library.
*
* By default, all internal SQL within the library is generated using the low case.
* If, however, you want all SQL to be capitalized instead, set `capSQL` = `true`.
*
* It is purely a cosmetic feature.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {string|Array<string>|null|undefined|function} [options.schema]
* Forces change of the default database schema(s) for every fresh connection, i.e.
* the library will execute `SET search_path TO schema_1, schema_2, ...` in the background
* whenever a fresh physical connection is allocated.
*
* Normally, one changes the default schema(s) by $[changing the database or the role], but sometimes you
* may want to switch the default schema(s) without persisting the change, and then use this option.
*
* It can be a string, an array of strings, or a callback function that takes `dc` (database context)
* as the only parameter (and as `this`), and returns schema(s) according to the database context. A callback function
* can also return nothing (`undefined` or `null`), if no schema change needed for the specified database context.
*
* The order of schema names matters, so if a table name exists in more than one schema, its default access resolves
* to the table from the first such schema on the list.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {boolean} [options.noWarnings=false]
* Disables all diagnostic warnings in the library (it is ill-advised).
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.connect]
* Global event {@link event:connect connect} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.disconnect]
* Global event {@link event:disconnect disconnect} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.query]
* Global event {@link event:query query} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.receive]
* Global event {@link event:receive receive} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.task]
* Global event {@link event:task task} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.transact]
* Global event {@link event:transact transact} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.error]
* Global event {@link event:error error} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @param {function} [options.extend]
* Global event {@link event:extend extend} handler.
*
* This option is dynamic (can be set before or after initialization).
*
* @see
* {@link module:pg-promise~end end},
* {@link module:pg-promise~as as},
* {@link module:pg-promise~errors errors},
* {@link module:pg-promise~helpers helpers},
* {@link module:pg-promise~minify minify},
* {@link module:pg-promise~ParameterizedQuery ParameterizedQuery},
* {@link module:pg-promise~PreparedStatement PreparedStatement},
* {@link module:pg-promise~pg pg},
* {@link module:pg-promise~QueryFile QueryFile},
* {@link module:pg-promise~queryResult queryResult},
* {@link module:pg-promise~spex spex},
* {@link module:pg-promise~txMode txMode},
* {@link module:pg-promise~utils utils}
*
*/
function $main(options) {
options = assert(options, [`pgFormatting`, `pgNative`, `promiseLib`, `noLocking`, `capSQL`, `noWarnings`,
`connect`, `disconnect`, `query`, `receive`, `task`, `transact`, `error`, `extend`, `schema`]);
let pg = npm.pg;
const p = parsePromise(options.promiseLib);
const config = {
version: npm.package.version,
promiseLib: p.promiseLib,
promise: p.promise
};
npm.utils.addReadProp(config, `$npm`, {}, true);
// Locking properties that cannot be changed later:
npm.utils.addReadProp(options, `promiseLib`, options.promiseLib);
npm.utils.addReadProp(options, `pgNative`, !!options.pgNative);
config.options = options;
// istanbul ignore next:
// we do not cover code specific to Native Bindings
if (options.pgNative) {
pg = npm.pg.native;
if (npm.utils.isNull(pg)) {
throw new Error(npm.text.nativeError);
}
} else {
if (!originalClientConnect) {
originalClientConnect = pg.Client.prototype.connect;
pg.Client.prototype.connect = function () {
const handler = msg => {
if (msg.parameterName === `server_version`) {
this.serverVersion = msg.parameterValue;
this.connection.removeListener(`parameterStatus`, handler);
}
};
this.connection.on(`parameterStatus`, handler);
return originalClientConnect.call(this, ...arguments);
};
}
}
const Database = require(`./database`)(config);
const inst = (cn, dc) => {
if (npm.utils.isText(cn) || (cn && typeof cn === `object`)) {
return new Database(cn, dc, config);
}
throw new TypeError(`Invalid connection details: ` + npm.utils.toJson(cn));
};
npm.utils.addReadProperties(inst, rootNameSpace);
/**
* @member {external:PG} pg
* @description
* Instance of the $[pg] library that's being used, depending on initialization option `pgNative`:
* - regular `pg` module instance, without option `pgNative`, or equal to `false` (default)
* - `pg` module instance with $[Native Bindings], if option `pgNative` was set.
*
* Available as `pgp.pg`, after initializing the library.
*/
inst.pg = pg; // keep it modifiable, so the protocol can be mocked
/**
* @member {function} end
* @readonly
* @description
* Shuts down all connection pools created in the process, so it can terminate without delay.
* It is available as `pgp.end`, after initializing the library.
*
* All {@link Database} objects created previously can no longer be used, and their query methods will be rejecting
* with {@link external:Error Error} = `Connection pool of the database object has been destroyed.`
*
* And if you want to shut down only a specific connection pool, you do so via the {@link Database}
* object that owns the pool: `db.$pool.end()` (see {@link Database#$pool Database.$pool}).
*
* For more details see $[Library de-initialization].
*/
npm.utils.addReadProp(inst, `end`, () => {
DatabasePool.shutDown();
});
/**
* @member {helpers} helpers
* @readonly
* @description
* Namespace for {@link helpers all query-formatting helper functions}.
*
* Available as `pgp.helpers`, after initializing the library.
*
* @see {@link helpers}.
*/
npm.utils.addReadProp(inst, `helpers`, npm.helpers(config));
/**
* @member {external:spex} spex
* @readonly
* @description
* Initialized instance of the $[spex] module, used by the library within tasks and transactions.
*
* Available as `pgp.spex`, after initializing the library.
*
* @see
* {@link Task#batch},
* {@link Task#page},
* {@link Task#sequence}
*/
npm.utils.addReadProp(inst, `spex`, config.$npm.spex);
config.pgp = inst;
npm.utils.lock(config, true, options);
return inst;
}
const rootNameSpace = {
/**
* @member {formatting} as
* @readonly
* @description
* Namespace for {@link formatting all query-formatting functions}.
*
* Available as `pgp.as`, before and after initializing the library.
*
* @see {@link formatting}.
*/
as: npm.formatting.as,
/**
* @member {external:pg-minify} minify
* @readonly
* @description
* Instance of the $[pg-minify] library used internally to minify SQL scripts.
*
* Available as `pgp.minify`, before and after initializing the library.
*/
minify: npm.minify,
/**
* @member {queryResult} queryResult
* @readonly
* @description
* Query Result Mask enumerator.
*
* Available as `pgp.queryResult`, before and after initializing the library.
*/
queryResult,
/**
* @member {PromiseAdapter} PromiseAdapter
* @readonly
* @description
* {@link PromiseAdapter} class.
*
* Available as `pgp.PromiseAdapter`, before and after initializing the library.
*/
PromiseAdapter,
/**
* @member {ParameterizedQuery} ParameterizedQuery
* @readonly
* @description
* {@link ParameterizedQuery} class.
*
* Available as `pgp.ParameterizedQuery`, before and after initializing the library.
*/
ParameterizedQuery,
/**
* @member {PreparedStatement} PreparedStatement
* @readonly
* @description
* {@link PreparedStatement} class.
*
* Available as `pgp.PreparedStatement`, before and after initializing the library.
*/
PreparedStatement,
/**
* @member {QueryFile} QueryFile
* @readonly
* @description
* {@link QueryFile} class.
*
* Available as `pgp.QueryFile`, before and after initializing the library.
*/
QueryFile,
/**
* @member {errors} errors
* @readonly
* @description
* {@link errors} - namespace for all error types.
*
* Available as `pgp.errors`, before and after initializing the library.
*/
errors: npm.errors,
/**
* @member {utils} utils
* @readonly
* @description
* {@link utils} - namespace for utility functions.
*
* Available as `pgp.utils`, before and after initializing the library.
*/
utils: npm.pubUtils,
/**
* @member {txMode} txMode
* @readonly
* @description
* {@link txMode Transaction Mode} namespace.
*
* Available as `pgp.txMode`, before and after initializing the library.
*/
txMode: npm.mode
};
npm.utils.addReadProperties($main, rootNameSpace);
module.exports = $main;
/**
* @external Promise
* @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
*/
/**
* @external PG
* @see https://node-postgres.com
*/
/**
* @external Client
* @see https://node-postgres.com/api/client
*/
/**
* @external pg-minify
* @see https://github.com/vitaly-t/pg-minify
*/
/**
* @external spex
* @see https://github.com/vitaly-t/spex
*/

View File

@@ -0,0 +1,43 @@
/*
* 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.
*/
/*
The most important regular expressions and data as used by the library,
isolated here to help with possible edge cases during integration.
*/
module.exports = {
// Searches for all Named Parameters, supporting any of the following syntax:
// ${propName}, $(propName), $[propName], $/propName/, $<propName>
// Nested property names are also supported: ${propName.abc}
namedParameters: /\$(?:({)|(\()|(<)|(\[)|(\/))\s*[a-zA-Z0-9$_.]+(\^|~|#|:raw|:alias|:name|:json|:csv|:list|:value)?\s*(?:(?=\2)(?=\3)(?=\4)(?=\5)}|(?=\1)(?=\3)(?=\4)(?=\5)\)|(?=\1)(?=\2)(?=\4)(?=\5)>|(?=\1)(?=\2)(?=\3)(?=\5)]|(?=\1)(?=\2)(?=\3)(?=\4)\/)/g,
// Searches for all variables $1, $2, ...$100000, and while it will find greater than $100000
// variables, the formatting engine is expected to throw an error for those.
multipleValues: /\$([1-9][0-9]{0,16}(?![0-9])(\^|~|#|:raw|:alias|:name|:json|:csv|:list|:value)?)/g,
// Searches for all occurrences of variable $1
singleValue: /\$1(?![0-9])(\^|~|#|:raw|:alias|:name|:json|:csv|:list|:value)?/g,
// Matches a valid column name for the Column type parser, according to the following rules:
// - can contain: any combination of a-z, A-Z, 0-9, $ or _
// - can contain ? at the start
// - can contain one of the supported filters/modifiers
validColumn: /\??[a-zA-Z0-9$_]+(\^|~|#|:raw|:alias|:name|:json|:csv|:list|:value)?/,
// Matches a valid open-name JavaScript variable, according to the following rules:
// - can contain: any combination of a-z, A-Z, 0-9, $ or _
validVariable: /[a-zA-Z0-9$_]+/,
// Matches a valid modifier in a column/property:
hasValidModifier: /\^|~|#|:raw|:alias|:name|:json|:csv|:list|:value/,
// List of all supported formatting modifiers:
validModifiers: [`^`, `~`, `#`, `:raw`, `:alias`, `:name`, `:json`, `:csv`, `:list`, `:value`]
};

View File

@@ -0,0 +1,86 @@
/*
* 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 {assert} = require(`./assert`);
/**
* @class PromiseAdapter
* @summary Adapter for the primary promise operations.
* @description
* Provides compatibility with promise libraries that cannot be recognized automatically,
* via functions that implement the primary operations with promises:
*
* - construct a new promise with a callback function
* - resolve a promise with some result data
* - reject a promise with a reason
* - resolve an array of promises
*
* The type is available from the library's root: `pgp.PromiseAdapter`.
*
* @param {object} api
* Promise API configuration object.
*
* Passing in anything other than an object will throw {@link external:TypeError TypeError} = `Adapter requires an api configuration object.`
*
* @param {function} api.create
* A function that takes a callback parameter and returns a new promise object.
* The callback parameter is expected to be `function(resolve, reject)`.
*
* Passing in anything other than a function will throw {@link external:TypeError TypeError} = `Function 'create' must be specified.`
*
* @param {function} api.resolve
* A function that takes an optional data parameter and resolves a promise with it.
*
* Passing in anything other than a function will throw {@link external:TypeError TypeError} = `Function 'resolve' must be specified.`
*
* @param {function} api.reject
* A function that takes an optional error parameter and rejects a promise with it.
*
* Passing in anything other than a function will throw {@link external:TypeError TypeError} = `Function 'reject' must be specified.`
*
* @param {function} api.all
* A function that resolves an array of promises.
*
* Passing in anything other than a function will throw {@link external:TypeError TypeError} = `Function 'all' must be specified.`
*
* @returns {PromiseAdapter}
*/
class PromiseAdapter {
constructor(api) {
if (!api || typeof api !== `object`) {
throw new TypeError(`Adapter requires an api configuration object.`);
}
api = assert(api, [`create`, `resolve`, `reject`, `all`]);
this.create = api.create;
this.resolve = api.resolve;
this.reject = api.reject;
this.all = api.all;
if (typeof this.create !== `function`) {
throw new TypeError(`Function 'create' must be specified.`);
}
if (typeof this.resolve !== `function`) {
throw new TypeError(`Function 'resolve' must be specified.`);
}
if (typeof this.reject !== `function`) {
throw new TypeError(`Function 'reject' must be specified.`);
}
if (typeof this.all !== `function`) {
throw new TypeError(`Function 'all' must be specified.`);
}
}
}
module.exports = {PromiseAdapter};

View File

@@ -0,0 +1,56 @@
/*
* 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 {PromiseAdapter} = require(`./promise-adapter`);
//////////////////////////////////////////
// Parses and validates a promise library;
function parse(pl) {
let promise;
if (pl instanceof PromiseAdapter) {
promise = function (func) {
return pl.create(func);
};
promise.resolve = pl.resolve;
promise.reject = pl.reject;
promise.all = pl.all;
return promise;
}
const t = typeof pl;
if (t === `function` || t === `object`) {
const Root = typeof pl.Promise === `function` ? pl.Promise : pl;
promise = function (func) {
return new Root(func);
};
promise.resolve = Root.resolve;
promise.reject = Root.reject;
promise.all = Root.all;
if (typeof promise.resolve === `function` &&
typeof promise.reject === `function` &&
typeof promise.all === `function`) {
return promise;
}
}
throw new TypeError(`Invalid promise library specified.`);
}
function parsePromise(promiseLib) {
const result = {promiseLib};
if (promiseLib) {
result.promise = parse(promiseLib);
} else {
result.promise = parse(Promise);
result.promiseLib = Promise;
}
return result;
}
module.exports = {parsePromise};

View File

@@ -0,0 +1,398 @@
/*
* 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 {InnerState} = require(`./inner-state`);
const {QueryFileError} = require(`./errors`);
const {assert} = require(`./assert`);
const {ColorConsole} = require(`./utils/color`);
const npm = {
fs: require(`fs`),
os: require(`os`),
path: require(`path`),
minify: require(`pg-minify`),
utils: require(`./utils`),
formatting: require(`./formatting`)
};
const file$query = Symbol(`QueryFile.query`);
/**
* @class QueryFile
* @description
*
* Represents an external SQL file. The type is available from the library's root: `pgp.QueryFile`.
*
* Reads a file with SQL and prepares it for execution, also parses and minifies it, if required.
* The SQL can be of any complexity, with both single and multi-line comments.
*
* The type can be used in place of the `query` parameter, with any query method directly, plus as `text` in {@link PreparedStatement}
* and {@link ParameterizedQuery}.
*
* It never throws any error, leaving it for query methods to reject with {@link errors.QueryFileError QueryFileError}.
*
* **IMPORTANT:** You should only create a single reusable object per file, in order to avoid repeated file reads,
* as the IO is a very expensive resource. If you do not follow it, you will be seeing the following warning:
* `Creating a duplicate QueryFile object for the same file`, which signals a bad-use pattern.
*
* @param {string} file
* Path to the SQL file with the query, either absolute or relative to the application's entry point file.
*
* If there is any problem reading the file, it will be reported when executing the query.
*
* @param {QueryFile.Options} [options]
* Set of configuration options, as documented by {@link QueryFile.Options}.
*
* @returns {QueryFile}
*
* @see
* {@link errors.QueryFileError QueryFileError},
* {@link QueryFile#toPostgres toPostgres}
*
* @example
* // File sql.js
*
* // Proper way to organize an sql provider:
* //
* // - have all sql files for Users in ./sql/users
* // - have all sql files for Products in ./sql/products
* // - have your sql provider module as ./sql/index.js
*
* const {QueryFile} = require('pg-promise');
* const {join: joinPath} = require('path');
*
* // Helper for linking to external query files:
* function sql(file) {
* const fullPath = joinPath(__dirname, file); // generating full path;
* return new QueryFile(fullPath, {minify: true});
* }
*
* module.exports = {
* // external queries for Users:
* users: {
* add: sql('users/create.sql'),
* search: sql('users/search.sql'),
* report: sql('users/report.sql'),
* },
* // external queries for Products:
* products: {
* add: sql('products/add.sql'),
* quote: sql('products/quote.sql'),
* search: sql('products/search.sql'),
* }
* };
*
* @example
* // Testing our SQL provider
*
* const db = require('./db'); // our database module;
* const {users: sql} = require('./sql'); // sql for users;
*
* module.exports = {
* addUser: (name, age) => db.none(sql.add, [name, age]),
* findUser: name => db.any(sql.search, name)
* };
*
*/
class QueryFile extends InnerState {
constructor(file, options) {
let filePath = file;
options = assert(options, {
debug: npm.utils.isDev(),
minify: (options && options.compress && options.minify === undefined) ? true : undefined,
compress: undefined,
params: undefined,
noWarnings: undefined
});
if (npm.utils.isText(filePath) && !npm.path.isAbsolute(filePath)) {
filePath = npm.path.join(npm.utils.startDir, filePath);
}
const {usedPath} = QueryFile.instance;
// istanbul ignore next:
if (!options.noWarnings) {
if (filePath in usedPath) {
usedPath[filePath]++;
ColorConsole.warn(`WARNING: Creating a duplicate QueryFile object for the same file - \n ${filePath}\n${npm.utils.getLocalStack(2, 3)}\n`);
} else {
usedPath[filePath] = 0;
}
}
const _inner = {
file,
filePath,
options,
sql: undefined,
error: undefined,
ready: undefined,
modTime: undefined
};
super(_inner);
this.prepare();
}
/**
* Global instance of the file-path repository.
*
* @return {{usedPath: {}}}
*/
static get instance() {
const s = Symbol.for(`pgPromiseQueryFile`);
let scope = global[s];
if (!scope) {
scope = {
usedPath: {} // used-path look-up dictionary
};
global[s] = scope;
}
return scope;
}
/**
* @name QueryFile#Symbol(QueryFile.$query)
* @type {string}
* @default undefined
* @readonly
* @private
* @summary Prepared query string.
* @description
* When property {@link QueryFile#error error} is set, the query is `undefined`.
*
* **IMPORTANT:** This property is for internal use by the library only, never use this
* property directly from your code.
*/
get [file$query]() {
return this._inner.sql;
}
/**
* @name QueryFile#error
* @type {errors.QueryFileError}
* @default undefined
* @readonly
* @description
* When in an error state, it is set to a {@link errors.QueryFileError QueryFileError} object. Otherwise, it is `undefined`.
*/
get error() {
return this._inner.error;
}
/**
* @name QueryFile#file
* @type {string}
* @readonly
* @description
* File name that was passed into the constructor.
*
* This property is primarily for internal use by the library.
*/
get file() {
return this._inner.file;
}
/**
* @name QueryFile#options
* @type {QueryFile.Options}
* @readonly
* @description
* Set of options, as configured during the object's construction.
*
* This property is primarily for internal use by the library.
*/
get options() {
return this._inner.options;
}
/**
* @summary Prepares the query for execution.
* @description
* If the query hasn't been prepared yet, it will read the file and process the content according
* to the parameters passed into the constructor.
*
* This method is primarily for internal use by the library.
*
* @param {boolean} [throwErrors=false]
* Throw any error encountered.
*/
prepare(throwErrors) {
const i = this._inner, options = i.options;
let lastMod;
if (options.debug && i.ready) {
try {
lastMod = npm.fs.statSync(i.filePath).mtime.getTime();
// istanbul ignore if;
if (lastMod === i.modTime) {
return;
}
i.ready = false;
} catch (e) {
i.sql = undefined;
i.ready = false;
i.error = e;
if (throwErrors) {
throw i.error;
}
return;
}
}
if (i.ready) {
return;
}
try {
i.sql = npm.fs.readFileSync(i.filePath, `utf8`);
i.modTime = lastMod || npm.fs.statSync(i.filePath).mtime.getTime();
if (options.minify && options.minify !== `after`) {
i.sql = npm.minify(i.sql, {compress: options.compress});
}
if (options.params !== undefined) {
i.sql = npm.formatting.as.format(i.sql, options.params, {partial: true});
}
if (options.minify && options.minify === `after`) {
i.sql = npm.minify(i.sql, {compress: options.compress});
}
i.ready = true;
i.error = undefined;
} catch (e) {
i.sql = undefined;
i.error = new QueryFileError(e, this);
if (throwErrors) {
throw i.error;
}
}
}
}
// Hiding the query as a symbol within the type,
// to make it even more difficult to misuse it:
QueryFile.$query = file$query;
/**
* @method QueryFile#toPostgres
* @description
* $[Custom Type Formatting], based on $[Symbolic CTF], i.e. the actual method is available only via {@link external:Symbol Symbol}:
*
* ```js
* const ctf = pgp.as.ctf; // Custom Type Formatting symbols namespace
* const query = qf[ctf.toPostgres](); // qf = an object of type QueryFile
* ```
*
* This is a raw formatting type (`rawType = true`), i.e. when used as a query-formatting parameter, type `QueryFile` injects SQL as raw text.
*
* If you need to support type `QueryFile` outside of query methods, this is the only safe way to get the most current SQL.
* And you would want to use this method dynamically, as it reloads the SQL automatically, if option `debug` is set.
* See {@link QueryFile.Options Options}.
*
* @param {QueryFile} [self]
* Optional self-reference, for ES6 arrow functions.
*
* @returns {string}
* SQL string from the file, according to the {@link QueryFile.Options options} specified.
*
*/
QueryFile.prototype[npm.formatting.as.ctf.toPostgres] = function (self) {
self = this instanceof QueryFile && this || self;
self.prepare(true);
return self[QueryFile.$query];
};
QueryFile.prototype[npm.formatting.as.ctf.rawType] = true; // use as pre-formatted
/**
* @method QueryFile#toString
* @description
* Creates a well-formatted multi-line string that represents the object's current state.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
QueryFile.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap = npm.utils.messageGap(level + 1);
const lines = [
`QueryFile {`
];
this.prepare();
lines.push(gap + `file: "` + this.file + `"`);
lines.push(gap + `options: ` + npm.utils.toJson(this.options));
if (this.error) {
lines.push(gap + `error: ` + this.error.toString(level + 1));
} else {
lines.push(gap + `query: "` + this[QueryFile.$query] + `"`);
}
lines.push(npm.utils.messageGap(level) + `}`);
return lines.join(npm.os.EOL);
};
npm.utils.addInspection(QueryFile, function () {
return this.toString();
});
module.exports = {QueryFile};
/**
* @typedef QueryFile.Options
* @description
* A set of configuration options as passed into the {@link QueryFile} constructor.
*
* @property {boolean} debug
* When in debug mode, the query file is checked for its last modification time on every query request,
* so if it changes, the file is read afresh.
*
* The default for this property is `true` when `NODE_ENV` = `development`,
* or `false` otherwise.
*
* @property {boolean|string} minify=false
* Parses and minifies the SQL using $[pg-minify]:
* - `false` - do not use $[pg-minify]
* - `true` - use $[pg-minify] to parse and minify SQL
* - `'after'` - use $[pg-minify] after applying static formatting parameters
* (option `params`), as opposed to before it (default)
*
* If option `compress` is set, then the default for `minify` is `true`.
*
* Failure to parse SQL will result in $[SQLParsingError].
*
* @property {boolean} compress=false
* Sets option `compress` as supported by $[pg-minify], to uglify the SQL:
* - `false` - no compression to be applied, keep minimum spaces for easier read
* - `true` - remove all unnecessary spaces from SQL
*
* This option has no meaning, if `minify` is explicitly set to `false`. However, if `minify` is not
* specified and `compress` is specified as `true`, then `minify` defaults to `true`.
*
* @property {array|object|value} params
*
* Static formatting parameters to be applied to the SQL, using the same method {@link formatting.format as.format},
* but with option `partial` = `true`.
*
* Most of the time query formatting is fully dynamic, and applied just before executing the query.
* In some cases though you may need to pre-format SQL with static values. Examples of it can be a
* schema name, or a configurable table name.
*
* This option makes two-step SQL formatting easy: you can pre-format the SQL initially, and then
* apply the second-step dynamic formatting when executing the query.
*
* @property {boolean} noWarnings=false
* Suppresses all warnings produced by the class. It is not recommended for general use, only in specific tests
* that may require it.
*
*/

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
/**
* @enum {number}
* @alias queryResult
* @readonly
* @description
* **Query Result Mask**
*
* Binary mask that represents the number of rows expected from a query method,
* used by generic {@link Database#query query} method, plus {@link Database#func func}.
*
* The mask is always the last optional parameter, which defaults to `queryResult.any`.
*
* Any combination of flags is supported, except for `one + many`.
*
* The type is available from the library's root: `pgp.queryResult`.
*
* @see {@link Database#query Database.query}, {@link Database#func Database.func}
*/
const queryResult = {
/** Single row is expected, to be resolved as a single row-object. */
one: 1,
/** One or more rows expected, to be resolved as an array, with at least 1 row-object. */
many: 2,
/** Expecting no rows, to be resolved with `null`. */
none: 4,
/** `many|none` - any result is expected, to be resolved with an array of rows-objects. */
any: 6
};
module.exports = {queryResult};

279
ProjectSourceCode/node_modules/pg-promise/lib/query.js generated vendored Normal file
View File

@@ -0,0 +1,279 @@
/*
* 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);
};
};

View File

@@ -0,0 +1,30 @@
/*
* 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 specialQueryType = {
result: 0,
multiResult: 1,
stream: 2
};
class SpecialQuery {
constructor(type) {
this.isResult = type === specialQueryType.result; // type used implicitly
this.isStream = type === specialQueryType.stream;
this.isMultiResult = type === specialQueryType.multiResult;
}
}
const cache = {
resultQuery: new SpecialQuery(specialQueryType.result),
multiResultQuery: new SpecialQuery(specialQueryType.multiResult),
streamQuery: new SpecialQuery(specialQueryType.stream)
};
module.exports = Object.assign({SpecialQuery}, cache);

127
ProjectSourceCode/node_modules/pg-promise/lib/stream.js generated vendored Normal file
View File

@@ -0,0 +1,127 @@
/*
* 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 npm = {
utils: require(`./utils`),
text: require(`./text`)
};
////////////////////////////////////////////
// Streams query data into any destination,
// with the help of pg-query-stream library.
function $stream(ctx, qs, initCB, config) {
const $p = config.promise;
// istanbul ignore next:
// we do not provide code coverage for the Native Bindings specifics
if (ctx.options.pgNative) {
return $p.reject(new Error(npm.text.nativeStreaming));
}
// Stream class was renamed again, see the following issue:
// https://github.com/brianc/node-postgres/issues/2412
if (!qs || !qs.constructor || qs.constructor.name !== `QueryStream`) {
// invalid or missing stream object;
return $p.reject(new TypeError(npm.text.invalidStream));
}
if (qs._reading || qs._closed) {
// stream object is in the wrong state;
return $p.reject(new Error(npm.text.invalidStreamState));
}
if (typeof initCB !== `function`) {
// parameter `initCB` must be passed as the initialization callback;
return $p.reject(new TypeError(npm.text.invalidStreamCB));
}
let error = Events.query(ctx.options, getContext());
if (error) {
error = getError(error);
Events.error(ctx.options, error, getContext());
return $p.reject(error);
}
const stream = ctx.db.client.query(qs);
stream.on(`data`, onData);
stream.on(`error`, onError);
stream.on(`end`, onEnd);
try {
initCB.call(this, stream); // the stream must be initialized during the call;
} catch (e) {
release();
error = getError(e);
Events.error(ctx.options, error, getContext());
return $p.reject(error);
}
const start = Date.now();
let resolve, reject, nRows = 0;
function onData(data) {
nRows++;
error = Events.receive(ctx.options, [data], undefined, getContext());
if (error) {
onError(error);
}
}
function onError(e) {
release();
stream.destroy();
e = getError(e);
Events.error(ctx.options, e, getContext());
reject(e);
}
function onEnd() {
release();
resolve({
processed: nRows, // total number of rows processed;
duration: Date.now() - start // duration, in milliseconds;
});
}
function release() {
stream.removeListener(`data`, onData);
stream.removeListener(`error`, onError);
stream.removeListener(`end`, onEnd);
}
function getError(e) {
return e instanceof npm.utils.InternalError ? e.error : e;
}
function getContext() {
let client;
if (ctx.db) {
client = ctx.db.client;
} else {
error = new Error(npm.text.looseQuery);
}
return {
client,
dc: ctx.dc,
query: qs.cursor.text,
params: qs.cursor.values,
ctx: ctx.ctx
};
}
return $p((res, rej) => {
resolve = res;
reject = rej;
});
}
module.exports = $stream;

416
ProjectSourceCode/node_modules/pg-promise/lib/task.js generated vendored Normal file
View File

@@ -0,0 +1,416 @@
/*
* 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 npm = {
spex: require(`spex`),
utils: require(`./utils`),
mode: require(`./tx-mode`),
query: require(`./query`),
text: require(`./text`)
};
/**
* @interface Task
* @description
* Extends {@link Database} for an automatic connection session, with methods for executing multiple database queries.
*
* The type isn't available directly, it can only be created via methods {@link Database#task Database.task}, {@link Database#tx Database.tx},
* or their derivations.
*
* When executing more than one request at a time, one should allocate and release the connection only once,
* while executing all the required queries within the same connection session. More importantly, a transaction
* can only work within a single connection.
*
* This is an interface for tasks/transactions to implement a connection session, during which you can
* execute multiple queries against the same connection that's released automatically when the task/transaction is finished.
*
* Each task/transaction manages the connection automatically. When executed on the root {@link Database} object, the connection
* is allocated from the pool, and once the method's callback has finished, the connection is released back to the pool.
* However, when invoked inside another task or transaction, the method reuses the parent connection.
*
* @see
* {@link Task#ctx ctx},
* {@link Task#batch batch},
* {@link Task#sequence sequence},
* {@link Task#page page}
*
* @example
* db.task(t => {
* // t = task protocol context;
* // t.ctx = Task Context;
* return t.one('select * from users where id=$1', 123)
* .then(user => {
* return t.any('select * from events where login=$1', user.name);
* });
* })
* .then(events => {
* // success;
* })
* .catch(error => {
* // error;
* });
*
*/
function Task(ctx, tag, isTX, config) {
const $p = config.promise;
/**
* @member {TaskContext} Task#ctx
* @readonly
* @description
* Task/Transaction Context object - contains individual properties for each task/transaction.
*
* @see event {@link event:query query}
*
* @example
*
* db.task(t => {
* return t.ctx; // task context object
* })
* .then(ctx => {
* console.log('Task Duration:', ctx.duration);
* });
*
* @example
*
* db.tx(t => {
* return t.ctx; // transaction context object
* })
* .then(ctx => {
* console.log('Transaction Duration:', ctx.duration);
* });
*/
this.ctx = ctx.ctx = {}; // task context object;
npm.utils.addReadProp(this.ctx, `isTX`, isTX);
if (`context` in ctx) {
npm.utils.addReadProp(this.ctx, `context`, ctx.context);
}
npm.utils.addReadProp(this.ctx, `connected`, !ctx.db);
npm.utils.addReadProp(this.ctx, `tag`, tag);
npm.utils.addReadProp(this.ctx, `dc`, ctx.dc);
npm.utils.addReadProp(this.ctx, `level`, ctx.level);
npm.utils.addReadProp(this.ctx, `inTransaction`, ctx.inTransaction);
if (isTX) {
npm.utils.addReadProp(this.ctx, `txLevel`, ctx.txLevel);
}
npm.utils.addReadProp(this.ctx, `parent`, ctx.parentCtx);
// generic query method;
this.query = function (query, values, qrm) {
if (!ctx.db) {
return $p.reject(new Error(npm.text.looseQuery));
}
return config.$npm.query.call(this, ctx, query, values, qrm);
};
/**
* @method Task#batch
* @description
* Settles a predefined array of mixed values by redirecting to method $[spex.batch].
*
* For complete method documentation see $[spex.batch].
*
* @param {array} values
* @param {Object} [options]
* Optional Parameters.
* @param {function} [options.cb]
*
* @returns {external:Promise}
*/
this.batch = function (values, options) {
return config.$npm.spex.batch.call(this, values, options);
};
/**
* @method Task#page
* @description
* Resolves a dynamic sequence of arrays/pages with mixed values, by redirecting to method $[spex.page].
*
* For complete method documentation see $[spex.page].
*
* @param {function} source
* @param {Object} [options]
* Optional Parameters.
* @param {function} [options.dest]
* @param {number} [options.limit=0]
*
* @returns {external:Promise}
*/
this.page = function (source, options) {
return config.$npm.spex.page.call(this, source, options);
};
/**
* @method Task#sequence
* @description
* Resolves a dynamic sequence of mixed values by redirecting to method $[spex.sequence].
*
* For complete method documentation see $[spex.sequence].
*
* @param {function} source
* @param {Object} [options]
* Optional Parameters.
* @param {function} [options.dest]
* @param {number} [options.limit=0]
* @param {boolean} [options.track=false]
*
* @returns {external:Promise}
*/
this.sequence = function (source, options) {
return config.$npm.spex.sequence.call(this, source, options);
};
}
/**
* @private
* @method Task.callback
* Callback invocation helper.
*
* @param ctx
* @param obj
* @param cb
* @param config
* @returns {Promise.<TResult>}
*/
const callback = (ctx, obj, cb, config) => {
const $p = config.promise;
let result;
try {
if (cb.constructor.name === `GeneratorFunction`) {
// v9.0 dropped all support for ES6 generator functions;
// Clients should use the new ES7 async/await syntax.
throw new TypeError(`ES6 generator functions are no longer supported!`);
}
result = cb.call(obj, obj); // invoking the callback function;
} catch (err) {
Events.error(ctx.options, err, {
client: ctx.db && ctx.db.client, // the error can be due to loss of connectivity
dc: ctx.dc,
ctx: ctx.ctx
});
return $p.reject(err); // reject with the error;
}
if (result && typeof result.then === `function`) {
return result; // result is a valid promise object;
}
return $p.resolve(result);
};
/**
* @private
* @method Task.execute
* Executes a task.
*
* @param ctx
* @param obj
* @param isTX
* @param config
* @returns {Promise.<TResult>}
*/
const execute = (ctx, obj, isTX, config) => {
const $p = config.promise;
// updates the task context and notifies the client;
function update(start, success, result) {
const c = ctx.ctx;
if (start) {
npm.utils.addReadProp(c, `start`, new Date());
} else {
c.finish = new Date();
c.success = success;
c.result = result;
c.duration = c.finish - c.start;
npm.utils.lock(c, true);
}
(isTX ? Events.transact : Events.task)(ctx.options, {
client: ctx.db && ctx.db.client, // loss of connectivity is possible at this point
dc: ctx.dc,
ctx: c
});
}
let cbData, cbReason, success,
spName; // Save-Point Name;
const capSQL = ctx.options.capSQL; // capitalize sql;
update(true);
if (isTX) {
// executing a transaction;
spName = `sp_${ctx.txLevel}_${ctx.nextTxCount}`;
return begin()
.then(() => callback(ctx, obj, ctx.cb, config)
.then(data => {
cbData = data; // save callback data;
success = true;
return commit();
}, err => {
cbReason = err; // save callback failure reason;
return rollback();
})
.then(() => {
if (success) {
update(false, true, cbData);
return cbData;
}
update(false, false, cbReason);
return $p.reject(cbReason);
},
err => {
// either COMMIT or ROLLBACK has failed, which is impossible
// to replicate in a test environment, so skipping from the test;
// istanbul ignore next:
update(false, false, err);
// istanbul ignore next:
return $p.reject(err);
}),
err => {
// BEGIN has failed, which is impossible to replicate in a test
// environment, so skipping the whole block from the test;
// istanbul ignore next:
update(false, false, err);
// istanbul ignore next:
return $p.reject(err);
});
}
function begin() {
if (!ctx.txLevel && ctx.mode instanceof npm.mode.TransactionMode) {
return exec(ctx.mode.begin(capSQL), `savepoint`);
}
return exec(`begin`, `savepoint`);
}
function commit() {
return exec(`commit`, `release savepoint`);
}
function rollback() {
return exec(`rollback`, `rollback to savepoint`);
}
function exec(top, nested) {
if (ctx.txLevel) {
return obj.none((capSQL ? nested.toUpperCase() : nested) + ` ` + spName);
}
return obj.none(capSQL ? top.toUpperCase() : top);
}
// executing a task;
return callback(ctx, obj, ctx.cb, config)
.then(data => {
update(false, true, data);
return data;
})
.catch(error => {
update(false, false, error);
return $p.reject(error);
});
};
module.exports = config => {
const npmLocal = config.$npm;
// istanbul ignore next:
// we keep 'npm.query' initialization here, even though it is always
// pre-initialized by the 'database' module, for integrity purpose.
npmLocal.query = npmLocal.query || npm.query(config);
npmLocal.spex = npmLocal.spex || npm.spex(config.promiseLib);
return {
Task, execute, callback
};
};
/**
* @typedef TaskContext
* @description
* Task/Transaction Context used via property {@link Task#ctx ctx} inside tasks (methods {@link Database#task Database.task} and {@link Database#taskIf Database.taskIf})
* and transactions (methods {@link Database#tx Database.tx} and {@link Database#txIf Database.txIf}).
*
* Properties `context`, `connected`, `parent`, `level`, `dc`, `isTX`, `tag`, `start`, `useCount` and `serverVersion` are set just before the operation has started,
* while properties `finish`, `duration`, `success` and `result` are set immediately after the operation has finished.
*
* @property {*} context
* If the operation was invoked with a calling context - `task.call(context,...)` or `tx.call(context,...)`,
* this property is set with the context that was passed in. Otherwise, the property doesn't exist.
*
* @property {*} dc
* _Database Context_ that was passed into the {@link Database} object during construction.
*
* @property {boolean} isTX
* Indicates whether this operation is a transaction (as opposed to a regular task).
*
* @property {number} duration
* Number of milliseconds consumed by the operation.
*
* Set after the operation has finished, it is simply a shortcut for `finish - start`.
*
* @property {number} level
* Task nesting level, starting from 0, counting both regular tasks and transactions.
*
* @property {number} txLevel
* Transaction nesting level, starting from 0. Transactions on level 0 use `BEGIN/COMMIT/ROLLBACK`,
* while transactions on nested levels use the corresponding `SAVEPOINT` commands.
*
* This property exists only within the context of a transaction (`isTX = true`).
*
* @property {boolean} inTransaction
* Available in both tasks and transactions, it simplifies checking when there is a transaction
* going on either on this level or above.
*
* For example, when you want to check for a containing transaction while inside a task, and
* only start a transaction when there is none yet.
*
* @property {TaskContext} parent
* Parent task/transaction context, or `null` when it is top-level.
*
* @property {boolean} connected
* Indicates when the task/transaction acquired the connection on its own (`connected = true`), and will release it once
* the operation has finished. When the value is `false`, the operation is reusing an existing connection.
*
* @property {*} tag
* Tag value as it was passed into the task. See methods {@link Database#task task} and {@link Database#tx tx}.
*
* @property {Date} start
* Date/Time of when this operation started the execution.
*
* @property {number} useCount
* Number of times the connection has been previously used, starting with 0 for a freshly
* allocated physical connection.
*
* @property {string} serverVersion
* Version of the PostgreSQL server to which we are connected.
* Not available with $[Native Bindings].
*
* @property {Date} finish
* Once the operation has finished, this property is set to the Data/Time of when it happened.
*
* @property {boolean} success
* Once the operation has finished, this property indicates whether it was successful.
*
* @property {*} result
* Once the operation has finished, this property contains the result, depending on property `success`:
* - data resolved by the operation, if `success = true`
* - error / rejection reason, if `success = false`
*
*/

40
ProjectSourceCode/node_modules/pg-promise/lib/text.js generated vendored Normal file
View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
/* All error messages used in the module */
const streamVersion = require(`../package.json`)
.devDependencies[`pg-query-stream`];
module.exports = {
nativeError: `Failed to initialize Native Bindings.`,
/* Database errors */
queryDisconnected: `Cannot execute a query on a disconnected client.`,
invalidQuery: `Invalid query format.`,
invalidFunction: `Invalid function name.`,
invalidProc: `Invalid procedure name.`,
invalidMask: `Invalid Query Result Mask specified.`,
looseQuery: `Querying against a released or lost connection.`,
/* result errors */
notEmpty: `No return data was expected.`,
noData: `No data returned from the query.`,
multiple: `Multiple rows were not expected.`,
/* streaming support */
nativeStreaming: `Streaming doesn't work with Native Bindings.`,
invalidStream: `Invalid or missing stream object: pg-query-stream >= v${streamVersion} was expected`,
invalidStreamState: `Invalid stream state.`,
invalidStreamCB: `Invalid or missing stream initialization callback.`,
/* connection errors */
poolDestroyed: `Connection pool of the database object has been destroyed.`,
clientEnd: `Abnormal client.end() call, due to invalid code or failed server connection.`
};

View File

@@ -0,0 +1,190 @@
/*
* 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 {InnerState} = require(`./inner-state`);
const {addInspection} = require(`./utils`);
const {assert} = require(`./assert`);
/**
* @enum {number}
* @alias txMode.isolationLevel
* @readonly
* @summary Transaction Isolation Level.
* @description
* The type is available from the {@link txMode} namespace.
*
* @see $[Transaction Isolation]
*/
const isolationLevel = {
/** Isolation level not specified. */
none: 0,
/** ISOLATION LEVEL SERIALIZABLE */
serializable: 1,
/** ISOLATION LEVEL REPEATABLE READ */
repeatableRead: 2,
/** ISOLATION LEVEL READ COMMITTED */
readCommitted: 3
// From the official documentation: http://www.postgresql.org/docs/9.5/static/sql-set-transaction.html
// The SQL standard defines one additional level, READ UNCOMMITTED. In PostgreSQL READ UNCOMMITTED is treated as READ COMMITTED.
// => skipping `READ UNCOMMITTED`.
};
/**
* @class txMode.TransactionMode
* @description
* Constructs a complete transaction-opening `BEGIN` command, from these options:
* - isolation level
* - access mode
* - deferrable mode
*
* The type is available from the {@link txMode} namespace.
*
* @param {} [options]
* Transaction Mode options.
*
* @param {txMode.isolationLevel} [options.tiLevel]
* Transaction Isolation Level.
*
* @param {boolean} [options.readOnly]
* Sets transaction access mode based on the read-only flag:
* - `undefined` - access mode not specified (default)
* - `true` - access mode is set to `READ ONLY`
* - `false` - access mode is set to `READ WRITE`
*
* @param {boolean} [options.deferrable]
* Sets transaction deferrable mode based on the boolean value:
* - `undefined` - deferrable mode not specified (default)
* - `true` - mode is set to `DEFERRABLE`
* - `false` - mode is set to `NOT DEFERRABLE`
*
* It is used only when `tiLevel`=`isolationLevel.serializable`
* and `readOnly`=`true`, or else it is ignored.
*
* @returns {txMode.TransactionMode}
*
* @see $[BEGIN], {@link txMode.isolationLevel}
*
* @example
*
* const {TransactionMode, isolationLevel} = pgp.txMode;
*
* // Create a reusable transaction mode (serializable + read-only + deferrable):
* const mode = new TransactionMode({
* tiLevel: isolationLevel.serializable,
* readOnly: true,
* deferrable: true
* });
*
* db.tx({mode}, t => {
* return t.any('SELECT * FROM table');
* })
* .then(data => {
* // success;
* })
* .catch(error => {
* // error
* });
*
* // Instead of the default BEGIN, such transaction will start with:
*
* // BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE
*
*/
class TransactionMode extends InnerState {
constructor(options) {
options = assert(options, [`tiLevel`, `deferrable`, `readOnly`]);
const {readOnly, deferrable} = options;
let {tiLevel} = options;
let level, accessMode, deferrableMode, begin = `begin`;
tiLevel = (tiLevel > 0) ? parseInt(tiLevel) : 0;
if (tiLevel > 0 && tiLevel < 4) {
const values = [`serializable`, `repeatable read`, `read committed`];
level = `isolation level ` + values[tiLevel - 1];
}
if (readOnly) {
accessMode = `read only`;
} else {
if (readOnly !== undefined) {
accessMode = `read write`;
}
}
// From the official documentation: http://www.postgresql.org/docs/9.5/static/sql-set-transaction.html
// The DEFERRABLE transaction property has no effect unless the transaction is also SERIALIZABLE and READ ONLY
if (tiLevel === isolationLevel.serializable && readOnly) {
if (deferrable) {
deferrableMode = `deferrable`;
} else {
if (deferrable !== undefined) {
deferrableMode = `not deferrable`;
}
}
}
if (level) {
begin += ` ` + level;
}
if (accessMode) {
begin += ` ` + accessMode;
}
if (deferrableMode) {
begin += ` ` + deferrableMode;
}
super({begin, capBegin: begin.toUpperCase()});
}
/**
* @method txMode.TransactionMode#begin
* @description
* Returns a complete BEGIN statement, according to all the parameters passed into the class.
*
* This method is primarily for internal use by the library.
*
* @param {boolean} [cap=false]
* Indicates whether the returned SQL must be capitalized.
*
* @returns {string}
*/
begin(cap) {
return cap ? this._inner.capBegin : this._inner.begin;
}
}
addInspection(TransactionMode, function () {
return this.begin(true);
});
/**
* @namespace txMode
* @description
* Transaction Mode namespace, available as `pgp.txMode`, before and after initializing the library.
*
* Extends the default `BEGIN` with Transaction Mode parameters:
* - isolation level
* - access mode
* - deferrable mode
*
* @property {function} TransactionMode
* {@link txMode.TransactionMode TransactionMode} class constructor.
*
* @property {txMode.isolationLevel} isolationLevel
* Transaction Isolation Level enumerator
*
* @see $[BEGIN]
*/
module.exports = {
isolationLevel,
TransactionMode
};

View File

@@ -0,0 +1,18 @@
/*
* 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 {ServerFormatting} = require(`./server-formatting`);
const {PreparedStatement} = require(`./prepared-statement`);
const {ParameterizedQuery} = require(`./parameterized-query`);
module.exports = {
ServerFormatting,
PreparedStatement,
ParameterizedQuery
};

View File

@@ -0,0 +1,250 @@
/*
* 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 {ServerFormatting} = require(`./server-formatting`);
const {ParameterizedQueryError} = require(`../errors`);
const {QueryFile} = require(`../query-file`);
const {assert} = require(`../assert`);
const npm = {
EOL: require(`os`).EOL,
utils: require(`../utils`)
};
/**
* @class ParameterizedQuery
* @description
* Constructs a new {@link ParameterizedQuery} object. All properties can also be set after the object's construction.
*
* This type extends the basic `{text, values}` object, i.e. when the basic object is used with a query method,
* a new {@link ParameterizedQuery} object is created in its place.
*
* The type can be used in place of the `query` parameter, with any query method directly.
*
* The type is available from the library's root: `pgp.ParameterizedQuery`.
*
* @param {string|QueryFile|Object} [options]
* Object configuration options / properties.
*
* @param {string|QueryFile} [options.text] - See property {@link ParameterizedQuery#text text}.
* @param {array} [options.values] - See property {@link ParameterizedQuery#values values}.
* @param {boolean} [options.binary] - See property {@link ParameterizedQuery#binary binary}.
* @param {string} [options.rowMode] - See property {@link ParameterizedQuery#rowMode rowMode}.
*
* @returns {ParameterizedQuery}
*
* @see
* {@link errors.ParameterizedQueryError ParameterizedQueryError}
*
* @example
*
* const {ParameterizedQuery: PQ} = require('pg-promise');
*
* // Creating a complete Parameterized Query with parameters:
* const findUser = new PQ({text: 'SELECT * FROM Users WHERE id = $1', values: [123]});
*
* db.one(findUser)
* .then(user => {
* // user found;
* })
* .catch(error => {
* // error;
* });
*
* @example
*
* const {ParameterizedQuery: PQ} = require('pg-promise');
*
* // Creating a reusable Parameterized Query without values:
* const addUser = new PQ('INSERT INTO Users(name, age) VALUES($1, $2)');
*
* // setting values explicitly:
* addUser.values = ['John', 30];
*
* db.none(addUser)
* .then(() => {
* // user added;
* })
* .catch(error=> {
* // error;
* });
*
* // setting values implicitly, by passing them into the query method:
* db.none(addUser, ['Mike', 25])
* .then(() => {
* // user added;
* })
* .catch(error=> {
* // error;
* });
*/
class ParameterizedQuery extends ServerFormatting {
constructor(options) {
if (typeof options === `string` || options instanceof QueryFile) {
options = {
text: options
};
} else {
options = assert(options, [`text`, `values`, `binary`, `rowMode`]);
}
super(options);
}
}
/**
* @method ParameterizedQuery#parse
* @description
* Parses the current object and returns a simple `{text, values}`, if successful,
* or else it returns a {@link errors.ParameterizedQueryError ParameterizedQueryError} object.
*
* This method is primarily for internal use by the library.
*
* @returns {{text, values}|errors.ParameterizedQueryError}
*/
ParameterizedQuery.prototype.parse = function () {
const _i = this._inner, options = _i.options;
const qf = options.text instanceof QueryFile ? options.text : null;
if (!_i.changed && !qf) {
return _i.target;
}
const errors = [], values = _i.target.values;
_i.target = {
text: options.text
};
_i.changed = true;
_i.currentError = undefined;
if (qf) {
qf.prepare();
if (qf.error) {
errors.push(qf.error);
} else {
_i.target.text = qf[QueryFile.$query];
}
}
if (!npm.utils.isText(_i.target.text)) {
errors.push(`Property 'text' must be a non-empty text string.`);
}
if (!npm.utils.isNull(values)) {
_i.target.values = values;
}
if (options.binary !== undefined) {
_i.target.binary = !!options.binary;
}
if (options.rowMode !== undefined) {
_i.target.rowMode = options.rowMode;
}
if (errors.length) {
return _i.currentError = new ParameterizedQueryError(errors[0], _i.target);
}
_i.changed = false;
return _i.target;
};
/**
* @method ParameterizedQuery#toString
* @description
* Creates a well-formatted multi-line string that represents the object's current state.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
ParameterizedQuery.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap = npm.utils.messageGap(level + 1);
const pq = this.parse();
const lines = [
`ParameterizedQuery {`
];
if (npm.utils.isText(pq.text)) {
lines.push(gap + `text: "` + pq.text + `"`);
}
if (this.values !== undefined) {
lines.push(gap + `values: ` + npm.utils.toJson(this.values));
}
if (this.binary !== undefined) {
lines.push(gap + `binary: ` + npm.utils.toJson(this.binary));
}
if (this.rowMode !== undefined) {
lines.push(gap + `rowMode: ` + npm.utils.toJson(this.rowMode));
}
if (this.error !== undefined) {
lines.push(gap + `error: ` + this.error.toString(level + 1));
}
lines.push(npm.utils.messageGap(level) + `}`);
return lines.join(npm.EOL);
};
module.exports = {ParameterizedQuery};
/**
* @name ParameterizedQuery#text
* @type {string|QueryFile}
* @description
* A non-empty query string or a {@link QueryFile} object.
*
* Only the basic variables (`$1`, `$2`, etc) can be used in the query, because _Parameterized Queries_
* are formatted on the server side.
*/
/**
* @name ParameterizedQuery#values
* @type {array}
* @description
* Query formatting parameters, depending on the type:
*
* - `null` / `undefined` means the query has no formatting parameters
* - `Array` - it is an array of formatting parameters
* - None of the above, means it is a single formatting value, which
* is then automatically wrapped into an array
*/
/**
* @name ParameterizedQuery#binary
* @type {boolean}
* @default undefined
* @description
* Activates binary result mode. The default is the text mode.
*
* @see {@link http://www.postgresql.org/docs/devel/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY Extended Query}
*/
/**
* @name ParameterizedQuery#rowMode
* @type {string}
* @default undefined
* @description
* Changes the way data arrives to the client, with only one value supported by $[pg]:
* - `array` will make all data rows arrive as arrays of values. By default, rows arrive as objects.
*/
/**
* @name ParameterizedQuery#error
* @type {errors.ParameterizedQueryError}
* @default undefined
* @readonly
* @description
* When in an error state, it is set to a {@link errors.ParameterizedQueryError ParameterizedQueryError} object. Otherwise, it is `undefined`.
*
* This property is primarily for internal use by the library.
*/

View File

@@ -0,0 +1,300 @@
/*
* 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 {ServerFormatting} = require(`./server-formatting`);
const {PreparedStatementError} = require(`../errors`);
const {QueryFile} = require(`../query-file`);
const {assert} = require(`../assert`);
const npm = {
EOL: require(`os`).EOL,
utils: require(`../utils`)
};
/**
* @class PreparedStatement
* @description
* Constructs a new $[Prepared Statement] object. All properties can also be set after the object's construction.
*
* This type extends the basic `{name, text, values}` object, i.e. when the basic object is used
* with a query method, a new {@link PreparedStatement} object is created in its place.
*
* The type can be used in place of the `query` parameter, with any query method directly.
*
* The type is available from the library's root: `pgp.PreparedStatement`.
*
* @param {Object} [options]
* Object configuration options / properties.
*
* @param {string} [options.name] - See property {@link PreparedStatement#name name}.
* @param {string|QueryFile} [options.text] - See property {@link PreparedStatement#text text}.
* @param {array} [options.values] - See property {@link PreparedStatement#values values}.
* @param {boolean} [options.binary] - See property {@link PreparedStatement#binary binary}.
* @param {string} [options.rowMode] - See property {@link PreparedStatement#rowMode rowMode}.
* @param {number} [options.rows] - See property {@link PreparedStatement#rows rows}.
*
* @returns {PreparedStatement}
*
* @see
* {@link errors.PreparedStatementError PreparedStatementError},
* {@link http://www.postgresql.org/docs/9.6/static/sql-prepare.html PostgreSQL Prepared Statements}
*
* @example
*
* const {PreparedStatement: PS} = require('pg-promise');
*
* // Creating a complete Prepared Statement with parameters:
* const findUser = new PS({name: 'find-user', text: 'SELECT * FROM Users WHERE id = $1', values: [123]});
*
* db.one(findUser)
* .then(user => {
* // user found;
* })
* .catch(error => {
* // error;
* });
*
* @example
*
* const {PreparedStatement: PS} = require('pg-promise');
*
* // Creating a reusable Prepared Statement without values:
* const addUser = new PS({name: 'add-user', text: 'INSERT INTO Users(name, age) VALUES($1, $2)'});
*
* // setting values explicitly:
* addUser.values = ['John', 30];
*
* db.none(addUser)
* .then(() => {
* // user added;
* })
* .catch(error => {
* // error;
* });
*
* // setting values implicitly, by passing them into the query method:
* db.none(addUser, ['Mike', 25])
* .then(() => {
* // user added;
* })
* .catch(error => {
* // error;
* });
*/
class PreparedStatement extends ServerFormatting {
constructor(options) {
options = assert(options, [`name`, `text`, `values`, `binary`, `rowMode`, `rows`]);
super(options);
}
/**
* @name PreparedStatement#name
* @type {string}
* @description
* An arbitrary name given to this particular prepared statement. It must be unique within a single session and is
* subsequently used to execute or deallocate a previously prepared statement.
*/
get name() {
return this._inner.options.name;
}
set name(value) {
const _i = this._inner;
if (value !== _i.options.name) {
_i.options.name = value;
_i.changed = true;
}
}
/**
* @name PreparedStatement#rows
* @type {number}
* @description
* Number of rows to return at a time from a Prepared Statement's portal.
* The default is 0, which means that all rows must be returned at once.
*/
get rows() {
return this._inner.options.rows;
}
set rows(value) {
const _i = this._inner;
if (value !== _i.options.rows) {
_i.options.rows = value;
_i.changed = true;
}
}
}
/**
* @method PreparedStatement#parse
* @description
* Parses the current object and returns a simple `{name, text, values}`, if successful,
* or else it returns a {@link errors.PreparedStatementError PreparedStatementError} object.
*
* This method is primarily for internal use by the library.
*
* @returns {{name, text, values}|errors.PreparedStatementError}
*/
PreparedStatement.prototype.parse = function () {
const _i = this._inner, options = _i.options;
const qf = options.text instanceof QueryFile ? options.text : null;
if (!_i.changed && !qf) {
return _i.target;
}
const errors = [], values = _i.target.values;
_i.target = {
name: options.name,
text: options.text
};
_i.changed = true;
_i.currentError = undefined;
if (!npm.utils.isText(_i.target.name)) {
errors.push(`Property 'name' must be a non-empty text string.`);
}
if (qf) {
qf.prepare();
if (qf.error) {
errors.push(qf.error);
} else {
_i.target.text = qf[QueryFile.$query];
}
}
if (!npm.utils.isText(_i.target.text)) {
errors.push(`Property 'text' must be a non-empty text string.`);
}
if (!npm.utils.isNull(values)) {
_i.target.values = values;
}
if (options.binary !== undefined) {
_i.target.binary = !!options.binary;
}
if (options.rowMode !== undefined) {
_i.target.rowMode = options.rowMode;
}
if (options.rows !== undefined) {
_i.target.rows = options.rows;
}
if (errors.length) {
return _i.currentError = new PreparedStatementError(errors[0], _i.target);
}
_i.changed = false;
return _i.target;
};
/**
* @method PreparedStatement#toString
* @description
* Creates a well-formatted multi-line string that represents the object's current state.
*
* It is called automatically when writing the object into the console.
*
* @param {number} [level=0]
* Nested output level, to provide visual offset.
*
* @returns {string}
*/
PreparedStatement.prototype.toString = function (level) {
level = level > 0 ? parseInt(level) : 0;
const gap = npm.utils.messageGap(level + 1);
const ps = this.parse();
const lines = [
`PreparedStatement {`,
gap + `name: ` + npm.utils.toJson(this.name)
];
if (npm.utils.isText(ps.text)) {
lines.push(gap + `text: "` + ps.text + `"`);
}
if (this.values !== undefined) {
lines.push(gap + `values: ` + npm.utils.toJson(this.values));
}
if (this.binary !== undefined) {
lines.push(gap + `binary: ` + npm.utils.toJson(this.binary));
}
if (this.rowMode !== undefined) {
lines.push(gap + `rowMode: ` + npm.utils.toJson(this.rowMode));
}
if (this.rows !== undefined) {
lines.push(gap + `rows: ` + npm.utils.toJson(this.rows));
}
if (this.error) {
lines.push(gap + `error: ` + this.error.toString(level + 1));
}
lines.push(npm.utils.messageGap(level) + `}`);
return lines.join(npm.EOL);
};
module.exports = {PreparedStatement};
/**
* @name PreparedStatement#text
* @type {string|QueryFile}
* @description
* A non-empty query string or a {@link QueryFile} object.
*
* Only the basic variables (`$1`, `$2`, etc) can be used in the query, because $[Prepared Statements]
* are formatted on the server side.
*
* Changing this property for the same {@link PreparedStatement#name name} will have no effect, because queries
* for Prepared Statements are cached by the server, with {@link PreparedStatement#name name} being the cache key.
*/
/**
* @name PreparedStatement#values
* @type {array}
* @description
* Query formatting parameters, depending on the type:
*
* - `null` / `undefined` means the query has no formatting parameters
* - `Array` - it is an array of formatting parameters
* - None of the above, means it is a single formatting value, which
* is then automatically wrapped into an array
*/
/**
* @name PreparedStatement#binary
* @type {boolean}
* @default undefined
* @description
* Activates binary result mode. The default is the text mode.
*
* @see {@link http://www.postgresql.org/docs/devel/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY Extended Query}
*/
/**
* @name PreparedStatement#rowMode
* @type {string}
* @default undefined
* @description
* Changes the way data arrives to the client, with only one value supported by $[pg]:
* - `array` will make all data rows arrive as arrays of values. By default, rows arrive as objects.
*/
/**
* @name PreparedStatement#error
* @type {errors.PreparedStatementError}
* @default undefined
* @description
* When in an error state, it is set to a {@link errors.PreparedStatementError PreparedStatementError} object. Otherwise, it is `undefined`.
*
* This property is primarily for internal use by the library.
*/

View File

@@ -0,0 +1,97 @@
const {InnerState} = require(`../inner-state`);
const {addInspection} = require(`../utils`);
const utils = require(`../utils`);
/**
* @private
* @class ServerFormatting
*/
class ServerFormatting extends InnerState {
constructor(options) {
const _inner = {
options,
changed: true,
currentError: undefined,
target: {}
};
super(_inner);
setValues.call(this, options.values);
}
get error() {
return this._inner.currentError;
}
get text() {
return this._inner.options.text;
}
set text(value) {
const _i = this._inner;
if (value !== _i.options.text) {
_i.options.text = value;
_i.changed = true;
}
}
get binary() {
return this._inner.options.binary;
}
set binary(value) {
const _i = this._inner;
if (value !== _i.options.binary) {
_i.options.binary = value;
_i.changed = true;
}
}
get rowMode() {
return this._inner.options.rowMode;
}
set rowMode(value) {
const _i = this._inner;
if (value !== _i.options.rowMode) {
_i.options.rowMode = value;
_i.changed = true;
}
}
get values() {
return this._inner.target.values;
}
set values(values) {
setValues.call(this, values);
}
}
/**
* @member ServerFormatting#parse
*/
function setValues(v) {
const target = this._inner.target;
if (Array.isArray(v)) {
if (v.length) {
target.values = v;
} else {
delete target.values;
}
} else {
if (utils.isNull(v)) {
delete target.values;
} else {
target.values = [v];
}
}
}
addInspection(ServerFormatting, function () {
return this.toString();
});
module.exports = {ServerFormatting};

View File

@@ -0,0 +1,13 @@
### `utils` namespace
This folder contains everything that's available via the [utils] namespace, before and after initialization:
```js
const pgpLib = require('pg-promise');
const pgp = pgpLib(/*initialization options*/);
pgpLib.utils; // `utils` namespace
pgp.utils; // `utils` namespace
```
[utils]:http://vitaly-t.github.io/pg-promise/utils.html

View File

@@ -0,0 +1,68 @@
const util = require(`util`);
class ColorConsole {
static log() {
ColorConsole.writeNormal([...arguments], 39); // white
}
static info() {
ColorConsole.writeNormal([...arguments], 36); // cyan
}
static success() {
ColorConsole.writeNormal([...arguments], 32); // green
}
static warn() {
ColorConsole.writeNormal([...arguments], 33); // yellow
}
static error() {
ColorConsole.writeError([...arguments], 31); // red
}
static writeNormal(params, color) {
// istanbul ignore else
if (process.stdout.isTTY) {
console.log.apply(null, ColorConsole.formatColor(params, color)); // eslint-disable-line no-console
} else {
console.log.apply(null, params); // eslint-disable-line no-console
}
}
static writeError(params, color) {
// istanbul ignore else
if (process.stderr.isTTY) {
console.error.apply(null, ColorConsole.formatColor(params, color)); // eslint-disable-line no-console
} else {
console.error.apply(null, params); // eslint-disable-line no-console
}
}
static formatColor(args, color) {
return args.map(a => `\x1b[${color}m${util.format(a)}\x1b[0m`);
}
}
ColorConsole.log.bright = function () {
ColorConsole.writeNormal([...arguments], 97); // light white
};
ColorConsole.info.bright = function () {
ColorConsole.writeNormal([...arguments], 93); // light cyan
};
ColorConsole.success.bright = function () {
ColorConsole.writeNormal([...arguments], 92); // light green
};
ColorConsole.warn.bright = function () {
ColorConsole.writeNormal([...arguments], 93); // light yellow
};
ColorConsole.error.bright = function () {
ColorConsole.writeError([...arguments], 91); // light red
};
module.exports = {ColorConsole};

View File

@@ -0,0 +1,235 @@
/*
* 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 npm = {
path: require(`path`),
util: require(`util`),
patterns: require(`../patterns`),
os: require(`os`)
};
////////////////////////////////////////////
// Simpler check for null/undefined;
function isNull(value) {
return value === null || value === undefined;
}
////////////////////////////////////////////////////////
// Verifies parameter for being a non-empty text string;
function isText(txt) {
return txt && typeof txt === `string` && /\S/.test(txt);
}
///////////////////////////////////////////////////////////
// Approximates the environment as being for development.
//
// Proper configuration is having NODE_ENV = 'development', but this
// method only checks for 'dev' being present, and regardless of the case.
function isDev() {
const env = global.process.env.NODE_ENV || ``;
return env.toLowerCase().indexOf(`dev`) !== -1;
}
///////////////////////////////////////////////////
// Locks all properties in an object to read-only,
// or freezes the entire object for any changes.
function lock(obj, freeze, options) {
if (options && options.noLocking) {
return;
}
if (freeze) {
Object.freeze(obj); // freeze the entire object, permanently;
} else {
const desc = {
writable: false,
configurable: false,
enumerable: true
};
for (const p in obj) {
Object.defineProperty(obj, p, desc);
}
}
}
/////////////////////////////////////////////
// Adds properties from source to the target,
// making them read-only and enumerable.
function addReadProperties(target, source) {
for (const p in source) {
addReadProp(target, p, source[p]);
}
}
///////////////////////////////////////////////////////
// Adds a read-only, non-deletable enumerable property.
function addReadProp(obj, name, value, hidden) {
Object.defineProperty(obj, name, {
value,
configurable: false,
enumerable: !hidden,
writable: false
});
}
//////////////////////////////////////////////////////////////
// Converts a connection string or object into its safe copy:
// if password is present, it is masked with symbol '#'.
function getSafeConnection(cn) {
const maskPassword = cs => cs.replace(/:(?![/])([^@]+)/, (_, m) => `:` + new Array(m.length + 1).join(`#`));
if (typeof cn === `object`) {
const copy = Object.assign({}, cn);
if (typeof copy.password === `string`) {
copy.password = copy.password.replace(/./g, `#`);
}
if (typeof copy.connectionString === `string`) {
copy.connectionString = maskPassword(copy.connectionString);
}
return copy;
}
return maskPassword(cn);
}
///////////////////////////////////////////
// Returns a space gap for console output;
function messageGap(level) {
return ` `.repeat(level * 4);
}
/////////////////////////////////////////
// Provides platform-neutral inheritance;
function inherits(child, parent) {
child.prototype.__proto__ = parent.prototype;
}
// istanbul ignore next
function getLocalStack(startIdx, maxLines) {
// from the call stack, we take up to maximum lines,
// starting with specified line index:
startIdx = startIdx || 0;
const endIdx = maxLines > 0 ? startIdx + maxLines : undefined;
return new Error().stack
.split(`\n`)
.filter(line => line.match(/\(.+\)/))
.slice(startIdx, endIdx)
.join(`\n`);
}
//////////////////////////////
// Internal error container;
function InternalError(error) {
this.error = error;
}
/////////////////////////////////////////////////////////////////
// Parses a property name, and gets its name from the object,
// if the property exists. Returns object {valid, has, target, value}:
// - valid - true/false, whether the syntax is valid
// - has - a flag that property exists; set when 'valid' = true
// - target - the target object that contains the property; set when 'has' = true
// - value - the value; set when 'has' = true
function getIfHas(obj, prop) {
const result = {valid: true};
if (prop.indexOf(`.`) === -1) {
result.has = prop in obj;
result.target = obj;
if (result.has) {
result.value = obj[prop];
}
} else {
const names = prop.split(`.`);
let missing, target;
for (let i = 0; i < names.length; i++) {
const n = names[i];
if (!n) {
result.valid = false;
return result;
}
if (!missing && hasProperty(obj, n)) {
target = obj;
obj = obj[n];
} else {
missing = true;
}
}
result.has = !missing;
if (result.has) {
result.target = target;
result.value = obj;
}
}
return result;
}
/////////////////////////////////////////////////////////////////////////
// Checks if the property exists in the object or value or its prototype;
function hasProperty(value, prop) {
return (value && typeof value === `object` && prop in value) ||
value !== null && value !== undefined && value[prop] !== undefined;
}
////////////////////////////////////////////////////////
// Adds prototype inspection
function addInspection(type, cb) {
type.prototype[npm.util.inspect.custom] = cb;
}
/////////////////////////////////////////////////////////////////////////////////////////
// Identifies a general connectivity error, after which no more queries can be executed.
// This is for detecting when to skip executing ROLLBACK for a failed transaction.
function isConnectivityError(err) {
const code = err && typeof err.code === `string` && err.code;
const cls = code && code.substr(0, 2); // Error Class
// istanbul ignore next (we cannot test-cover all error cases):
return code === `ECONNRESET` || (cls === `08` && code !== `08P01`) || (cls === `57` && code !== `57014`);
// Code 'ECONNRESET' - Connectivity issue handled by the driver.
// Class 08 - Connection Exception (except for 08P01, for protocol violation).
// Class 57 - Operator Intervention (except for 57014, for cancelled queries).
//
// ERROR CODES: https://www.postgresql.org/docs/9.6/static/errcodes-appendix.html
}
///////////////////////////////////////////////////////////////
// Does JSON.stringify, with support for BigInt (irreversible)
function toJson(data) {
if (data !== undefined) {
return JSON.stringify(data, (_, v) => typeof v === `bigint` ? `${v}#bigint` : v)
.replace(/"(-?\d+)#bigint"/g, (_, a) => a);
}
}
const platform = npm.os.platform();
const exp = {
toJson,
getIfHas,
addInspection,
InternalError,
getLocalStack,
lock,
isText,
isNull,
isDev,
platform: {
isWindows: platform === `win32`,
isMac: platform === `darwin`
},
addReadProp,
addReadProperties,
getSafeConnection,
messageGap,
inherits,
isConnectivityError
};
const mainFile = process.argv[1];
// istanbul ignore next
exp.startDir = mainFile ? npm.path.dirname(mainFile) : process.cwd();
module.exports = exp;

View File

@@ -0,0 +1,312 @@
/*
* 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 {assert} = require(`../assert`);
const npm = {
fs: require(`fs`),
path: require(`path`),
utils: require(`./`),
package: require(`../../package.json`)
};
/**
* @method utils.camelize
* @description
* Camelizes a text string.
*
* Case-changing characters include:
* - _hyphen_
* - _underscore_
* - _period_
* - _space_
*
* @param {string} text
* Input text string.
*
* @returns {string}
* Camelized text string.
*
* @see
* {@link utils.camelizeVar camelizeVar}
*
*/
function camelize(text) {
text = text.replace(/[-_\s.]+(.)?/g, (_, c) => c ? c.toUpperCase() : ``);
return text.substr(0, 1).toLowerCase() + text.substr(1);
}
/**
* @method utils.camelizeVar
* @description
* Camelizes a text string, while making it compliant with JavaScript variable names:
* - contains symbols `a-z`, `A-Z`, `0-9`, `_` and `$`
* - cannot have leading digits
*
* First, it removes all symbols that do not meet the above criteria, except for _hyphen_, _period_ and _space_,
* and then it forwards into {@link utils.camelize camelize}.
*
* @param {string} text
* Input text string.
*
* If it doesn't contain any symbols to make up a valid variable name, the result will be an empty string.
*
* @returns {string}
* Camelized text string that can be used as an open property name.
*
* @see
* {@link utils.camelize camelize}
*
*/
function camelizeVar(text) {
text = text.replace(/[^a-zA-Z0-9$_\-\s.]/g, ``).replace(/^[0-9_\-\s.]+/, ``);
return camelize(text);
}
function _enumSql(dir, options, cb, namePath) {
const tree = {};
npm.fs.readdirSync(dir).forEach(file => {
let stat;
const fullPath = npm.path.join(dir, file);
try {
stat = npm.fs.statSync(fullPath);
} catch (e) {
// while it is very easy to test manually, it is very difficult to test for
// access-denied errors automatically; therefore excluding from the coverage:
// istanbul ignore next
if (options.ignoreErrors) {
return; // on to the next file/folder;
}
// istanbul ignore next
throw e;
}
if (stat.isDirectory()) {
if (options.recursive) {
const dirName = camelizeVar(file);
const np = namePath ? (namePath + `.` + dirName) : dirName;
const t = _enumSql(fullPath, options, cb, np);
if (Object.keys(t).length) {
if (!dirName.length || dirName in tree) {
if (!options.ignoreErrors) {
throw new Error(`Empty or duplicate camelized folder name: ` + fullPath);
}
}
tree[dirName] = t;
}
}
} else {
if (npm.path.extname(file).toLowerCase() === `.sql`) {
const name = camelizeVar(file.replace(/\.[^/.]+$/, ``));
if (!name.length || name in tree) {
if (!options.ignoreErrors) {
throw new Error(`Empty or duplicate camelized file name: ` + fullPath);
}
}
tree[name] = fullPath;
if (cb) {
const result = cb(fullPath, name, namePath ? (namePath + `.` + name) : name);
if (result !== undefined) {
tree[name] = result;
}
}
}
}
});
return tree;
}
/**
* @method utils.enumSql
* @description
* Synchronously enumerates all SQL files (within a given directory) into a camelized SQL tree.
*
* All property names within the tree are camelized via {@link utils.camelizeVar camelizeVar},
* so they can be used in the code directly, as open property names.
*
* @param {string} dir
* Directory path where SQL files are located, either absolute or relative to the current directory.
*
* SQL files are identified by using `.sql` extension (case-insensitive).
*
* @param {{}} [options]
* Search options.
*
* @param {boolean} [options.recursive=false]
* Include sub-directories into the search.
*
* Sub-directories without SQL files will be skipped from the result.
*
* @param {boolean} [options.ignoreErrors=false]
* Ignore the following types of errors:
* - access errors, when there is no read access to a file or folder
* - empty or duplicate camelized property names
*
* This flag does not affect errors related to invalid input parameters, or if you pass in a
* non-existing directory.
*
* @param {function} [cb]
* A callback function that takes three arguments:
* - `file` - SQL file path, relative or absolute, according to how you specified the search directory
* - `name` - name of the property that represents the SQL file
* - `path` - property resolution path (full property name)
*
* If the function returns anything other than `undefined`, it overrides the corresponding property value in the tree.
*
* @returns {object}
* Camelized SQL tree object, with each value being an SQL file path (unless changed via the callback).
*
* @example
*
* // simple SQL tree generation for further processing:
* const tree = pgp.utils.enumSql('../sql', {recursive: true});
*
* @example
*
* // generating an SQL tree for dynamic use of names:
* const sql = pgp.utils.enumSql(__dirname, {recursive: true}, file => {
* return new pgp.QueryFile(file, {minify: true});
* });
*
* @example
*
* const {join: joinPath} = require('path');
*
* // replacing each relative path in the tree with a full one:
* const tree = pgp.utils.enumSql('../sql', {recursive: true}, file => {
* return joinPath(__dirname, file);
* });
*
*/
function enumSql(dir, options, cb) {
if (!npm.utils.isText(dir)) {
throw new TypeError(`Parameter 'dir' must be a non-empty text string.`);
}
options = assert(options, [`recursive`, `ignoreErrors`]);
cb = (typeof cb === `function`) ? cb : null;
return _enumSql(dir, options, cb, ``);
}
/**
* @method utils.taskArgs
* @description
* Normalizes/prepares arguments for tasks and transactions.
*
* Its main purpose is to simplify adding custom methods {@link Database#task task}, {@link Database#taskIf taskIf},
* {@link Database#tx tx} and {@link Database#txIf txIf} within event {@link event:extend extend}, as the those methods use fairly
* complex logic for parsing inputs.
*
* @param args {Object}
* Array-like object of `arguments` that was passed into the method. It is expected that the `arguments`
* are always made of two parameters - `(options, cb)`, same as all the default task/transaction methods.
*
* And if your custom method needs additional parameters, they should be passed in as extra properties within `options`.
*
* @returns {Array}
* Array of arguments that can be passed into a task or transaction.
*
* It is extended with properties `options` and `cb` to access the corresponding array elements `[0]` and `[1]` by name.
*
* @example
*
* // Registering a custom transaction method that assigns a default Transaction Mode:
*
* const initOptions = {
* extend: obj => {
* obj.myTx = function(options, cb) {
* const args = pgp.utils.taskArgs(arguments); // prepare arguments
*
* if (!('mode' in args.options)) {
* // if no 'mode' was specified, set default for transaction mode:
* args.options.mode = myTxModeObject; // of type pgp.txMode.TransactionMode
* }
*
* return obj.tx.apply(this, args);
* // or explicitly, if needed:
* // return obj.tx.call(this, args.options, args.cb);
* }
* }
* };
*
*/
function taskArgs(args) {
if (!args || typeof args.length !== `number`) {
throw new TypeError(`Parameter 'args' must be an array-like object of arguments.`);
}
let options = args[0], cb;
if (typeof options === `function`) {
cb = options;
options = {};
if (cb.name) {
options.tag = cb.name;
}
} else {
if (typeof args[1] === `function`) {
cb = args[1];
}
if (typeof options === `string` || typeof options === `number`) {
options = {tag: options};
} else {
options = (typeof options === `object` && options) || {};
if (!(`tag` in options) && cb && cb.name) {
options.tag = cb.name;
}
}
}
const res = [options, cb];
Object.defineProperty(res, `options`, {
get: function () {
return this[0];
},
set: function (newValue) {
this[0] = newValue;
},
enumerable: true
});
Object.defineProperty(res, `cb`, {
get: function () {
return this[1];
},
set: function (newValue) {
this[1] = newValue;
},
enumerable: true
});
return res;
}
/**
* @namespace utils
*
* @description
* Namespace for general-purpose static functions, available as `pgp.utils`, before and after initializing the library.
*
* @property {function} camelize
* {@link utils.camelize camelize} - camelizes a text string
*
* @property {function} camelizeVar
* {@link utils.camelizeVar camelizeVar} - camelizes a text string as a variable
*
* @property {function} enumSql
* {@link utils.enumSql enumSql} - enumerates SQL files in a directory
*
* @property {function} taskArgs
* {@link utils.taskArgs taskArgs} - prepares arguments for tasks and transactions
*/
module.exports = {
camelize,
camelizeVar,
enumSql,
taskArgs
};