Socket
Socket
Sign inDemoInstall

knex

Package Overview
Dependencies
24
Maintainers
4
Versions
252
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.95.10 to 0.95.11

lib/dialects/oracledb/schema/oracledb-tablecompiler.js

17

lib/dialects/mssql/mssql-formatter.js

@@ -15,4 +15,21 @@ const Formatter = require('../../formatter');

}
/**
* Returns its argument with single quotes escaped, so it can be included into a single-quoted string.
*
* For example, it converts "has'quote" to "has''quote".
*
* This assumes QUOTED_IDENTIFIER ON so it is only ' that need escaping,
* never ", because " cannot be used to quote a string when that's on;
* otherwise we'd need to be aware of whether the string is quoted with " or '.
*
* This assumption is consistent with the SQL Knex generates.
* @param {string} string
* @returns {string}
*/
escapingStringDelimiters(string) {
return (string || '').replace(/'/g, "''");
}
}
module.exports = MSSQL_Formatter;

27

lib/dialects/mssql/query/mssql-querycompiler.js

@@ -34,2 +34,26 @@ // MSSQL Query Compiler

with() {
// WITH RECURSIVE is a syntax error:
// SQL Server does not syntactically distinguish recursive and non-recursive CTEs.
// So mark all statements as non-recursive, generate the SQL, then restore.
// This approach ensures any changes in base class with() get propagated here.
const undoList = [];
if (this.grouped.with) {
for (const stmt of this.grouped.with) {
if (stmt.recursive) {
undoList.push(stmt);
stmt.recursive = false;
}
}
}
const result = super.with();
// Restore the recursive markings, in case this same query gets cloned and passed to other drivers.
for (const stmt of undoList) {
stmt.recursive = true;
}
return result;
}
select() {

@@ -433,3 +457,4 @@ const sql = this.with();

let sql = `select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from information_schema.columns where table_name = ? and table_catalog = ?`;
// GOTCHA: INFORMATION_SCHEMA.COLUMNS must be capitalized to work when the database has a case-sensitive collation. [#4573]
let sql = `select [COLUMN_NAME], [COLUMN_DEFAULT], [DATA_TYPE], [CHARACTER_MAXIMUM_LENGTH], [IS_NULLABLE] from INFORMATION_SCHEMA.COLUMNS where table_name = ? and table_catalog = ?`;
const bindings = [table, this.client.database()];

@@ -436,0 +461,0 @@

@@ -95,3 +95,7 @@ // MSSQL Column Compiler

comment(comment) {
comment(/** @type {string} */ comment) {
if (!comment) {
return;
}
// XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without database collation info.

@@ -105,2 +109,21 @@ // (Yes, even if the column has its own collation, the sqlvariant still uses the database collation.)

}
// See: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-addextendedproperty-transact-sql?view=sql-server-ver15#b-adding-an-extended-property-to-a-column-in-a-table
const value = this.formatter.escapingStringDelimiters(comment);
const level0name = this.tableCompiler.schemaNameRaw || 'dbo';
const level1name = this.formatter.escapingStringDelimiters(
this.tableCompiler.tableNameRaw
);
const level2name = this.formatter.escapingStringDelimiters(
this.args[0] || this.defaults('columnName')
);
const args = `N'MS_Description', N'${value}', N'Schema', N'${level0name}', N'Table', N'${level1name}', N'Column', N'${level2name}'`;
this.pushAdditional(function () {
const isAlreadyDefined = `EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N'${level0name}', N'Table', N'${level1name}', N'Column', N'${level2name}'))`;
this.pushQuery(
`IF ${isAlreadyDefined}\n EXEC sys.sp_updateextendedproperty ${args}\nELSE\n EXEC sys.sp_addextendedproperty ${args}`
);
});
return '';

@@ -107,0 +130,0 @@ }

40

lib/dialects/mssql/schema/mssql-tablecompiler.js

@@ -27,12 +27,35 @@ /* eslint max-len:0 */

this.pushQuery(sql);
if (this.single.comment) {
const { comment } = this.single;
// XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without database collation info.
if (comment.length > 7500 / 2)
this.client.logger.warn(
'Your comment might be longer than the max comment length for MSSQL of 7,500 bytes.'
);
this.comment(this.single.comment);
}
}
this.pushQuery(sql);
comment(/** @type {string} */ comment) {
if (!comment) {
return;
}
// XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without server collation info.
// When I checked in SQL Server 2019, the ctext column in sys.syscomments is defined as a varbinary(8000), so it doesn't even have its own defined collation.
if (comment.length > 7500 / 2) {
this.client.logger.warn(
'Your comment might be longer than the max comment length for MSSQL of 7,500 bytes.'
);
}
// See: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-addextendedproperty-transact-sql?view=sql-server-ver15#f-adding-an-extended-property-to-a-table
const value = this.formatter.escapingStringDelimiters(comment);
const level0name = this.formatter.escapingStringDelimiters(
this.schemaNameRaw || 'dbo'
);
const level1name = this.formatter.escapingStringDelimiters(
this.tableNameRaw
);
const args = `N'MS_Description', N'${value}', N'Schema', N'${level0name}', N'Table', N'${level1name}'`;
const isAlreadyDefined = `EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N'${level0name}', N'Table', N'${level1name}', NULL, NULL))`;
this.pushQuery(
`IF ${isAlreadyDefined}\n EXEC sys.sp_updateextendedproperty ${args}\nELSE\n EXEC sys.sp_addextendedproperty ${args}`
);
}

@@ -133,5 +156,2 @@

// Compiles the comment on the table.
comment() {}
changeType() {}

@@ -138,0 +158,0 @@

@@ -13,2 +13,3 @@ // Oracledb Client

const QueryCompiler = require('./query/oracledb-querycompiler');
const TableCompiler = require('./schema/oracledb-tablecompiler');
const ColumnCompiler = require('./schema/oracledb-columncompiler');

