@riao/dbal
Version:
559 lines • 19.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataDefinitionBuilder = void 0;
const statement_builder_1 = require("../builder/statement-builder");
const column_1 = require("../column");
const dml_1 = require("../dml");
const expression_1 = require("../expression");
const functions_1 = require("../functions");
const function_token_1 = require("../functions/function-token");
class DataDefinitionBuilder extends statement_builder_1.StatementBuilder {
constructor() {
super(...arguments);
this.columnTypes = column_1.ColumnType;
this.queryBuilderType = dml_1.DatabaseQueryBuilder;
}
getQueryBuilder() {
return new this.queryBuilderType();
}
getColumnTypes() {
return this.columnTypes;
}
// ------------------------------------------------------------------------
// Create Database
// ------------------------------------------------------------------------
createDatabaseStatement() {
this.sql.append('CREATE DATABASE ');
return this;
}
createDatabase(options) {
this.createDatabaseStatement();
this.sql.append(options.name);
return this;
}
// ------------------------------------------------------------------------
// Create User
// ------------------------------------------------------------------------
createUserStatement() {
this.sql.append('CREATE USER ');
return this;
}
createUserPassword(password) {
this.sql.append(`WITH PASSWORD '${password}' `);
return this;
}
createUser(options) {
this.createUserStatement();
this.sql.append(options.name + ' ');
if (options.password) {
this.createUserPassword(options.password);
}
return this;
}
// ------------------------------------------------------------------------
// Create Table
// ------------------------------------------------------------------------
createTableStatement() {
this.sql.append('CREATE TABLE ');
return this;
}
ifNotExists() {
this.sql.append('IF NOT EXISTS ');
return this;
}
createColumnType(column) {
this.sql.append(this.columnTypes[column.type]);
if ('length' in column) {
this.sql.append(`(${column.length})`);
}
if ('significant' in column && 'decimal' in column) {
const significant = column.significant + column.decimal;
const decimal = column.decimal;
this.sql.append(`(${significant}, ${decimal})`);
}
this.sql.space();
return this;
}
columnRequired() {
this.sql.append('NOT NULL ');
return this;
}
columnDefaultValue(column) {
this.sql.append('DEFAULT ');
if (column.default === null) {
this.columnDefaultNull();
}
else if (column.default === true) {
this.columnDefaultTrue();
}
else if (column.default === false) {
this.columnDefaultFalse();
}
else if ((0, expression_1.isExpressionToken)(column.default)) {
// CURRENT_TIMESTAMP does not work when wrapped in parenthesis for
// default value; but all other expressions should be wrapped
let wrapParens = true;
if ((0, functions_1.isDatabaseFunction)(column.default) &&
column.default.fn ===
function_token_1.DatabaseFunctionKeys.CURRENT_TIMESTAMP) {
wrapParens = false;
}
else {
this.sql.openParens();
}
const dml = this.getQueryBuilder();
dml.expression(column.default);
this.appendBuilder(dml);
if (wrapParens) {
this.sql.closeParens();
}
this.sql.append(' ');
}
else {
this.sql.append(column.default + ' ');
}
return this;
}
columnDefaultNull() {
this.sql.append('NULL ');
return this;
}
columnDefaultTrue() {
this.sql.append('TRUE ');
return this;
}
columnDefaultFalse() {
this.sql.append('FALSE ');
return this;
}
columnAutoIncrement() {
this.sql.append('AUTO_INCREMENT ');
return this;
}
createTableColumn(column) {
this.sql.columnName(column.name);
this.sql.space();
this.createColumnType(column);
if (column.required) {
this.columnRequired();
}
if (column.default !== undefined) {
this.columnDefaultValue(column);
}
if (column.autoIncrement) {
this.columnAutoIncrement();
}
this.sql.trimEnd(' ');
return this;
}
createTablePrimaryKeys(names) {
this.sql.append(', PRIMARY KEY (');
this.sql.append(names.map((key) => this.sql.getEnclosedName(key)).join(','));
this.sql.append(')');
return this;
}
createTableColumns(options) {
var _a;
this.sql.append('(');
if (options.foreignKeys === undefined) {
options.foreignKeys = [];
}
for (const column of options.columns) {
this.createTableColumn(column);
this.sql.append(', ');
if ('fk' in column) {
options.foreignKeys.push(Object.assign(Object.assign({}, column.fk), { column: column.name }));
}
}
this.sql.trimEnd(', ');
const primaryKeys = options.columns
.filter((column) => column.primaryKey)
.map((column) => column.name);
if (primaryKeys.length > 0) {
this.createTablePrimaryKeys(primaryKeys);
}
for (const uniqueColumn of options.columns.filter((column) => column.isUnique)) {
this.sql.append(', ');
this.uniqueConstraint(options.name, uniqueColumn.name);
}
for (const fk of (_a = options.foreignKeys) !== null && _a !== void 0 ? _a : []) {
this.sql.append(', ');
this.foreignKeyConstraint(options.name, fk);
}
this.sql.trimEnd(' ');
this.sql.append(')');
return this;
}
createTable(options) {
this.createTableStatement();
if (options.ifNotExists) {
this.ifNotExists();
}
this.sql.tableName(options.name);
this.sql.space();
this.createTableColumns(options);
this.createTableTriggers(options);
return this;
}
constraintStatement() {
this.sql.append('CONSTRAINT ');
return this;
}
createTableTriggers(options) {
const pk = options.columns.find((column) => column.primaryKey);
const triggers = options.columns
.filter((column) => column.triggers !== undefined)
.map((column) => column.triggers({
table: options.name,
column: column.name,
idColumn: pk.name,
}))
.flat();
// eslint-disable-next-line @typescript-eslint/no-this-alias
let i = this;
for (const trigger of triggers) {
i.next = i.createNext();
i = i.next;
i.createTrigger(trigger.getTrigger({
queryBuilder: new this.queryBuilderType(),
}));
}
return this;
}
// ------------------------------------------------------------------------
// Indexes
// ------------------------------------------------------------------------
createIndex(options) {
if (!options.name) {
options.name = `idx_${options.table}_${options.column}`;
}
this.sql.append(`CREATE INDEX ${options.name} ON `);
this.sql.tableName(options.table);
this.sql.openParens();
this.sql.columnName(options.column);
this.sql.closeParens();
return this;
}
// ------------------------------------------------------------------------
// Grant permissions
// ------------------------------------------------------------------------
grantOnDatabase(database) {
this.sql.append(database + '.*');
return this;
}
grantOn(on) {
if (on === '*') {
this.sql.append('*.*');
}
else if ('database' in on && 'table' in on) {
this.sql.append(on.database + '.' + on.table);
}
else if ('database' in on) {
this.grantOnDatabase(on.database);
}
this.sql.space();
return this;
}
/**
* IMPORTANT: UNSTABLE. Grant is currently only compatible with
* mysql and mssql and may cause unintended side-effects and/or
* privilege escalation.
* This should not be used in most circumstances!
* Use a dedicated database connection for this method, as it may change
* the database connection's default database.
* This method and it's arguments are likely to change in future versions
*
* @param options Grant options
* @returns
*/
grant(options) {
if (!Array.isArray(options.privileges)) {
options.privileges = [options.privileges];
}
if (!Array.isArray(options.to)) {
options.to = [options.to];
}
this.sql.append('GRANT ');
this.sql.append(options.privileges.join(', ') + ' ');
this.sql.append('ON ');
this.grantOn(options.on);
this.sql.append('TO ' + options.to.join(', ') + ' ');
return this;
}
// ------------------------------------------------------------------------
// Foreign Key Constraint
// ------------------------------------------------------------------------
foreignKeyStatement() {
this.sql.append('FOREIGN KEY ');
return this;
}
foreignKeyName(childTable, fk) {
var _a;
const columns = 'columns' in fk ? fk.columns : [fk.column];
this.sql.append((_a = fk.name) !== null && _a !== void 0 ? _a : `fk_${childTable}_${columns.join('_')}`);
this.sql.space();
return this;
}
foreignKeyColumns(columns) {
this.sql.openParens();
this.sql.commaSeparate(columns.map((column) => this.sql.getEnclosedName(column)));
this.sql.closeParens();
return this;
}
referencesStatement(table, columns) {
this.sql.append('REFERENCES ');
this.sql.tableName(table);
this.sql.openParens();
this.sql.commaSeparate(columns.map((column) => this.sql.getEnclosedName(column)));
this.sql.closeParens();
return this;
}
fkOnUpdate(option) {
this.sql.append('ON UPDATE ' + option + ' ');
return this;
}
fkOnDelete(option) {
this.sql.append('ON DELETE ' + option + ' ');
return this;
}
foreignKeyConstraint(childTable, fk) {
const columns = 'columns' in fk ? fk.columns : [fk.column];
const referencesColumns = 'referencesColumns' in fk
? fk.referencesColumns
: [fk.referencesColumn];
this.constraintStatement();
this.foreignKeyName(childTable, fk);
this.foreignKeyStatement();
this.foreignKeyColumns(columns);
this.referencesStatement(fk.referencesTable, referencesColumns);
if (fk.onUpdate) {
this.fkOnUpdate(fk.onUpdate);
}
if (fk.onDelete) {
this.fkOnDelete(fk.onDelete);
}
}
// ------------------------------------------------------------------------
// Constraints
// ------------------------------------------------------------------------
disableForeignKeyChecks() {
this.sql.append('SET FOREIGN_KEY_CHECKS=0');
return this;
}
enableForeignKeyChecks() {
this.sql.append('SET FOREIGN_KEY_CHECKS=1');
return this;
}
uniqueConstraint(table, column) {
this.sql.append(`CONSTRAINT uq_${table}_${column} `);
this.sql.append('UNIQUE(' + this.sql.getEnclosedName(column) + ')');
return this;
}
// ------------------------------------------------------------------------
// Alter Table
// ------------------------------------------------------------------------
alterTableStatement(table) {
this.sql.append('ALTER TABLE ');
this.sql.tableName(table);
this.sql.space();
return this;
}
addColumns(options) {
this.alterTableStatement(options.table);
this.sql.append('ADD ');
const addForeignKeys = [];
for (const column of options.columns) {
this.createTableColumn(column);
this.sql.append(', ');
if (column.fk) {
addForeignKeys.push(Object.assign(Object.assign({}, column.fk), { column: column.name }));
}
}
this.sql.trimEnd(', ');
if (addForeignKeys.length) {
let i;
for (const fk of addForeignKeys) {
i = (i !== null && i !== void 0 ? i : this).createNext();
i.addForeignKey(Object.assign(Object.assign({}, fk), { table: options.table }));
}
}
return this;
}
addForeignKey(options) {
this.alterTableStatement(options.table);
this.sql.append('ADD ');
this.foreignKeyConstraint(options.table, options);
return this;
}
alterColumnStatement(column) {
this.sql.append('ALTER COLUMN ');
this.sql.columnName(column);
this.sql.space();
return this;
}
changeColumn(options) {
this.alterTableStatement(options.table);
this.alterColumnStatement(options.column);
this.createTableColumn(options.options);
if (options.options.fk !== undefined) {
// TODO: Add changeColumn inline FK support
throw new Error('Foreign Keys are currently not supported with changeColumn(). ' +
'Please submit an issue on Github to request this feature.');
}
return this;
}
dropColumn(options) {
this.alterTableStatement(options.table);
this.sql.append('DROP COLUMN ');
this.sql.columnName(options.column);
return this;
}
dropForeignKey(options) {
this.alterTableStatement(options.table);
this.sql.append('DROP ');
this.foreignKeyStatement();
this.sql.append(options.fk);
return this;
}
renameTable(options) {
this.alterTableStatement(options.table);
this.sql.append('RENAME ');
this.sql.tableName(options.to);
return this;
}
// ------------------------------------------------------------------------
// Drop Database
// ------------------------------------------------------------------------
dropDatabase(options) {
this.sql.append('DROP DATABASE ');
if (options.ifExists) {
this.sql.append('IF EXISTS ');
}
this.sql.append(options.name);
return this;
}
// ------------------------------------------------------------------------
// Drop Table
// ------------------------------------------------------------------------
dropTable(options) {
if (Array.isArray(options.tables)) {
options.tables = options.tables.join(',');
}
this.sql.append('DROP TABLE ');
if (options.ifExists) {
this.sql.append('IF EXISTS ');
}
this.sql.append(options.tables
.split(',')
.map((table) => this.sql.getEnclosedName(table))
.join(','));
return this;
}
// ------------------------------------------------------------------------
// Drop User
// ------------------------------------------------------------------------
dropUser(options) {
if (Array.isArray(options.names)) {
options.names = options.names.join(',');
}
this.sql.append('DROP USER ');
if (options.ifExists) {
this.sql.append('IF EXISTS ');
}
this.sql.append(options.names);
return this;
}
// ------------------------------------------------------------------------
// Truncate Table
// ------------------------------------------------------------------------
truncate(options) {
this.sql.append('TRUNCATE TABLE ');
this.sql.tableName(options.table);
return this;
}
// ------------------------------------------------------------------------
// Triggers
// ------------------------------------------------------------------------
createTrigger(options) {
this.createTriggerStatement();
if (!options.name) {
options.name = this.getTriggerName(options);
}
this.sql.append(options.name + ' ');
this.createTriggerTableEvent(options);
this.createTriggerForEachRow();
this.createTriggerBody(options.body);
this.sql.endStatement();
return this;
}
createTriggerStatement() {
this.sql.append('CREATE TRIGGER ');
return this;
}
getTriggerName(options) {
return (`${options.table}_` +
`${options.timing.toLowerCase()}_` +
`${options.event.toLowerCase()}`);
}
createTriggerTableEvent(options) {
this.createTriggerEvent(options);
this.createTriggerTable(options.table);
return this;
}
createTriggerTable(table) {
this.sql.append('ON ');
this.sql.tableName(table);
this.sql.space();
return this;
}
createTriggerEvent(options) {
this.sql.append(options.timing + ' ' + options.event + ' ');
return this;
}
createTriggerBody(sql) {
this.createTriggerBeginStatement();
if (typeof sql === 'string') {
this.sql.append(sql);
}
else if (sql instanceof dml_1.DatabaseQueryBuilder) {
this.appendBuilder(sql);
}
this.sql.endStatement();
this.createTriggerEndStatement();
return this;
}
createTriggerBeginStatement() {
this.sql.append('BEGIN ');
return this;
}
createTriggerEndStatement() {
this.sql.append('END');
return this;
}
createTriggerForEachRow() {
this.sql.append('FOR EACH ROW ');
return this;
}
dropTrigger(options) {
this.dropTriggerStatement();
if (options.ifExists) {
this.ifExists();
}
this.dropTriggerName(options);
return this;
}
dropTriggerStatement() {
this.sql.append('DROP TRIGGER ');
return this;
}
ifExists() {
this.sql.append('IF EXISTS ');
return this;
}
dropTriggerName(options) {
this.sql.columnName(options.name);
return this;
}
}
exports.DataDefinitionBuilder = DataDefinitionBuilder;
//# sourceMappingURL=ddl-builder.js.map