409 lines
11 KiB
JavaScript
Raw Normal View History

2016-03-02 17:07:05 +01:00
// SQLite3_DDL
//
// All of the SQLite3 specific DDL helpers for renaming/dropping
// columns and changing datatypes.
// -------
const identity = require('lodash/identity');
const { nanonum } = require('../../../util/nanoid');
const {
createNewTable,
copyData,
dropOriginal,
renameTable,
getTableSql,
} = require('./internal/sqlite-ddl-operations');
const { parseCreateTable, parseCreateIndex } = require('./internal/parser');
const {
compileCreateTable,
compileCreateIndex,
} = require('./internal/compiler');
const { isEqualId, includesId } = require('./internal/utils');
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.
class SQLite3_DDL {
constructor(client, tableCompiler, pragma, connection) {
this.client = client;
this.tableCompiler = tableCompiler;
this.pragma = pragma;
this.tableNameRaw = this.tableCompiler.tableNameRaw;
this.alteredName = `_knex_temp_alter${nanonum(3)}`;
this.connection = connection;
this.formatter = (value) =>
this.client.customWrapIdentifier(value, identity);
this.wrap = (value) => this.client.wrapIdentifierImpl(value);
}
2016-03-02 17:07:05 +01:00
tableName() {
return this.formatter(this.tableNameRaw);
}
getTableSql() {
const tableName = this.tableName();
this.trx.disableProcessing();
return this.trx.raw(getTableSql(tableName)).then((result) => {
this.trx.enableProcessing();
return {
createTable: result.filter((create) => create.type === 'table')[0].sql,
createIndices: result
.filter((create) => create.type === 'index')
.map((create) => create.sql),
};
});
}
2016-03-02 17:07:05 +01:00
renameTable() {
return this.trx.raw(renameTable(this.alteredName, this.tableName()));
}
2016-03-02 17:07:05 +01:00
dropOriginal() {
return this.trx.raw(dropOriginal(this.tableName()));
}
2016-03-02 17:07:05 +01:00
copyData(columns) {
return this.trx.raw(copyData(this.tableName(), this.alteredName, columns));
}
2016-03-02 17:07:05 +01:00
createNewTable(sql) {
return this.trx.raw(
createNewTable(sql, this.tableName(), this.alteredName)
);
}
2016-03-02 17:07:05 +01:00
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) =>
isEqualId(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
? { name: null, conflict: null }
: null;
2021-03-02 23:42:43 +01:00
column.constraints.null = newColumnInfo.notNull
? null
: column.constraints.null;
}
return column;
});
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.alter(newTable, createIndices);
},
{ connection: this.connection }
);
}
dropColumn(columns) {
return this.client.transaction(
async (trx) => {
this.trx = trx;
const { createTable, createIndices } = await this.getTableSql();
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)
);
} 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;
}
}
);
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);
},
{ connection: this.connection }
);
}
dropForeign(columns, foreignKeyName) {
return this.client.transaction(
async (trx) => {
this.trx = trx;
const { createTable, createIndices } = await this.getTableSql();
const parsedTable = parseCreateTable(createTable);
if (!foreignKeyName) {
parsedTable.columns = parsedTable.columns.map((column) => ({
...column,
references: includesId(columns, column.name)
? null
: column.references,
}));
}
parsedTable.constraints = parsedTable.constraints.filter(
(constraint) => {
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;
}
}
);
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.alter(newTable, createIndices);
},
{ connection: this.connection }
);
}
dropPrimary(constraintName) {
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) => ({
...column,
primary: null,
}));
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;
}
}
);
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.alter(newTable, createIndices);
},
{ connection: this.connection }
);
}
primary(columns, constraintName) {
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) => ({
...column,
primary: null,
}));
parsedTable.constraints = parsedTable.constraints.filter(
(constraint) => constraint.type !== 'PRIMARY KEY'
);
parsedTable.constraints.push({
type: 'PRIMARY KEY',
name: constraintName || null,
columns: columns.map((column) => ({
name: column,
expression: false,
collation: null,
order: null,
})),
conflict: null,
});
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.alter(newTable, createIndices);
},
{ connection: this.connection }
);
}
foreign(foreignInfo) {
return this.client.transaction(
async (trx) => {
this.trx = trx;
const { createTable, createIndices } = await this.getTableSql();
const parsedTable = parseCreateTable(createTable);
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,
},
});
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.generateAlterCommands(newTable, createIndices);
},
{ connection: this.connection }
);
}
setNullable(column, isNullable) {
return this.client.transaction(
async (trx) => {
this.trx = trx;
const { createTable, createIndices } = await this.getTableSql();
const parsedTable = parseCreateTable(createTable);
const parsedColumn = parsedTable.columns.find((c) =>
isEqualId(column, c.name)
);
if (!parsedColumn) {
throw new Error(
`.setNullable: Column ${column} does not exist in table ${this.tableName()}.`
);
}
parsedColumn.constraints.notnull = isNullable
? null
: { name: null, conflict: null };
parsedColumn.constraints.null = isNullable
? parsedColumn.constraints.null
: null;
const newTable = compileCreateTable(parsedTable, this.wrap);
return this.generateAlterCommands(newTable, createIndices);
},
{ connection: this.connection }
);
}
async alter(newSql, createIndices, columns) {
await this.createNewTable(newSql);
await this.copyData(columns);
await this.dropOriginal();
await this.renameTable();
for (const createIndex of createIndices) {
await this.trx.raw(createIndex);
}
}
generateAlterCommands(newSql, createIndices, columns) {
const result = [];
result.push(createNewTable(newSql, this.tableName(), this.alteredName));
result.push(copyData(this.tableName(), this.alteredName, columns));
result.push(dropOriginal(this.tableName()));
result.push(renameTable(this.alteredName, this.tableName()));
for (const createIndex of createIndices) {
result.push(createIndex);
}
return result;
}
}
2016-03-02 17:07:05 +01:00
module.exports = SQLite3_DDL;