Update to NPM version

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

View File

@@ -0,0 +1,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};