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

21
ProjectSourceCode/node_modules/pg-promise/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2018 Vitaly Tomilov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1136
ProjectSourceCode/node_modules/pg-promise/README.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

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
};

62
ProjectSourceCode/node_modules/pg-promise/package.json generated vendored Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "pg-promise",
"version": "10.15.4",
"description": "PostgreSQL interface for Node.js",
"main": "lib/index.js",
"typings": "typescript/pg-promise.d.ts",
"scripts": {
"spelling": "cspell --config=.cspell.json \"**/*.{md,ts,js}\" --no-progress",
"coverage": "istanbul cover ./node_modules/jasmine-node/bin/jasmine-node test",
"doc": "jsdoc -c ./jsdoc/jsdoc.js ./jsdoc/README.md -t ./jsdoc/templates/custom",
"lint": "eslint ./lib ./test/*.js ./test/db --fix",
"test": "jasmine-node --captureExceptions test",
"test:native": "jasmine-node test --config PG_NATIVE true",
"tslint": "tslint ./typescript/*.ts"
},
"files": [
"lib",
"typescript"
],
"homepage": "https://github.com/vitaly-t/pg-promise",
"repository": {
"type": "git",
"url": "https://github.com/vitaly-t/pg-promise.git"
},
"bugs": {
"url": "https://github.com/vitaly-t/pg-promise/issues",
"email": "vitaly.tomilov@gmail.com"
},
"keywords": [
"pg",
"promise",
"postgres"
],
"author": {
"name": "Vitaly Tomilov",
"email": "vitaly.tomilov@gmail.com"
},
"license": "MIT",
"engines": {
"node": ">=12.0"
},
"dependencies": {
"assert-options": "0.8.0",
"pg": "8.8.0",
"pg-minify": "1.6.2",
"spex": "3.2.0"
},
"devDependencies": {
"@types/node": "18.11.9",
"bluebird": "3.7.2",
"coveralls": "3.1.1",
"cspell": "6.15.0",
"eslint": "8.28.0",
"istanbul": "0.4.5",
"jasmine-node": "3.0.0",
"jsdoc": "3.6.11",
"JSONStream": "1.3.5",
"pg-query-stream": "4.2.4",
"tslint": "6.1.3",
"typescript": "4.9.3"
}
}

View File

@@ -0,0 +1,63 @@
## TypeScript for pg-promise
Complete TypeScript declarations for [pg-promise].
### Inclusion
Typescript should be able to pick up the definitions without any manual configuration.
### Simple Usage
```ts
import pgPromise from 'pg-promise';
const pgp = pgPromise({/* Initialization Options */});
const db = pgp('postgres://username:password@host:port/database');
const {value} = await db.one('SELECT 123 as value');
```
#### With Extensions
The library supports dynamic protocol extensions, via event [extend], which requires
explicit extension interface to be declared and parameterized, as shown below.
```ts
import * as pgPromise from 'pg-promise';
// your protocol extensions:
interface IExtensions {
findUser(userId: number): Promise<any>;
}
// pg-promise initialization options:
const options: pgPromise.IInitOptions<IExtensions> = {
extend(obj) {
obj.findUser = userId => {
return obj.one('SELECT * FROM Users WHERE id = $1', [userId]);
}
}
};
// initializing the library:
const pgp = pgPromise(options);
// database object:
const db = pgp('postgres://username:password@host:port/database');
// protocol is extended on each level:
const user = await db.findUser(123);
// ...including inside tasks and transactions:
await db.task(async t => {
const user = await t.findUser(123);
// ...etc
});
```
For a comprehensive example, check out [pg-promise-demo].
[pg-promise-demo]:https://github.com/vitaly-t/pg-promise-demo
[extend]:http://vitaly-t.github.io/pg-promise/global.html#event:extend
[pg-promise]:https://github.com/vitaly-t/pg-promise

View File