@@ -62,2 +63,6 @@ const { BlobHelper, ReturningHelper, isConnectionError } = require('./utils');

tableCompiler() {
return new TableCompiler(this, ...arguments);
}
columnCompiler() {

@@ -64,0 +69,0 @@ return new ColumnCompiler(this, ...arguments);

@@ -204,2 +204,25 @@ const clone = require('lodash/clone');

with() {
// WITH RECURSIVE is a syntax error in Oracle SQL.
// So mark all statements as non-recursive, generate the SQL, then restore.
// This approach ensures any changes in base class with() get propagated here.
const undoList = [];
if (this.grouped.with) {
for (const stmt of this.grouped.with) {
if (stmt.recursive) {
undoList.push(stmt);
stmt.recursive = false;
}
}
}
const result = super.with();
// Restore the recursive markings, in case this same query gets cloned and passed to other drivers.
for (const stmt of undoList) {
stmt.recursive = true;
}
return result;
}
_addReturningToSqlAndConvert(sql, outBinding, tableName, returning) {

@@ -206,0 +229,0 @@ const self = this;

@@ -24,2 +24,12 @@ /* eslint max-len: 0 */

_setNullableState(column, isNullable) {
const constraintAction = isNullable ? 'drop not null' : 'set not null';
const sql = `alter table ${this.tableName()} alter column ${this.formatter.wrap(
column
)} ${constraintAction}`;
return this.pushQuery({
sql: sql,
});
}
compileAdd(builder) {

@@ -26,0 +36,0 @@ const table = this.formatter.wrap(builder);

@@ -7,15 +7,8 @@ // SQLite3_DDL

const find = require('lodash/find');
const fromPairs = require('lodash/fromPairs');
const isEmpty = require('lodash/isEmpty');
const negate = require('lodash/negate');
const omit = require('lodash/omit');
const identity = require('lodash/identity');
const { nanonum } = require('../../../util/nanoid');
const { COMMA_NO_PAREN_REGEX } = require('../../../constants');
const {
createNewTable,
copyAllData,
copyData,
dropOriginal,
copyData,
renameTable,

@@ -29,2 +22,3 @@ getTableSql,

} = require('./internal/compiler');
const { isEqualId, includesId } = require('./internal/utils');

@@ -51,16 +45,2 @@ // So altering the schema in SQLite3 is a major pain.

async getColumn(column) {
const currentCol = find(this.pragma, (col) => {
return (
this.client.wrapIdentifier(col.name).toLowerCase() ===
this.client.wrapIdentifier(column).toLowerCase()
);
});
if (!currentCol)
throw new Error(
`The column ${column} is not in the ${this.tableName()} table`
);
return currentCol;
}
getTableSql() {

@@ -89,12 +69,4 @@ const tableName = this.tableName();

async copyData(iterator) {
const commands = await copyData(
this.trx,
iterator,
this.tableName(),
this.alteredName
);
for (const command of commands) {
await this.trx.raw(command);
}
copyData(columns) {
return this.trx.raw(copyData(this.tableName(), this.alteredName, columns));
}

@@ -108,143 +80,3 @@

_doReplace(sql, from, to) {
const oneLineSql = sql.replace(/\s+/g, ' ');
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
const tableName = matched[1];
const defs = matched[2];
if (!defs) {
throw new Error('No column definitions in this statement!');
}
let parens = 0,
args = [],
ptr = 0;
let i = 0;
const x = defs.length;
for (i = 0; i < x; i++) {
switch (defs[i]) {
case '(':
parens++;
break;
case ')':
parens--;
break;
case ',':
if (parens === 0) {
args.push(defs.slice(ptr, i));
ptr = i + 1;
}
break;
case ' ':
if (ptr === i) {
ptr = i + 1;
}
break;
}
}
args.push(defs.slice(ptr, i));
const fromIdentifier = from.replace(/[`"'[\]]/g, '');
args = args.map((item) => {
let split = item.trim().split(' ');
// SQLite supports all quoting mechanisms prevalent in all major dialects of SQL
// and preserves the original quoting in sqlite_master.
//
// Also, identifiers are never case sensitive, not even when quoted.
//
// Ref: https://www.sqlite.org/lang_keywords.html
const fromMatchCandidates = [
new RegExp(`\`${fromIdentifier}\``, 'i'),
new RegExp(`"${fromIdentifier}"`, 'i'),
new RegExp(`'${fromIdentifier}'`, 'i'),
new RegExp(`\\[${fromIdentifier}\\]`, 'i'),
];
if (fromIdentifier.match(/^\S+$/)) {
fromMatchCandidates.push(new RegExp(`\\b${fromIdentifier}\\b`, 'i'));
}
const doesMatchFromIdentifier = (target) =>
fromMatchCandidates.some((c) => target.match(c));
const replaceFromIdentifier = (target) =>
fromMatchCandidates.reduce(
(result, candidate) => result.replace(candidate, to),
target
);
if (doesMatchFromIdentifier(split[0])) {
// column definition
if (to) {
split[0] = to;
return split.join(' ');
}
return ''; // for deletions
}
// skip constraint name
const idx = /constraint/i.test(split[0]) ? 2 : 0;
// primary key and unique constraints have one or more
// columns from this table listed between (); replace
// one if it matches
if (/primary|unique/i.test(split[idx])) {
const ret = item.replace(/\(.*\)/, replaceFromIdentifier);
// If any member columns are dropped then uniqueness/pk constraint
// can not be retained
if (ret !== item && isEmpty(to)) return '';
return ret;
}
// foreign keys have one or more columns from this table
// listed between (); replace one if it matches
// foreign keys also have a 'references' clause
// which may reference THIS table; if it does, replace
// column references in that too!
if (/foreign/.test(split[idx])) {
split = item.split(/ references /i);
// the quoted column names save us from having to do anything
// other than a straight replace here
const replacedKeySpec = replaceFromIdentifier(split[0]);
if (split[0] !== replacedKeySpec) {
// If we are removing one or more columns of a foreign
// key, then we should not retain the key at all
if (isEmpty(to)) return '';
else split[0] = replacedKeySpec;
}
if (split[1].slice(0, tableName.length) === tableName) {
// self-referential foreign key
const replacedKeyTargetSpec = split[1].replace(
/\(.*\)/,
replaceFromIdentifier
);
if (split[1] !== replacedKeyTargetSpec) {
// If we are removing one or more columns of a foreign
// key, then we should not retain the key at all
if (isEmpty(to)) return '';
else split[1] = replacedKeyTargetSpec;
}
}
return split.join(' references ');
}
return item;
});
args = args.filter(negate(isEmpty));
if (args.length === 0) {
throw new Error('Unable to drop last column from table');
}
return oneLineSql
.replace(/\(.*\)/, () => `(${args.join(', ')})`)
.replace(/,\s*([,)])/, '$1');
}
async alterColumn(columns) {
alterColumn(columns) {
return this.client.transaction(

@@ -259,3 +91,5 @@ async (trx) => {

parsedTable.columns = parsedTable.columns.map((column) => {
const newColumnInfo = columns.find((c) => c.name === column.name);
const newColumnInfo = columns.find((c) =>
isEqualId(c.name, column.name)
);

@@ -288,5 +122,3 @@ if (newColumnInfo) {

return this.alter(newTable, createIndices, (row) => {
return row;
});
return this.alter(newTable, createIndices);
},

@@ -297,44 +129,68 @@ { connection: this.connection }

async dropColumn(columns) {
dropColumn(columns) {
return this.client.transaction(
(trx) => {
async (trx) => {
this.trx = trx;
return Promise.all(columns.map((column) => this.getColumn(column)))
.then(() => this.getTableSql())
.then(({ createTable, createIndices }) => {
let newSql = createTable.slice();
columns.forEach((column) => {
const a = this.client.wrapIdentifier(column);
newSql = this._doReplace(newSql, a, '');
});
if (createTable === newSql) {
throw new Error('Unable to find the column to change');
}
const mappedColumns = Object.keys(
this.client.postProcessResponse(
fromPairs(columns.map((column) => [column, column]))
)
);
const newIndices = [];
for (const createIndex of createIndices) {
const parsedIndex = parseCreateIndex(createIndex);
const { createTable, createIndices } = await this.getTableSql();
parsedIndex.columns = parsedIndex.columns.filter(
(newColumn) =>
newColumn.expression ||
!columns.some(
(column) => newColumn.name === this.formatter(column)
)
const parsedTable = parseCreateTable(createTable);
parsedTable.columns = parsedTable.columns.filter(
(parsedColumn) =>
parsedColumn.expression || !includesId(columns, parsedColumn.name)
);
if (parsedTable.columns.length === 0) {
throw new Error('Unable to drop last column from table');
}
parsedTable.constraints = parsedTable.constraints.filter(
(constraint) => {
if (
constraint.type === 'PRIMARY KEY' ||
constraint.type === 'UNIQUE'
) {
return constraint.columns.every(
(constraintColumn) =>
constraintColumn.expression ||
!includesId(columns, constraintColumn.name)
);
if (parsedIndex.columns.length > 0) {
newIndices.push(compileCreateIndex(parsedIndex, this.wrap));
}
} else if (constraint.type === 'FOREIGN KEY') {
return (
constraint.columns.every(
(constraintColumnName) =>
!includesId(columns, constraintColumnName)
) &&
(constraint.references.table !== parsedTable.table ||
constraint.references.columns.every(
(referenceColumnName) =>
!includesId(columns, referenceColumnName)
))
);
} else {
return true;
}
}
);
return this.alter(newSql, newIndices, (row) =>
omit(row, ...mappedColumns)
);
});
const newColumns = parsedTable.columns.map((column) => column.name);
const newTable = compileCreateTable(parsedTable, this.wrap);
const newIndices = [];
for (const createIndex of createIndices) {
const parsedIndex = parseCreateIndex(createIndex);
parsedIndex.columns = parsedIndex.columns.filter(
(parsedColumn) =>
parsedColumn.expression || !includesId(columns, parsedColumn.name)
);
if (parsedIndex.columns.length > 0) {
newIndices.push(compileCreateIndex(parsedIndex, this.wrap));
}
}
return this.alter(newTable, newIndices, newColumns);
},

@@ -345,3 +201,3 @@ { connection: this.connection }

async dropForeign(columns, foreignKeyName) {
dropForeign(columns, foreignKeyName) {
return this.client.transaction(

@@ -358,3 +214,3 @@ async (trx) => {

...column,
references: columns.includes(column.name)
references: includesId(columns, column.name)
? null

@@ -367,10 +223,17 @@ : column.references,

(constraint) => {
if (foreignKeyName) {
return constraint.name !== foreignKeyName;
if (constraint.type === 'FOREIGN KEY') {
if (foreignKeyName) {
return (
!constraint.name ||
!isEqualId(constraint.name, foreignKeyName)
);
}
return constraint.columns.every(
(constraintColumnName) =>
!includesId(columns, constraintColumnName)
);
} else {
return true;
}
return (
constraint.columns.some((column) => columns.includes(column)) ===
false
);
}

@@ -381,5 +244,3 @@ );

return this.alter(newTable, createIndices, (row) => {
return row;
});
return this.alter(newTable, createIndices);
},

@@ -390,3 +251,3 @@ { connection: this.connection }

async dropPrimary(constraintName) {
dropPrimary(constraintName) {
return this.client.transaction(

@@ -398,35 +259,29 @@ async (trx) => {

const oneLineSql = createTable.replace(/\s+/g, ' ');
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
const parsedTable = parseCreateTable(createTable);
const defs = matched[2];
parsedTable.columns = parsedTable.columns.map((column) => ({
...column,
primary: null,
}));
if (!defs) {
throw new Error('No column definitions in this statement!');
}
const updatedDefs = defs
.split(COMMA_NO_PAREN_REGEX)
.map((line) => line.trim())
.filter((defLine) => {
if (
defLine.startsWith('constraint') === false &&
defLine.includes('primary key') === false
)
return true;
if (constraintName) {
if (defLine.includes(constraintName)) return false;
return true;
parsedTable.constraints = parsedTable.constraints.filter(
(constraint) => {
if (constraint.type === 'PRIMARY KEY') {
if (constraintName) {
return (
!constraint.name ||
!isEqualId(constraint.name, constraintName)
);
} else {
return false;
}
} else {
return true;
}
})
.join(', ');
}
);
const newSql = oneLineSql.replace(defs, updatedDefs);
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.alter(newSql, createIndices, (row) => {
return row;
});
return this.alter(newTable, createIndices);
},

@@ -437,3 +292,3 @@ { connection: this.connection }

async primary(columns, constraintName) {
primary(columns, constraintName) {
return this.client.transaction(

@@ -445,33 +300,28 @@ async (trx) => {

const oneLineSql = createTable.replace(/\s+/g, ' ');
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
const parsedTable = parseCreateTable(createTable);
const columnDefinitions = matched[2];
parsedTable.columns = parsedTable.columns.map((column) => ({
...column,
primary: null,
}));
if (!columnDefinitions) {
throw new Error('No column definitions in this statement!');
}
parsedTable.constraints = parsedTable.constraints.filter(
(constraint) => constraint.type !== 'PRIMARY KEY'
);
const primaryKeyDef = `primary key(${columns.join(',')})`;
const constraintDef = constraintName
? `constraint ${constraintName} ${primaryKeyDef}`
: primaryKeyDef;
parsedTable.constraints.push({
type: 'PRIMARY KEY',
name: constraintName || null,
columns: columns.map((column) => ({
name: column,
expression: false,
collation: null,
order: null,
})),
conflict: null,
});
const newColumnDefinitions = [
...columnDefinitions
.split(COMMA_NO_PAREN_REGEX)
.map((line) => line.trim())
.filter((line) => line.startsWith('primary') === false)
.map((line) => line.replace(/primary key/i, '')),
constraintDef,
].join(', ');
const newTable = compileCreateTable(parsedTable, this.wrap);
const newSQL = oneLineSql.replace(
columnDefinitions,
newColumnDefinitions
);
return this.alter(newSQL, createIndices, (row) => {
return row;
});
return this.alter(newTable, createIndices);
},

@@ -482,3 +332,3 @@ { connection: this.connection }

async foreign(foreignInfo) {
foreign(foreignInfo) {
return this.client.transaction(

@@ -490,46 +340,21 @@ async (trx) => {

const oneLineSql = createTable.replace(/\s+/g, ' ');
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
const parsedTable = parseCreateTable(createTable);
const columnDefinitions = matched[2];
parsedTable.constraints.push({
type: 'FOREIGN KEY',
name: foreignInfo.keyName || null,
columns: foreignInfo.column,
references: {
table: foreignInfo.inTable,
columns: foreignInfo.references,
delete: foreignInfo.onDelete || null,
update: foreignInfo.onUpdate || null,
match: null,
deferrable: null,
},
});
if (!columnDefinitions) {
throw new Error('No column definitions in this statement!');
}
const newTable = compileCreateTable(parsedTable, this.wrap);
const newColumnDefinitions = columnDefinitions
.split(COMMA_NO_PAREN_REGEX)
.map((line) => line.trim());
let newForeignSQL = '';
if (foreignInfo.keyName) {
newForeignSQL += `CONSTRAINT ${foreignInfo.keyName}`;
}
newForeignSQL += ` FOREIGN KEY (${foreignInfo.column.join(', ')}) `;
newForeignSQL += ` REFERENCES ${foreignInfo.inTable} (${foreignInfo.references})`;
if (foreignInfo.onUpdate) {
newForeignSQL += ` ON UPDATE ${foreignInfo.onUpdate}`;
}
if (foreignInfo.onDelete) {
newForeignSQL += ` ON DELETE ${foreignInfo.onDelete}`;
}
newColumnDefinitions.push(newForeignSQL);
const newSQL = oneLineSql.replace(
columnDefinitions,
newColumnDefinitions.join(', ')
);
return await this.generateAlterCommands(
newSQL,
createIndices,
(row) => {
return row;
}
);
return this.generateAlterCommands(newTable, createIndices);
},

@@ -540,12 +365,5 @@ { connection: this.connection }

/**
* @fixme
*
* There's a bunch of overlap between renameColumn/dropColumn/dropForeign/primary/foreign.
* It'll be helpful to refactor this file heavily to combine/optimize some of these calls
*/
async alter(newSql, createIndices, mapRow) {
async alter(newSql, createIndices, columns) {
await this.createNewTable(newSql);
await this.copyData(mapRow);
await this.copyData(columns);
await this.dropOriginal();

@@ -559,7 +377,7 @@ await this.renameTable();

async generateAlterCommands(newSql, createIndices, mapRow) {
generateAlterCommands(newSql, createIndices, columns) {
const result = [];
result.push(createNewTable(newSql, this.tableName(), this.alteredName));
result.push(copyAllData(this.tableName(), this.alteredName));
result.push(copyData(this.tableName(), this.alteredName, columns));
result.push(dropOriginal(this.tableName()));

@@ -566,0 +384,0 @@ result.push(renameTable(this.alteredName, this.tableName()));

@@ -5,3 +5,4 @@ const { tokenize } = require('./tokenizer');

const TOKENS = {
keyword: /(?:ABORT|ACTION|ADD|AFTER|ALL|ALTER|ALWAYS|ANALYZE|AND|AS|ASC|ATTACH|AUTOINCREMENT|BEFORE|BEGIN|BETWEEN|BY|CASCADE|CASE|CAST|CHECK|COLLATE|COLUMN|COMMIT|CONFLICT|CONSTRAINT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|DATABASE|DEFAULT|DEFERRED|DEFERRABLE|DELETE|DESC|DETACH|DISTINCT|DO|DROP|END|EACH|ELSE|ESCAPE|EXCEPT|EXCLUSIVE|EXCLUDE|EXISTS|EXPLAIN|FAIL|FILTER|FIRST|FOLLOWING|FOR|FOREIGN|FROM|FULL|GENERATED|GLOB|GROUP|GROUPS|HAVING|IF|IGNORE|IMMEDIATE|IN|INDEX|INDEXED|INITIALLY|INNER|INSERT|INSTEAD|INTERSECT|INTO|IS|ISNULL|JOIN|KEY|LAST|LEFT|LIKE|LIMIT|MATCH|MATERIALIZED|NATURAL|NO|NOT|NOTHING|NOTNULL|NULL|NULLS|OF|OFFSET|ON|OR|ORDER|OTHERS|OUTER|OVER|PARTITION|PLAN|PRAGMA|PRECEDING|PRIMARY|QUERY|RAISE|RANGE|RECURSIVE|REFERENCES|REGEXP|REINDEX|RELEASE|RENAME|REPLACE|RESTRICT|RETURNING|RIGHT|ROLLBACK|ROW|ROWS|SAVEPOINT|SELECT|SET|TABLE|TEMP|TEMPORARY|THEN|TIES|TO|TRANSACTION|TRIGGER|UNBOUNDED|UNION|UNIQUE|UPDATE|USING|VACUUM|VALUES|VIEW|VIRTUAL|WHEN|WHERE|WINDOW|WITH|WITHOUT)(?=\s+|-|\(|\)|;|\+|\*|\/|%|==|=|<=|<>|<<|<|>=|>>|>|!=|,|&|~|\|\||\||\.)/,
keyword:
/(?:ABORT|ACTION|ADD|AFTER|ALL|ALTER|ALWAYS|ANALYZE|AND|AS|ASC|ATTACH|AUTOINCREMENT|BEFORE|BEGIN|BETWEEN|BY|CASCADE|CASE|CAST|CHECK|COLLATE|COLUMN|COMMIT|CONFLICT|CONSTRAINT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|DATABASE|DEFAULT|DEFERRED|DEFERRABLE|DELETE|DESC|DETACH|DISTINCT|DO|DROP|END|EACH|ELSE|ESCAPE|EXCEPT|EXCLUSIVE|EXCLUDE|EXISTS|EXPLAIN|FAIL|FILTER|FIRST|FOLLOWING|FOR|FOREIGN|FROM|FULL|GENERATED|GLOB|GROUP|GROUPS|HAVING|IF|IGNORE|IMMEDIATE|IN|INDEX|INDEXED|INITIALLY|INNER|INSERT|INSTEAD|INTERSECT|INTO|IS|ISNULL|JOIN|KEY|LAST|LEFT|LIKE|LIMIT|MATCH|MATERIALIZED|NATURAL|NO|NOT|NOTHING|NOTNULL|NULL|NULLS|OF|OFFSET|ON|OR|ORDER|OTHERS|OUTER|OVER|PARTITION|PLAN|PRAGMA|PRECEDING|PRIMARY|QUERY|RAISE|RANGE|RECURSIVE|REFERENCES|REGEXP|REINDEX|RELEASE|RENAME|REPLACE|RESTRICT|RETURNING|RIGHT|ROLLBACK|ROW|ROWS|SAVEPOINT|SELECT|SET|TABLE|TEMP|TEMPORARY|THEN|TIES|TO|TRANSACTION|TRIGGER|UNBOUNDED|UNION|UNIQUE|UPDATE|USING|VACUUM|VALUES|VIEW|VIRTUAL|WHEN|WHERE|WINDOW|WITH|WITHOUT)(?=\s+|-|\(|\)|;|\+|\*|\/|%|==|=|<=|<>|<<|<|>=|>>|>|!=|,|&|~|\|\||\||\.)/,
id: /"[^"]*(?:""[^"]*)*"|`[^`]*(?:``[^`]*)*`|\[[^[\]]*\]|[a-z_][a-z0-9_$]*/,

@@ -595,6 +596,4 @@ string: /'[^']*(?:''[^']*)*'/,

function identifier(ctx) {
return t({ type: 'id' }, (v) =>
/^["`[][^]*["`\]]$/.test(v.text)
? v.text.substring(1, v.text.length - 1)
: v.text
return a([t({ type: 'id' }), t({ type: 'string' })], (v) =>
/^["`['][^]*["`\]']$/.test(v) ? v.substring(1, v.length - 1) : v
)(ctx);

@@ -620,2 +619,3 @@ }

t({ type: 'string' }),
t({ type: 'id' }),
t({ type: 'blob' }),

@@ -622,0 +622,0 @@ t({ text: 'NULL' }),

@@ -1,16 +0,1 @@

const identity = require('lodash/identity');
const chunk = require('lodash/chunk');
function insertChunked(trx, chunkSize, target, iterator, existingData) {
const result = [];
iterator = iterator || identity;
const chunked = chunk(existingData, chunkSize);
for (const batch of chunked) {
result.push(
trx.queryBuilder().table(target).insert(batch.map(iterator)).toQuery()
);
}
return result;
}
function createNewTable(sql, tablename, alteredName) {

@@ -20,12 +5,10 @@ return sql.replace(tablename, alteredName);

// ToDo To be removed
async function copyData(trx, iterator, tableName, alteredName) {
const existingData = await trx.raw(`SELECT * FROM "${tableName}"`);
return insertChunked(trx, 20, alteredName, iterator, existingData);
function copyData(sourceTable, targetTable, columns) {
return `INSERT INTO ${targetTable} SELECT ${
columns === undefined
? '*'
: columns.map((column) => `"${column}"`).join(', ')
} FROM ${sourceTable};`;
}
function copyAllData(sourceTable, targetTable) {
return `INSERT INTO ${targetTable} SELECT * FROM ${sourceTable};`;
}
function dropOriginal(tableName) {

@@ -44,8 +27,7 @@ return `DROP TABLE "${tableName}"`;

module.exports = {
copyAllData,
createNewTable,
copyData,
dropOriginal,
copyData,
renameTable,
getTableSql,
};

@@ -84,2 +84,8 @@ const filter = require('lodash/filter');

columns = Array.isArray(columns) ? columns : [columns];
columns = columns.map((column) =>
this.client.customWrapIdentifier(column, identity)
);
indexName = this.client.customWrapIdentifier(indexName, identity);
this.pushQuery({

@@ -99,2 +105,4 @@ sql: `PRAGMA table_info(${this.tableName()})`,

constraintName = this.client.customWrapIdentifier(constraintName, identity);
this.pushQuery({

@@ -151,4 +159,7 @@ sql: `PRAGMA table_info(${this.tableName()})`,

columns = this.formatter.columnize(columns);
columns = Array.isArray(columns) ? columns : [columns];
columns = columns.map((column) =>
this.client.customWrapIdentifier(column, identity)
);
constraintName = this.client.customWrapIdentifier(constraintName, identity);

@@ -176,3 +187,3 @@ if (this.method !== 'create' && this.method !== 'createIfNot') {

* @param {string | undefined} foreignInfo.keyName - Name of the foreign key constraint
* @param {string} foreignInfo.references - What column it references in the other table
* @param {string | string[]} foreignInfo.references - What column it references in the other table
* @param {string} foreignInfo.inTable - What table is referenced in this constraint

@@ -186,8 +197,18 @@ * @param {string} [foreignInfo.onUpdate] - What to do on updates

if (this.method !== 'create' && this.method !== 'createIfNot') {
foreignInfo.column = this.formatter.columnize(foreignInfo.column);
foreignInfo.column = Array.isArray(foreignInfo.column)
? foreignInfo.column
: [foreignInfo.column];
foreignInfo.inTable = this.formatter.columnize(foreignInfo.inTable);
foreignInfo.references = this.formatter.columnize(foreignInfo.references);
foreignInfo.column = foreignInfo.column.map((column) =>
this.client.customWrapIdentifier(column, identity)
);
foreignInfo.inTable = this.client.customWrapIdentifier(
foreignInfo.inTable,
identity
);
foreignInfo.references = Array.isArray(foreignInfo.references)
? foreignInfo.references
: [foreignInfo.references];
foreignInfo.references = foreignInfo.references.map((column) =>
this.client.customWrapIdentifier(column, identity)
);

@@ -252,5 +273,15 @@ this.pushQuery({

_setNullableState(column, isNullable) {
const fnCalled = isNullable ? '.setNullable' : '.dropNullable';
throw new Error(`${fnCalled} is not supported for SQLite.`);
}
dropColumn() {
const compiler = this;
const columns = values(arguments);
const columnsWrapped = columns.map((column) =>
this.client.customWrapIdentifier(column, identity)
);
this.pushQuery({

@@ -261,3 +292,3 @@ sql: `PRAGMA table_info(${this.tableName()})`,

.ddl(compiler, pragma, this.connection)
.dropColumn(columns);
.dropColumn(columnsWrapped);
},

@@ -264,0 +295,0 @@ });

@@ -241,3 +241,3 @@ const { KnexTimeoutError } = require('../util/timeout');

for (const query of queries) {
results.push(await this.query(query));
results.push(await this.queryArray([query]));
}

@@ -244,0 +244,0 @@ return results;

@@ -72,3 +72,12 @@ const { parse } = require('pg-connection-string');

for (const [key, value] of parsed.searchParams.entries()) {
connection[key] = value;
const isMySQLLike = ['mysql:', 'mariadb:'].includes(parsed.protocol);
if (isMySQLLike) {
try {
connection[key] = JSON.parse(value);
} catch (err) {
connection[key] = value;
}
} else {
connection[key] = value;
}
}

@@ -75,0 +84,0 @@ }

@@ -86,5 +86,4 @@ // Migrator

migrations.map(async (migration) => {
const migrationContents = await this.config.migrationSource.getMigration(
migration
);
const migrationContents =
await this.config.migrationSource.getMigration(migration);
return !this._useTransaction(migrationContents);

@@ -105,65 +104,60 @@ })

// Runs the next migration that has not yet been run
up(config) {
async up(config) {
this._disableProcessing();
this.config = getMergedConfig(config, this.config, this.knex.client.logger);
return migrationListResolver
.listAllAndCompleted(this.config, this.knex)
.then((value) => {
if (!this.config.disableMigrationsListValidation) {
validateMigrationList(this.config.migrationSource, value);
}
return value;
})
.then(([all, completed]) => {
const newMigrations = getNewMigrations(
this.config.migrationSource,
all,
completed
);
const allAndCompleted = await migrationListResolver.listAllAndCompleted(
this.config,
this.knex
);
let migrationToRun;
const name = this.config.name;
if (name) {
if (!completed.includes(name)) {
migrationToRun = newMigrations.find((migration) => {
return (
this.config.migrationSource.getMigrationName(migration) === name
);
});
if (!migrationToRun) {
throw new Error(`Migration "${name}" not found.`);
}
}
} else {
migrationToRun = newMigrations[0];
}
if (!this.config.disableMigrationsListValidation) {
validateMigrationList(this.config.migrationSource, allAndCompleted);
}
const [all, completed] = allAndCompleted;
return {
migrationToRun,
useTransaction:
!migrationToRun ||
this._useTransaction(
this.config.migrationSource.getMigration(migrationToRun)
),
};
})
.then(({ migrationToRun, useTransaction }) => {
const migrationsToRun = [];
if (migrationToRun) {
migrationsToRun.push(migrationToRun);
const newMigrations = getNewMigrations(
this.config.migrationSource,
all,
completed
);
let migrationToRun;
const name = this.config.name;
if (name) {
if (!completed.includes(name)) {
migrationToRun = newMigrations.find((migration) => {
return (
this.config.migrationSource.getMigrationName(migration) === name
);
});
if (!migrationToRun) {
throw new Error(`Migration "${name}" not found.`);
}
}
} else {
migrationToRun = newMigrations[0];
}
const transactionForAll =
!this.config.disableTransactions &&
(!migrationToRun || useTransaction);
const useTransaction =
!migrationToRun ||
this._useTransaction(
await this.config.migrationSource.getMigration(migrationToRun)
);
if (transactionForAll) {
return this.knex.transaction((trx) => {
return this._runBatch(migrationsToRun, 'up', trx);
});
} else {
return this._runBatch(migrationsToRun, 'up');
}
const migrationsToRun = [];
if (migrationToRun) {
migrationsToRun.push(migrationToRun);
}
const transactionForAll =
!this.config.disableTransactions && (!migrationToRun || useTransaction);
if (transactionForAll) {
return await this.knex.transaction((trx) => {
return this._runBatch(migrationsToRun, 'up', trx);
});
} else {
return await this._runBatch(migrationsToRun, 'up');
}
}

@@ -437,5 +431,4 @@

async _validateMigrationStructure(migration) {
const migrationName = this.config.migrationSource.getMigrationName(
migration
);
const migrationName =
this.config.migrationSource.getMigrationName(migration);
// maybe promise

@@ -507,5 +500,4 @@ const migrationContent = await this.config.migrationSource.getMigration(

this._activeMigration.fileName = name;
const migrationContent = this.config.migrationSource.getMigration(
migration
);
const migrationContent =
this.config.migrationSource.getMigration(migration);

@@ -512,0 +504,0 @@ // We're going to run each of the migrations in the current "up".

@@ -21,3 +21,3 @@ const assert = require('assert');

partitionBy(column) {
partitionBy(column, direction) {
assert(

@@ -32,3 +32,3 @@ Array.isArray(column) || typeof column === 'string',

} else {
this.partitions.push(column);
this.partitions.push({ column: column, order: direction });
}

@@ -38,3 +38,3 @@ return this;

orderBy(column) {
orderBy(column, direction) {
assert(

@@ -49,3 +49,3 @@ Array.isArray(column) || typeof column === 'string',

} else {
this.order.push(column);
this.order.push({ column: column, order: direction });
}

@@ -52,0 +52,0 @@ return this;

@@ -117,9 +117,13 @@ // Builder

with(alias, statement) {
validateWithArgs(alias, statement, 'with');
return this.withWrapped(alias, statement);
with(alias, statementOrColumnList, nothingOrStatement) {
validateWithArgs(alias, statementOrColumnList, nothingOrStatement, 'with');
return this.withWrapped(alias, statementOrColumnList, nothingOrStatement);
}
// Helper for compiling any advanced `with` queries.
withWrapped(alias, query) {
withWrapped(alias, statementOrColumnList, nothingOrStatement) {
const [query, columnList] =
typeof nothingOrStatement === 'undefined'
? [statementOrColumnList, undefined]
: [nothingOrStatement, statementOrColumnList];
this._statements.push({

@@ -129,2 +133,3 @@ grouping: 'with',

alias: alias,
columnList,
value: query,

@@ -138,10 +143,19 @@ });

withRecursive(alias, statement) {
validateWithArgs(alias, statement, 'withRecursive');
return this.withRecursiveWrapped(alias, statement);
withRecursive(alias, statementOrColumnList, nothingOrStatement) {
validateWithArgs(
alias,
statementOrColumnList,
nothingOrStatement,
'withRecursive'
);
return this.withRecursiveWrapped(
alias,
statementOrColumnList,
nothingOrStatement
);
}
// Helper for compiling any advanced `withRecursive` queries.
withRecursiveWrapped(alias, query) {
this.withWrapped(alias, query);
withRecursiveWrapped(alias, statementOrColumnList, nothingOrStatement) {
this.withWrapped(alias, statementOrColumnList, nothingOrStatement);
this._statements[this._statements.length - 1].recursive = true;

@@ -1253,5 +1267,6 @@ return this;

Array.isArray(second) ||
typeof second === 'string',
typeof second === 'string' ||
typeof second === 'object',
`The second argument to an analytic function must be either a function, a raw,
an array of string or a single string.`
an array of string or object, an object or a single string.`
);

@@ -1261,4 +1276,6 @@

assert(
Array.isArray(third) || typeof third === 'string',
'The third argument to an analytic function must be either a string or an array of string.'
Array.isArray(third) ||
typeof third === 'string' ||
typeof third === 'object',
'The third argument to an analytic function must be either a string, an array of string or object or an object.'
);

@@ -1280,5 +1297,5 @@ }

} else {
const order = typeof second === 'string' ? [second] : second;
const order = !Array.isArray(second) ? [second] : second;
let partitions = third || [];
partitions = typeof partitions === 'string' ? [partitions] : partitions;
partitions = !Array.isArray(partitions) ? [partitions] : partitions;
analytic = {

@@ -1398,15 +1415,42 @@ grouping: 'columns',

const validateWithArgs = function (alias, statement, method) {
const isValidStatementArg = (statement) =>
typeof statement === 'function' ||
statement instanceof Builder ||
(statement && statement.isRawInstance);
const validateWithArgs = function (
alias,
statementOrColumnList,
nothingOrStatement,
method
) {
const [query, columnList] =
typeof nothingOrStatement === 'undefined'
? [statementOrColumnList, undefined]
: [nothingOrStatement, statementOrColumnList];
if (typeof alias !== 'string') {
throw new Error(`${method}() first argument must be a string`);
}
if (
typeof statement === 'function' ||
statement instanceof Builder ||
(statement && statement.isRawInstance)
) {
if (isValidStatementArg(query) && typeof columnList === 'undefined') {
// Validated as two-arg variant (alias, statement).
return;
}
// Attempt to interpret as three-arg variant (alias, columnList, statement).
const isNonEmptyNameList =
Array.isArray(columnList) &&
columnList.length > 0 &&
columnList.every((it) => typeof it === 'string');
if (!isNonEmptyNameList) {
throw new Error(
`${method}() second argument must be a statement or non-empty column name list.`
);
}
if (isValidStatementArg(query)) {
return;
}
throw new Error(
`${method}() second argument must be a function / QueryBuilder or a raw`
`${method}() third argument must be a function / QueryBuilder or a raw when its second argument is a column name list`
);

@@ -1413,0 +1457,0 @@ };

@@ -1038,3 +1038,5 @@ // Query Compiler

map(stmt.partitions, function (partition) {
return self.formatter.columnize(partition);
if (isString(partition)) {
return self.formatter.columnize(partition);
} else return self.formatter.columnize(partition.column) + (partition.order ? ' ' + partition.order : '');
}).join(', ') + ' ';

@@ -1045,3 +1047,5 @@ }

sql += map(stmt.order, function (order) {
return self.formatter.columnize(order);
if (isString(order)) {
return self.formatter.columnize(order);
} else return self.formatter.columnize(order.column) + (order.order ? ' ' + order.order : '');
}).join(', ');

@@ -1088,2 +1092,12 @@ }

);
const columnList = statement.columnList
? '(' +
columnize_(
statement.columnList,
this.builder,
this.client,
this.bindingsHolder
) +
')'
: '';
return (

@@ -1097,2 +1111,3 @@ (val &&

) +
columnList +
' as (' +

@@ -1099,0 +1114,0 @@ val +

@@ -287,2 +287,22 @@ // TableBuilder

setNullable(column) {
this._statements.push({
grouping: 'alterTable',
method: 'setNullable',
args: [column],
});
return this;
},
dropNullable(column) {
this._statements.push({
grouping: 'alterTable',
method: 'dropNullable',
args: [column],
});
return this;
},
// TODO: changeType

@@ -289,0 +309,0 @@ };

@@ -289,2 +289,45 @@ /* eslint max-len:0 */

//Default implementation of setNullable. Overwrite on dialect-specific tablecompiler when needed
//(See postgres/mssql for reference)
_setNullableState(column, nullable) {
const tableName = this.tableName();
const columnName = this.formatter.columnize(column);
const alterColumnPrefix = this.alterColumnsPrefix;
return this.pushQuery({
sql: 'SELECT 1',
output: () => {
return this.client
.queryBuilder()
.from(this.tableNameRaw)
.columnInfo(column)
.then((columnInfo) => {
if (isEmpty(columnInfo)) {
throw new Error(
`.setNullable: Column ${columnName} does not exist in table ${tableName}.`
);
}
const nullableType = nullable ? 'null' : 'not null';
const columnType =
columnInfo.type +
(columnInfo.maxLength ? `(${columnInfo.maxLength})` : '');
const defaultValue =
columnInfo.defaultValue !== null &&
columnInfo.defaultValue !== void 0
? `default '${columnInfo.defaultValue}'`
: '';
const sql = `alter table ${tableName} ${alterColumnPrefix} ${columnName} ${columnType} ${nullableType} ${defaultValue}`;
return this.client.raw(sql);
});
},
});
}
setNullable(column) {
return this._setNullableState(column, true);
}
dropNullable(column) {
return this._setNullableState(column, false);
}
// If no name was specified for this index, we will create one using a basic

@@ -314,4 +357,5 @@ // convention of the table name, followed by the columns, followed by an

TableCompiler.prototype.alterColumnsPrefix = 'alter column ';
TableCompiler.prototype.modifyColumnPrefix = 'modify column ';
TableCompiler.prototype.dropColumnPrefix = 'drop column ';
module.exports = TableCompiler;
{
"name": "knex",
"version": "0.95.10",
"version": "0.95.11",
"description": "A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser",

@@ -29,2 +29,3 @@ "main": "knex",

"test:mysql2": "cross-env DB=mysql2 npm run test:db",
"test:oracledb": "cross-env DB=oracledb npm run test:db",
"test:sqlite": "cross-env DB=sqlite3 npm run test:db",

@@ -41,2 +42,4 @@ "test:postgres": "cross-env DB=postgres npm run test:db",

"db:stop:mysql": "docker-compose -f scripts/docker-compose.yml down",
"db:start:oracle": "docker-compose -f scripts/docker-compose.yml up --build -d oracledbxe && docker-compose -f scripts/docker-compose.yml up waitoracledbxe",
"db:stop:oracle": "docker-compose -f scripts/docker-compose.yml down",
"stress:init": "docker-compose -f scripts/stress-test/docker-compose.yml up --no-start && docker-compose -f scripts/stress-test/docker-compose.yml start",

@@ -85,3 +88,3 @@ "stress:test": "node scripts/stress-test/knex-stress-test.js | grep -A 5 -B 60 -- '- STATS '",

"devDependencies": {
"@types/node": "^16.6.2",
"@types/node": "^16.7.10",
"chai": "^4.3.4",

@@ -93,6 +96,6 @@ "chai-as-promised": "^7.1.1",

"cross-env": "^7.0.3",
"dtslint": "4.1.4",
"dtslint": "4.1.6",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.1",
"eslint-plugin-import": "^2.24.2",
"husky": "^4.3.8",

@@ -102,3 +105,3 @@ "jake": "^8.1.1",

"lint-staged": "^11.1.2",
"mocha": "^9.1.0",
"mocha": "^9.1.1",
"mock-fs": "^4.13.0",

@@ -123,3 +126,3 @@ "mysql": "^2.18.1",

"tsd": "^0.17.0",
"typescript": "4.3.5"
"typescript": "4.4.2"
},

@@ -126,0 +129,0 @@ "buildDependencies": [

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc