mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
feat: naming strategy (#19848)
* feat: naming strategy * feat: detect renames
This commit is contained in:
parent
1d19d308e2
commit
9e48ae3052
@ -9,7 +9,13 @@ import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import 'src/schema';
|
||||
import { schemaDiff, schemaFromCode, schemaFromDatabase } from 'src/sql-tools';
|
||||
import {
|
||||
DefaultNamingStrategy,
|
||||
HashNamingStrategy,
|
||||
schemaDiff,
|
||||
schemaFromCode,
|
||||
schemaFromDatabase,
|
||||
} from 'src/sql-tools';
|
||||
import { asPostgresConnectionConfig, getKyselyConfig } from 'src/utils/database';
|
||||
|
||||
const main = async () => {
|
||||
@ -107,7 +113,22 @@ const compare = async () => {
|
||||
const { database } = configRepository.getEnv();
|
||||
const db = postgres(asPostgresConnectionConfig(database.config));
|
||||
|
||||
const source = schemaFromCode({ overrides: true });
|
||||
const tables = new Set<string>();
|
||||
const preferred = new DefaultNamingStrategy();
|
||||
const fallback = new HashNamingStrategy();
|
||||
|
||||
const source = schemaFromCode({
|
||||
overrides: true,
|
||||
namingStrategy: {
|
||||
getName(item) {
|
||||
if ('tableName' in item && tables.has(item.tableName)) {
|
||||
return preferred.getName(item);
|
||||
}
|
||||
|
||||
return fallback.getName(item);
|
||||
},
|
||||
},
|
||||
});
|
||||
const target = await schemaFromDatabase(db, {});
|
||||
|
||||
console.log(source.warnings.join('\n'));
|
||||
|
@ -35,7 +35,11 @@ export class StackTable {
|
||||
updateId!: Generated<string>;
|
||||
|
||||
//TODO: Add constraint to ensure primary asset exists in the assets array
|
||||
@ForeignKeyColumn(() => AssetTable, { nullable: false, unique: true })
|
||||
@ForeignKeyColumn(() => AssetTable, {
|
||||
nullable: false,
|
||||
unique: true,
|
||||
uniqueConstraintName: 'REL_91704e101438fd0653f582426d',
|
||||
})
|
||||
primaryAssetId!: string;
|
||||
|
||||
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
|
@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest';
|
||||
const testColumn: DatabaseColumn = {
|
||||
name: 'test',
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
nullable: false,
|
||||
isArray: false,
|
||||
type: 'character varying',
|
||||
|
@ -1,7 +1,32 @@
|
||||
import { getColumnType, isDefaultEqual } from 'src/sql-tools/helpers';
|
||||
import { asRenameKey, getColumnType, isDefaultEqual } from 'src/sql-tools/helpers';
|
||||
import { Comparer, DatabaseColumn, Reason, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const compareColumns: Comparer<DatabaseColumn> = {
|
||||
export const compareColumns = {
|
||||
getRenameKey: (column) => {
|
||||
return asRenameKey([
|
||||
column.tableName,
|
||||
column.type,
|
||||
column.nullable,
|
||||
column.default,
|
||||
column.storage,
|
||||
column.primary,
|
||||
column.isArray,
|
||||
column.length,
|
||||
column.identity,
|
||||
column.enumName,
|
||||
column.numericPrecision,
|
||||
column.numericScale,
|
||||
]);
|
||||
},
|
||||
onRename: (source, target) => [
|
||||
{
|
||||
type: 'ColumnRename',
|
||||
tableName: source.tableName,
|
||||
oldName: target.name,
|
||||
newName: source.name,
|
||||
reason: Reason.Rename,
|
||||
},
|
||||
],
|
||||
onMissing: (source) => [
|
||||
{
|
||||
type: 'ColumnAdd',
|
||||
@ -67,7 +92,7 @@ export const compareColumns: Comparer<DatabaseColumn> = {
|
||||
|
||||
return items;
|
||||
},
|
||||
};
|
||||
} satisfies Comparer<DatabaseColumn>;
|
||||
|
||||
const dropAndRecreateColumn = (source: DatabaseColumn, target: DatabaseColumn, reason: string): SchemaDiff[] => {
|
||||
return [
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { haveEqualColumns } from 'src/sql-tools/helpers';
|
||||
import { asRenameKey, haveEqualColumns } from 'src/sql-tools/helpers';
|
||||
import {
|
||||
CompareFunction,
|
||||
Comparer,
|
||||
@ -13,6 +13,37 @@ import {
|
||||
} from 'src/sql-tools/types';
|
||||
|
||||
export const compareConstraints: Comparer<DatabaseConstraint> = {
|
||||
getRenameKey: (constraint) => {
|
||||
switch (constraint.type) {
|
||||
case ConstraintType.PRIMARY_KEY:
|
||||
case ConstraintType.UNIQUE: {
|
||||
return asRenameKey([constraint.type, constraint.tableName, ...constraint.columnNames.toSorted()]);
|
||||
}
|
||||
|
||||
case ConstraintType.FOREIGN_KEY: {
|
||||
return asRenameKey([
|
||||
constraint.type,
|
||||
constraint.tableName,
|
||||
...constraint.columnNames.toSorted(),
|
||||
constraint.referenceTableName,
|
||||
...constraint.referenceColumnNames.toSorted(),
|
||||
]);
|
||||
}
|
||||
|
||||
case ConstraintType.CHECK: {
|
||||
return asRenameKey([constraint.type, constraint.tableName, constraint.expression]);
|
||||
}
|
||||
}
|
||||
},
|
||||
onRename: (source, target) => [
|
||||
{
|
||||
type: 'ConstraintRename',
|
||||
tableName: target.tableName,
|
||||
oldName: target.name,
|
||||
newName: source.name,
|
||||
reason: Reason.Rename,
|
||||
},
|
||||
],
|
||||
onMissing: (source) => [
|
||||
{
|
||||
type: 'ConstraintAdd',
|
||||
|
@ -1,7 +1,23 @@
|
||||
import { haveEqualColumns } from 'src/sql-tools/helpers';
|
||||
import { asRenameKey, haveEqualColumns } from 'src/sql-tools/helpers';
|
||||
import { Comparer, DatabaseIndex, Reason } from 'src/sql-tools/types';
|
||||
|
||||
export const compareIndexes: Comparer<DatabaseIndex> = {
|
||||
getRenameKey: (index) => {
|
||||
if (index.override) {
|
||||
return index.override.value.sql.replace(index.name, 'INDEX_NAME');
|
||||
}
|
||||
|
||||
return asRenameKey([index.tableName, ...(index.columnNames || []).toSorted(), index.unique]);
|
||||
},
|
||||
onRename: (source, target) => [
|
||||
{
|
||||
type: 'IndexRename',
|
||||
tableName: source.tableName,
|
||||
oldName: target.name,
|
||||
newName: source.name,
|
||||
reason: Reason.Rename,
|
||||
},
|
||||
],
|
||||
onMissing: (source) => [
|
||||
{
|
||||
type: 'IndexCreate',
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { DefaultNamingStrategy } from 'src/sql-tools/naming/default.naming';
|
||||
import { HashNamingStrategy } from 'src/sql-tools/naming/hash.naming';
|
||||
import { NamingInterface, NamingItem } from 'src/sql-tools/naming/naming.interface';
|
||||
import {
|
||||
BaseContextOptions,
|
||||
DatabaseEnum,
|
||||
@ -11,6 +14,26 @@ import {
|
||||
|
||||
const asOverrideKey = (type: string, name: string) => `${type}:${name}`;
|
||||
|
||||
const isNamingInterface = (strategy: any): strategy is NamingInterface => {
|
||||
return typeof strategy === 'object' && typeof strategy.getName === 'function';
|
||||
};
|
||||
|
||||
const asNamingStrategy = (strategy: 'hash' | 'default' | NamingInterface): NamingInterface => {
|
||||
if (isNamingInterface(strategy)) {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
switch (strategy) {
|
||||
case 'hash': {
|
||||
return new HashNamingStrategy();
|
||||
}
|
||||
|
||||
default: {
|
||||
return new DefaultNamingStrategy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class BaseContext {
|
||||
databaseName: string;
|
||||
schemaName: string;
|
||||
@ -24,10 +47,17 @@ export class BaseContext {
|
||||
overrides: DatabaseOverride[] = [];
|
||||
warnings: string[] = [];
|
||||
|
||||
private namingStrategy: NamingInterface;
|
||||
|
||||
constructor(options: BaseContextOptions) {
|
||||
this.databaseName = options.databaseName ?? 'postgres';
|
||||
this.schemaName = options.schemaName ?? 'public';
|
||||
this.overrideTableName = options.overrideTableName ?? 'migration_overrides';
|
||||
this.namingStrategy = asNamingStrategy(options.namingStrategy ?? 'hash');
|
||||
}
|
||||
|
||||
getNameFor(item: NamingItem) {
|
||||
return this.namingStrategy.getName(item);
|
||||
}
|
||||
|
||||
getTableByName(name: string) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||
import { BaseContext } from 'src/sql-tools/contexts/base-context';
|
||||
import { ColumnOptions, TableOptions } from 'src/sql-tools/decorators';
|
||||
import { asKey } from 'src/sql-tools/helpers';
|
||||
import { ColumnOptions } from 'src/sql-tools/decorators/column.decorator';
|
||||
import { TableOptions } from 'src/sql-tools/decorators/table.decorator';
|
||||
import { DatabaseColumn, DatabaseTable, SchemaFromCodeOptions } from 'src/sql-tools/types';
|
||||
|
||||
type TableMetadata = { options: TableOptions; object: Function; methodToColumn: Map<string | symbol, DatabaseColumn> };
|
||||
@ -59,19 +59,6 @@ export class ProcessorContext extends BaseContext {
|
||||
tableMetadata.methodToColumn.set(propertyName, column);
|
||||
}
|
||||
|
||||
asIndexName(table: string, columns?: string[], where?: string) {
|
||||
const items: string[] = [];
|
||||
for (const columnName of columns ?? []) {
|
||||
items.push(columnName);
|
||||
}
|
||||
|
||||
if (where) {
|
||||
items.push(where);
|
||||
}
|
||||
|
||||
return asKey('IDX_', table, items);
|
||||
}
|
||||
|
||||
warnMissingTable(context: string, object: object, propertyName?: symbol | string) {
|
||||
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
||||
this.warn(context, `Unable to find table (${label})`);
|
||||
|
@ -1,22 +0,0 @@
|
||||
export * from 'src/sql-tools/decorators/after-delete.decorator';
|
||||
export * from 'src/sql-tools/decorators/after-insert.decorator';
|
||||
export * from 'src/sql-tools/decorators/before-update.decorator';
|
||||
export * from 'src/sql-tools/decorators/check.decorator';
|
||||
export * from 'src/sql-tools/decorators/column.decorator';
|
||||
export * from 'src/sql-tools/decorators/configuration-parameter.decorator';
|
||||
export * from 'src/sql-tools/decorators/create-date-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/database.decorator';
|
||||
export * from 'src/sql-tools/decorators/delete-date-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/extension.decorator';
|
||||
export * from 'src/sql-tools/decorators/extensions.decorator';
|
||||
export * from 'src/sql-tools/decorators/foreign-key-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/foreign-key-constraint.decorator';
|
||||
export * from 'src/sql-tools/decorators/generated-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/index.decorator';
|
||||
export * from 'src/sql-tools/decorators/primary-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/primary-generated-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/table.decorator';
|
||||
export * from 'src/sql-tools/decorators/trigger-function.decorator';
|
||||
export * from 'src/sql-tools/decorators/trigger.decorator';
|
||||
export * from 'src/sql-tools/decorators/unique.decorator';
|
||||
export * from 'src/sql-tools/decorators/update-date-column.decorator';
|
@ -2,13 +2,6 @@ import { createHash } from 'node:crypto';
|
||||
import { ColumnValue } from 'src/sql-tools/decorators/column.decorator';
|
||||
import { Comparer, DatabaseColumn, DatabaseOverride, IgnoreOptions, SchemaDiff } from 'src/sql-tools/types';
|
||||
|
||||
export const asMetadataKey = (name: string) => `sql-tools:${name}`;
|
||||
|
||||
export const asSnakeCase = (name: string): string => name.replaceAll(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
||||
// match TypeORM
|
||||
export const asKey = (prefix: string, tableName: string, values: string[]) =>
|
||||
(prefix + sha1(`${tableName}_${values.toSorted().join('_')}`)).slice(0, 30);
|
||||
|
||||
export const asOptions = <T extends { name?: string }>(options: string | T): T => {
|
||||
if (typeof options === 'string') {
|
||||
return { name: options } as T;
|
||||
@ -79,6 +72,10 @@ export const compare = <T extends { name: string; synchronize: boolean }>(
|
||||
const items: SchemaDiff[] = [];
|
||||
|
||||
const keys = new Set([...Object.keys(sourceMap), ...Object.keys(targetMap)]);
|
||||
const missingKeys = new Set<string>();
|
||||
const extraKeys = new Set<string>();
|
||||
|
||||
// common keys
|
||||
for (const key of keys) {
|
||||
const source = sourceMap[key];
|
||||
const target = targetMap[key];
|
||||
@ -92,22 +89,63 @@ export const compare = <T extends { name: string; synchronize: boolean }>(
|
||||
}
|
||||
|
||||
if (source && !target) {
|
||||
items.push(...comparer.onMissing(source));
|
||||
} else if (!source && target) {
|
||||
items.push(...comparer.onExtra(target));
|
||||
} else {
|
||||
if (
|
||||
haveEqualOverrides(
|
||||
source as unknown as { override?: DatabaseOverride },
|
||||
target as unknown as { override?: DatabaseOverride },
|
||||
)
|
||||
) {
|
||||
missingKeys.add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!source && target) {
|
||||
extraKeys.add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
haveEqualOverrides(
|
||||
source as unknown as { override?: DatabaseOverride },
|
||||
target as unknown as { override?: DatabaseOverride },
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
items.push(...comparer.onCompare(source, target));
|
||||
}
|
||||
|
||||
// renames
|
||||
if (comparer.getRenameKey && comparer.onRename) {
|
||||
const renameMap: Record<string, string> = {};
|
||||
for (const sourceKey of missingKeys) {
|
||||
const source = sourceMap[sourceKey];
|
||||
const renameKey = comparer.getRenameKey(source);
|
||||
renameMap[renameKey] = sourceKey;
|
||||
}
|
||||
|
||||
for (const targetKey of extraKeys) {
|
||||
const target = targetMap[targetKey];
|
||||
const renameKey = comparer.getRenameKey(target);
|
||||
const sourceKey = renameMap[renameKey];
|
||||
if (!sourceKey) {
|
||||
continue;
|
||||
}
|
||||
items.push(...comparer.onCompare(source, target));
|
||||
|
||||
const source = sourceMap[sourceKey];
|
||||
|
||||
items.push(...comparer.onRename(source, target));
|
||||
|
||||
missingKeys.delete(sourceKey);
|
||||
extraKeys.delete(targetKey);
|
||||
}
|
||||
}
|
||||
|
||||
// missing
|
||||
for (const key of missingKeys) {
|
||||
items.push(...comparer.onMissing(sourceMap[key]));
|
||||
}
|
||||
|
||||
// extra
|
||||
for (const key of extraKeys) {
|
||||
items.push(...comparer.onExtra(targetMap[key]));
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
@ -186,8 +224,6 @@ export const asColumnComment = (tableName: string, columnName: string, comment:
|
||||
|
||||
export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', ');
|
||||
|
||||
export const asForeignKeyConstraintName = (table: string, columns: string[]) => asKey('FK_', table, [...columns]);
|
||||
|
||||
export const asJsonString = (value: unknown): string => {
|
||||
return `'${escape(JSON.stringify(value))}'::jsonb`;
|
||||
};
|
||||
@ -202,3 +238,6 @@ const escape = (value: string) => {
|
||||
.replaceAll(/[\r]/g, String.raw`\r`)
|
||||
.replaceAll(/[\t]/g, String.raw`\t`);
|
||||
};
|
||||
|
||||
export const asRenameKey = (values: Array<string | boolean | number | undefined>) =>
|
||||
values.map((value) => value ?? '').join('|');
|
||||
|
50
server/src/sql-tools/naming/default.naming.ts
Normal file
50
server/src/sql-tools/naming/default.naming.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { sha1 } from 'src/sql-tools/helpers';
|
||||
import { NamingItem } from 'src/sql-tools/naming/naming.interface';
|
||||
|
||||
const asSnakeCase = (name: string): string => name.replaceAll(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
||||
|
||||
export class DefaultNamingStrategy {
|
||||
getName(item: NamingItem): string {
|
||||
switch (item.type) {
|
||||
case 'database': {
|
||||
return asSnakeCase(item.name);
|
||||
}
|
||||
|
||||
case 'table': {
|
||||
return asSnakeCase(item.name);
|
||||
}
|
||||
|
||||
case 'column': {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
case 'primaryKey': {
|
||||
return `${item.tableName}_pkey`;
|
||||
}
|
||||
|
||||
case 'foreignKey': {
|
||||
return `${item.tableName}_${item.columnNames.join('_')}_fkey`;
|
||||
}
|
||||
|
||||
case 'check': {
|
||||
return `${item.tableName}_${sha1(item.expression).slice(0, 8)}_chk`;
|
||||
}
|
||||
|
||||
case 'unique': {
|
||||
return `${item.tableName}_${item.columnNames.join('_')}_uq`;
|
||||
}
|
||||
|
||||
case 'index': {
|
||||
if (item.columnNames) {
|
||||
return `${item.tableName}_${item.columnNames.join('_')}_idx`;
|
||||
}
|
||||
|
||||
return `${item.tableName}_${sha1(item.expression || item.where || '').slice(0, 8)}_idx`;
|
||||
}
|
||||
|
||||
case 'trigger': {
|
||||
return `${item.tableName}_${item.functionName}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
server/src/sql-tools/naming/hash.naming.ts
Normal file
51
server/src/sql-tools/naming/hash.naming.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { sha1 } from 'src/sql-tools/helpers';
|
||||
import { DefaultNamingStrategy } from 'src/sql-tools/naming/default.naming';
|
||||
import { NamingInterface, NamingItem } from 'src/sql-tools/naming/naming.interface';
|
||||
|
||||
const fallback = new DefaultNamingStrategy();
|
||||
|
||||
const asKey = (prefix: string, tableName: string, values: string[]) =>
|
||||
(prefix + sha1(`${tableName}_${values.toSorted().join('_')}`)).slice(0, 30);
|
||||
|
||||
export class HashNamingStrategy implements NamingInterface {
|
||||
getName(item: NamingItem): string {
|
||||
switch (item.type) {
|
||||
case 'primaryKey': {
|
||||
return asKey('PK_', item.tableName, item.columnNames);
|
||||
}
|
||||
|
||||
case 'foreignKey': {
|
||||
return asKey('FK_', item.tableName, item.columnNames);
|
||||
}
|
||||
|
||||
case 'check': {
|
||||
return asKey('CHK_', item.tableName, [item.expression]);
|
||||
}
|
||||
|
||||
case 'unique': {
|
||||
return asKey('UQ_', item.tableName, item.columnNames);
|
||||
}
|
||||
|
||||
case 'index': {
|
||||
const items: string[] = [];
|
||||
for (const columnName of item.columnNames ?? []) {
|
||||
items.push(columnName);
|
||||
}
|
||||
|
||||
if (item.where) {
|
||||
items.push(item.where);
|
||||
}
|
||||
|
||||
return asKey('IDX_', item.tableName, items);
|
||||
}
|
||||
|
||||
case 'trigger': {
|
||||
return asKey('TR_', item.tableName, [...item.actions, item.scope, item.timing, item.functionName]);
|
||||
}
|
||||
|
||||
default: {
|
||||
return fallback.getName(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
server/src/sql-tools/naming/naming.interface.ts
Normal file
59
server/src/sql-tools/naming/naming.interface.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types';
|
||||
|
||||
export type NamingItem =
|
||||
| {
|
||||
type: 'database';
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
type: 'table';
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
type: 'column';
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
type: 'primaryKey';
|
||||
tableName: string;
|
||||
columnNames: string[];
|
||||
}
|
||||
| {
|
||||
type: 'foreignKey';
|
||||
tableName: string;
|
||||
columnNames: string[];
|
||||
referenceTableName: string;
|
||||
referenceColumnNames: string[];
|
||||
}
|
||||
| {
|
||||
type: 'check';
|
||||
tableName: string;
|
||||
expression: string;
|
||||
}
|
||||
| {
|
||||
type: 'unique';
|
||||
tableName: string;
|
||||
columnNames: string[];
|
||||
}
|
||||
| {
|
||||
type: 'index';
|
||||
tableName: string;
|
||||
columnNames?: string[];
|
||||
expression?: string;
|
||||
where?: string;
|
||||
}
|
||||
| {
|
||||
type: 'trigger';
|
||||
tableName: string;
|
||||
functionName: string;
|
||||
actions: TriggerAction[];
|
||||
scope: TriggerScope;
|
||||
timing: TriggerTiming;
|
||||
columnNames?: string[];
|
||||
expression?: string;
|
||||
where?: string;
|
||||
};
|
||||
|
||||
export interface NamingInterface {
|
||||
getName(item: NamingItem): string;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import { asKey } from 'src/sql-tools/helpers';
|
||||
import { ConstraintType, Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processCheckConstraints: Processor = (ctx, items) => {
|
||||
@ -15,12 +14,10 @@ export const processCheckConstraints: Processor = (ctx, items) => {
|
||||
|
||||
table.constraints.push({
|
||||
type: ConstraintType.CHECK,
|
||||
name: options.name || asCheckConstraintName(tableName, options.expression),
|
||||
name: options.name || ctx.getNameFor({ type: 'check', tableName, expression: options.expression }),
|
||||
tableName,
|
||||
expression: options.expression,
|
||||
synchronize: options.synchronize ?? true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const asCheckConstraintName = (table: string, expression: string) => asKey('CHK_', table, [expression]);
|
||||
|
@ -13,7 +13,7 @@ export const processColumns: Processor = (ctx, items) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const columnName = options.name ?? String(propertyName);
|
||||
const columnName = options.name ?? ctx.getNameFor({ type: 'column', name: String(propertyName) });
|
||||
const existingColumn = table.columns.find((column) => column.name === columnName);
|
||||
if (existingColumn) {
|
||||
// TODO log warnings if column name is not unique
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { asSnakeCase } from 'src/sql-tools/helpers';
|
||||
import { Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processDatabases: Processor = (ctx, items) => {
|
||||
for (const {
|
||||
item: { object, options },
|
||||
} of items.filter((item) => item.type === 'database')) {
|
||||
ctx.databaseName = options.name || asSnakeCase(object.name);
|
||||
ctx.databaseName = options.name || ctx.getNameFor({ type: 'database', name: object.name });
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { asForeignKeyConstraintName, asKey } from 'src/sql-tools/helpers';
|
||||
import { ActionType, ConstraintType, Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processForeignKeyColumns: Processor = (ctx, items) => {
|
||||
@ -31,15 +30,24 @@ export const processForeignKeyColumns: Processor = (ctx, items) => {
|
||||
column.type = referenceColumns[0].type;
|
||||
}
|
||||
|
||||
const referenceTableName = referenceTable.name;
|
||||
const referenceColumnNames = referenceColumns.map((column) => column.name);
|
||||
const name = options.constraintName || asForeignKeyConstraintName(table.name, columnNames);
|
||||
const name =
|
||||
options.constraintName ||
|
||||
ctx.getNameFor({
|
||||
type: 'foreignKey',
|
||||
tableName: table.name,
|
||||
columnNames,
|
||||
referenceTableName,
|
||||
referenceColumnNames,
|
||||
});
|
||||
|
||||
table.constraints.push({
|
||||
name,
|
||||
tableName: table.name,
|
||||
columnNames,
|
||||
type: ConstraintType.FOREIGN_KEY,
|
||||
referenceTableName: referenceTable.name,
|
||||
referenceTableName,
|
||||
referenceColumnNames,
|
||||
onUpdate: options.onUpdate as ActionType,
|
||||
onDelete: options.onDelete as ActionType,
|
||||
@ -48,7 +56,7 @@ export const processForeignKeyColumns: Processor = (ctx, items) => {
|
||||
|
||||
if (options.unique || options.uniqueConstraintName) {
|
||||
table.constraints.push({
|
||||
name: options.uniqueConstraintName || asRelationKeyConstraintName(table.name, columnNames),
|
||||
name: options.uniqueConstraintName || ctx.getNameFor({ type: 'unique', tableName: table.name, columnNames }),
|
||||
tableName: table.name,
|
||||
columnNames,
|
||||
type: ConstraintType.UNIQUE,
|
||||
@ -57,5 +65,3 @@ export const processForeignKeyColumns: Processor = (ctx, items) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const asRelationKeyConstraintName = (table: string, columns: string[]) => asKey('REL_', table, columns);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { asForeignKeyConstraintName } from 'src/sql-tools/helpers';
|
||||
import { ActionType, ConstraintType, Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processForeignKeyConstraints: Processor = (ctx, items) => {
|
||||
@ -46,18 +45,27 @@ export const processForeignKeyConstraints: Processor = (ctx, items) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const referenceColumns =
|
||||
const referenceTableName = referenceTable.name;
|
||||
const referenceColumnNames =
|
||||
options.referenceColumns || referenceTable.columns.filter(({ primary }) => primary).map(({ name }) => name);
|
||||
|
||||
const name = options.name || asForeignKeyConstraintName(table.name, options.columns);
|
||||
const name =
|
||||
options.name ||
|
||||
ctx.getNameFor({
|
||||
type: 'foreignKey',
|
||||
tableName: table.name,
|
||||
columnNames: options.columns,
|
||||
referenceTableName,
|
||||
referenceColumnNames,
|
||||
});
|
||||
|
||||
table.constraints.push({
|
||||
type: ConstraintType.FOREIGN_KEY,
|
||||
name,
|
||||
tableName: table.name,
|
||||
columnNames: options.columns,
|
||||
referenceTableName: referenceTable.name,
|
||||
referenceColumnNames: referenceColumns,
|
||||
referenceTableName,
|
||||
referenceColumnNames,
|
||||
onUpdate: options.onUpdate as ActionType,
|
||||
onDelete: options.onDelete as ActionType,
|
||||
synchronize: options.synchronize ?? true,
|
||||
@ -68,8 +76,15 @@ export const processForeignKeyConstraints: Processor = (ctx, items) => {
|
||||
}
|
||||
|
||||
if (options.index || options.indexName || ctx.options.createForeignKeyIndexes) {
|
||||
const indexName =
|
||||
options.indexName ||
|
||||
ctx.getNameFor({
|
||||
type: 'index',
|
||||
tableName: table.name,
|
||||
columnNames: options.columns,
|
||||
});
|
||||
table.indexes.push({
|
||||
name: options.indexName || ctx.asIndexName(table.name, options.columns),
|
||||
name: indexName,
|
||||
tableName: table.name,
|
||||
columnNames: options.columns,
|
||||
unique: false,
|
||||
|
@ -10,8 +10,17 @@ export const processIndexes: Processor = (ctx, items) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const indexName =
|
||||
options.name ||
|
||||
ctx.getNameFor({
|
||||
type: 'index',
|
||||
tableName: table.name,
|
||||
columnNames: options.columns,
|
||||
where: options.where,
|
||||
});
|
||||
|
||||
table.indexes.push({
|
||||
name: options.name || ctx.asIndexName(table.name, options.columns, options.where),
|
||||
name: indexName,
|
||||
tableName: table.name,
|
||||
unique: options.unique ?? false,
|
||||
expression: options.expression,
|
||||
@ -50,7 +59,13 @@ export const processIndexes: Processor = (ctx, items) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const indexName = options.indexName || ctx.asIndexName(table.name, [column.name]);
|
||||
const indexName =
|
||||
options.indexName ||
|
||||
ctx.getNameFor({
|
||||
type: 'index',
|
||||
tableName: table.name,
|
||||
columnNames: [column.name],
|
||||
});
|
||||
|
||||
const isIndexPresent = table.indexes.some((index) => index.name === indexName);
|
||||
if (isIndexPresent) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { asKey } from 'src/sql-tools/helpers';
|
||||
import { ConstraintType, Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processPrimaryKeyConstraints: Processor = (ctx) => {
|
||||
@ -15,7 +14,13 @@ export const processPrimaryKeyConstraints: Processor = (ctx) => {
|
||||
const tableMetadata = ctx.getTableMetadata(table);
|
||||
table.constraints.push({
|
||||
type: ConstraintType.PRIMARY_KEY,
|
||||
name: tableMetadata.options.primaryConstraintName || asPrimaryKeyConstraintName(table.name, columnNames),
|
||||
name:
|
||||
tableMetadata.options.primaryConstraintName ||
|
||||
ctx.getNameFor({
|
||||
type: 'primaryKey',
|
||||
tableName: table.name,
|
||||
columnNames,
|
||||
}),
|
||||
tableName: table.name,
|
||||
columnNames,
|
||||
synchronize: tableMetadata.options.synchronize ?? true,
|
||||
@ -23,5 +28,3 @@ export const processPrimaryKeyConstraints: Processor = (ctx) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const asPrimaryKeyConstraintName = (table: string, columns: string[]) => asKey('PK_', table, columns);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { asSnakeCase } from 'src/sql-tools/helpers';
|
||||
import { Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processTables: Processor = (ctx, items) => {
|
||||
@ -14,7 +13,7 @@ export const processTables: Processor = (ctx, items) => {
|
||||
|
||||
ctx.addTable(
|
||||
{
|
||||
name: options.name || asSnakeCase(object.name),
|
||||
name: options.name || ctx.getNameFor({ type: 'table', name: object.name }),
|
||||
columns: [],
|
||||
constraints: [],
|
||||
indexes: [],
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { TriggerOptions } from 'src/sql-tools/decorators/trigger.decorator';
|
||||
import { asKey } from 'src/sql-tools/helpers';
|
||||
import { Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processTriggers: Processor = (ctx, items) => {
|
||||
@ -12,8 +10,19 @@ export const processTriggers: Processor = (ctx, items) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const triggerName =
|
||||
options.name ||
|
||||
ctx.getNameFor({
|
||||
type: 'trigger',
|
||||
tableName: table.name,
|
||||
actions: options.actions,
|
||||
scope: options.scope,
|
||||
timing: options.timing,
|
||||
functionName: options.functionName,
|
||||
});
|
||||
|
||||
table.triggers.push({
|
||||
name: options.name || asTriggerName(table.name, options),
|
||||
name: triggerName,
|
||||
tableName: table.name,
|
||||
timing: options.timing,
|
||||
actions: options.actions,
|
||||
@ -26,6 +35,3 @@ export const processTriggers: Processor = (ctx, items) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const asTriggerName = (table: string, trigger: TriggerOptions) =>
|
||||
asKey('TR_', table, [...trigger.actions, trigger.scope, trigger.timing, trigger.functionName]);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { asKey } from 'src/sql-tools/helpers';
|
||||
import { ConstraintType, Processor } from 'src/sql-tools/types';
|
||||
|
||||
export const processUniqueConstraints: Processor = (ctx, items) => {
|
||||
@ -16,7 +15,7 @@ export const processUniqueConstraints: Processor = (ctx, items) => {
|
||||
|
||||
table.constraints.push({
|
||||
type: ConstraintType.UNIQUE,
|
||||
name: options.name || asUniqueConstraintName(tableName, columnNames),
|
||||
name: options.name || ctx.getNameFor({ type: 'unique', tableName, columnNames }),
|
||||
tableName,
|
||||
columnNames,
|
||||
synchronize: options.synchronize ?? true,
|
||||
@ -41,9 +40,17 @@ export const processUniqueConstraints: Processor = (ctx, items) => {
|
||||
}
|
||||
|
||||
if (type === 'column' && !options.primary && (options.unique || options.uniqueConstraintName)) {
|
||||
const uniqueConstraintName =
|
||||
options.uniqueConstraintName ||
|
||||
ctx.getNameFor({
|
||||
type: 'unique',
|
||||
tableName: table.name,
|
||||
columnNames: [column.name],
|
||||
});
|
||||
|
||||
table.constraints.push({
|
||||
type: ConstraintType.UNIQUE,
|
||||
name: options.uniqueConstraintName || asUniqueConstraintName(table.name, [column.name]),
|
||||
name: uniqueConstraintName,
|
||||
tableName: table.name,
|
||||
columnNames: [column.name],
|
||||
synchronize: options.synchronize ?? true,
|
||||
@ -51,5 +58,3 @@ export const processUniqueConstraints: Processor = (ctx, items) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const asUniqueConstraintName = (table: string, columns: string[]) => asKey('UQ_', table, columns);
|
||||
|
@ -1,4 +1,28 @@
|
||||
export * from 'src/sql-tools/decorators';
|
||||
export * from 'src/sql-tools/decorators/after-delete.decorator';
|
||||
export * from 'src/sql-tools/decorators/after-insert.decorator';
|
||||
export * from 'src/sql-tools/decorators/before-update.decorator';
|
||||
export * from 'src/sql-tools/decorators/check.decorator';
|
||||
export * from 'src/sql-tools/decorators/column.decorator';
|
||||
export * from 'src/sql-tools/decorators/configuration-parameter.decorator';
|
||||
export * from 'src/sql-tools/decorators/create-date-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/database.decorator';
|
||||
export * from 'src/sql-tools/decorators/delete-date-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/extension.decorator';
|
||||
export * from 'src/sql-tools/decorators/extensions.decorator';
|
||||
export * from 'src/sql-tools/decorators/foreign-key-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/foreign-key-constraint.decorator';
|
||||
export * from 'src/sql-tools/decorators/generated-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/index.decorator';
|
||||
export * from 'src/sql-tools/decorators/primary-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/primary-generated-column.decorator';
|
||||
export * from 'src/sql-tools/decorators/table.decorator';
|
||||
export * from 'src/sql-tools/decorators/trigger-function.decorator';
|
||||
export * from 'src/sql-tools/decorators/trigger.decorator';
|
||||
export * from 'src/sql-tools/decorators/unique.decorator';
|
||||
export * from 'src/sql-tools/decorators/update-date-column.decorator';
|
||||
export * from 'src/sql-tools/naming/default.naming';
|
||||
export * from 'src/sql-tools/naming/hash.naming';
|
||||
export * from 'src/sql-tools/naming/naming.interface';
|
||||
export * from 'src/sql-tools/register-enum';
|
||||
export * from 'src/sql-tools/register-function';
|
||||
export { schemaDiff, schemaDiffToSql } from 'src/sql-tools/schema-diff';
|
||||
|
@ -76,6 +76,8 @@ export const readColumns: Reader = async (ctx, db) => {
|
||||
|
||||
const item: DatabaseColumn = {
|
||||
type: column.data_type as ColumnType,
|
||||
// TODO infer this from PK constraints
|
||||
primary: false,
|
||||
name: columnName,
|
||||
tableName: column.table_name,
|
||||
nullable: column.is_nullable === 'YES',
|
||||
|
@ -28,6 +28,7 @@ const fromColumn = (column: Partial<Omit<DatabaseColumn, 'tableName'>>): Databas
|
||||
columns: [
|
||||
{
|
||||
name: 'column1',
|
||||
primary: false,
|
||||
synchronize: true,
|
||||
isArray: false,
|
||||
type: 'character varying',
|
||||
@ -63,6 +64,7 @@ const fromConstraint = (constraint?: DatabaseConstraint): DatabaseSchema => {
|
||||
columns: [
|
||||
{
|
||||
name: 'column1',
|
||||
primary: false,
|
||||
synchronize: true,
|
||||
isArray: false,
|
||||
type: 'character varying',
|
||||
@ -97,6 +99,7 @@ const fromIndex = (index?: DatabaseIndex): DatabaseSchema => {
|
||||
columns: [
|
||||
{
|
||||
name: 'column1',
|
||||
primary: false,
|
||||
synchronize: true,
|
||||
isArray: false,
|
||||
type: 'character varying',
|
||||
@ -140,6 +143,7 @@ const newSchema = (schema: {
|
||||
columns.push({
|
||||
tableName,
|
||||
name: columnName,
|
||||
primary: false,
|
||||
type: column.type || 'character varying',
|
||||
isArray: column.isArray ?? false,
|
||||
nullable: column.nullable ?? false,
|
||||
@ -182,6 +186,7 @@ describe(schemaDiff.name, () => {
|
||||
const column: DatabaseColumn = {
|
||||
type: 'character varying',
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
name: 'column1',
|
||||
isArray: false,
|
||||
nullable: false,
|
||||
@ -264,6 +269,7 @@ describe(schemaDiff.name, () => {
|
||||
column: {
|
||||
tableName: 'table1',
|
||||
isArray: false,
|
||||
primary: false,
|
||||
name: 'column2',
|
||||
nullable: false,
|
||||
type: 'character varying',
|
||||
|
@ -40,10 +40,13 @@ export const schemaDiff = (source: DatabaseSchema, target: DatabaseSchema, optio
|
||||
TableDrop: [],
|
||||
ColumnAdd: [],
|
||||
ColumnAlter: [],
|
||||
ColumnRename: [],
|
||||
ColumnDrop: [],
|
||||
ConstraintAdd: [],
|
||||
ConstraintDrop: [],
|
||||
ConstraintRename: [],
|
||||
IndexCreate: [],
|
||||
IndexRename: [],
|
||||
IndexDrop: [],
|
||||
TriggerCreate: [],
|
||||
TriggerDrop: [],
|
||||
@ -72,11 +75,14 @@ export const schemaDiff = (source: DatabaseSchema, target: DatabaseSchema, optio
|
||||
...itemMap.TableCreate,
|
||||
...itemMap.ColumnAlter,
|
||||
...itemMap.ColumnAdd,
|
||||
...itemMap.ColumnRename,
|
||||
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.PRIMARY_KEY),
|
||||
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.FOREIGN_KEY),
|
||||
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.UNIQUE),
|
||||
...constraintAdds.filter(({ constraint }) => constraint.type === ConstraintType.CHECK),
|
||||
...itemMap.ConstraintRename,
|
||||
...itemMap.IndexCreate,
|
||||
...itemMap.IndexRename,
|
||||
...itemMap.TriggerCreate,
|
||||
...itemMap.ColumnDrop,
|
||||
...itemMap.TableDrop,
|
||||
|
@ -20,9 +20,9 @@ export const schemaFromCode = (options: SchemaFromCodeOptions = {}) => {
|
||||
name: ctx.overrideTableName,
|
||||
columns: [
|
||||
{
|
||||
primary: true,
|
||||
name: 'name',
|
||||
tableName: ctx.overrideTableName,
|
||||
primary: true,
|
||||
type: 'character varying',
|
||||
nullable: false,
|
||||
isArray: false,
|
||||
@ -31,6 +31,7 @@ export const schemaFromCode = (options: SchemaFromCodeOptions = {}) => {
|
||||
{
|
||||
name: 'value',
|
||||
tableName: ctx.overrideTableName,
|
||||
primary: false,
|
||||
type: 'jsonb',
|
||||
nullable: false,
|
||||
isArray: false,
|
||||
|
@ -13,6 +13,7 @@ describe(transformColumns.name, () => {
|
||||
column: {
|
||||
name: 'column1',
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
type: 'character varying',
|
||||
nullable: false,
|
||||
isArray: false,
|
||||
@ -30,6 +31,7 @@ describe(transformColumns.name, () => {
|
||||
column: {
|
||||
name: 'column1',
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
type: 'character varying',
|
||||
nullable: true,
|
||||
isArray: false,
|
||||
@ -47,6 +49,7 @@ describe(transformColumns.name, () => {
|
||||
column: {
|
||||
name: 'column1',
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
type: 'character varying',
|
||||
enumName: 'table1_column1_enum',
|
||||
nullable: true,
|
||||
@ -65,6 +68,7 @@ describe(transformColumns.name, () => {
|
||||
column: {
|
||||
name: 'column1',
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
type: 'boolean',
|
||||
nullable: true,
|
||||
isArray: true,
|
||||
|
@ -12,8 +12,12 @@ export const transformColumns: SqlTransformer = (ctx, item) => {
|
||||
return asColumnAlter(item.tableName, item.columnName, item.changes);
|
||||
}
|
||||
|
||||
case 'ColumnRename': {
|
||||
return `ALTER TABLE "${item.tableName}" RENAME COLUMN "${item.oldName}" TO "${item.newName}";`;
|
||||
}
|
||||
|
||||
case 'ColumnDrop': {
|
||||
return asColumnDrop(item.tableName, item.columnName);
|
||||
return `ALTER TABLE "${item.tableName}" DROP COLUMN "${item.columnName}";`;
|
||||
}
|
||||
|
||||
default: {
|
||||
@ -28,10 +32,6 @@ const asColumnAdd = (column: DatabaseColumn): string => {
|
||||
);
|
||||
};
|
||||
|
||||
const asColumnDrop = (tableName: string, columnName: string): string => {
|
||||
return `ALTER TABLE "${tableName}" DROP COLUMN "${columnName}";`;
|
||||
};
|
||||
|
||||
export const asColumnAlter = (tableName: string, columnName: string, changes: ColumnChanges): string[] => {
|
||||
const base = `ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}"`;
|
||||
const items: string[] = [];
|
||||
|
@ -5,11 +5,15 @@ import { ActionType, ConstraintType, DatabaseConstraint } from 'src/sql-tools/ty
|
||||
export const transformConstraints: SqlTransformer = (ctx, item) => {
|
||||
switch (item.type) {
|
||||
case 'ConstraintAdd': {
|
||||
return asConstraintAdd(item.constraint);
|
||||
return `ALTER TABLE "${item.constraint.tableName}" ADD ${asConstraintBody(item.constraint)};`;
|
||||
}
|
||||
|
||||
case 'ConstraintRename': {
|
||||
return `ALTER TABLE "${item.tableName}" RENAME CONSTRAINT "${item.oldName}" TO "${item.newName}";`;
|
||||
}
|
||||
|
||||
case 'ConstraintDrop': {
|
||||
return asConstraintDrop(item.tableName, item.constraintName);
|
||||
return `ALTER TABLE "${item.tableName}" DROP CONSTRAINT "${item.constraintName}";`;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
@ -52,11 +56,3 @@ export const asConstraintBody = (constraint: DatabaseConstraint): string => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => {
|
||||
return `ALTER TABLE "${constraint.tableName}" ADD ${asConstraintBody(constraint)};`;
|
||||
};
|
||||
|
||||
export const asConstraintDrop = (tableName: string, constraintName: string): string => {
|
||||
return `ALTER TABLE "${tableName}" DROP CONSTRAINT "${constraintName}";`;
|
||||
};
|
||||
|
@ -8,8 +8,12 @@ export const transformIndexes: SqlTransformer = (ctx, item) => {
|
||||
return asIndexCreate(item.index);
|
||||
}
|
||||
|
||||
case 'IndexRename': {
|
||||
return `ALTER INDEX "${item.oldName}" RENAME TO "${item.newName}";`;
|
||||
}
|
||||
|
||||
case 'IndexDrop': {
|
||||
return asIndexDrop(item.indexName);
|
||||
return `DROP INDEX "${item.indexName}";`;
|
||||
}
|
||||
|
||||
default: {
|
||||
@ -50,7 +54,3 @@ export const asIndexCreate = (index: DatabaseIndex): string => {
|
||||
|
||||
return sql + ';';
|
||||
};
|
||||
|
||||
export const asIndexDrop = (indexName: string): string => {
|
||||
return `DROP INDEX "${indexName}";`;
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ const table1: DatabaseTable = {
|
||||
},
|
||||
{
|
||||
name: 'column2',
|
||||
primary: false,
|
||||
tableName: 'table1',
|
||||
type: 'character varying',
|
||||
nullable: true,
|
||||
@ -106,6 +107,7 @@ describe(transformTables.name, () => {
|
||||
columns: [
|
||||
{
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
name: 'column1',
|
||||
type: 'character varying',
|
||||
isArray: false,
|
||||
@ -137,6 +139,7 @@ describe(transformTables.name, () => {
|
||||
{
|
||||
tableName: 'table1',
|
||||
name: 'column1',
|
||||
primary: false,
|
||||
type: 'character varying',
|
||||
isArray: false,
|
||||
nullable: true,
|
||||
@ -167,6 +170,7 @@ describe(transformTables.name, () => {
|
||||
columns: [
|
||||
{
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
name: 'column1',
|
||||
type: 'character varying',
|
||||
length: 2,
|
||||
@ -198,6 +202,7 @@ describe(transformTables.name, () => {
|
||||
columns: [
|
||||
{
|
||||
tableName: 'table1',
|
||||
primary: false,
|
||||
name: 'column1',
|
||||
type: 'character varying',
|
||||
isArray: true,
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { Kysely, ColumnType as KyselyColumnType } from 'kysely';
|
||||
import { ProcessorContext } from 'src/sql-tools/contexts/processor-context';
|
||||
import { ReaderContext } from 'src/sql-tools/contexts/reader-context';
|
||||
import { NamingInterface } from 'src/sql-tools/naming/naming.interface';
|
||||
import { RegisterItem } from 'src/sql-tools/register-item';
|
||||
|
||||
export type BaseContextOptions = {
|
||||
databaseName?: string;
|
||||
schemaName?: string;
|
||||
overrideTableName?: string;
|
||||
namingStrategy?: 'default' | 'hash' | NamingInterface;
|
||||
};
|
||||
|
||||
export type SchemaFromCodeOptions = BaseContextOptions & {
|
||||
@ -386,7 +388,7 @@ export type DatabaseConstraint =
|
||||
| DatabaseCheckConstraint;
|
||||
|
||||
export type DatabaseColumn = {
|
||||
primary?: boolean;
|
||||
primary: boolean;
|
||||
name: string;
|
||||
tableName: string;
|
||||
comment?: string;
|
||||
@ -487,11 +489,14 @@ export type SchemaDiff = { reason: string } & (
|
||||
| { type: 'TableCreate'; table: DatabaseTable }
|
||||
| { type: 'TableDrop'; tableName: string }
|
||||
| { type: 'ColumnAdd'; column: DatabaseColumn }
|
||||
| { type: 'ColumnRename'; tableName: string; oldName: string; newName: string }
|
||||
| { type: 'ColumnAlter'; tableName: string; columnName: string; changes: ColumnChanges }
|
||||
| { type: 'ColumnDrop'; tableName: string; columnName: string }
|
||||
| { type: 'ConstraintAdd'; constraint: DatabaseConstraint }
|
||||
| { type: 'ConstraintRename'; tableName: string; oldName: string; newName: string }
|
||||
| { type: 'ConstraintDrop'; tableName: string; constraintName: string }
|
||||
| { type: 'IndexCreate'; index: DatabaseIndex }
|
||||
| { type: 'IndexRename'; tableName: string; oldName: string; newName: string }
|
||||
| { type: 'IndexDrop'; indexName: string }
|
||||
| { type: 'TriggerCreate'; trigger: DatabaseTrigger }
|
||||
| { type: 'TriggerDrop'; tableName: string; triggerName: string }
|
||||
@ -509,11 +514,15 @@ export type Comparer<T> = {
|
||||
onMissing: (source: T) => SchemaDiff[];
|
||||
onExtra: (target: T) => SchemaDiff[];
|
||||
onCompare: CompareFunction<T>;
|
||||
/** if two items have the same key, they are considered identical and can be renamed via `onRename` */
|
||||
getRenameKey?: (item: T) => string;
|
||||
onRename?: (source: T, target: T) => SchemaDiff[];
|
||||
};
|
||||
|
||||
export enum Reason {
|
||||
MissingInSource = 'missing in source',
|
||||
MissingInTarget = 'missing in target',
|
||||
Rename = 'name has changed',
|
||||
}
|
||||
|
||||
export type Timestamp = KyselyColumnType<Date, Date | string, Date | string>;
|
||||
|
@ -83,7 +83,7 @@ export const schema: DatabaseSchema = {
|
||||
},
|
||||
{
|
||||
type: ConstraintType.UNIQUE,
|
||||
name: 'REL_3fcca5cc563abf256fc346e3ff',
|
||||
name: 'UQ_3fcca5cc563abf256fc346e3ff4',
|
||||
tableName: 'table2',
|
||||
columnNames: ['parentId'],
|
||||
synchronize: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user