2016-03-02 17:07:05 +01:00
|
|
|
// SQLite3_DDL
|
|
|
|
//
|
|
|
|
// All of the SQLite3 specific DDL helpers for renaming/dropping
|
|
|
|
// columns and changing datatypes.
|
|
|
|
// -------
|
|
|
|
|
2020-04-18 20:41:23 +03:00
|
|
|
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');
|
2021-02-05 15:35:30 +01:00
|
|
|
const identity = require('lodash/identity');
|
2021-01-03 04:10:26 +02:00
|
|
|
const { nanonum } = require('../../../util/nanoid');
|
2020-12-26 12:10:40 -05:00
|
|
|
const { COMMA_NO_PAREN_REGEX } = require('../../../constants');
|
2021-01-01 19:42:19 +02:00
|
|
|
const {
|
2021-02-03 19:49:42 +01:00
|
|
|
createNewTable,
|
2021-01-03 04:10:26 +02:00
|
|
|
copyAllData,
|
2021-01-01 19:42:19 +02:00
|
|
|
dropOriginal,
|
2021-02-03 19:49:42 +01:00
|
|
|
copyData,
|
2021-01-01 19:42:19 +02:00
|
|
|
renameTable,
|
2021-01-01 20:35:54 +02:00
|
|
|
getTableSql,
|
2021-01-01 19:42:19 +02:00
|
|
|
} = require('./internal/sqlite-ddl-operations');
|
2021-02-26 21:36:46 +01:00
|
|
|
const { parseCreateTable, parseCreateIndex } = require('./internal/parser');
|
|
|
|
const {
|
|
|
|
compileCreateTable,
|
|
|
|
compileCreateIndex,
|
|
|
|
} = require('./internal/compiler');
|
2016-03-02 17:07:05 +01:00
|
|
|
|
|
|
|
// So altering the schema in SQLite3 is a major pain.
|
|
|
|
// We have our own object to deal with the renaming and altering the types
|
|
|
|
// for sqlite3 things.
|
2021-01-01 19:42:19 +02:00
|
|
|
class SQLite3_DDL {
|
|
|
|
constructor(client, tableCompiler, pragma, connection) {
|
|
|
|
this.client = client;
|
|
|
|
this.tableCompiler = tableCompiler;
|
|
|
|
this.pragma = pragma;
|
|
|
|
this.tableNameRaw = this.tableCompiler.tableNameRaw;
|
2021-01-03 04:10:26 +02:00
|
|
|
this.alteredName = `_knex_temp_alter${nanonum(3)}`;
|
2021-01-01 19:42:19 +02:00
|
|
|
this.connection = connection;
|
2021-02-05 15:35:30 +01:00
|
|
|
this.formatter = (value) =>
|
|
|
|
this.client.customWrapIdentifier(value, identity);
|
|
|
|
this.wrap = (value) => this.client.wrapIdentifierImpl(value);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2019-03-13 22:58:59 +01:00
|
|
|
tableName() {
|
2021-02-05 15:35:30 +01:00
|
|
|
return this.formatter(this.tableNameRaw);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2019-03-13 22:58:59 +01:00
|
|
|
|
2021-01-01 19:42:19 +02:00
|
|
|
async getColumn(column) {
|
2019-03-13 22:58:59 +01:00
|
|
|
const currentCol = find(this.pragma, (col) => {
|
|
|
|
return (
|
2019-10-12 02:08:01 +05:30
|
|
|
this.client.wrapIdentifier(col.name).toLowerCase() ===
|
|
|
|
this.client.wrapIdentifier(column).toLowerCase()
|
2019-03-13 22:58:59 +01:00
|
|
|
);
|
|
|
|
});
|
2018-07-09 08:10:34 -04:00
|
|
|
if (!currentCol)
|
|
|
|
throw new Error(
|
2019-03-13 22:58:59 +01:00
|
|
|
`The column ${column} is not in the ${this.tableName()} table`
|
2018-07-09 08:10:34 -04:00
|
|
|
);
|
2016-03-02 17:07:05 +01:00
|
|
|
return currentCol;
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
getTableSql() {
|
2021-02-05 15:35:30 +01:00
|
|
|
const tableName = this.tableName();
|
|
|
|
|
2019-03-13 22:58:59 +01:00
|
|
|
this.trx.disableProcessing();
|
2021-02-05 15:35:30 +01:00
|
|
|
return this.trx.raw(getTableSql(tableName)).then((result) => {
|
2021-01-01 20:35:54 +02:00
|
|
|
this.trx.enableProcessing();
|
2021-02-05 15:35:30 +01:00
|
|
|
return {
|
|
|
|
createTable: result.filter((create) => create.type === 'table')[0].sql,
|
|
|
|
createIndices: result
|
|
|
|
.filter((create) => create.type === 'index')
|
|
|
|
.map((create) => create.sql),
|
|
|
|
};
|
2021-01-01 20:35:54 +02:00
|
|
|
});
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2021-02-03 19:49:42 +01:00
|
|
|
renameTable() {
|
|
|
|
return this.trx.raw(renameTable(this.alteredName, this.tableName()));
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
dropOriginal() {
|
2021-01-01 19:42:19 +02:00
|
|
|
return this.trx.raw(dropOriginal(this.tableName()));
|
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2021-02-03 19:49:42 +01:00
|
|
|
async copyData(iterator) {
|
2021-01-03 04:10:26 +02:00
|
|
|
const commands = await copyData(
|
|
|
|
this.trx,
|
|
|
|
iterator,
|
|
|
|
this.tableName(),
|
|
|
|
this.alteredName
|
|
|
|
);
|
|
|
|
for (const command of commands) {
|
|
|
|
await this.trx.raw(command);
|
|
|
|
}
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2021-02-03 19:49:42 +01:00
|
|
|
createNewTable(sql) {
|
2019-10-16 00:03:35 +03:00
|
|
|
return this.trx.raw(
|
2021-02-03 19:49:42 +01:00
|
|
|
createNewTable(sql, this.tableName(), this.alteredName)
|
2019-10-16 00:03:35 +03:00
|
|
|
);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2018-07-09 08:10:34 -04:00
|
|
|
_doReplace(sql, from, to) {
|
2019-10-06 18:27:52 +02:00
|
|
|
const oneLineSql = sql.replace(/\s+/g, ' ');
|
2019-10-12 02:08:01 +05:30
|
|
|
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
const tableName = matched[1];
|
|
|
|
const defs = matched[2];
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2018-07-09 08:10:34 -04:00
|
|
|
if (!defs) {
|
|
|
|
throw new Error('No column definitions in this statement!');
|
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2018-07-09 08:10:34 -04:00
|
|
|
let parens = 0,
|
|
|
|
args = [],
|
|
|
|
ptr = 0;
|
2016-05-17 01:01:34 +10:00
|
|
|
let i = 0;
|
|
|
|
const x = defs.length;
|
|
|
|
for (i = 0; i < x; i++) {
|
2016-03-02 17:07:05 +01:00
|
|
|
switch (defs[i]) {
|
|
|
|
case '(':
|
|
|
|
parens++;
|
2016-05-17 01:01:34 +10:00
|
|
|
break;
|
2016-03-02 17:07:05 +01:00
|
|
|
case ')':
|
|
|
|
parens--;
|
2016-05-17 01:01:34 +10:00
|
|
|
break;
|
2016-03-02 17:07:05 +01:00
|
|
|
case ',':
|
|
|
|
if (parens === 0) {
|
|
|
|
args.push(defs.slice(ptr, i));
|
|
|
|
ptr = i + 1;
|
|
|
|
}
|
2016-05-17 01:01:34 +10:00
|
|
|
break;
|
2016-03-02 17:07:05 +01:00
|
|
|
case ' ':
|
|
|
|
if (ptr === i) {
|
|
|
|
ptr = i + 1;
|
|
|
|
}
|
2016-05-17 01:01:34 +10:00
|
|
|
break;
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
args.push(defs.slice(ptr, i));
|
|
|
|
|
2019-10-12 02:08:01 +05:30
|
|
|
const fromIdentifier = from.replace(/[`"'[\]]/g, '');
|
2018-10-03 05:02:37 +02:00
|
|
|
|
2019-10-16 00:03:35 +03:00
|
|
|
args = args.map((item) => {
|
2019-10-06 18:27:52 +02:00
|
|
|
let split = item.trim().split(' ');
|
|
|
|
|
2019-10-12 02:08:01 +05:30
|
|
|
// 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.
|
2019-10-06 18:27:52 +02:00
|
|
|
//
|
2019-10-12 02:08:01 +05:30
|
|
|
// Ref: https://www.sqlite.org/lang_keywords.html
|
|
|
|
const fromMatchCandidates = [
|
|
|
|
new RegExp(`\`${fromIdentifier}\``, 'i'),
|
|
|
|
new RegExp(`"${fromIdentifier}"`, 'i'),
|
|
|
|
new RegExp(`'${fromIdentifier}'`, 'i'),
|
2019-10-16 00:03:35 +03:00
|
|
|
new RegExp(`\\[${fromIdentifier}\\]`, 'i'),
|
2019-10-12 02:08:01 +05:30
|
|
|
];
|
|
|
|
if (fromIdentifier.match(/^\S+$/)) {
|
|
|
|
fromMatchCandidates.push(new RegExp(`\\b${fromIdentifier}\\b`, 'i'));
|
2019-10-06 18:27:52 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2019-10-12 02:08:01 +05:30
|
|
|
const doesMatchFromIdentifier = (target) =>
|
2020-04-18 20:41:23 +03:00
|
|
|
fromMatchCandidates.some((c) => target.match(c));
|
2019-10-12 02:08:01 +05:30
|
|
|
|
|
|
|
const replaceFromIdentifier = (target) =>
|
2019-10-16 00:03:35 +03:00
|
|
|
fromMatchCandidates.reduce(
|
|
|
|
(result, candidate) => result.replace(candidate, to),
|
|
|
|
target
|
|
|
|
);
|
2019-10-12 02:08:01 +05:30
|
|
|
|
|
|
|
if (doesMatchFromIdentifier(split[0])) {
|
2016-03-02 17:07:05 +01:00
|
|
|
// column definition
|
|
|
|
if (to) {
|
|
|
|
split[0] = to;
|
|
|
|
return split.join(' ');
|
|
|
|
}
|
|
|
|
return ''; // for deletions
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip constraint name
|
2018-07-09 08:10:34 -04:00
|
|
|
const idx = /constraint/i.test(split[0]) ? 2 : 0;
|
2016-03-02 17:07:05 +01:00
|
|
|
|
|
|
|
// 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])) {
|
2019-10-16 00:03:35 +03:00
|
|
|
const ret = item.replace(/\(.*\)/, replaceFromIdentifier);
|
2019-10-12 02:08:01 +05:30
|
|
|
// If any member columns are dropped then uniqueness/pk constraint
|
|
|
|
// can not be retained
|
|
|
|
if (ret !== item && isEmpty(to)) return '';
|
|
|
|
return ret;
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2019-10-12 02:08:01 +05:30
|
|
|
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;
|
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
|
|
|
if (split[1].slice(0, tableName.length) === tableName) {
|
2019-10-12 02:08:01 +05:30
|
|
|
// self-referential foreign key
|
2019-10-16 00:03:35 +03:00
|
|
|
const replacedKeyTargetSpec = split[1].replace(
|
|
|
|
/\(.*\)/,
|
|
|
|
replaceFromIdentifier
|
|
|
|
);
|
2019-10-12 02:08:01 +05:30
|
|
|
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;
|
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
return split.join(' references ');
|
|
|
|
}
|
|
|
|
|
|
|
|
return item;
|
|
|
|
});
|
2019-10-12 02:08:01 +05:30
|
|
|
|
|
|
|
args = args.filter(negate(isEmpty));
|
|
|
|
|
|
|
|
if (args.length === 0) {
|
|
|
|
throw new Error('Unable to drop last column from table');
|
|
|
|
}
|
|
|
|
|
2019-10-06 18:27:52 +02:00
|
|
|
return oneLineSql
|
2018-07-09 08:10:34 -04:00
|
|
|
.replace(/\(.*\)/, () => `(${args.join(', ')})`)
|
|
|
|
.replace(/,\s*([,)])/, '$1');
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2021-02-26 21:36:46 +01:00
|
|
|
async alterColumn(columns) {
|
|
|
|
return this.client.transaction(
|
|
|
|
async (trx) => {
|
|
|
|
this.trx = trx;
|
|
|
|
|
|
|
|
const { createTable, createIndices } = await this.getTableSql();
|
|
|
|
|
|
|
|
const parsedTable = parseCreateTable(createTable);
|
|
|
|
|
|
|
|
parsedTable.columns = parsedTable.columns.map((column) => {
|
|
|
|
const newColumnInfo = columns.find((c) => c.name === column.name);
|
|
|
|
|
|
|
|
if (newColumnInfo) {
|
|
|
|
column.type = newColumnInfo.type;
|
|
|
|
|
|
|
|
column.constraints.default =
|
|
|
|
newColumnInfo.defaultTo !== null
|
|
|
|
? {
|
|
|
|
name: null,
|
|
|
|
value: newColumnInfo.defaultTo,
|
|
|
|
expression: false,
|
|
|
|
}
|
|
|
|
: null;
|
|
|
|
|
2021-03-02 23:42:43 +01:00
|
|
|
column.constraints.notnull = newColumnInfo.notNull
|
2021-02-26 21:36:46 +01:00
|
|
|
? { name: null, conflict: null }
|
|
|
|
: null;
|
2021-03-02 23:42:43 +01:00
|
|
|
|
|
|
|
column.constraints.null = newColumnInfo.notNull
|
|
|
|
? null
|
|
|
|
: column.constraints.null;
|
2021-02-26 21:36:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return column;
|
|
|
|
});
|
|
|
|
|
|
|
|
const newTable = compileCreateTable(parsedTable, this.wrap);
|
|
|
|
|
2021-03-03 19:50:45 +01:00
|
|
|
return this.alter(newTable, createIndices, (row) => {
|
|
|
|
return row;
|
|
|
|
});
|
2021-02-26 21:36:46 +01:00
|
|
|
},
|
|
|
|
{ connection: this.connection }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-01-01 19:42:19 +02:00
|
|
|
async dropColumn(columns) {
|
2018-07-09 08:10:34 -04:00
|
|
|
return this.client.transaction(
|
|
|
|
(trx) => {
|
|
|
|
this.trx = trx;
|
2019-10-16 00:03:35 +03:00
|
|
|
return Promise.all(columns.map((column) => this.getColumn(column)))
|
|
|
|
.then(() => this.getTableSql())
|
2021-02-05 15:35:30 +01:00
|
|
|
.then(({ createTable, createIndices }) => {
|
|
|
|
let newSql = createTable.slice();
|
2018-07-09 08:10:34 -04:00
|
|
|
columns.forEach((column) => {
|
|
|
|
const a = this.client.wrapIdentifier(column);
|
|
|
|
newSql = this._doReplace(newSql, a, '');
|
|
|
|
});
|
2021-02-05 15:35:30 +01:00
|
|
|
if (createTable === newSql) {
|
2018-07-09 08:10:34 -04:00
|
|
|
throw new Error('Unable to find the column to change');
|
|
|
|
}
|
2019-03-13 22:58:59 +01:00
|
|
|
const mappedColumns = Object.keys(
|
|
|
|
this.client.postProcessResponse(
|
|
|
|
fromPairs(columns.map((column) => [column, column]))
|
|
|
|
)
|
|
|
|
);
|
2021-02-05 15:35:30 +01:00
|
|
|
|
|
|
|
const newIndices = [];
|
|
|
|
for (const createIndex of createIndices) {
|
|
|
|
const parsedIndex = parseCreateIndex(createIndex);
|
|
|
|
|
|
|
|
parsedIndex.columns = parsedIndex.columns.filter(
|
|
|
|
(newColumn) =>
|
2021-02-26 21:36:46 +01:00
|
|
|
newColumn.expression ||
|
|
|
|
!columns.some(
|
|
|
|
(column) => newColumn.name === this.formatter(column)
|
2021-02-05 15:35:30 +01:00
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (parsedIndex.columns.length > 0) {
|
|
|
|
newIndices.push(compileCreateIndex(parsedIndex, this.wrap));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.alter(newSql, newIndices, (row) =>
|
|
|
|
omit(row, ...mappedColumns)
|
|
|
|
);
|
2018-07-09 08:10:34 -04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
{ connection: this.connection }
|
|
|
|
);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2019-10-16 00:03:35 +03:00
|
|
|
|
2021-03-15 16:45:28 -04:00
|
|
|
async dropForeign(columns, foreignKeyName) {
|
2020-12-08 07:49:41 -05:00
|
|
|
return this.client.transaction(
|
|
|
|
async (trx) => {
|
|
|
|
this.trx = trx;
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const { createTable, createIndices } = await this.getTableSql();
|
2020-12-08 07:49:41 -05:00
|
|
|
|
2021-03-15 16:45:28 -04:00
|
|
|
const parsedTable = parseCreateTable(createTable);
|
2020-12-08 07:49:41 -05:00
|
|
|
|
2021-03-15 16:45:28 -04:00
|
|
|
if (!foreignKeyName) {
|
|
|
|
parsedTable.columns = parsedTable.columns.map((column) => ({
|
|
|
|
...column,
|
|
|
|
references: columns.includes(column.name)
|
|
|
|
? null
|
|
|
|
: column.references,
|
|
|
|
}));
|
2020-12-08 07:49:41 -05:00
|
|
|
}
|
|
|
|
|
2021-03-15 16:45:28 -04:00
|
|
|
parsedTable.constraints = parsedTable.constraints.filter(
|
|
|
|
(constraint) => {
|
|
|
|
if (foreignKeyName) {
|
|
|
|
return constraint.name !== foreignKeyName;
|
2020-12-08 07:49:41 -05:00
|
|
|
}
|
|
|
|
|
2021-03-15 16:45:28 -04:00
|
|
|
return (
|
|
|
|
constraint.columns.some((column) => columns.includes(column)) ===
|
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
const newTable = compileCreateTable(parsedTable, this.wrap);
|
2020-12-08 07:49:41 -05:00
|
|
|
|
2021-03-15 16:45:28 -04:00
|
|
|
return this.alter(newTable, createIndices, (row) => {
|
2020-12-08 07:49:41 -05:00
|
|
|
return row;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
{ connection: this.connection }
|
|
|
|
);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2020-12-08 07:49:41 -05:00
|
|
|
|
2021-01-01 19:42:19 +02:00
|
|
|
async dropPrimary(constraintName) {
|
2020-12-26 12:10:40 -05:00
|
|
|
return this.client.transaction(
|
|
|
|
async (trx) => {
|
|
|
|
this.trx = trx;
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const { createTable, createIndices } = await this.getTableSql();
|
2020-12-26 12:10:40 -05:00
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const oneLineSql = createTable.replace(/\s+/g, ' ');
|
2020-12-26 12:10:40 -05:00
|
|
|
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
|
|
|
|
|
|
|
|
const defs = matched[2];
|
|
|
|
|
|
|
|
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;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.join(', ');
|
|
|
|
|
|
|
|
const newSql = oneLineSql.replace(defs, updatedDefs);
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
return this.alter(newSql, createIndices, (row) => {
|
2020-12-26 12:10:40 -05:00
|
|
|
return row;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
{ connection: this.connection }
|
|
|
|
);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2020-12-26 12:10:40 -05:00
|
|
|
|
2021-01-01 19:42:19 +02:00
|
|
|
async primary(columns, constraintName) {
|
2020-12-26 12:10:40 -05:00
|
|
|
return this.client.transaction(
|
|
|
|
async (trx) => {
|
|
|
|
this.trx = trx;
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const { createTable, createIndices } = await this.getTableSql();
|
2020-12-26 12:10:40 -05:00
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const oneLineSql = createTable.replace(/\s+/g, ' ');
|
|
|
|
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
|
2020-12-26 12:10:40 -05:00
|
|
|
|
|
|
|
const columnDefinitions = matched[2];
|
|
|
|
|
|
|
|
if (!columnDefinitions) {
|
|
|
|
throw new Error('No column definitions in this statement!');
|
|
|
|
}
|
|
|
|
|
|
|
|
const primaryKeyDef = `primary key(${columns.join(',')})`;
|
|
|
|
const constraintDef = constraintName
|
|
|
|
? `constraint ${constraintName} ${primaryKeyDef}`
|
|
|
|
: primaryKeyDef;
|
|
|
|
|
|
|
|
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(', ');
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const newSQL = oneLineSql.replace(
|
2020-12-26 12:10:40 -05:00
|
|
|
columnDefinitions,
|
|
|
|
newColumnDefinitions
|
|
|
|
);
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
return this.alter(newSQL, createIndices, (row) => {
|
2020-12-26 12:10:40 -05:00
|
|
|
return row;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
{ connection: this.connection }
|
|
|
|
);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2020-12-26 12:10:40 -05:00
|
|
|
|
2021-01-01 19:42:19 +02:00
|
|
|
async foreign(foreignInfo) {
|
2020-12-26 12:10:40 -05:00
|
|
|
return this.client.transaction(
|
|
|
|
async (trx) => {
|
|
|
|
this.trx = trx;
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const { createTable, createIndices } = await this.getTableSql();
|
2020-12-26 12:10:40 -05:00
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const oneLineSql = createTable.replace(/\s+/g, ' ');
|
|
|
|
const matched = oneLineSql.match(/^CREATE TABLE\s+(\S+)\s*\((.*)\)/);
|
2020-12-26 12:10:40 -05:00
|
|
|
|
|
|
|
const columnDefinitions = matched[2];
|
|
|
|
|
|
|
|
if (!columnDefinitions) {
|
|
|
|
throw new Error('No column definitions in this statement!');
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2021-01-12 23:07:07 +01:00
|
|
|
newForeignSQL += ` ON DELETE ${foreignInfo.onDelete}`;
|
2020-12-26 12:10:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
newColumnDefinitions.push(newForeignSQL);
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
const newSQL = oneLineSql.replace(
|
2020-12-26 12:10:40 -05:00
|
|
|
columnDefinitions,
|
|
|
|
newColumnDefinitions.join(', ')
|
|
|
|
);
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
return await this.generateAlterCommands(
|
|
|
|
newSQL,
|
|
|
|
createIndices,
|
|
|
|
(row) => {
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
);
|
2020-12-26 12:10:40 -05:00
|
|
|
},
|
|
|
|
{ connection: this.connection }
|
|
|
|
);
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2020-12-26 12:10:40 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
async alter(newSql, createIndices, mapRow) {
|
|
|
|
await this.createNewTable(newSql);
|
|
|
|
await this.copyData(mapRow);
|
|
|
|
await this.dropOriginal();
|
|
|
|
await this.renameTable();
|
|
|
|
|
|
|
|
for (const createIndex of createIndices) {
|
|
|
|
await this.trx.raw(createIndex);
|
|
|
|
}
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2021-01-03 04:10:26 +02:00
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
async generateAlterCommands(newSql, createIndices, mapRow) {
|
2021-01-03 04:10:26 +02:00
|
|
|
const result = [];
|
|
|
|
|
2021-02-03 19:49:42 +01:00
|
|
|
result.push(createNewTable(newSql, this.tableName(), this.alteredName));
|
2021-01-03 04:10:26 +02:00
|
|
|
result.push(copyAllData(this.tableName(), this.alteredName));
|
|
|
|
result.push(dropOriginal(this.tableName()));
|
2021-02-03 19:49:42 +01:00
|
|
|
result.push(renameTable(this.alteredName, this.tableName()));
|
2021-01-03 04:10:26 +02:00
|
|
|
|
2021-02-05 15:35:30 +01:00
|
|
|
for (const createIndex of createIndices) {
|
|
|
|
result.push(createIndex);
|
|
|
|
}
|
|
|
|
|
2021-01-03 04:10:26 +02:00
|
|
|
return result;
|
|
|
|
}
|
2021-01-01 19:42:19 +02:00
|
|
|
}
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2019-06-04 00:37:17 +02:00
|
|
|
module.exports = SQLite3_DDL;
|