2016-05-17 01:01:34 +10:00
|
|
|
/* eslint max-len:0 */
|
2016-03-02 17:07:05 +01:00
|
|
|
|
|
|
|
// Table Compiler
|
|
|
|
// -------
|
2018-05-29 10:09:13 -04:00
|
|
|
import { pushAdditional, pushQuery, unshiftQuery } from './helpers';
|
2016-05-17 01:01:34 +10:00
|
|
|
import * as helpers from '../helpers';
|
2018-02-01 23:41:01 +01:00
|
|
|
import { groupBy, reduce, map, first, tail, isEmpty, indexOf, isArray, isUndefined } from 'lodash'
|
2016-03-02 17:07:05 +01:00
|
|
|
|
|
|
|
function TableCompiler(client, tableBuilder) {
|
|
|
|
this.client = client
|
2018-02-01 23:41:01 +01:00
|
|
|
this.tableBuilder = tableBuilder;
|
2016-03-02 17:07:05 +01:00
|
|
|
this.method = tableBuilder._method;
|
|
|
|
this.schemaNameRaw = tableBuilder._schemaName;
|
|
|
|
this.tableNameRaw = tableBuilder._tableName;
|
|
|
|
this.single = tableBuilder._single;
|
|
|
|
this.grouped = groupBy(tableBuilder._statements, 'grouping');
|
2018-02-01 23:41:01 +01:00
|
|
|
this.formatter = client.formatter(tableBuilder);
|
2016-03-02 17:07:05 +01:00
|
|
|
this.sequence = [];
|
|
|
|
this._formatting = client.config && client.config.formatting
|
|
|
|
}
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
TableCompiler.prototype.pushQuery = pushQuery
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
TableCompiler.prototype.pushAdditional = pushAdditional
|
2016-03-02 17:07:05 +01:00
|
|
|
|
2018-05-29 10:09:13 -04:00
|
|
|
TableCompiler.prototype.unshiftQuery = unshiftQuery
|
|
|
|
|
2016-03-02 17:07:05 +01:00
|
|
|
// Convert the tableCompiler toSQL
|
|
|
|
TableCompiler.prototype.toSQL = function () {
|
|
|
|
this[this.method]();
|
|
|
|
return this.sequence;
|
|
|
|
};
|
|
|
|
|
|
|
|
TableCompiler.prototype.lowerCase = true;
|
|
|
|
|
|
|
|
// Column Compilation
|
|
|
|
// -------
|
|
|
|
|
|
|
|
// If this is a table "creation", we need to first run through all
|
|
|
|
// of the columns to build them into a single string,
|
|
|
|
// and then run through anything else and push it to the query sequence.
|
|
|
|
TableCompiler.prototype.createAlterTableMethods = null;
|
|
|
|
TableCompiler.prototype.create = function (ifNot) {
|
2017-02-16 20:34:59 +02:00
|
|
|
const columnBuilders = this.getColumns();
|
|
|
|
const columns = columnBuilders.map(col => col.toSQL());
|
2016-05-17 01:01:34 +10:00
|
|
|
const columnTypes = this.getColumnTypes(columns);
|
2016-03-02 17:07:05 +01:00
|
|
|
if (this.createAlterTableMethods) {
|
|
|
|
this.alterTableForCreate(columnTypes);
|
|
|
|
}
|
|
|
|
this.createQuery(columnTypes, ifNot);
|
|
|
|
this.columnQueries(columns);
|
|
|
|
delete this.single.comment;
|
|
|
|
this.alterTable();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Only create the table if it doesn't exist.
|
|
|
|
TableCompiler.prototype.createIfNot = function () {
|
|
|
|
this.create(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
// If we're altering the table, we need to one-by-one
|
|
|
|
// go through and handle each of the queries associated
|
|
|
|
// with altering the table's schema.
|
|
|
|
TableCompiler.prototype.alter = function () {
|
2017-02-16 20:34:59 +02:00
|
|
|
const addColBuilders = this.getColumns();
|
|
|
|
const addColumns = addColBuilders.map(col => col.toSQL());
|
|
|
|
const alterColBuilders = this.getColumns('alter');
|
|
|
|
const alterColumns = alterColBuilders.map(col => col.toSQL());
|
|
|
|
const addColumnTypes = this.getColumnTypes(addColumns);
|
|
|
|
const alterColumnTypes = this.getColumnTypes(alterColumns);
|
|
|
|
|
|
|
|
this.addColumns(addColumnTypes);
|
|
|
|
this.alterColumns(alterColumnTypes, alterColBuilders);
|
|
|
|
this.columnQueries(addColumns);
|
|
|
|
this.columnQueries(alterColumns);
|
2016-03-02 17:07:05 +01:00
|
|
|
this.alterTable();
|
|
|
|
};
|
|
|
|
|
|
|
|
TableCompiler.prototype.foreign = function (foreignData) {
|
|
|
|
if (foreignData.inTable && foreignData.references) {
|
2017-02-17 00:35:43 +02:00
|
|
|
const keyName = foreignData.keyName ? this.formatter.wrap(foreignData.keyName) : this._indexCommand('foreign', this.tableNameRaw, foreignData.column);
|
2016-05-17 01:01:34 +10:00
|
|
|
const column = this.formatter.columnize(foreignData.column);
|
|
|
|
const references = this.formatter.columnize(foreignData.references);
|
|
|
|
const inTable = this.formatter.wrap(foreignData.inTable);
|
|
|
|
const onUpdate = foreignData.onUpdate ? (this.lowerCase ? ' on update ' : ' ON UPDATE ') + foreignData.onUpdate : '';
|
|
|
|
const onDelete = foreignData.onDelete ? (this.lowerCase ? ' on delete ' : ' ON DELETE ') + foreignData.onDelete : '';
|
2016-03-02 17:07:05 +01:00
|
|
|
if (this.lowerCase) {
|
2016-05-17 01:01:34 +10:00
|
|
|
this.pushQuery((!this.forCreate ? `alter table ${this.tableName()} add ` : '') + 'constraint ' + keyName + ' ' +
|
2016-03-02 17:07:05 +01:00
|
|
|
'foreign key (' + column + ') references ' + inTable + ' (' + references + ')' + onUpdate + onDelete);
|
|
|
|
} else {
|
2016-05-17 01:01:34 +10:00
|
|
|
this.pushQuery((!this.forCreate ? `ALTER TABLE ${this.tableName()} ADD ` : '') + 'CONSTRAINT ' + keyName + ' ' +
|
2016-03-02 17:07:05 +01:00
|
|
|
'FOREIGN KEY (' + column + ') REFERENCES ' + inTable + ' (' + references + ')' + onUpdate + onDelete);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get all of the column sql & bindings individually for building the table queries.
|
2016-05-17 01:01:34 +10:00
|
|
|
TableCompiler.prototype.getColumnTypes = columns =>
|
|
|
|
reduce(map(columns, first), function (memo, column) {
|
2016-03-02 17:07:05 +01:00
|
|
|
memo.sql.push(column.sql);
|
|
|
|
memo.bindings.concat(column.bindings);
|
|
|
|
return memo;
|
2016-05-17 01:01:34 +10:00
|
|
|
}, { sql: [], bindings: [] })
|
|
|
|
;
|
2016-03-02 17:07:05 +01:00
|
|
|
|
|
|
|
// Adds all of the additional queries from the "column"
|
|
|
|
TableCompiler.prototype.columnQueries = function (columns) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const queries = reduce(map(columns, tail), function (memo, column) {
|
2016-03-02 17:07:05 +01:00
|
|
|
if (!isEmpty(column)) return memo.concat(column);
|
|
|
|
return memo;
|
|
|
|
}, []);
|
2017-02-16 20:34:59 +02:00
|
|
|
for (const q of queries) {
|
|
|
|
this.pushQuery(q);
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add a new column.
|
|
|
|
TableCompiler.prototype.addColumnsPrefix = 'add column ';
|
|
|
|
|
|
|
|
// All of the columns to "add" for the query
|
2017-02-16 20:34:59 +02:00
|
|
|
TableCompiler.prototype.addColumns = function (columns, prefix) {
|
|
|
|
prefix = prefix || this.addColumnsPrefix;
|
|
|
|
|
2016-03-02 17:07:05 +01:00
|
|
|
if (columns.sql.length > 0) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const columnSql = map(columns.sql, (column) => {
|
2017-02-16 20:34:59 +02:00
|
|
|
return prefix + column;
|
2016-03-02 17:07:05 +01:00
|
|
|
});
|
|
|
|
this.pushQuery({
|
|
|
|
sql: (this.lowerCase ? 'alter table ' : 'ALTER TABLE ') + this.tableName() + ' ' + columnSql.join(', '),
|
|
|
|
bindings: columns.bindings
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-02-16 20:34:59 +02:00
|
|
|
// Alter column
|
|
|
|
TableCompiler.prototype.alterColumnsPrefix = 'alter column ';
|
|
|
|
|
|
|
|
TableCompiler.prototype.alterColumns = function (columns, colBuilders) {
|
|
|
|
if (columns.sql.length > 0) {
|
|
|
|
this.addColumns(columns, this.alterColumnsPrefix, colBuilders);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-03-02 17:07:05 +01:00
|
|
|
// Compile the columns as needed for the current create or alter table
|
2017-02-16 20:34:59 +02:00
|
|
|
TableCompiler.prototype.getColumns = function (method) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const columns = this.grouped.columns || [];
|
2017-02-16 20:34:59 +02:00
|
|
|
method = method || 'add';
|
|
|
|
|
2018-02-01 23:41:01 +01:00
|
|
|
const queryContext = this.tableBuilder.queryContext();
|
|
|
|
|
2017-02-16 20:34:59 +02:00
|
|
|
return columns
|
|
|
|
.filter(column => column.builder._method === method)
|
2018-02-01 23:41:01 +01:00
|
|
|
.map(column => {
|
|
|
|
// pass queryContext down to columnBuilder but do not overwrite it if already set
|
|
|
|
if (!isUndefined(queryContext) && isUndefined(column.builder.queryContext())) {
|
|
|
|
column.builder.queryContext(queryContext);
|
|
|
|
}
|
|
|
|
return this.client.columnCompiler(this, column.builder);
|
|
|
|
});
|
2016-03-02 17:07:05 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
TableCompiler.prototype.tableName = function () {
|
2016-05-17 01:01:34 +10:00
|
|
|
const name = this.schemaNameRaw ?
|
2016-03-02 17:07:05 +01:00
|
|
|
`${this.schemaNameRaw}.${this.tableNameRaw}`
|
|
|
|
: this.tableNameRaw;
|
|
|
|
|
|
|
|
return this.formatter.wrap(name);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Generate all of the alter column statements necessary for the query.
|
|
|
|
TableCompiler.prototype.alterTable = function () {
|
2016-05-17 01:01:34 +10:00
|
|
|
const alterTable = this.grouped.alterTable || [];
|
|
|
|
for (let i = 0, l = alterTable.length; i < l; i++) {
|
|
|
|
const statement = alterTable[i];
|
2016-03-02 17:07:05 +01:00
|
|
|
if (this[statement.method]) {
|
|
|
|
this[statement.method].apply(this, statement.args);
|
|
|
|
} else {
|
2016-05-17 01:01:34 +10:00
|
|
|
helpers.error(`Debug: ${statement.method} does not exist`);
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
}
|
2016-05-17 01:01:34 +10:00
|
|
|
for (const item in this.single) {
|
2016-03-02 17:07:05 +01:00
|
|
|
if (typeof this[item] === 'function') this[item](this.single[item]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TableCompiler.prototype.alterTableForCreate = function (columnTypes) {
|
|
|
|
this.forCreate = true;
|
2016-05-17 01:01:34 +10:00
|
|
|
const savedSequence = this.sequence;
|
|
|
|
const alterTable = this.grouped.alterTable || [];
|
2016-03-02 17:07:05 +01:00
|
|
|
this.grouped.alterTable = [];
|
2016-05-17 01:01:34 +10:00
|
|
|
for (let i = 0, l = alterTable.length; i < l; i++) {
|
|
|
|
const statement = alterTable[i];
|
2016-03-02 17:07:05 +01:00
|
|
|
if (indexOf(this.createAlterTableMethods, statement.method) < 0) {
|
|
|
|
this.grouped.alterTable.push(statement);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (this[statement.method]) {
|
|
|
|
this.sequence = [];
|
|
|
|
this[statement.method].apply(this, statement.args);
|
|
|
|
columnTypes.sql.push(this.sequence[0].sql);
|
|
|
|
} else {
|
2016-05-17 01:01:34 +10:00
|
|
|
helpers.error(`Debug: ${statement.method} does not exist`);
|
2016-03-02 17:07:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.sequence = savedSequence;
|
|
|
|
this.forCreate = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Drop the index on the current table.
|
|
|
|
TableCompiler.prototype.dropIndex = function (value) {
|
2016-05-17 01:01:34 +10:00
|
|
|
this.pushQuery(`drop index${value}`);
|
2016-03-02 17:07:05 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// Drop the unique
|
|
|
|
TableCompiler.prototype.dropUnique =
|
|
|
|
TableCompiler.prototype.dropForeign = function () {
|
|
|
|
throw new Error('Method implemented in the dialect driver');
|
|
|
|
};
|
|
|
|
|
|
|
|
TableCompiler.prototype.dropColumnPrefix = 'drop column ';
|
|
|
|
TableCompiler.prototype.dropColumn = function () {
|
2016-05-17 01:01:34 +10:00
|
|
|
const columns = helpers.normalizeArr.apply(null, arguments);
|
|
|
|
const drops = map(isArray(columns) ? columns : [columns], (column) => {
|
2016-03-02 17:07:05 +01:00
|
|
|
return this.dropColumnPrefix + this.formatter.wrap(column);
|
|
|
|
});
|
2016-05-17 01:01:34 +10:00
|
|
|
this.pushQuery(
|
|
|
|
(this.lowerCase ? 'alter table ' : 'ALTER TABLE ') +
|
|
|
|
this.tableName() + ' ' + drops.join(', ')
|
|
|
|
);
|
2016-03-02 17:07:05 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// If no name was specified for this index, we will create one using a basic
|
|
|
|
// convention of the table name, followed by the columns, followed by an
|
|
|
|
// index type, such as primary or index, which makes the index unique.
|
|
|
|
TableCompiler.prototype._indexCommand = function (type, tableName, columns) {
|
|
|
|
if (!isArray(columns)) columns = columns ? [columns] : [];
|
2016-05-17 01:01:34 +10:00
|
|
|
const table = tableName.replace(/\.|-/g, '_');
|
|
|
|
const indexName = (table + '_' + columns.join('_') + '_' + type).toLowerCase();
|
2015-07-15 23:16:30 -03:00
|
|
|
return this.formatter.wrap(indexName);
|
2016-03-02 17:07:05 +01:00
|
|
|
};
|
|
|
|
|
2016-05-17 01:01:34 +10:00
|
|
|
export default TableCompiler;
|