@@ -0,0 +1,699 @@
/*
* 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.
*/
/////////////////////////////////////////
// Requires pg-promise v10.14.0 or later.
/////////////////////////////////////////
// We use ES6 as static promise here, because generic promises are still not supported.
// Follow the links below:
// http://stackoverflow.com/questions/36593087/using-a-custom-promise-as-a-generic-type
// https://github.com/Microsoft/TypeScript/issues/1213
type XPromise<T> = Promise<T>;
import * as pg from './pg-subset';
import * as pgMinify from 'pg-minify';
import * as spexLib from 'spex';
// internal namespace for "txMode" property:
declare namespace _txMode {
// Transaction Isolation Level;
// API: http://vitaly-t.github.io/pg-promise/txMode.html#.isolationLevel
enum isolationLevel {
none = 0,
serializable = 1,
repeatableRead = 2,
readCommitted = 3
}
// TransactionMode class;
// API: http://vitaly-t.github.io/pg-promise/txMode.TransactionMode.html
class TransactionMode {
constructor(options?: { tiLevel?: isolationLevel, readOnly?: boolean, deferrable?: boolean })
begin(cap?: boolean): string
}
}
// Main protocol of the library;
// API: http://vitaly-t.github.io/pg-promise/module-pg-promise.html
declare namespace pgPromise {
interface IQueryFileOptions {
debug?: boolean
minify?: boolean | 'after'
compress?: boolean
params?: any
noWarnings?: boolean
}
interface IFormattingOptions {
capSQL?: boolean
partial?: boolean
def?: any
}
interface ILostContext<C extends pg.IClient = pg.IClient> {
cn: string
dc: any
start: Date
client: C
}
interface IConnectionOptions<C extends pg.IClient = pg.IClient> {
direct?: boolean
onLost?(err: any, e: ILostContext<C>): void
}
interface IPreparedStatement {
name?: string
text?: string | QueryFile
values?: any[]
binary?: boolean
rowMode?: 'array' | null | void
rows?: number
}
interface IParameterizedQuery {
text?: string | QueryFile
values?: any[]
binary?: boolean
rowMode?: void | 'array'
}
interface IPreparedParsed {
name: string
text: string
values: any[]
binary: boolean
rowMode: void | 'array'
rows: number
}
interface IParameterizedParsed {
text: string
values: any[]
binary: boolean
rowMode: void | 'array'
}
interface IColumnDescriptor<T> {
source: T
name: string
value: any
exists: boolean
}
interface IColumnConfig<T> {
name: string
prop?: string
mod?: FormattingFilter
cast?: string
cnd?: boolean
def?: any
init?(col: IColumnDescriptor<T>): any
skip?(col: IColumnDescriptor<T>): boolean
}
interface IColumnSetOptions {
table?: string | ITable | TableName
inherit?: boolean
}
interface ITable {
table: string
schema?: string
}
interface IPromiseConfig {
create(resolve: (value?: any) => void, reject?: (reason?: any) => void): XPromise<any>
resolve(value?: any): void
reject(reason?: any): void
all(iterable: any): XPromise<any>
}
type FormattingFilter = '^' | '~' | '#' | ':raw' | ':alias' | ':name' | ':json' | ':csv' | ':list' | ':value';
type QueryColumns<T> = Column<T> | ColumnSet<T> | Array<string | IColumnConfig<T> | Column<T>>;
type QueryParam =
string
| QueryFile
| IPreparedStatement
| IParameterizedQuery
| PreparedStatement
| ParameterizedQuery
| ((values?: any) => QueryParam);
type ValidSchema = string | string[] | null | void;
// helpers.TableName class;
// API: http://vitaly-t.github.io/pg-promise/helpers.TableName.html
class TableName {
constructor(table: string | ITable)
// these are all read-only:
readonly name: string;
readonly table: string;
readonly schema: string;
toString(): string
}
// helpers.Column class;
// API: http://vitaly-t.github.io/pg-promise/helpers.Column.html
class Column<T = unknown> {
constructor(col: string | IColumnConfig<T>);
// these are all read-only:
readonly name: string;
readonly prop: string;
readonly mod: FormattingFilter;
readonly cast: string;
readonly cnd: boolean;
readonly def: any;
readonly castText: string;
readonly escapedName: string;
readonly variable: string;
readonly init: (col: IColumnDescriptor<T>) => any
readonly skip: (col: IColumnDescriptor<T>) => boolean
toString(level?: number): string
}
// helpers.Column class;
// API: http://vitaly-t.github.io/pg-promise/helpers.ColumnSet.html
class ColumnSet<T = unknown> {
constructor(columns: Column<T>, options?: IColumnSetOptions)
constructor(columns: Array<string | IColumnConfig<T> | Column<T>>, options?: IColumnSetOptions)
constructor(columns: object, options?: IColumnSetOptions)
readonly columns: Column<T>[];
readonly names: string;
readonly table: TableName;
readonly variables: string;
assign(source?: { source?: object, prefix?: string }): string
assignColumns(options?: { from?: string, to?: string, skip?: string | string[] | ((c: Column<T>) => boolean) }): string
extend<S>(columns: Column<T> | ColumnSet<T> | Array<string | IColumnConfig<T> | Column<T>>): ColumnSet<S>
merge<S>(columns: Column<T> | ColumnSet<T> | Array<string | IColumnConfig<T> | Column<T>>): ColumnSet<S>
prepare(obj: object): object
toString(level?: number): string
}
const minify: typeof pgMinify;
// Query Result Mask;
// API: http://vitaly-t.github.io/pg-promise/global.html#queryResult
enum queryResult {
one = 1,
many = 2,
none = 4,
any = 6
}
// PreparedStatement class;
// API: http://vitaly-t.github.io/pg-promise/PreparedStatement.html
class PreparedStatement {
constructor(options?: IPreparedStatement)
// standard properties:
name: string;
text: string | QueryFile;
values: any[];
// advanced properties:
binary: boolean;
rowMode: void | 'array';
rows: number;
parse(): IPreparedParsed | errors.PreparedStatementError
toString(level?: number): string
}
// ParameterizedQuery class;
// API: http://vitaly-t.github.io/pg-promise/ParameterizedQuery.html
class ParameterizedQuery {
constructor(options?: string | QueryFile | IParameterizedQuery)
// standard properties:
text: string | QueryFile;
values: any[];
// advanced properties:
binary: boolean;
rowMode: void | 'array';
parse(): IParameterizedParsed | errors.ParameterizedQueryError
toString(level?: number): string
}
// QueryFile class;
// API: http://vitaly-t.github.io/pg-promise/QueryFile.html
class QueryFile {
constructor(file: string, options?: IQueryFileOptions)
readonly error: Error;
readonly file: string;
readonly options: any;
prepare(): void
toString(level?: number): string
}
// PromiseAdapter class;
// API: http://vitaly-t.github.io/pg-promise/PromiseAdapter.html
class PromiseAdapter {
constructor(api: IPromiseConfig)
}
const txMode: typeof _txMode;
const utils: IUtils;
const as: IFormatting;
// Database full protocol;
// API: http://vitaly-t.github.io/pg-promise/Database.html
//
// We export this interface only to be able to help IntelliSense cast extension types correctly,
// which doesn't always work, depending on the version of IntelliSense being used.
interface IDatabase<Ext, C extends pg.IClient = pg.IClient> extends IBaseProtocol<Ext> {
connect(options?: IConnectionOptions<C>): XPromise<IConnected<Ext, C>>
/////////////////////////////////////////////////////////////////////////////
// Hidden, read-only properties, for integrating with third-party libraries:
readonly $config: ILibConfig<Ext, C>
readonly $cn: string | pg.IConnectionParameters<C>
readonly $dc: any
readonly $pool: pg.IPool
}
interface IResultExt<T = unknown> extends pg.IResult<T> {
// Property 'duration' exists only in the following context:
// - for single-query events 'receive'
// - for method Database.result
duration?: number
}
// Post-initialization interface;
// API: http://vitaly-t.github.io/pg-promise/module-pg-promise.html
interface IMain<Ext = {}, C extends pg.IClient = pg.IClient> {
<T = Ext, C extends pg.IClient = pg.IClient>(cn: string | pg.IConnectionParameters<C>, dc?: any): IDatabase<T, C> & T
readonly PromiseAdapter: typeof PromiseAdapter
readonly PreparedStatement: typeof PreparedStatement
readonly ParameterizedQuery: typeof ParameterizedQuery
readonly QueryFile: typeof QueryFile
readonly queryResult: typeof queryResult
readonly minify: typeof pgMinify
readonly spex: spexLib.ISpex
readonly errors: typeof errors
readonly utils: IUtils
readonly txMode: typeof txMode
readonly helpers: IHelpers
readonly as: IFormatting
readonly pg: typeof pg
end(): void
}
// Additional methods available inside tasks + transactions;
// API: http://vitaly-t.github.io/pg-promise/Task.html
interface ITask<Ext> extends IBaseProtocol<Ext>, spexLib.ISpexBase {
readonly ctx: ITaskContext
}
// Base database protocol
// API: http://vitaly-t.github.io/pg-promise/Database.html
interface IBaseProtocol<Ext> {
// API: http://vitaly-t.github.io/pg-promise/Database.html#query
query<T = any>(query: QueryParam, values?: any, qrm?: queryResult): XPromise<T>
// result-specific methods;
// API: http://vitaly-t.github.io/pg-promise/Database.html#none
none(query: QueryParam, values?: any): XPromise<null>
// API: http://vitaly-t.github.io/pg-promise/Database.html#one
one<T = any>(query: QueryParam, values?: any, cb?: (value: any) => T, thisArg?: any): XPromise<T>
// API: http://vitaly-t.github.io/pg-promise/Database.html#oneOrNone
oneOrNone<T = any>(query: QueryParam, values?: any, cb?: (value: any) => T, thisArg?: any): XPromise<T | null>
// API: http://vitaly-t.github.io/pg-promise/Database.html#many
many<T = any>(query: QueryParam, values?: any): XPromise<T[]>
// API: http://vitaly-t.github.io/pg-promise/Database.html#manyOrNone
manyOrNone<T = any>(query: QueryParam, values?: any): XPromise<T[]>
// API: http://vitaly-t.github.io/pg-promise/Database.html#any
any<T = any>(query: QueryParam, values?: any): XPromise<T[]>
// API: http://vitaly-t.github.io/pg-promise/Database.html#result
result<T, R = IResultExt<T>>(query: QueryParam, values?: any, cb?: (value: IResultExt<T>) => R, thisArg?: any): XPromise<R>
// API: http://vitaly-t.github.io/pg-promise/Database.html#multiResult
multiResult(query: QueryParam, values?: any): XPromise<pg.IResult[]>
// API: http://vitaly-t.github.io/pg-promise/Database.html#multi
multi<T = any>(query: QueryParam, values?: any): XPromise<Array<T[]>>
// API: http://vitaly-t.github.io/pg-promise/Database.html#stream
stream(qs: NodeJS.ReadableStream, init: (stream: NodeJS.ReadableStream) => void): XPromise<{ processed: number, duration: number }>
// API: http://vitaly-t.github.io/pg-promise/Database.html#func
func<T = any>(funcName: string, values?: any, qrm?: queryResult): XPromise<T>
// API: http://vitaly-t.github.io/pg-promise/Database.html#proc
proc<T = any>(procName: string, values?: any, cb?: (value: any) => T, thisArg?: any): XPromise<T | null>
// API: http://vitaly-t.github.io/pg-promise/Database.html#map
map<T = any>(query: QueryParam, values: any, cb: (row: any, index: number, data: any[]) => T, thisArg?: any): XPromise<T[]>
// API: http://vitaly-t.github.io/pg-promise/Database.html#each
each<T = any>(query: QueryParam, values: any, cb: (row: any, index: number, data: any[]) => void, thisArg?: any): XPromise<T[]>
// Tasks;
// API: http://vitaly-t.github.io/pg-promise/Database.html#task
task<T = any>(cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
task<T = any>(tag: string | number, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
task<T = any>(options: { tag?: any }, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
// Conditional Tasks;
// API: http://vitaly-t.github.io/pg-promise/Database.html#taskIf
taskIf<T = any>(cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
taskIf<T = any>(tag: string | number, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
taskIf<T = any>(options: { tag?: any, cnd?: boolean | ((t: ITask<Ext> & Ext) => boolean) }, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
// Transactions;
// API: http://vitaly-t.github.io/pg-promise/Database.html#tx
tx<T = any>(cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
tx<T = any>(tag: string | number, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
tx<T = any>(options: { tag?: any, mode?: _txMode.TransactionMode | null }, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
// Conditional Transactions;
// API: http://vitaly-t.github.io/pg-promise/Database.html#txIf
txIf<T = any>(cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
txIf<T = any>(tag: string | number, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
txIf<T = any>(options: { tag?: any, mode?: _txMode.TransactionMode | null, reusable?: boolean | ((t: ITask<Ext> & Ext) => boolean), cnd?: boolean | ((t: ITask<Ext> & Ext) => boolean) }, cb: (t: ITask<Ext> & Ext) => T | XPromise<T>): XPromise<T>
}
// Database object in connected state;
// API: https://vitaly-t.github.io/pg-promise/Database.html#connect
interface IConnected<Ext, C extends pg.IClient> extends IBaseProtocol<Ext>, spexLib.ISpexBase {
readonly client: C
// Note that for normal connections (working with the pool), method `done` accepts `kill`
// flag to terminate the connection within the pool, so it can be auto-recreated;
// And in this case the method returns nothing / void.
// But for direct connections (connect({direct: true})), `kill` flag is ignored, because
// the connection is always closed physically, which may take time, and so in this case
// the method returns a Promise, to indicate when the connection finished closing.
done(kill?: boolean): void | XPromise<void>;
// Repeated calls are not allowed, and will throw an error.
}
// Event context extension for tasks + transactions;
// See: http://vitaly-t.github.io/pg-promise/global.html#TaskContext
interface ITaskContext {
// these are set in the beginning of each task/transaction:
readonly context: any
readonly parent: ITaskContext | null
readonly connected: boolean
readonly inTransaction: boolean
readonly level: number
readonly useCount: number
readonly isTX: boolean
readonly start: Date
readonly tag: any
readonly dc: any
// these are set at the end of each task/transaction:
readonly finish?: Date
readonly duration?: number
readonly success?: boolean
readonly result?: any
// this exists only inside transactions (isTX = true):
readonly txLevel?: number
// Version of PostgreSQL Server to which we are connected;
// This property is not available with Native Bindings!
readonly serverVersion: string
}
// Generic Event Context interface;
// See: http://vitaly-t.github.io/pg-promise/global.html#EventContext
interface IEventContext<C extends pg.IClient = pg.IClient> {
client: C
cn: any
dc: any
query: any
params: any
ctx: ITaskContext
}
// Errors namespace
// API: http://vitaly-t.github.io/pg-promise/errors.html
namespace errors {
// QueryResultError interface;
// API: http://vitaly-t.github.io/pg-promise/errors.QueryResultError.html
class QueryResultError extends Error {
// standard error properties:
name: string;
message: string;
stack: string;
// extended properties:
result: pg.IResult;
received: number;
code: queryResultErrorCode;
query: string;
values: any;
// API: http://vitaly-t.github.io/pg-promise/errors.QueryResultError.html#toString
toString(): string
}
// QueryFileError interface;
// API: http://vitaly-t.github.io/pg-promise/errors.QueryFileError.html
class QueryFileError extends Error {
// standard error properties:
name: string;
message: string;
stack: string;
// extended properties:
file: string;
options: IQueryFileOptions;
error: pgMinify.SQLParsingError;
toString(level?: number): string
}
// PreparedStatementError interface;
// API: http://vitaly-t.github.io/pg-promise/errors.PreparedStatementError.html
class PreparedStatementError extends Error {
// standard error properties:
name: string;
message: string;
stack: string;
// extended properties:
error: QueryFileError;
toString(level?: number): string
}
// ParameterizedQueryError interface;
// API: http://vitaly-t.github.io/pg-promise/errors.ParameterizedQueryError.html
class ParameterizedQueryError extends Error {
// standard error properties:
name: string;
message: string;
stack: string;
// extended properties:
error: QueryFileError;
toString(level?: number): string
}
// Query Result Error Code;
// API: http://vitaly-t.github.io/pg-promise/errors.html#.queryResultErrorCode
enum queryResultErrorCode {
noData = 0,
notEmpty = 1,
multiple = 2
}
}
// Library's Initialization Options
// API: http://vitaly-t.github.io/pg-promise/module-pg-promise.html
interface IInitOptions<Ext = {}, C extends pg.IClient = pg.IClient> {
noWarnings?: boolean
pgFormatting?: boolean
pgNative?: boolean
promiseLib?: any
noLocking?: boolean
capSQL?: boolean
schema?: ValidSchema | ((dc: any) => ValidSchema)
connect?(client: C, dc: any, useCount: number): void
disconnect?(client: C, dc: any): void
query?(e: IEventContext<C>): void
// NOTE: result is undefined when data comes from QueryStream, i.e. via method Database.stream
receive?(data: any[], result: IResultExt | void, e: IEventContext<C>): void
task?(e: IEventContext<C>): void
transact?(e: IEventContext<C>): void
error?(err: any, e: IEventContext<C>): void
extend?(obj: IDatabase<Ext, C> & Ext, dc: any): void
}
// API: http://vitaly-t.github.io/pg-promise/Database.html#$config
interface ILibConfig<Ext, C extends pg.IClient = pg.IClient> {
version: string
promiseLib: any
promise: IGenericPromise
options: IInitOptions<Ext, C>
pgp: IMain<Ext, C>
$npm: any
}
// Custom-Type Formatting object
// API: https://github.com/vitaly-t/pg-promise#custom-type-formatting
interface ICTFObject {
toPostgres(a: any): any
}
// Query formatting namespace;
// API: http://vitaly-t.github.io/pg-promise/formatting.html
interface IFormatting {
ctf: { toPostgres: symbol, rawType: symbol }
alias(name: string | (() => string)): string
array(arr: any[] | (() => any[]), options?: { capSQL?: boolean }): string
bool(value: any | (() => any)): string
buffer(obj: object | (() => object), raw?: boolean): string
csv(values: any | (() => any)): string
date(d: Date | (() => Date), raw?: boolean): string
format(query: string | QueryFile | ICTFObject, values?: any, options?: IFormattingOptions): string
func(func: (cc: any) => any, raw?: boolean, cc?: any): string
json(data: any | (() => any), raw?: boolean): string
name(name: any | (() => any)): string
number(value: number | bigint | (() => number | bigint)): string
text(value: any | (() => any), raw?: boolean): string
value(value: any | (() => any)): string
}
interface ITaskArguments<T> extends IArguments {
options: { tag?: any, cnd?: any, mode?: _txMode.TransactionMode | null } & T
cb(): any
}
// General-purpose functions
// API: http://vitaly-t.github.io/pg-promise/utils.html
interface IUtils {
camelize(text: string): string
camelizeVar(text: string): string
enumSql(dir: string, options?: { recursive?: boolean, ignoreErrors?: boolean }, cb?: (file: string, name: string, path: string) => any): any
taskArgs<T = {}>(args: IArguments): ITaskArguments<T>
}
// Query Formatting Helpers
// API: http://vitaly-t.github.io/pg-promise/helpers.html
interface IHelpers {
concat(queries: Array<string | QueryFile | { query: string | QueryFile, values?: any, options?: IFormattingOptions }>): string
insert(data: object | object[], columns?: QueryColumns<any> | null, table?: string | ITable | TableName): string
update(data: object | object[], columns?: QueryColumns<any> | null, table?: string | ITable | TableName, options?: { tableAlias?: string, valueAlias?: string, emptyUpdate?: any }): any
values(data: object | object[], columns?: QueryColumns<any> | null): string
sets(data: object, columns?: QueryColumns<any> | null): string
Column: typeof Column
ColumnSet: typeof ColumnSet
TableName: typeof TableName
}
interface IGenericPromise {
(cb: (resolve: (value?: any) => void, reject: (reason?: any) => void) => void): XPromise<any>
resolve(value?: any): void
reject(reason?: any): void
all(iterable: any): XPromise<any>
}
}
// Default library interface (before initialization)
// API: http://vitaly-t.github.io/pg-promise/module-pg-promise.html
declare function pgPromise<Ext = {}, C extends pg.IClient = pg.IClient>(options?: pgPromise.IInitOptions<Ext, C>): pgPromise.IMain<Ext, C>
export = pgPromise;

View File

@@ -0,0 +1,334 @@
/*
* 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.
*/
//////////////////////////////////////////////////////////////////////////////
// Declaring only a subset of the 'pg' module that's useful within pg-promise.
//
// Calling it 'pg-subset' to avoid a conflict in case the application also
// includes the official 'pg' typings.
//
// Supported version of pg: 8.7.1 and later.
//
// pg: https://github.com/brianc/node-postgres
//////////////////////////////////////////////////////////////////////////////
import {EventEmitter} from 'events';
import {checkServerIdentity} from 'tls';
declare namespace pg {
import Socket = NodeJS.Socket;
interface IColumn {
name: string
oid: number
dataTypeID: number
// NOTE: properties below are not available within Native Bindings:
tableID: number
columnID: number
dataTypeSize: number
dataTypeModifier: number
format: string
}
interface IResult<T = unknown> extends Iterable<T> {
command: string
rowCount: number
rows: T[]
fields: IColumn[]
// properties below are not available within Native Bindings:
rowAsArray: boolean
_types: {
_types: any,
text: any,
binary: any
};
_parsers: Array<Function>;
}
// SSL configuration;
// For property types and documentation see:
// http://nodejs.org/api/tls.html#tls_tls_connect_options_callback
interface ISSLConfig {
ca?: string | Buffer | Array<string | Buffer>
pfx?: string | Buffer | Array<string | Buffer | object>
cert?: string | Buffer | Array<string | Buffer>
key?: string | Buffer | Array<Buffer | object>
passphrase?: string
rejectUnauthorized?: boolean
checkServerIdentity?: typeof checkServerIdentity
secureOptions?: number
NPNProtocols?: string[] | Buffer | Buffer[] | Uint8Array | Uint8Array[]
}
type DynamicPassword = string | (() => string) | (() => Promise<string>);
// See:
// 1) https://github.com/brianc/node-postgres/blob/master/packages/pg/lib/defaults.js
// 2) https://github.com/brianc/node-pg-pool
interface IConnectionParameters<C extends IClient = IClient> {
connectionString?: string
host?: string
database?: string
user?: string
password?: DynamicPassword
port?: number
ssl?: boolean | ISSLConfig
binary?: boolean
client_encoding?: string
encoding?: string
application_name?: string
fallback_application_name?: string
isDomainSocket?: boolean
max?: number
maxUses?: number
idleTimeoutMillis?: number
parseInputDatesAsUTC?: boolean
rows?: number
statement_timeout?: boolean | number
query_timeout?: boolean | number
connectionTimeoutMillis?: number
keepAliveInitialDelayMillis?: number
keepAlive?: boolean
keepalives?: number
keepalives_idle?: number
Client?: new(config: string | IConnectionParameters) => C
Promise?: any
types?: ITypeOverrides
allowExitOnIdle?: boolean
maxLifetimeSeconds?: number
}
// Type id-s supported by PostgreSQL, copied from:
// http://github.com/brianc/node-pg-types/blob/master/lib/builtins.js
enum TypeId {
BOOL = 16,
BYTEA = 17,
CHAR = 18,
INT8 = 20,
INT2 = 21,
INT4 = 23,
REGPROC = 24,
TEXT = 25,
OID = 26,
TID = 27,
XID = 28,
CID = 29,
JSON = 114,
XML = 142,
PG_NODE_TREE = 194,
SMGR = 210,
PATH = 602,
POLYGON = 604,
CIDR = 650,
FLOAT4 = 700,
FLOAT8 = 701,
ABSTIME = 702,
RELTIME = 703,
TINTERVAL = 704,
CIRCLE = 718,
MACADDR8 = 774,
MONEY = 790,
MACADDR = 829,
INET = 869,
ACLITEM = 1033,
BPCHAR = 1042,
VARCHAR = 1043,
DATE = 1082,
TIME = 1083,
TIMESTAMP = 1114,
TIMESTAMPTZ = 1184,
INTERVAL = 1186,
TIMETZ = 1266,
BIT = 1560,
VARBIT = 1562,
NUMERIC = 1700,
REFCURSOR = 1790,
REGPROCEDURE = 2202,
REGOPER = 2203,
REGOPERATOR = 2204,
REGCLASS = 2205,
REGTYPE = 2206,
UUID = 2950,
TXID_SNAPSHOT = 2970,
PG_LSN = 3220,
PG_NDISTINCT = 3361,
PG_DEPENDENCIES = 3402,
TSVECTOR = 3614,
TSQUERY = 3615,
GTSVECTOR = 3642,
REGCONFIG = 3734,
REGDICTIONARY = 3769,
JSONB = 3802,
REGNAMESPACE = 4089,
REGROLE = 4096
}
type ParserFormat = 'text' | 'binary';
// Interface for TypeOverrides;
// See: https://github.com/brianc/node-postgres/blob/master/packages/pg/lib/type-overrides.js
interface ITypeOverrides {
setTypeParser(id: TypeId, parseFn: string | ((value: string) => any)): void
setTypeParser(id: TypeId, format: ParserFormat, parseFn: string | ((value: string) => any)): void
getTypeParser(id: TypeId, format?: ParserFormat): any
}
// Interface of 'pg-types' module;
// See: https://github.com/brianc/node-pg-types
interface ITypes extends ITypeOverrides {
arrayParser(source: string, transform: (entry: any) => any): any[]
builtins: typeof TypeId
}
interface IDefaults {
// connection string for overriding defaults
connectionString: string
// database host. defaults to localhost
host: string
// database user's name
user: string
// name of database to connect
database: string
// database user's password
password: DynamicPassword
// database port
port: number
// number of rows to return at a time from a prepared statement's
// portal. 0 will return all rows at once
rows: number
// binary result mode
binary: boolean
// Connection pool options - see https://github.com/brianc/node-pg-pool
// number of connections to use in connection pool
// 0 will disable connection pooling
max: number
// max milliseconds a client can go unused before it is removed from the pool and destroyed;
//
// Made unavailable in v10.5.0, due to the following:
// - https://github.com/brianc/node-postgres/issues/2139
// - https://github.com/vitaly-t/pg-promise/issues/703
//
// idleTimeoutMillis: number
client_encoding: string
ssl: boolean | ISSLConfig
application_name: string
fallback_application_name: string
parseInputDatesAsUTC: boolean
// max milliseconds any query using this connection will execute for before timing out in error.
// false=unlimited
statement_timeout: boolean | number
// max milliseconds to wait for query to complete (client side)
query_timeout: boolean | number
keepalives: number
keepalives_idle: number
}
// interface IPool, as per the following implementation:
// https://github.com/brianc/node-postgres/blob/master/packages/pg-pool/index.js#L61
// NOTE: We declare only what can be used from pg-promise
interface IPool extends EventEmitter {
connect(): Promise<IClient>;
end(): Promise<undefined>;
end(cb: (err: Error) => any): any;
readonly options: { [name: string]: any }; // connection options
readonly ended: boolean;
readonly ending: boolean;
readonly waitingCount: number;
readonly idleCount: number;
readonly totalCount: number;
}
interface IQuery {
// this type is not used within pg-promise;
}
interface IConnection extends EventEmitter {
/*
While there are many other properties exist within the connection,
the only one that may be remotely useful is the stream, to be able
to listen to its events, from within a custom Client class.
*/
stream: Socket
}
interface IClient extends EventEmitter {
query<T>(config: any, values: any[], callback: (err: Error, result: IResult<T>) => void): void
query<T>(config: any, callback: (err: Error, result: IResult<T>) => void): void
query<T>(config: any, values: any[]): Promise<IResult<T>>
query<T>(config: any): Promise<IResult<T>>
release(): void
connectionParameters: IConnectionParameters
database: string
user: string
password: DynamicPassword
port: number
host: string
//////////////////////////////////////////////////////////////
// Properties below are not available within Native Bindings:
readonly serverVersion: string // PostgreSQL Server to which the client is connected
connection: IConnection
queryQueue: IQuery[]
binary: boolean
ssl: boolean | ISSLConfig
secretKey: number
processID: number
encoding: string
readyForQuery: boolean
activeQuery: IQuery
}
const defaults: IDefaults;
const types: ITypes;
const Client: new(config: string | IConnectionParameters) => IClient;
}
export = pg;

View File

@@ -0,0 +1,21 @@
{
"rules": {
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"quotemark": [
true,
"single"
],
"class-name": true,
"interface-name": [
true,
"always-prefix"
],
"no-consecutive-blank-lines": true
}
}