685 lines
26 KiB
JavaScript
685 lines
26 KiB
JavaScript
import { RawQueryFragment, Utils } from '@mikro-orm/core';
|
|
/** Base class for database-specific schema helpers. Provides SQL generation for DDL operations. */
|
|
export class SchemaHelper {
|
|
platform;
|
|
constructor(platform) {
|
|
this.platform = platform;
|
|
}
|
|
/** Returns SQL to prepend to schema migration scripts (e.g., disabling FK checks). */
|
|
getSchemaBeginning(_charset, disableForeignKeys) {
|
|
if (disableForeignKeys) {
|
|
return `${this.disableForeignKeysSQL()}\n`;
|
|
}
|
|
return '';
|
|
}
|
|
/** Returns SQL to disable foreign key checks. */
|
|
disableForeignKeysSQL() {
|
|
return '';
|
|
}
|
|
/** Returns SQL to re-enable foreign key checks. */
|
|
enableForeignKeysSQL() {
|
|
return '';
|
|
}
|
|
/** Returns SQL to append to schema migration scripts (e.g., re-enabling FK checks). */
|
|
getSchemaEnd(disableForeignKeys) {
|
|
if (disableForeignKeys) {
|
|
return `${this.enableForeignKeysSQL()}\n`;
|
|
}
|
|
return '';
|
|
}
|
|
finalizeTable(table, charset, collate) {
|
|
return '';
|
|
}
|
|
appendComments(table) {
|
|
return [];
|
|
}
|
|
supportsSchemaConstraints() {
|
|
return true;
|
|
}
|
|
async getPrimaryKeys(connection, indexes = [], tableName, schemaName) {
|
|
const pks = indexes.filter(i => i.primary).map(pk => pk.columnNames);
|
|
return Utils.flatten(pks);
|
|
}
|
|
inferLengthFromColumnType(type) {
|
|
const match = /^\w+\s*(?:\(\s*(\d+)\s*\)|$)/.exec(type);
|
|
if (!match) {
|
|
return;
|
|
}
|
|
return +match[1];
|
|
}
|
|
getTableKey(t) {
|
|
const unquote = str => str.replace(/['"`]/g, '');
|
|
const parts = t.table_name.split('.');
|
|
if (parts.length > 1) {
|
|
return `${unquote(parts[0])}.${unquote(parts[1])}`;
|
|
}
|
|
if (t.schema_name) {
|
|
return `${unquote(t.schema_name)}.${unquote(t.table_name)}`;
|
|
}
|
|
return unquote(t.table_name);
|
|
}
|
|
getCreateNativeEnumSQL(name, values, schema) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
getDropNativeEnumSQL(name, schema) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
getAlterNativeEnumSQL(name, schema, value, items, oldItems) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
/** Returns the SQL query to list all tables in the database. */
|
|
getListTablesSQL() {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
/** Retrieves all tables from the database. */
|
|
async getAllTables(connection, schemas) {
|
|
return connection.execute(this.getListTablesSQL());
|
|
}
|
|
getListViewsSQL() {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
async loadViews(schema, connection, schemaName) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
/** Returns SQL to rename a column in a table. */
|
|
getRenameColumnSQL(tableName, oldColumnName, to, schemaName) {
|
|
tableName = this.quote(tableName);
|
|
oldColumnName = this.quote(oldColumnName);
|
|
const columnName = this.quote(to.name);
|
|
const schemaReference = schemaName !== undefined && schemaName !== 'public' ? '"' + schemaName + '".' : '';
|
|
const tableReference = schemaReference + tableName;
|
|
return `alter table ${tableReference} rename column ${oldColumnName} to ${columnName}`;
|
|
}
|
|
/** Returns SQL to create an index on a table. */
|
|
getCreateIndexSQL(tableName, index) {
|
|
/* v8 ignore next */
|
|
if (index.expression) {
|
|
return index.expression;
|
|
}
|
|
if (index.fillFactor != null && (index.fillFactor < 0 || index.fillFactor > 100)) {
|
|
throw new Error(`fillFactor must be between 0 and 100, got ${index.fillFactor} for index '${index.keyName}'`);
|
|
}
|
|
tableName = this.quote(tableName);
|
|
const keyName = this.quote(index.keyName);
|
|
const defer = index.deferMode ? ` deferrable initially ${index.deferMode}` : '';
|
|
let sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${tableName}`;
|
|
if (index.unique && index.constraint) {
|
|
sql = `alter table ${tableName} add constraint ${keyName} unique`;
|
|
}
|
|
if (index.columnNames.some(column => column.includes('.'))) {
|
|
// JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
|
|
sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${tableName}`;
|
|
const columns = this.platform.getJsonIndexDefinition(index);
|
|
return `${sql} (${columns.join(', ')})${this.getCreateIndexSuffix(index)}${defer}`;
|
|
}
|
|
// Build column list with advanced options
|
|
const columns = this.getIndexColumns(index);
|
|
sql += ` (${columns})`;
|
|
// Add INCLUDE clause for covering indexes (PostgreSQL, MSSQL)
|
|
if (index.include?.length) {
|
|
sql += ` include (${index.include.map(c => this.quote(c)).join(', ')})`;
|
|
}
|
|
return sql + this.getCreateIndexSuffix(index) + defer;
|
|
}
|
|
/**
|
|
* Hook for adding driver-specific index options (e.g., fill factor for PostgreSQL).
|
|
*/
|
|
getCreateIndexSuffix(_index) {
|
|
return '';
|
|
}
|
|
/**
|
|
* Build the column list for an index, supporting advanced options like sort order, nulls ordering, and collation.
|
|
* Note: Prefix length is only supported by MySQL/MariaDB which override this method.
|
|
*/
|
|
getIndexColumns(index) {
|
|
if (index.columns?.length) {
|
|
return index.columns
|
|
.map(col => {
|
|
let colDef = this.quote(col.name);
|
|
// Collation comes after column name (SQLite syntax: column COLLATE name)
|
|
if (col.collation) {
|
|
colDef += ` collate ${col.collation}`;
|
|
}
|
|
// Sort order
|
|
if (col.sort) {
|
|
colDef += ` ${col.sort}`;
|
|
}
|
|
// NULLS ordering (PostgreSQL)
|
|
if (col.nulls) {
|
|
colDef += ` nulls ${col.nulls}`;
|
|
}
|
|
return colDef;
|
|
})
|
|
.join(', ');
|
|
}
|
|
return index.columnNames.map(c => this.quote(c)).join(', ');
|
|
}
|
|
/** Returns SQL to drop an index. */
|
|
getDropIndexSQL(tableName, index) {
|
|
return `drop index ${this.quote(index.keyName)}`;
|
|
}
|
|
getRenameIndexSQL(tableName, index, oldIndexName) {
|
|
return [
|
|
this.getDropIndexSQL(tableName, { ...index, keyName: oldIndexName }),
|
|
this.getCreateIndexSQL(tableName, index),
|
|
];
|
|
}
|
|
/** Returns SQL statements to apply a table difference (add/drop/alter columns, indexes, foreign keys). */
|
|
alterTable(diff, safe) {
|
|
const ret = [];
|
|
const [schemaName, tableName] = this.splitTableName(diff.name);
|
|
if (this.platform.supportsNativeEnums()) {
|
|
const changedNativeEnums = [];
|
|
for (const { column, changedProperties } of Object.values(diff.changedColumns)) {
|
|
if (!column.nativeEnumName) {
|
|
continue;
|
|
}
|
|
const key =
|
|
schemaName && schemaName !== this.platform.getDefaultSchemaName() && !column.nativeEnumName.includes('.')
|
|
? schemaName + '.' + column.nativeEnumName
|
|
: column.nativeEnumName;
|
|
if (changedProperties.has('enumItems') && key in diff.fromTable.nativeEnums) {
|
|
changedNativeEnums.push([column.nativeEnumName, column.enumItems, diff.fromTable.nativeEnums[key].items]);
|
|
}
|
|
}
|
|
Utils.removeDuplicates(changedNativeEnums).forEach(([enumName, itemsNew, itemsOld]) => {
|
|
// postgres allows only adding new items
|
|
const newItems = itemsNew.filter(val => !itemsOld.includes(val));
|
|
if (enumName.includes('.')) {
|
|
const [enumSchemaName, rawEnumName] = enumName.split('.');
|
|
ret.push(
|
|
...newItems.map(val => this.getAlterNativeEnumSQL(rawEnumName, enumSchemaName, val, itemsNew, itemsOld)),
|
|
);
|
|
return;
|
|
}
|
|
ret.push(...newItems.map(val => this.getAlterNativeEnumSQL(enumName, schemaName, val, itemsNew, itemsOld)));
|
|
});
|
|
}
|
|
for (const index of Object.values(diff.removedIndexes)) {
|
|
ret.push(this.dropIndex(diff.name, index));
|
|
}
|
|
for (const index of Object.values(diff.changedIndexes)) {
|
|
ret.push(this.dropIndex(diff.name, index));
|
|
}
|
|
for (const check of Object.values(diff.removedChecks)) {
|
|
ret.push(this.dropConstraint(diff.name, check.name));
|
|
}
|
|
for (const check of Object.values(diff.changedChecks)) {
|
|
ret.push(this.dropConstraint(diff.name, check.name));
|
|
}
|
|
/* v8 ignore next */
|
|
if (!safe && Object.values(diff.removedColumns).length > 0) {
|
|
ret.push(this.getDropColumnsSQL(tableName, Object.values(diff.removedColumns), schemaName));
|
|
}
|
|
if (Object.values(diff.addedColumns).length > 0) {
|
|
this.append(ret, this.getAddColumnsSQL(diff.toTable, Object.values(diff.addedColumns)));
|
|
}
|
|
for (const column of Object.values(diff.addedColumns)) {
|
|
const foreignKey = Object.values(diff.addedForeignKeys).find(
|
|
fk => fk.columnNames.length === 1 && fk.columnNames[0] === column.name,
|
|
);
|
|
if (foreignKey && this.options.createForeignKeyConstraints) {
|
|
delete diff.addedForeignKeys[foreignKey.constraintName];
|
|
ret.push(this.createForeignKey(diff.toTable, foreignKey));
|
|
}
|
|
}
|
|
for (const { column, changedProperties } of Object.values(diff.changedColumns)) {
|
|
if (changedProperties.size === 1 && changedProperties.has('comment')) {
|
|
continue;
|
|
}
|
|
if (changedProperties.size === 1 && changedProperties.has('enumItems') && column.nativeEnumName) {
|
|
continue;
|
|
}
|
|
this.append(ret, this.alterTableColumn(column, diff.fromTable, changedProperties));
|
|
}
|
|
for (const { column, changedProperties } of Object.values(diff.changedColumns).filter(diff =>
|
|
diff.changedProperties.has('comment'),
|
|
)) {
|
|
if (
|
|
['type', 'nullable', 'autoincrement', 'unsigned', 'default', 'enumItems'].some(t => changedProperties.has(t))
|
|
) {
|
|
continue; // will be handled via column update
|
|
}
|
|
ret.push(this.getChangeColumnCommentSQL(tableName, column, schemaName));
|
|
}
|
|
for (const [oldColumnName, column] of Object.entries(diff.renamedColumns)) {
|
|
ret.push(this.getRenameColumnSQL(tableName, oldColumnName, column, schemaName));
|
|
}
|
|
for (const foreignKey of Object.values(diff.addedForeignKeys)) {
|
|
ret.push(this.createForeignKey(diff.toTable, foreignKey));
|
|
}
|
|
for (const foreignKey of Object.values(diff.changedForeignKeys)) {
|
|
ret.push(this.createForeignKey(diff.toTable, foreignKey));
|
|
}
|
|
for (const index of Object.values(diff.addedIndexes)) {
|
|
ret.push(this.createIndex(index, diff.toTable));
|
|
}
|
|
for (const index of Object.values(diff.changedIndexes)) {
|
|
ret.push(this.createIndex(index, diff.toTable, true));
|
|
}
|
|
for (const [oldIndexName, index] of Object.entries(diff.renamedIndexes)) {
|
|
if (index.unique) {
|
|
ret.push(this.dropIndex(diff.name, index, oldIndexName));
|
|
ret.push(this.createIndex(index, diff.toTable));
|
|
} else {
|
|
ret.push(...this.getRenameIndexSQL(diff.name, index, oldIndexName));
|
|
}
|
|
}
|
|
for (const check of Object.values(diff.addedChecks)) {
|
|
ret.push(this.createCheck(diff.toTable, check));
|
|
}
|
|
for (const check of Object.values(diff.changedChecks)) {
|
|
ret.push(this.createCheck(diff.toTable, check));
|
|
}
|
|
if ('changedComment' in diff) {
|
|
ret.push(this.alterTableComment(diff.toTable, diff.changedComment));
|
|
}
|
|
return ret;
|
|
}
|
|
/** Returns SQL to add columns to an existing table. */
|
|
getAddColumnsSQL(table, columns) {
|
|
const adds = columns
|
|
.map(column => {
|
|
return `add ${this.createTableColumn(column, table)}`;
|
|
})
|
|
.join(', ');
|
|
return [`alter table ${table.getQuotedName()} ${adds}`];
|
|
}
|
|
getDropColumnsSQL(tableName, columns, schemaName) {
|
|
const name = this.quote(this.getTableName(tableName, schemaName));
|
|
const drops = columns.map(column => `drop column ${this.quote(column.name)}`).join(', ');
|
|
return `alter table ${name} ${drops}`;
|
|
}
|
|
hasNonDefaultPrimaryKeyName(table) {
|
|
const pkIndex = table.getPrimaryKey();
|
|
if (!pkIndex || !this.platform.supportsCustomPrimaryKeyNames()) {
|
|
return false;
|
|
}
|
|
const defaultName = this.platform.getDefaultPrimaryName(table.name, pkIndex.columnNames);
|
|
return pkIndex?.keyName !== defaultName;
|
|
}
|
|
/* v8 ignore next */
|
|
castColumn(name, type) {
|
|
return '';
|
|
}
|
|
alterTableColumn(column, table, changedProperties) {
|
|
const sql = [];
|
|
if (changedProperties.has('default') && column.default == null) {
|
|
sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} drop default`);
|
|
}
|
|
if (changedProperties.has('type')) {
|
|
let type = column.type + (column.generated ? ` generated always as ${column.generated}` : '');
|
|
if (column.nativeEnumName) {
|
|
type = this.quote(this.getTableName(type, table.schema));
|
|
}
|
|
sql.push(
|
|
`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} type ${type + this.castColumn(column.name, type)}`,
|
|
);
|
|
}
|
|
if (changedProperties.has('default') && column.default != null) {
|
|
sql.push(
|
|
`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} set default ${column.default}`,
|
|
);
|
|
}
|
|
if (changedProperties.has('nullable')) {
|
|
const action = column.nullable ? 'drop' : 'set';
|
|
sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} ${action} not null`);
|
|
}
|
|
return sql;
|
|
}
|
|
createTableColumn(column, table, changedProperties) {
|
|
const compositePK = table.getPrimaryKey()?.composite;
|
|
const primaryKey = !changedProperties && !this.hasNonDefaultPrimaryKeyName(table);
|
|
const columnType = column.type + (column.generated ? ` generated always as ${column.generated}` : '');
|
|
const useDefault = column.default != null && column.default !== 'null' && !column.autoincrement;
|
|
const col = [this.quote(column.name), columnType];
|
|
Utils.runIfNotEmpty(() => col.push('unsigned'), column.unsigned && this.platform.supportsUnsigned());
|
|
Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
|
|
Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
|
|
Utils.runIfNotEmpty(() => col.push('auto_increment'), column.autoincrement);
|
|
Utils.runIfNotEmpty(() => col.push('unique'), column.autoincrement && !column.primary);
|
|
if (
|
|
column.autoincrement &&
|
|
!column.generated &&
|
|
!compositePK &&
|
|
(!changedProperties || changedProperties.has('autoincrement') || changedProperties.has('type'))
|
|
) {
|
|
Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
|
|
}
|
|
if (useDefault) {
|
|
// https://dev.mysql.com/doc/refman/9.0/en/data-type-defaults.html
|
|
const needsExpression = [
|
|
'blob',
|
|
'text',
|
|
'json',
|
|
'point',
|
|
'linestring',
|
|
'polygon',
|
|
'multipoint',
|
|
'multilinestring',
|
|
'multipolygon',
|
|
'geometrycollection',
|
|
].some(type => column.type.toLowerCase().startsWith(type));
|
|
const defaultSql = needsExpression && !column.default.startsWith('(') ? `(${column.default})` : column.default;
|
|
col.push(`default ${defaultSql}`);
|
|
}
|
|
Utils.runIfNotEmpty(() => col.push(column.extra), column.extra);
|
|
Utils.runIfNotEmpty(() => col.push(`comment ${this.platform.quoteValue(column.comment)}`), column.comment);
|
|
return col.join(' ');
|
|
}
|
|
getPreAlterTable(tableDiff, safe) {
|
|
return [];
|
|
}
|
|
getPostAlterTable(tableDiff, safe) {
|
|
return [];
|
|
}
|
|
getChangeColumnCommentSQL(tableName, to, schemaName) {
|
|
return '';
|
|
}
|
|
async getNamespaces(connection) {
|
|
return [];
|
|
}
|
|
async mapIndexes(indexes) {
|
|
const map = {};
|
|
indexes.forEach(index => {
|
|
if (map[index.keyName]) {
|
|
if (index.columnNames.length > 0) {
|
|
map[index.keyName].composite = true;
|
|
map[index.keyName].columnNames.push(index.columnNames[0]);
|
|
}
|
|
// Merge columns array for advanced column options (sort, length, collation, etc.)
|
|
if (index.columns?.length) {
|
|
map[index.keyName].columns ??= [];
|
|
map[index.keyName].columns.push(index.columns[0]);
|
|
}
|
|
// Merge INCLUDE columns
|
|
if (index.include?.length) {
|
|
map[index.keyName].include ??= [];
|
|
map[index.keyName].include.push(index.include[0]);
|
|
}
|
|
} else {
|
|
map[index.keyName] = index;
|
|
}
|
|
});
|
|
return Object.values(map);
|
|
}
|
|
mapForeignKeys(fks, tableName, schemaName) {
|
|
return fks.reduce((ret, fk) => {
|
|
if (ret[fk.constraint_name]) {
|
|
ret[fk.constraint_name].columnNames.push(fk.column_name);
|
|
ret[fk.constraint_name].referencedColumnNames.push(fk.referenced_column_name);
|
|
} else {
|
|
ret[fk.constraint_name] = {
|
|
columnNames: [fk.column_name],
|
|
constraintName: fk.constraint_name,
|
|
localTableName: schemaName ? `${schemaName}.${tableName}` : tableName,
|
|
referencedTableName: fk.referenced_schema_name
|
|
? `${fk.referenced_schema_name}.${fk.referenced_table_name}`
|
|
: fk.referenced_table_name,
|
|
referencedColumnNames: [fk.referenced_column_name],
|
|
updateRule: fk.update_rule.toLowerCase(),
|
|
deleteRule: fk.delete_rule.toLowerCase(),
|
|
deferMode: fk.defer_mode,
|
|
};
|
|
}
|
|
return ret;
|
|
}, {});
|
|
}
|
|
normalizeDefaultValue(defaultValue, length, defaultValues = {}) {
|
|
if (defaultValue == null) {
|
|
return defaultValue;
|
|
}
|
|
if (defaultValue instanceof RawQueryFragment) {
|
|
return this.platform.formatQuery(defaultValue.sql, defaultValue.params);
|
|
}
|
|
const genericValue = defaultValue.replace(/\(\d+\)/, '(?)').toLowerCase();
|
|
const norm = defaultValues[genericValue];
|
|
if (!norm) {
|
|
return defaultValue;
|
|
}
|
|
return norm[0].replace('(?)', length != null ? `(${length})` : '');
|
|
}
|
|
getCreateDatabaseSQL(name) {
|
|
name = this.quote(name);
|
|
// two line breaks to force separate execution
|
|
return `create database ${name};\n\nuse ${name}`;
|
|
}
|
|
getDropDatabaseSQL(name) {
|
|
return `drop database if exists ${this.quote(name)}`;
|
|
}
|
|
/* v8 ignore next */
|
|
getCreateNamespaceSQL(name) {
|
|
return `create schema if not exists ${this.quote(name)}`;
|
|
}
|
|
/* v8 ignore next */
|
|
getDropNamespaceSQL(name) {
|
|
return `drop schema if exists ${this.quote(name)}`;
|
|
}
|
|
getDatabaseExistsSQL(name) {
|
|
return `select 1 from information_schema.schemata where schema_name = '${name}'`;
|
|
}
|
|
getDatabaseNotExistsError(dbName) {
|
|
return `Unknown database '${dbName}'`;
|
|
}
|
|
getManagementDbName() {
|
|
return 'information_schema';
|
|
}
|
|
getDefaultEmptyString() {
|
|
return "''";
|
|
}
|
|
async databaseExists(connection, name) {
|
|
try {
|
|
const res = await connection.execute(this.getDatabaseExistsSQL(name));
|
|
return res.length > 0;
|
|
} catch (e) {
|
|
if (e instanceof Error && e.message.includes(this.getDatabaseNotExistsError(name))) {
|
|
return false;
|
|
}
|
|
/* v8 ignore next */
|
|
throw e;
|
|
}
|
|
}
|
|
append(array, sql, pad = false) {
|
|
const length = array.length;
|
|
for (const row of Utils.asArray(sql)) {
|
|
if (!row) {
|
|
continue;
|
|
}
|
|
let tmp = row.trim();
|
|
if (!tmp.endsWith(';')) {
|
|
tmp += ';';
|
|
}
|
|
array.push(tmp);
|
|
}
|
|
if (pad && array.length > length) {
|
|
array.push('');
|
|
}
|
|
}
|
|
/** Returns SQL statements to create a table with all its columns, primary key, indexes, and checks. */
|
|
createTable(table, alter) {
|
|
let sql = `create table ${table.getQuotedName()} (`;
|
|
const columns = table.getColumns();
|
|
const lastColumn = columns[columns.length - 1].name;
|
|
for (const column of columns) {
|
|
const col = this.createTableColumn(column, table);
|
|
if (col) {
|
|
const comma = column.name === lastColumn ? '' : ', ';
|
|
sql += col + comma;
|
|
}
|
|
}
|
|
const primaryKey = table.getPrimaryKey();
|
|
const createPrimary =
|
|
!table.getColumns().some(c => c.autoincrement && c.primary) || this.hasNonDefaultPrimaryKeyName(table);
|
|
if (createPrimary && primaryKey) {
|
|
const name = this.hasNonDefaultPrimaryKeyName(table) ? `constraint ${this.quote(primaryKey.keyName)} ` : '';
|
|
sql += `, ${name}primary key (${primaryKey.columnNames.map(c => this.quote(c)).join(', ')})`;
|
|
}
|
|
sql += ')';
|
|
sql += this.finalizeTable(
|
|
table,
|
|
this.platform.getConfig().get('charset'),
|
|
this.platform.getConfig().get('collate'),
|
|
);
|
|
const ret = [];
|
|
this.append(ret, sql);
|
|
this.append(ret, this.appendComments(table));
|
|
for (const index of table.getIndexes()) {
|
|
this.append(ret, this.createIndex(index, table));
|
|
}
|
|
if (!alter) {
|
|
for (const check of table.getChecks()) {
|
|
this.append(ret, this.createCheck(table, check));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
alterTableComment(table, comment) {
|
|
return `alter table ${table.getQuotedName()} comment = ${this.platform.quoteValue(comment ?? '')}`;
|
|
}
|
|
/** Returns SQL to create a foreign key constraint on a table. */
|
|
createForeignKey(table, foreignKey, alterTable = true, inline = false) {
|
|
if (!this.options.createForeignKeyConstraints) {
|
|
return '';
|
|
}
|
|
const constraintName = this.quote(foreignKey.constraintName);
|
|
const columnNames = foreignKey.columnNames.map(c => this.quote(c)).join(', ');
|
|
const referencedColumnNames = foreignKey.referencedColumnNames.map(c => this.quote(c)).join(', ');
|
|
const referencedTableName = this.quote(this.getReferencedTableName(foreignKey.referencedTableName, table.schema));
|
|
const sql = [];
|
|
if (alterTable) {
|
|
sql.push(`alter table ${table.getQuotedName()} add`);
|
|
}
|
|
sql.push(`constraint ${constraintName}`);
|
|
if (!inline) {
|
|
sql.push(`foreign key (${columnNames})`);
|
|
}
|
|
sql.push(`references ${referencedTableName} (${referencedColumnNames})`);
|
|
if (foreignKey.localTableName !== foreignKey.referencedTableName || this.platform.supportsMultipleCascadePaths()) {
|
|
if (foreignKey.updateRule) {
|
|
sql.push(`on update ${foreignKey.updateRule}`);
|
|
}
|
|
if (foreignKey.deleteRule) {
|
|
sql.push(`on delete ${foreignKey.deleteRule}`);
|
|
}
|
|
}
|
|
if (foreignKey.deferMode) {
|
|
sql.push(`deferrable initially ${foreignKey.deferMode}`);
|
|
}
|
|
return sql.join(' ');
|
|
}
|
|
splitTableName(name, skipDefaultSchema = false) {
|
|
const parts = name.split('.');
|
|
const tableName = parts.pop();
|
|
let schemaName = parts.pop();
|
|
if (skipDefaultSchema && schemaName === this.platform.getDefaultSchemaName()) {
|
|
schemaName = undefined;
|
|
}
|
|
return [schemaName, tableName];
|
|
}
|
|
getReferencedTableName(referencedTableName, schema) {
|
|
const [schemaName, tableName] = this.splitTableName(referencedTableName);
|
|
schema = schemaName ?? schema ?? this.platform.getConfig().get('schema');
|
|
/* v8 ignore next */
|
|
if (schema && schemaName === '*') {
|
|
return `${schema}.${referencedTableName.replace(/^\*\./, '')}`;
|
|
}
|
|
return this.getTableName(tableName, schema);
|
|
}
|
|
createIndex(index, table, createPrimary = false) {
|
|
if (index.primary && !createPrimary) {
|
|
return '';
|
|
}
|
|
if (index.expression) {
|
|
return index.expression;
|
|
}
|
|
const columns = index.columnNames.map(c => this.quote(c)).join(', ');
|
|
const defer = index.deferMode ? ` deferrable initially ${index.deferMode}` : '';
|
|
if (index.primary) {
|
|
const keyName = this.hasNonDefaultPrimaryKeyName(table) ? `constraint ${index.keyName} ` : '';
|
|
return `alter table ${table.getQuotedName()} add ${keyName}primary key (${columns})${defer}`;
|
|
}
|
|
if (index.type === 'fulltext') {
|
|
const columns = index.columnNames.map(name => ({ name, type: table.getColumn(name).type }));
|
|
if (this.platform.supportsCreatingFullTextIndex()) {
|
|
return this.platform.getFullTextIndexExpression(index.keyName, table.schema, table.name, columns);
|
|
}
|
|
}
|
|
return this.getCreateIndexSQL(table.getShortestName(), index);
|
|
}
|
|
createCheck(table, check) {
|
|
return `alter table ${table.getQuotedName()} add constraint ${this.quote(check.name)} check (${check.expression})`;
|
|
}
|
|
getTableName(table, schema) {
|
|
if (schema && schema !== this.platform.getDefaultSchemaName()) {
|
|
return `${schema}.${table}`;
|
|
}
|
|
return table;
|
|
}
|
|
getTablesGroupedBySchemas(tables) {
|
|
return tables.reduce((acc, table) => {
|
|
const schemaTables = acc.get(table.schema_name);
|
|
if (!schemaTables) {
|
|
acc.set(table.schema_name, [table]);
|
|
return acc;
|
|
}
|
|
schemaTables.push(table);
|
|
return acc;
|
|
}, new Map());
|
|
}
|
|
get options() {
|
|
return this.platform.getConfig().get('schemaGenerator');
|
|
}
|
|
processComment(comment) {
|
|
return comment;
|
|
}
|
|
quote(...keys) {
|
|
return this.platform.quoteIdentifier(keys.filter(Boolean).join('.'));
|
|
}
|
|
dropForeignKey(tableName, constraintName) {
|
|
return `alter table ${this.quote(tableName)} drop foreign key ${this.quote(constraintName)}`;
|
|
}
|
|
dropIndex(table, index, oldIndexName = index.keyName) {
|
|
if (index.primary) {
|
|
return `alter table ${this.quote(table)} drop primary key`;
|
|
}
|
|
return `alter table ${this.quote(table)} drop index ${this.quote(oldIndexName)}`;
|
|
}
|
|
dropConstraint(table, name) {
|
|
return `alter table ${this.quote(table)} drop constraint ${this.quote(name)}`;
|
|
}
|
|
/** Returns SQL to drop a table if it exists. */
|
|
dropTableIfExists(name, schema) {
|
|
let sql = `drop table if exists ${this.quote(this.getTableName(name, schema))}`;
|
|
if (this.platform.usesCascadeStatement()) {
|
|
sql += ' cascade';
|
|
}
|
|
return sql;
|
|
}
|
|
createView(name, schema, definition) {
|
|
const viewName = this.quote(this.getTableName(name, schema));
|
|
return `create view ${viewName} as ${definition}`;
|
|
}
|
|
dropViewIfExists(name, schema) {
|
|
let sql = `drop view if exists ${this.quote(this.getTableName(name, schema))}`;
|
|
if (this.platform.usesCascadeStatement()) {
|
|
sql += ' cascade';
|
|
}
|
|
return sql;
|
|
}
|
|
createMaterializedView(name, schema, definition, withData = true) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
dropMaterializedViewIfExists(name, schema) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
refreshMaterializedView(name, schema, concurrently = false) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
getListMaterializedViewsSQL() {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
async loadMaterializedViews(schema, connection, schemaName) {
|
|
throw new Error('Not supported by given driver');
|
|
}
|
|
}
|