UNPKG

@riao/dbal

Version:
566 lines 20 kB
"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 if (typeof column.default === 'string') { const escaped = column.default .replace(/\\/g, '\\\\') .replace(/'/g, '\\\'') .replace(/"/g, '\\"'); this.sql.append(`'${escaped}' `); } 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