diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e8f0c84b3..9ce9caddf4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,6 +343,9 @@ importers: '@extism/extism': specifier: 2.0.0-rc13 version: 2.0.0-rc13 + '@immich/sql-tools': + specifier: ^0.2.0 + version: 0.2.0 '@nestjs/bullmq': specifier: ^11.0.1 version: 11.0.4(@nestjs/common@11.1.13(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(bullmq@5.68.0) @@ -3013,6 +3016,9 @@ packages: '@immich/justified-layout-wasm@0.4.3': resolution: {integrity: sha512-fpcQ7zPhP3Cp1bEXhONVYSUeIANa2uzaQFGKufUZQo5FO7aFT77szTVChhlCy4XaVy5R4ZvgSkA/1TJmeORz7Q==} + '@immich/sql-tools@0.2.0': + resolution: {integrity: sha512-AH0GRIUYrckNKuid5uO33vgRbGaznhRtArdQ91K310A1oUFjaoNzOaZyZhXwEmft3WYeC1bx4fdgUeois2QH5A==} + '@immich/svelte-markdown-preprocess@0.2.1': resolution: {integrity: sha512-mbr/g75lO8Zh+ELCuYrZP0XB4gf2UbK8rJcGYMYxFJJzMMunV+sm9FqtV1dbwW2dpXzCZGz1XPCEZ6oo526TbA==} peerDependencies: @@ -8291,6 +8297,10 @@ packages: postgres: optional: true + kysely@0.28.11: + resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==} + engines: {node: '>=20.0.0'} + kysely@0.28.2: resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==} engines: {node: '>=18.0.0'} @@ -14812,6 +14822,13 @@ snapshots: '@immich/justified-layout-wasm@0.4.3': {} + '@immich/sql-tools@0.2.0': + dependencies: + kysely: 0.28.11 + kysely-postgres-js: 3.0.0(kysely@0.28.11)(postgres@3.4.8) + pg-connection-string: 2.11.0 + postgres: 3.4.8 + '@immich/svelte-markdown-preprocess@0.2.1(svelte@5.51.5)': dependencies: front-matter: 4.0.2 @@ -20822,12 +20839,20 @@ snapshots: type-is: 2.0.1 vary: 1.1.2 + kysely-postgres-js@3.0.0(kysely@0.28.11)(postgres@3.4.8): + dependencies: + kysely: 0.28.11 + optionalDependencies: + postgres: 3.4.8 + kysely-postgres-js@3.0.0(kysely@0.28.2)(postgres@3.4.8): dependencies: kysely: 0.28.2 optionalDependencies: postgres: 3.4.8 + kysely@0.28.11: {} + kysely@0.28.2: {} langium@3.3.1: diff --git a/server/package.json b/server/package.json index 814934b1be..727aab636f 100644 --- a/server/package.json +++ b/server/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@extism/extism": "2.0.0-rc13", + "@immich/sql-tools": "^0.2.0", "@nestjs/bullmq": "^11.0.1", "@nestjs/common": "^11.0.4", "@nestjs/core": "^11.0.4", diff --git a/server/src/bin/migrations.ts b/server/src/bin/migrations.ts index 588f358023..bfa0f1733c 100644 --- a/server/src/bin/migrations.ts +++ b/server/src/bin/migrations.ts @@ -1,16 +1,15 @@ #!/usr/bin/env node process.env.DB_URL = process.env.DB_URL || 'postgres://postgres:postgres@localhost:5432/immich'; +import { schemaDiff, schemaFromCode, schemaFromDatabase } from '@immich/sql-tools'; import { Kysely, sql } from 'kysely'; import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'; import { basename, dirname, extname, join } from 'node:path'; -import postgres from 'postgres'; 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 { asPostgresConnectionConfig, getKyselyConfig } from 'src/utils/database'; +import { getKyselyConfig } from 'src/utils/database'; const main = async () => { const command = process.argv[2]; @@ -130,10 +129,9 @@ const create = (path: string, up: string[], down: string[]) => { const compare = async () => { const configRepository = new ConfigRepository(); const { database } = configRepository.getEnv(); - const db = postgres(asPostgresConnectionConfig(database.config)); const source = schemaFromCode({ overrides: true, namingStrategy: 'default' }); - const target = await schemaFromDatabase(db, {}); + const target = await schemaFromDatabase({ connection: database.config }); console.log(source.warnings.join('\n')); diff --git a/server/src/commands/schema-check.ts b/server/src/commands/schema-check.ts index c6e90fd9ca..e0ccae8469 100644 --- a/server/src/commands/schema-check.ts +++ b/server/src/commands/schema-check.ts @@ -1,7 +1,7 @@ +import { asHuman } from '@immich/sql-tools'; import { Command, CommandRunner } from 'nest-commander'; import { ErrorMessages } from 'src/constants'; import { CliService } from 'src/services/cli.service'; -import { asHuman } from 'src/sql-tools/schema-diff'; @Command({ name: 'schema-check', diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 87a3900a7f..695adb4a36 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -1,10 +1,10 @@ +import { BeforeUpdateTrigger, Column, ColumnOptions } from '@immich/sql-tools'; import { SetMetadata, applyDecorators } from '@nestjs/common'; import { ApiOperation, ApiOperationOptions, ApiProperty, ApiPropertyOptions, ApiTags } from '@nestjs/swagger'; import _ from 'lodash'; import { ApiCustomExtension, ApiTag, ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum'; import { EmitEvent } from 'src/repositories/event.repository'; import { immich_uuid_v7, updated_at } from 'src/schema/functions'; -import { BeforeUpdateTrigger, Column, ColumnOptions } from 'src/sql-tools'; import { setUnion } from 'src/utils/set'; const GeneratedUuidV7Column = (options: Omit = {}) => diff --git a/server/src/dtos/env.dto.ts b/server/src/dtos/env.dto.ts index e088a33413..6115308140 100644 --- a/server/src/dtos/env.dto.ts +++ b/server/src/dtos/env.dto.ts @@ -1,6 +1,7 @@ +import { DatabaseSslMode } from '@immich/sql-tools'; import { Transform, Type } from 'class-transformer'; import { IsEnum, IsInt, IsString, Matches } from 'class-validator'; -import { DatabaseSslMode, ImmichEnvironment, LogFormat, LogLevel } from 'src/enum'; +import { ImmichEnvironment, LogFormat, LogLevel } from 'src/enum'; import { IsIPRange, Optional, ValidateBoolean } from 'src/validation'; export class EnvDto { diff --git a/server/src/enum.ts b/server/src/enum.ts index 8f509754da..44b2f564ab 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -821,14 +821,6 @@ export enum OAuthTokenEndpointAuthMethod { ClientSecretBasic = 'client_secret_basic', } -export enum DatabaseSslMode { - Disable = 'disable', - Allow = 'allow', - Prefer = 'prefer', - Require = 'require', - VerifyFull = 'verify-full', -} - export enum AssetVisibility { Archive = 'archive', Timeline = 'timeline', diff --git a/server/src/main.ts b/server/src/main.ts index a8e3178a43..f2491f07bc 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -52,9 +52,9 @@ class Workers { try { const value = await systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode); return value?.isMaintenanceMode || false; - } catch (error) { + } catch (error: Error | any) { // Table doesn't exist (migrations haven't run yet) - if (error instanceof PostgresError && error.code === '42P01') { + if ((error as PostgresError).code === '42P01') { return false; } diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 54a5d1987f..957a308e7d 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -1,3 +1,4 @@ +import { DatabaseConnectionParams } from '@immich/sql-tools'; import { RegisterQueueOptions } from '@nestjs/bullmq'; import { Inject, Injectable, Optional } from '@nestjs/common'; import { QueueOptions } from 'bullmq'; @@ -21,7 +22,7 @@ import { LogLevel, QueueName, } from 'src/enum'; -import { DatabaseConnectionParams, VectorExtension } from 'src/types'; +import { VectorExtension } from 'src/types'; import { setDifference } from 'src/utils/set'; export interface EnvData { diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 650820b18e..06bdef5abf 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,3 +1,4 @@ +import { schemaDiff, schemaFromCode, schemaFromDatabase } from '@immich/sql-tools'; import { Injectable } from '@nestjs/common'; import AsyncLock from 'async-lock'; import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely'; @@ -21,7 +22,6 @@ import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import 'src/schema'; // make sure all schema definitions are imported for schemaFromCode import { DB } from 'src/schema'; -import { schemaDiff, schemaFromCode, schemaFromDatabase } from 'src/sql-tools'; import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types'; import { vectorIndexQuery } from 'src/utils/database'; import { isValidInteger } from 'src/validation'; @@ -289,7 +289,8 @@ export class DatabaseRepository { async getSchemaDrift() { const source = schemaFromCode({ overrides: true, namingStrategy: 'default' }); - const target = await schemaFromDatabase(this.db, {}); + const { database } = this.configRepository.getEnv(); + const target = await schemaFromDatabase({ connection: database.config }); const drift = schemaDiff(source, target, { tables: { ignoreExtra: true }, diff --git a/server/src/schema/enums.ts b/server/src/schema/enums.ts index a1134df6bc..c68f152779 100644 --- a/server/src/schema/enums.ts +++ b/server/src/schema/enums.ts @@ -1,5 +1,5 @@ +import { registerEnum } from '@immich/sql-tools'; import { AssetStatus, AssetVisibility, SourceType } from 'src/enum'; -import { registerEnum } from 'src/sql-tools'; export const assets_status_enum = registerEnum({ name: 'assets_status_enum', diff --git a/server/src/schema/functions.ts b/server/src/schema/functions.ts index d7dabfef4c..6acfc45750 100644 --- a/server/src/schema/functions.ts +++ b/server/src/schema/functions.ts @@ -1,4 +1,4 @@ -import { registerFunction } from 'src/sql-tools'; +import { registerFunction } from '@immich/sql-tools'; export const immich_uuid_v7 = registerFunction({ name: 'immich_uuid_v7', diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index 4dc3d40312..790973785f 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -1,3 +1,4 @@ +import { Database, Extensions, Generated, Int8 } from '@immich/sql-tools'; import { asset_face_source_type, asset_visibility_enum, assets_status_enum } from 'src/schema/enums'; import { album_delete_audit, @@ -72,7 +73,6 @@ import { UserMetadataTable } from 'src/schema/tables/user-metadata.table'; import { UserTable } from 'src/schema/tables/user.table'; import { VersionHistoryTable } from 'src/schema/tables/version-history.table'; import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; -import { Database, Extensions, Generated, Int8 } from 'src/sql-tools'; @Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql']) @Database({ name: 'immich' }) diff --git a/server/src/schema/tables/activity.table.ts b/server/src/schema/tables/activity.table.ts index dfa7c98e42..4a3cc196ee 100644 --- a/server/src/schema/tables/activity.table.ts +++ b/server/src/schema/tables/activity.table.ts @@ -1,8 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AlbumAssetTable } from 'src/schema/tables/album-asset.table'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { Check, Column, @@ -15,7 +10,12 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { AlbumAssetTable } from 'src/schema/tables/album-asset.table'; +import { AlbumTable } from 'src/schema/tables/album.table'; +import { AssetTable } from 'src/schema/tables/asset.table'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('activity') @UpdatedAtTrigger('activity_updatedAt') diff --git a/server/src/schema/tables/album-asset-audit.table.ts b/server/src/schema/tables/album-asset-audit.table.ts index ab8fd9ae89..176d32575a 100644 --- a/server/src/schema/tables/album-asset-audit.table.ts +++ b/server/src/schema/tables/album-asset-audit.table.ts @@ -1,6 +1,6 @@ +import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; import { AlbumTable } from 'src/schema/tables/album.table'; -import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('album_asset_audit') export class AlbumAssetAuditTable { diff --git a/server/src/schema/tables/album-asset.table.ts b/server/src/schema/tables/album-asset.table.ts index dea271239b..5853e846f1 100644 --- a/server/src/schema/tables/album-asset.table.ts +++ b/server/src/schema/tables/album-asset.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { album_asset_delete_audit } from 'src/schema/functions'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { AssetTable } from 'src/schema/tables/asset.table'; import { AfterDeleteTrigger, CreateDateColumn, @@ -10,7 +6,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { album_asset_delete_audit } from 'src/schema/functions'; +import { AlbumTable } from 'src/schema/tables/album.table'; +import { AssetTable } from 'src/schema/tables/asset.table'; @Table({ name: 'album_asset' }) @UpdatedAtTrigger('album_asset_updatedAt') diff --git a/server/src/schema/tables/album-audit.table.ts b/server/src/schema/tables/album-audit.table.ts index 432c51c36a..7865f6bfa8 100644 --- a/server/src/schema/tables/album-audit.table.ts +++ b/server/src/schema/tables/album-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('album_audit') export class AlbumAuditTable { diff --git a/server/src/schema/tables/album-user-audit.table.ts b/server/src/schema/tables/album-user-audit.table.ts index 2259511bdd..d4798761e0 100644 --- a/server/src/schema/tables/album-user-audit.table.ts +++ b/server/src/schema/tables/album-user-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('album_user_audit') export class AlbumUserAuditTable { diff --git a/server/src/schema/tables/album-user.table.ts b/server/src/schema/tables/album-user.table.ts index 761aabc1af..2e38041daf 100644 --- a/server/src/schema/tables/album-user.table.ts +++ b/server/src/schema/tables/album-user.table.ts @@ -1,8 +1,3 @@ -import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AlbumUserRole } from 'src/enum'; -import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, AfterInsertTrigger, @@ -13,7 +8,12 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { AlbumUserRole } from 'src/enum'; +import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions'; +import { AlbumTable } from 'src/schema/tables/album.table'; +import { UserTable } from 'src/schema/tables/user.table'; @Table({ name: 'album_user' }) // Pre-existing indices from original album <--> user ManyToMany mapping diff --git a/server/src/schema/tables/album.table.ts b/server/src/schema/tables/album.table.ts index 5628db3d03..81b846c0f4 100644 --- a/server/src/schema/tables/album.table.ts +++ b/server/src/schema/tables/album.table.ts @@ -1,8 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetOrder } from 'src/enum'; -import { album_delete_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, Column, @@ -14,7 +9,12 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { AssetOrder } from 'src/enum'; +import { album_delete_audit } from 'src/schema/functions'; +import { AssetTable } from 'src/schema/tables/asset.table'; +import { UserTable } from 'src/schema/tables/user.table'; @Table({ name: 'album' }) @UpdatedAtTrigger('album_updatedAt') diff --git a/server/src/schema/tables/api-key.table.ts b/server/src/schema/tables/api-key.table.ts index efbf18afaa..6cb4d5026e 100644 --- a/server/src/schema/tables/api-key.table.ts +++ b/server/src/schema/tables/api-key.table.ts @@ -1,6 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { Permission } from 'src/enum'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -10,7 +7,10 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { Permission } from 'src/enum'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('api_key') @UpdatedAtTrigger('api_key_updatedAt') diff --git a/server/src/schema/tables/asset-audit.table.ts b/server/src/schema/tables/asset-audit.table.ts index 86c3f6f28b..fee6dde59a 100644 --- a/server/src/schema/tables/asset-audit.table.ts +++ b/server/src/schema/tables/asset-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('asset_audit') export class AssetAuditTable { diff --git a/server/src/schema/tables/asset-edit.table.ts b/server/src/schema/tables/asset-edit.table.ts index 886b62dc0b..51d3ed0a4a 100644 --- a/server/src/schema/tables/asset-edit.table.ts +++ b/server/src/schema/tables/asset-edit.table.ts @@ -1,6 +1,3 @@ -import { AssetEditAction, AssetEditActionParameter } from 'src/dtos/editing.dto'; -import { asset_edit_delete, asset_edit_insert } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; import { AfterDeleteTrigger, AfterInsertTrigger, @@ -10,7 +7,10 @@ import { PrimaryGeneratedColumn, Table, Unique, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { AssetEditAction, AssetEditActionParameter } from 'src/dtos/editing.dto'; +import { asset_edit_delete, asset_edit_insert } from 'src/schema/functions'; +import { AssetTable } from 'src/schema/tables/asset.table'; @Table('asset_edit') @AfterInsertTrigger({ scope: 'statement', function: asset_edit_insert, referencingNewTableAs: 'inserted_edit' }) diff --git a/server/src/schema/tables/asset-exif.table.ts b/server/src/schema/tables/asset-exif.table.ts index 9dacb547cf..1ae8f731a9 100644 --- a/server/src/schema/tables/asset-exif.table.ts +++ b/server/src/schema/tables/asset-exif.table.ts @@ -1,7 +1,7 @@ +import { Column, ForeignKeyColumn, Generated, Int8, Table, Timestamp, UpdateDateColumn } from '@immich/sql-tools'; import { LockableProperty } from 'src/database'; import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Generated, Int8, Table, Timestamp, UpdateDateColumn } from 'src/sql-tools'; @Table('asset_exif') @UpdatedAtTrigger('asset_exif_updatedAt') diff --git a/server/src/schema/tables/asset-face-audit.table.ts b/server/src/schema/tables/asset-face-audit.table.ts index 4f03c22aa0..2e61904800 100644 --- a/server/src/schema/tables/asset-face-audit.table.ts +++ b/server/src/schema/tables/asset-face-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('asset_face_audit') export class AssetFaceAuditTable { diff --git a/server/src/schema/tables/asset-face.table.ts b/server/src/schema/tables/asset-face.table.ts index 8a3b3ac611..b67e5e5dac 100644 --- a/server/src/schema/tables/asset-face.table.ts +++ b/server/src/schema/tables/asset-face.table.ts @@ -1,9 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { SourceType } from 'src/enum'; -import { asset_face_source_type } from 'src/schema/enums'; -import { asset_face_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { PersonTable } from 'src/schema/tables/person.table'; import { AfterDeleteTrigger, Column, @@ -15,7 +9,13 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { SourceType } from 'src/enum'; +import { asset_face_source_type } from 'src/schema/enums'; +import { asset_face_audit } from 'src/schema/functions'; +import { AssetTable } from 'src/schema/tables/asset.table'; +import { PersonTable } from 'src/schema/tables/person.table'; @Table({ name: 'asset_face' }) @UpdatedAtTrigger('asset_face_updatedAt') diff --git a/server/src/schema/tables/asset-file.table.ts b/server/src/schema/tables/asset-file.table.ts index 73b5171a47..7fdde5fed1 100644 --- a/server/src/schema/tables/asset-file.table.ts +++ b/server/src/schema/tables/asset-file.table.ts @@ -1,6 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetFileType } from 'src/enum'; -import { AssetTable } from 'src/schema/tables/asset.table'; import { Column, CreateDateColumn, @@ -11,7 +8,10 @@ import { Timestamp, Unique, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { AssetFileType } from 'src/enum'; +import { AssetTable } from 'src/schema/tables/asset.table'; @Table('asset_file') @Unique({ columns: ['assetId', 'type', 'isEdited'] }) diff --git a/server/src/schema/tables/asset-job-status.table.ts b/server/src/schema/tables/asset-job-status.table.ts index 62194825e5..4d889ade46 100644 --- a/server/src/schema/tables/asset-job-status.table.ts +++ b/server/src/schema/tables/asset-job-status.table.ts @@ -1,5 +1,5 @@ +import { Column, ForeignKeyColumn, Table, Timestamp } from '@immich/sql-tools'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Table, Timestamp } from 'src/sql-tools'; @Table('asset_job_status') export class AssetJobStatusTable { diff --git a/server/src/schema/tables/asset-metadata-audit.table.ts b/server/src/schema/tables/asset-metadata-audit.table.ts index 16272eacf7..15c0b47edc 100644 --- a/server/src/schema/tables/asset-metadata-audit.table.ts +++ b/server/src/schema/tables/asset-metadata-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('asset_metadata_audit') export class AssetMetadataAuditTable { diff --git a/server/src/schema/tables/asset-metadata.table.ts b/server/src/schema/tables/asset-metadata.table.ts index 8a7af1360f..53e3121a41 100644 --- a/server/src/schema/tables/asset-metadata.table.ts +++ b/server/src/schema/tables/asset-metadata.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetMetadataKey } from 'src/enum'; -import { asset_metadata_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; import { AfterDeleteTrigger, Column, @@ -11,7 +7,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { AssetMetadataKey } from 'src/enum'; +import { asset_metadata_audit } from 'src/schema/functions'; +import { AssetTable } from 'src/schema/tables/asset.table'; @UpdatedAtTrigger('asset_metadata_updated_at') @Table('asset_metadata') diff --git a/server/src/schema/tables/asset-ocr.table.ts b/server/src/schema/tables/asset-ocr.table.ts index b9b0838cbe..b58224a247 100644 --- a/server/src/schema/tables/asset-ocr.table.ts +++ b/server/src/schema/tables/asset-ocr.table.ts @@ -1,5 +1,5 @@ +import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from '@immich/sql-tools'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; @Table('asset_ocr') export class AssetOcrTable { diff --git a/server/src/schema/tables/asset.table.ts b/server/src/schema/tables/asset.table.ts index 765a2900e5..12e9c36125 100644 --- a/server/src/schema/tables/asset.table.ts +++ b/server/src/schema/tables/asset.table.ts @@ -1,10 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetStatus, AssetType, AssetVisibility } from 'src/enum'; -import { asset_visibility_enum, assets_status_enum } from 'src/schema/enums'; -import { asset_delete_audit } from 'src/schema/functions'; -import { LibraryTable } from 'src/schema/tables/library.table'; -import { StackTable } from 'src/schema/tables/stack.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, Column, @@ -17,7 +10,14 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { AssetStatus, AssetType, AssetVisibility } from 'src/enum'; +import { asset_visibility_enum, assets_status_enum } from 'src/schema/enums'; +import { asset_delete_audit } from 'src/schema/functions'; +import { LibraryTable } from 'src/schema/tables/library.table'; +import { StackTable } from 'src/schema/tables/stack.table'; +import { UserTable } from 'src/schema/tables/user.table'; import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database'; @Table('asset') diff --git a/server/src/schema/tables/audit.table.ts b/server/src/schema/tables/audit.table.ts index 15b4990814..78c9a57c09 100644 --- a/server/src/schema/tables/audit.table.ts +++ b/server/src/schema/tables/audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Index, PrimaryColumn, Table, Timestamp } from '@immich/sql-tools'; import { DatabaseAction, EntityType } from 'src/enum'; -import { Column, CreateDateColumn, Generated, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools'; @Table('audit') @Index({ columns: ['ownerId', 'createdAt'] }) diff --git a/server/src/schema/tables/face-search.table.ts b/server/src/schema/tables/face-search.table.ts index ff63879404..7c585437c8 100644 --- a/server/src/schema/tables/face-search.table.ts +++ b/server/src/schema/tables/face-search.table.ts @@ -1,5 +1,5 @@ +import { Column, ForeignKeyColumn, Index, Table } from '@immich/sql-tools'; import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; @Table({ name: 'face_search' }) @Index({ diff --git a/server/src/schema/tables/geodata-places.table.ts b/server/src/schema/tables/geodata-places.table.ts index eec2b240d0..101ddb759f 100644 --- a/server/src/schema/tables/geodata-places.table.ts +++ b/server/src/schema/tables/geodata-places.table.ts @@ -1,4 +1,4 @@ -import { Column, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools'; +import { Column, Index, PrimaryColumn, Table, Timestamp } from '@immich/sql-tools'; @Table({ name: 'geodata_places', primaryConstraintName: 'geodata_places_pkey' }) @Index({ diff --git a/server/src/schema/tables/library.table.ts b/server/src/schema/tables/library.table.ts index 57ad144c8e..2f79a3e78d 100644 --- a/server/src/schema/tables/library.table.ts +++ b/server/src/schema/tables/library.table.ts @@ -1,5 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -10,7 +8,9 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('library') @UpdatedAtTrigger('library_updatedAt') diff --git a/server/src/schema/tables/memory-asset-audit.table.ts b/server/src/schema/tables/memory-asset-audit.table.ts index 218c2f19ff..67c434c45a 100644 --- a/server/src/schema/tables/memory-asset-audit.table.ts +++ b/server/src/schema/tables/memory-asset-audit.table.ts @@ -1,6 +1,6 @@ +import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; import { MemoryTable } from 'src/schema/tables/memory.table'; -import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('memory_asset_audit') export class MemoryAssetAuditTable { diff --git a/server/src/schema/tables/memory-asset.table.ts b/server/src/schema/tables/memory-asset.table.ts index b162000ca0..b44c78c3b9 100644 --- a/server/src/schema/tables/memory-asset.table.ts +++ b/server/src/schema/tables/memory-asset.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { memory_asset_delete_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { MemoryTable } from 'src/schema/tables/memory.table'; import { AfterDeleteTrigger, CreateDateColumn, @@ -10,7 +6,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { memory_asset_delete_audit } from 'src/schema/functions'; +import { AssetTable } from 'src/schema/tables/asset.table'; +import { MemoryTable } from 'src/schema/tables/memory.table'; @Table('memory_asset') @UpdatedAtTrigger('memory_asset_updatedAt') diff --git a/server/src/schema/tables/memory-audit.table.ts b/server/src/schema/tables/memory-audit.table.ts index 167caf8e6e..6d278676b7 100644 --- a/server/src/schema/tables/memory-audit.table.ts +++ b/server/src/schema/tables/memory-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('memory_audit') export class MemoryAuditTable { diff --git a/server/src/schema/tables/memory.table.ts b/server/src/schema/tables/memory.table.ts index 408f7bca19..8b9867b4cc 100644 --- a/server/src/schema/tables/memory.table.ts +++ b/server/src/schema/tables/memory.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { MemoryType } from 'src/enum'; -import { memory_delete_audit } from 'src/schema/functions'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, Column, @@ -13,7 +9,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { MemoryType } from 'src/enum'; +import { memory_delete_audit } from 'src/schema/functions'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('memory') @UpdatedAtTrigger('memory_updatedAt') diff --git a/server/src/schema/tables/move.table.ts b/server/src/schema/tables/move.table.ts index 1afda2767a..c7229431f7 100644 --- a/server/src/schema/tables/move.table.ts +++ b/server/src/schema/tables/move.table.ts @@ -1,5 +1,5 @@ +import { Column, Generated, PrimaryGeneratedColumn, Table, Unique } from '@immich/sql-tools'; import { PathType } from 'src/enum'; -import { Column, Generated, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools'; @Table('move_history') // path lock (per entity) diff --git a/server/src/schema/tables/natural-earth-countries.table.ts b/server/src/schema/tables/natural-earth-countries.table.ts index c59d15fc21..06f189264e 100644 --- a/server/src/schema/tables/natural-earth-countries.table.ts +++ b/server/src/schema/tables/natural-earth-countries.table.ts @@ -1,4 +1,4 @@ -import { Column, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; +import { Column, Generated, PrimaryGeneratedColumn, Table } from '@immich/sql-tools'; @Table({ name: 'naturalearth_countries', primaryConstraintName: 'naturalearth_countries_pkey' }) export class NaturalEarthCountriesTable { diff --git a/server/src/schema/tables/notification.table.ts b/server/src/schema/tables/notification.table.ts index 01a93a73e5..6bf65808f1 100644 --- a/server/src/schema/tables/notification.table.ts +++ b/server/src/schema/tables/notification.table.ts @@ -1,6 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { NotificationLevel, NotificationType } from 'src/enum'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -11,7 +8,10 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { NotificationLevel, NotificationType } from 'src/enum'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('notification') @UpdatedAtTrigger('notification_updatedAt') diff --git a/server/src/schema/tables/ocr-search.table.ts b/server/src/schema/tables/ocr-search.table.ts index 3449725adb..74aefb333b 100644 --- a/server/src/schema/tables/ocr-search.table.ts +++ b/server/src/schema/tables/ocr-search.table.ts @@ -1,5 +1,5 @@ +import { Column, ForeignKeyColumn, Index, Table } from '@immich/sql-tools'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; @Table('ocr_search') @Index({ diff --git a/server/src/schema/tables/partner-audit.table.ts b/server/src/schema/tables/partner-audit.table.ts index fa2f0c27cc..3cfd1854e1 100644 --- a/server/src/schema/tables/partner-audit.table.ts +++ b/server/src/schema/tables/partner-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('partner_audit') export class PartnerAuditTable { diff --git a/server/src/schema/tables/partner.table.ts b/server/src/schema/tables/partner.table.ts index 8fc332cb12..408cac650f 100644 --- a/server/src/schema/tables/partner.table.ts +++ b/server/src/schema/tables/partner.table.ts @@ -1,6 +1,3 @@ -import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { partner_delete_audit } from 'src/schema/functions'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, Column, @@ -10,7 +7,10 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { partner_delete_audit } from 'src/schema/functions'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('partner') @UpdatedAtTrigger('partner_updatedAt') diff --git a/server/src/schema/tables/person-audit.table.ts b/server/src/schema/tables/person-audit.table.ts index 8a899a1808..4fb55f1744 100644 --- a/server/src/schema/tables/person-audit.table.ts +++ b/server/src/schema/tables/person-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('person_audit') export class PersonAuditTable { diff --git a/server/src/schema/tables/person.table.ts b/server/src/schema/tables/person.table.ts index 3b523a39d2..02fb85b757 100644 --- a/server/src/schema/tables/person.table.ts +++ b/server/src/schema/tables/person.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { person_delete_audit } from 'src/schema/functions'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, Check, @@ -13,7 +9,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { person_delete_audit } from 'src/schema/functions'; +import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('person') @UpdatedAtTrigger('person_updatedAt') diff --git a/server/src/schema/tables/plugin.table.ts b/server/src/schema/tables/plugin.table.ts index 3de7ca63c9..5f82807f23 100644 --- a/server/src/schema/tables/plugin.table.ts +++ b/server/src/schema/tables/plugin.table.ts @@ -1,4 +1,3 @@ -import { PluginContext } from 'src/enum'; import { Column, CreateDateColumn, @@ -9,7 +8,8 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { PluginContext } from 'src/enum'; import type { JSONSchema } from 'src/types/plugin-schema.types'; @Table('plugin') diff --git a/server/src/schema/tables/session.table.ts b/server/src/schema/tables/session.table.ts index 466152d35d..396a847e7e 100644 --- a/server/src/schema/tables/session.table.ts +++ b/server/src/schema/tables/session.table.ts @@ -1,5 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -9,7 +7,9 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { UserTable } from 'src/schema/tables/user.table'; @Table({ name: 'session' }) @UpdatedAtTrigger('session_updatedAt') diff --git a/server/src/schema/tables/shared-link-asset.table.ts b/server/src/schema/tables/shared-link-asset.table.ts index 37e6a3d9f0..ff96f69980 100644 --- a/server/src/schema/tables/shared-link-asset.table.ts +++ b/server/src/schema/tables/shared-link-asset.table.ts @@ -1,6 +1,6 @@ +import { ForeignKeyColumn, Table } from '@immich/sql-tools'; import { AssetTable } from 'src/schema/tables/asset.table'; import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; -import { ForeignKeyColumn, Table } from 'src/sql-tools'; @Table('shared_link_asset') export class SharedLinkAssetTable { diff --git a/server/src/schema/tables/shared-link.table.ts b/server/src/schema/tables/shared-link.table.ts index 80e2d7cdf4..d99520388a 100644 --- a/server/src/schema/tables/shared-link.table.ts +++ b/server/src/schema/tables/shared-link.table.ts @@ -1,6 +1,3 @@ -import { SharedLinkType } from 'src/enum'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -9,7 +6,10 @@ import { PrimaryGeneratedColumn, Table, Timestamp, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { SharedLinkType } from 'src/enum'; +import { AlbumTable } from 'src/schema/tables/album.table'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('shared_link') export class SharedLinkTable { diff --git a/server/src/schema/tables/smart-search.table.ts b/server/src/schema/tables/smart-search.table.ts index dc140efb2f..31071e6134 100644 --- a/server/src/schema/tables/smart-search.table.ts +++ b/server/src/schema/tables/smart-search.table.ts @@ -1,5 +1,5 @@ +import { Column, ForeignKeyColumn, Index, Table } from '@immich/sql-tools'; import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; @Table({ name: 'smart_search' }) @Index({ diff --git a/server/src/schema/tables/stack-audit.table.ts b/server/src/schema/tables/stack-audit.table.ts index d46ff95e57..3a62545cd2 100644 --- a/server/src/schema/tables/stack-audit.table.ts +++ b/server/src/schema/tables/stack-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('stack_audit') export class StackAuditTable { diff --git a/server/src/schema/tables/stack.table.ts b/server/src/schema/tables/stack.table.ts index 9c9eb81373..3f903e065a 100644 --- a/server/src/schema/tables/stack.table.ts +++ b/server/src/schema/tables/stack.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { stack_delete_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, CreateDateColumn, @@ -11,7 +7,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { stack_delete_audit } from 'src/schema/functions'; +import { AssetTable } from 'src/schema/tables/asset.table'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('stack') @UpdatedAtTrigger('stack_updatedAt') diff --git a/server/src/schema/tables/sync-checkpoint.table.ts b/server/src/schema/tables/sync-checkpoint.table.ts index 6ad4c54a86..d9ada5aed0 100644 --- a/server/src/schema/tables/sync-checkpoint.table.ts +++ b/server/src/schema/tables/sync-checkpoint.table.ts @@ -1,6 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { SyncEntityType } from 'src/enum'; -import { SessionTable } from 'src/schema/tables/session.table'; import { Column, CreateDateColumn, @@ -10,7 +7,10 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { SyncEntityType } from 'src/enum'; +import { SessionTable } from 'src/schema/tables/session.table'; @Table('session_sync_checkpoint') @UpdatedAtTrigger('session_sync_checkpoint_updatedAt') diff --git a/server/src/schema/tables/system-metadata.table.ts b/server/src/schema/tables/system-metadata.table.ts index 8657768db6..9f21172505 100644 --- a/server/src/schema/tables/system-metadata.table.ts +++ b/server/src/schema/tables/system-metadata.table.ts @@ -1,5 +1,5 @@ +import { Column, PrimaryColumn, Table } from '@immich/sql-tools'; import { SystemMetadataKey } from 'src/enum'; -import { Column, PrimaryColumn, Table } from 'src/sql-tools'; import { SystemMetadata } from 'src/types'; @Table('system_metadata') diff --git a/server/src/schema/tables/tag-asset.table.ts b/server/src/schema/tables/tag-asset.table.ts index 3ea2361b4f..9d7ea026c6 100644 --- a/server/src/schema/tables/tag-asset.table.ts +++ b/server/src/schema/tables/tag-asset.table.ts @@ -1,6 +1,6 @@ +import { ForeignKeyColumn, Index, Table } from '@immich/sql-tools'; import { AssetTable } from 'src/schema/tables/asset.table'; import { TagTable } from 'src/schema/tables/tag.table'; -import { ForeignKeyColumn, Index, Table } from 'src/sql-tools'; @Index({ columns: ['assetId', 'tagId'] }) @Table('tag_asset') diff --git a/server/src/schema/tables/tag-closure.table.ts b/server/src/schema/tables/tag-closure.table.ts index aeb8c8cf11..2e1c83a20f 100644 --- a/server/src/schema/tables/tag-closure.table.ts +++ b/server/src/schema/tables/tag-closure.table.ts @@ -1,5 +1,5 @@ +import { ForeignKeyColumn, Table } from '@immich/sql-tools'; import { TagTable } from 'src/schema/tables/tag.table'; -import { ForeignKeyColumn, Table } from 'src/sql-tools'; @Table('tag_closure') export class TagClosureTable { diff --git a/server/src/schema/tables/tag.table.ts b/server/src/schema/tables/tag.table.ts index dc1fa2947b..2a07239d84 100644 --- a/server/src/schema/tables/tag.table.ts +++ b/server/src/schema/tables/tag.table.ts @@ -1,5 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -10,7 +8,9 @@ import { Timestamp, Unique, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { UserTable } from 'src/schema/tables/user.table'; @Table('tag') @UpdatedAtTrigger('tag_updatedAt') diff --git a/server/src/schema/tables/user-audit.table.ts b/server/src/schema/tables/user-audit.table.ts index 084b42fb65..36f89dfa7d 100644 --- a/server/src/schema/tables/user-audit.table.ts +++ b/server/src/schema/tables/user-audit.table.ts @@ -1,5 +1,5 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('user_audit') export class UserAuditTable { diff --git a/server/src/schema/tables/user-metadata-audit.table.ts b/server/src/schema/tables/user-metadata-audit.table.ts index 63f503ab85..17dee673b4 100644 --- a/server/src/schema/tables/user-metadata-audit.table.ts +++ b/server/src/schema/tables/user-metadata-audit.table.ts @@ -1,6 +1,6 @@ +import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools'; import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; import { UserMetadataKey } from 'src/enum'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; @Table('user_metadata_audit') export class UserMetadataAuditTable { diff --git a/server/src/schema/tables/user-metadata.table.ts b/server/src/schema/tables/user-metadata.table.ts index a453ec6677..6983ed3dda 100644 --- a/server/src/schema/tables/user-metadata.table.ts +++ b/server/src/schema/tables/user-metadata.table.ts @@ -1,7 +1,3 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserMetadataKey } from 'src/enum'; -import { user_metadata_audit } from 'src/schema/functions'; -import { UserTable } from 'src/schema/tables/user.table'; import { AfterDeleteTrigger, Column, @@ -11,7 +7,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { UserMetadataKey } from 'src/enum'; +import { user_metadata_audit } from 'src/schema/functions'; +import { UserTable } from 'src/schema/tables/user.table'; import { UserMetadata, UserMetadataItem } from 'src/types'; @UpdatedAtTrigger('user_metadata_updated_at') diff --git a/server/src/schema/tables/user.table.ts b/server/src/schema/tables/user.table.ts index 46d6656382..3a340d976b 100644 --- a/server/src/schema/tables/user.table.ts +++ b/server/src/schema/tables/user.table.ts @@ -1,7 +1,3 @@ -import { ColumnType } from 'kysely'; -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserAvatarColor, UserStatus } from 'src/enum'; -import { user_delete_audit } from 'src/schema/functions'; import { AfterDeleteTrigger, Column, @@ -13,7 +9,11 @@ import { Table, Timestamp, UpdateDateColumn, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { ColumnType } from 'kysely'; +import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; +import { UserAvatarColor, UserStatus } from 'src/enum'; +import { user_delete_audit } from 'src/schema/functions'; @Table('user') @UpdatedAtTrigger('user_updatedAt') diff --git a/server/src/schema/tables/version-history.table.ts b/server/src/schema/tables/version-history.table.ts index 143852c527..12eab7fd69 100644 --- a/server/src/schema/tables/version-history.table.ts +++ b/server/src/schema/tables/version-history.table.ts @@ -1,4 +1,4 @@ -import { Column, CreateDateColumn, Generated, PrimaryGeneratedColumn, Table, Timestamp } from 'src/sql-tools'; +import { Column, CreateDateColumn, Generated, PrimaryGeneratedColumn, Table, Timestamp } from '@immich/sql-tools'; @Table('version_history') export class VersionHistoryTable { diff --git a/server/src/schema/tables/workflow.table.ts b/server/src/schema/tables/workflow.table.ts index 62a5531d8e..163518e039 100644 --- a/server/src/schema/tables/workflow.table.ts +++ b/server/src/schema/tables/workflow.table.ts @@ -1,6 +1,3 @@ -import { PluginTriggerType } from 'src/enum'; -import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; -import { UserTable } from 'src/schema/tables/user.table'; import { Column, CreateDateColumn, @@ -10,7 +7,10 @@ import { PrimaryGeneratedColumn, Table, Timestamp, -} from 'src/sql-tools'; +} from '@immich/sql-tools'; +import { PluginTriggerType } from 'src/enum'; +import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; +import { UserTable } from 'src/schema/tables/user.table'; import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; @Table('workflow') diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 479fd130a6..22f06e2ed9 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -1,3 +1,4 @@ +import { schemaDiff } from '@immich/sql-tools'; import { Injectable } from '@nestjs/common'; import { isAbsolute, join } from 'node:path'; import { SALT_ROUNDS } from 'src/constants'; @@ -5,7 +6,6 @@ import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; import { MaintenanceAction, SystemMetadataKey } from 'src/enum'; import { BaseService } from 'src/services/base.service'; -import { schemaDiff } from 'src/sql-tools'; import { createMaintenanceLoginUrl, generateMaintenanceSecret } from 'src/utils/maintenance'; import { getExternalDomain } from 'src/utils/misc'; diff --git a/server/src/sql-tools/comparers/column.comparer.spec.ts b/server/src/sql-tools/comparers/column.comparer.spec.ts deleted file mode 100644 index ef2afb348a..0000000000 --- a/server/src/sql-tools/comparers/column.comparer.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { compareColumns } from 'src/sql-tools/comparers/column.comparer'; -import { DatabaseColumn, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testColumn: DatabaseColumn = { - name: 'test', - tableName: 'table1', - primary: false, - nullable: false, - isArray: false, - type: 'character varying', - synchronize: true, -}; - -describe('compareColumns', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareColumns().onExtra(testColumn)).toEqual([ - { - tableName: 'table1', - columnName: 'test', - type: 'ColumnDrop', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareColumns().onMissing(testColumn)).toEqual([ - { - type: 'ColumnAdd', - column: testColumn, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareColumns().onCompare(testColumn, testColumn)).toEqual([]); - }); - - it('should detect a change in type', () => { - const source: DatabaseColumn = { ...testColumn }; - const target: DatabaseColumn = { ...testColumn, type: 'text' }; - const reason = 'column type is different (character varying vs text)'; - expect(compareColumns().onCompare(source, target)).toEqual([ - { - columnName: 'test', - tableName: 'table1', - type: 'ColumnDrop', - reason, - }, - { - type: 'ColumnAdd', - column: source, - reason, - }, - ]); - }); - - it('should detect a change in default', () => { - const source: DatabaseColumn = { ...testColumn, nullable: true }; - const target: DatabaseColumn = { ...testColumn, nullable: true, default: "''" }; - const reason = `default is different (null vs '')`; - expect(compareColumns().onCompare(source, target)).toEqual([ - { - columnName: 'test', - tableName: 'table1', - type: 'ColumnAlter', - changes: { - default: 'NULL', - }, - reason, - }, - ]); - }); - - it('should detect a comment change', () => { - const source: DatabaseColumn = { ...testColumn, comment: 'new comment' }; - const target: DatabaseColumn = { ...testColumn, comment: 'old comment' }; - const reason = 'comment is different (new comment vs old comment)'; - expect(compareColumns().onCompare(source, target)).toEqual([ - { - columnName: 'test', - tableName: 'table1', - type: 'ColumnAlter', - changes: { - comment: 'new comment', - }, - reason, - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/column.comparer.ts b/server/src/sql-tools/comparers/column.comparer.ts deleted file mode 100644 index 54ffb34ffa..0000000000 --- a/server/src/sql-tools/comparers/column.comparer.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { asRenameKey, getColumnType, isDefaultEqual } from 'src/sql-tools/helpers'; -import { Comparer, DatabaseColumn, Reason, SchemaDiff } from 'src/sql-tools/types'; - -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', - column: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'ColumnDrop', - tableName: target.tableName, - columnName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - const sourceType = getColumnType(source); - const targetType = getColumnType(target); - - const isTypeChanged = sourceType !== targetType; - - if (isTypeChanged) { - // TODO: convert between types via UPDATE when possible - return dropAndRecreateColumn(source, target, `column type is different (${sourceType} vs ${targetType})`); - } - - const items: SchemaDiff[] = []; - if (source.nullable !== target.nullable) { - items.push({ - type: 'ColumnAlter', - tableName: source.tableName, - columnName: source.name, - changes: { - nullable: source.nullable, - }, - reason: `nullable is different (${source.nullable} vs ${target.nullable})`, - }); - } - - if (!isDefaultEqual(source, target)) { - items.push({ - type: 'ColumnAlter', - tableName: source.tableName, - columnName: source.name, - changes: { - default: String(source.default ?? 'NULL'), - }, - reason: `default is different (${source.default ?? 'null'} vs ${target.default})`, - }); - } - - if (source.comment !== target.comment) { - items.push({ - type: 'ColumnAlter', - tableName: source.tableName, - columnName: source.name, - changes: { - comment: String(source.comment), - }, - reason: `comment is different (${source.comment} vs ${target.comment})`, - }); - } - - return items; - }, - }) satisfies Comparer; - -const dropAndRecreateColumn = (source: DatabaseColumn, target: DatabaseColumn, reason: string): SchemaDiff[] => { - return [ - { - type: 'ColumnDrop', - tableName: target.tableName, - columnName: target.name, - reason, - }, - { type: 'ColumnAdd', column: source, reason }, - ]; -}; diff --git a/server/src/sql-tools/comparers/constraint.comparer.spec.ts b/server/src/sql-tools/comparers/constraint.comparer.spec.ts deleted file mode 100644 index 216728f8c4..0000000000 --- a/server/src/sql-tools/comparers/constraint.comparer.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { compareConstraints } from 'src/sql-tools/comparers/constraint.comparer'; -import { ConstraintType, DatabaseConstraint, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testConstraint: DatabaseConstraint = { - type: ConstraintType.PRIMARY_KEY, - name: 'test', - tableName: 'table1', - columnNames: ['column1'], - synchronize: true, -}; - -describe('compareConstraints', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareConstraints().onExtra(testConstraint)).toEqual([ - { - type: 'ConstraintDrop', - constraintName: 'test', - tableName: 'table1', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareConstraints().onMissing(testConstraint)).toEqual([ - { - type: 'ConstraintAdd', - constraint: testConstraint, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareConstraints().onCompare(testConstraint, testConstraint)).toEqual([]); - }); - - it('should detect a change in type', () => { - const source: DatabaseConstraint = { ...testConstraint }; - const target: DatabaseConstraint = { ...testConstraint, columnNames: ['column1', 'column2'] }; - const reason = 'Primary key columns are different: (column1 vs column1,column2)'; - expect(compareConstraints().onCompare(source, target)).toEqual([ - { - constraintName: 'test', - tableName: 'table1', - type: 'ConstraintDrop', - reason, - }, - { - type: 'ConstraintAdd', - constraint: source, - reason, - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/constraint.comparer.ts b/server/src/sql-tools/comparers/constraint.comparer.ts deleted file mode 100644 index 03128878d5..0000000000 --- a/server/src/sql-tools/comparers/constraint.comparer.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { asRenameKey, haveEqualColumns } from 'src/sql-tools/helpers'; -import { - CompareFunction, - Comparer, - ConstraintType, - DatabaseCheckConstraint, - DatabaseConstraint, - DatabaseForeignKeyConstraint, - DatabasePrimaryKeyConstraint, - DatabaseUniqueConstraint, - Reason, - SchemaDiff, -} from 'src/sql-tools/types'; - -export const compareConstraints = (): Comparer => ({ - 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: { - const expression = constraint.expression.replaceAll('(', '').replaceAll(')', ''); - return asRenameKey([constraint.type, constraint.tableName, expression]); - } - } - }, - onRename: (source, target) => [ - { - type: 'ConstraintRename', - tableName: target.tableName, - oldName: target.name, - newName: source.name, - reason: Reason.Rename, - }, - ], - onMissing: (source) => [ - { - type: 'ConstraintAdd', - constraint: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'ConstraintDrop', - tableName: target.tableName, - constraintName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - switch (source.type) { - case ConstraintType.PRIMARY_KEY: { - return comparePrimaryKeyConstraint(source, target as DatabasePrimaryKeyConstraint); - } - - case ConstraintType.FOREIGN_KEY: { - return compareForeignKeyConstraint(source, target as DatabaseForeignKeyConstraint); - } - - case ConstraintType.UNIQUE: { - return compareUniqueConstraint(source, target as DatabaseUniqueConstraint); - } - - case ConstraintType.CHECK: { - return compareCheckConstraint(source, target as DatabaseCheckConstraint); - } - - default: { - return []; - } - } - }, -}); - -const comparePrimaryKeyConstraint: CompareFunction = (source, target) => { - if (!haveEqualColumns(source.columnNames, target.columnNames)) { - return dropAndRecreateConstraint( - source, - target, - `Primary key columns are different: (${source.columnNames} vs ${target.columnNames})`, - ); - } - - return []; -}; - -const compareForeignKeyConstraint: CompareFunction = (source, target) => { - let reason = ''; - - const sourceDeleteAction = source.onDelete ?? 'NO ACTION'; - const targetDeleteAction = target.onDelete ?? 'NO ACTION'; - - const sourceUpdateAction = source.onUpdate ?? 'NO ACTION'; - const targetUpdateAction = target.onUpdate ?? 'NO ACTION'; - - if (!haveEqualColumns(source.columnNames, target.columnNames)) { - reason = `columns are different (${source.columnNames} vs ${target.columnNames})`; - } else if (!haveEqualColumns(source.referenceColumnNames, target.referenceColumnNames)) { - reason = `reference columns are different (${source.referenceColumnNames} vs ${target.referenceColumnNames})`; - } else if (source.referenceTableName !== target.referenceTableName) { - reason = `reference table is different (${source.referenceTableName} vs ${target.referenceTableName})`; - } else if (sourceDeleteAction !== targetDeleteAction) { - reason = `ON DELETE action is different (${sourceDeleteAction} vs ${targetDeleteAction})`; - } else if (sourceUpdateAction !== targetUpdateAction) { - reason = `ON UPDATE action is different (${sourceUpdateAction} vs ${targetUpdateAction})`; - } - - if (reason) { - return dropAndRecreateConstraint(source, target, reason); - } - - return []; -}; - -const compareUniqueConstraint: CompareFunction = (source, target) => { - let reason = ''; - - if (!haveEqualColumns(source.columnNames, target.columnNames)) { - reason = `columns are different (${source.columnNames} vs ${target.columnNames})`; - } - - if (reason) { - return dropAndRecreateConstraint(source, target, reason); - } - - return []; -}; - -const compareCheckConstraint: CompareFunction = (source, target) => { - if (source.expression !== target.expression) { - // comparing expressions is hard because postgres reconstructs it with different formatting - // for now if the constraint exists with the same name, we will just skip it - } - - return []; -}; - -const dropAndRecreateConstraint = ( - source: DatabaseConstraint, - target: DatabaseConstraint, - reason: string, -): SchemaDiff[] => { - return [ - { - type: 'ConstraintDrop', - tableName: target.tableName, - constraintName: target.name, - reason, - }, - { type: 'ConstraintAdd', constraint: source, reason }, - ]; -}; diff --git a/server/src/sql-tools/comparers/enum.comparer.spec.ts b/server/src/sql-tools/comparers/enum.comparer.spec.ts deleted file mode 100644 index d788c7cd71..0000000000 --- a/server/src/sql-tools/comparers/enum.comparer.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { compareEnums } from 'src/sql-tools/comparers/enum.comparer'; -import { DatabaseEnum, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testEnum: DatabaseEnum = { name: 'test', values: ['foo', 'bar'], synchronize: true }; - -describe('compareEnums', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareEnums().onExtra(testEnum)).toEqual([ - { - enumName: 'test', - type: 'EnumDrop', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareEnums().onMissing(testEnum)).toEqual([ - { - type: 'EnumCreate', - enum: testEnum, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareEnums().onCompare(testEnum, testEnum)).toEqual([]); - }); - - it('should drop and recreate when values list is different', () => { - const source = { name: 'test', values: ['foo', 'bar'], synchronize: true }; - const target = { name: 'test', values: ['foo', 'bar', 'world'], synchronize: true }; - expect(compareEnums().onCompare(source, target)).toEqual([ - { - enumName: 'test', - type: 'EnumDrop', - reason: 'enum values has changed (foo,bar vs foo,bar,world)', - }, - { - type: 'EnumCreate', - enum: source, - reason: 'enum values has changed (foo,bar vs foo,bar,world)', - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/enum.comparer.ts b/server/src/sql-tools/comparers/enum.comparer.ts deleted file mode 100644 index efc08ae727..0000000000 --- a/server/src/sql-tools/comparers/enum.comparer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Comparer, DatabaseEnum, Reason } from 'src/sql-tools/types'; - -export const compareEnums = (): Comparer => ({ - onMissing: (source) => [ - { - type: 'EnumCreate', - enum: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'EnumDrop', - enumName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - if (source.values.toString() !== target.values.toString()) { - // TODO add or remove values if the lists are different or the order has changed - const reason = `enum values has changed (${source.values} vs ${target.values})`; - return [ - { - type: 'EnumDrop', - enumName: source.name, - reason, - }, - { - type: 'EnumCreate', - enum: source, - reason, - }, - ]; - } - - return []; - }, -}); diff --git a/server/src/sql-tools/comparers/extension.comparer.spec.ts b/server/src/sql-tools/comparers/extension.comparer.spec.ts deleted file mode 100644 index df70ccc761..0000000000 --- a/server/src/sql-tools/comparers/extension.comparer.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { compareExtensions } from 'src/sql-tools/comparers/extension.comparer'; -import { Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testExtension = { name: 'test', synchronize: true }; - -describe('compareExtensions', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareExtensions().onExtra(testExtension)).toEqual([ - { - extensionName: 'test', - type: 'ExtensionDrop', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareExtensions().onMissing(testExtension)).toEqual([ - { - type: 'ExtensionCreate', - extension: testExtension, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareExtensions().onCompare(testExtension, testExtension)).toEqual([]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/extension.comparer.ts b/server/src/sql-tools/comparers/extension.comparer.ts deleted file mode 100644 index 3cb70dadc4..0000000000 --- a/server/src/sql-tools/comparers/extension.comparer.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Comparer, DatabaseExtension, Reason } from 'src/sql-tools/types'; - -export const compareExtensions = (): Comparer => ({ - onMissing: (source) => [ - { - type: 'ExtensionCreate', - extension: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'ExtensionDrop', - extensionName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: () => { - // if the name matches they are the same - return []; - }, -}); diff --git a/server/src/sql-tools/comparers/function.comparer.spec.ts b/server/src/sql-tools/comparers/function.comparer.spec.ts deleted file mode 100644 index 3d18aaf50a..0000000000 --- a/server/src/sql-tools/comparers/function.comparer.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { compareFunctions } from 'src/sql-tools/comparers/function.comparer'; -import { DatabaseFunction, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testFunction: DatabaseFunction = { - name: 'test', - expression: 'CREATE FUNCTION something something something', - synchronize: true, -}; - -describe('compareFunctions', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareFunctions().onExtra(testFunction)).toEqual([ - { - functionName: 'test', - type: 'FunctionDrop', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareFunctions().onMissing(testFunction)).toEqual([ - { - type: 'FunctionCreate', - function: testFunction, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should ignore functions with the same hash', () => { - expect(compareFunctions().onCompare(testFunction, testFunction)).toEqual([]); - }); - - it('should report differences if functions have different hashes', () => { - const source: DatabaseFunction = { ...testFunction, expression: 'SELECT 1' }; - const target: DatabaseFunction = { ...testFunction, expression: 'SELECT 2' }; - expect(compareFunctions().onCompare(source, target)).toEqual([ - { - type: 'FunctionCreate', - reason: 'function expression has changed (SELECT 1 vs SELECT 2)', - function: source, - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/function.comparer.ts b/server/src/sql-tools/comparers/function.comparer.ts deleted file mode 100644 index c6217ee708..0000000000 --- a/server/src/sql-tools/comparers/function.comparer.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Comparer, DatabaseFunction, Reason } from 'src/sql-tools/types'; - -export const compareFunctions = (): Comparer => ({ - onMissing: (source) => [ - { - type: 'FunctionCreate', - function: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'FunctionDrop', - functionName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - if (source.expression !== target.expression) { - const reason = `function expression has changed (${source.expression} vs ${target.expression})`; - return [ - { - type: 'FunctionCreate', - function: source, - reason, - }, - ]; - } - - return []; - }, -}); diff --git a/server/src/sql-tools/comparers/index.comparer.spec.ts b/server/src/sql-tools/comparers/index.comparer.spec.ts deleted file mode 100644 index 9ae7f34f04..0000000000 --- a/server/src/sql-tools/comparers/index.comparer.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { compareIndexes } from 'src/sql-tools/comparers/index.comparer'; -import { DatabaseIndex, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testIndex: DatabaseIndex = { - name: 'test', - tableName: 'table1', - columnNames: ['column1', 'column2'], - unique: false, - synchronize: true, -}; - -describe('compareIndexes', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareIndexes().onExtra(testIndex)).toEqual([ - { - type: 'IndexDrop', - indexName: 'test', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareIndexes().onMissing(testIndex)).toEqual([ - { - type: 'IndexCreate', - index: testIndex, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareIndexes().onCompare(testIndex, testIndex)).toEqual([]); - }); - - it('should drop and recreate when column list is different', () => { - const source = { - name: 'test', - tableName: 'table1', - columnNames: ['column1'], - unique: true, - synchronize: true, - }; - const target = { - name: 'test', - tableName: 'table1', - columnNames: ['column1', 'column2'], - unique: true, - synchronize: true, - }; - expect(compareIndexes().onCompare(source, target)).toEqual([ - { - indexName: 'test', - type: 'IndexDrop', - reason: 'columns are different (column1 vs column1,column2)', - }, - { - type: 'IndexCreate', - index: source, - reason: 'columns are different (column1 vs column1,column2)', - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/index.comparer.ts b/server/src/sql-tools/comparers/index.comparer.ts deleted file mode 100644 index e474302c6e..0000000000 --- a/server/src/sql-tools/comparers/index.comparer.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { asRenameKey, haveEqualColumns } from 'src/sql-tools/helpers'; -import { Comparer, DatabaseIndex, Reason } from 'src/sql-tools/types'; - -export const compareIndexes = (): Comparer => ({ - getRenameKey: (index) => { - if (index.override) { - return index.override.value.sql.replace(index.name, 'INDEX_NAME'); - } - - return asRenameKey([index.tableName, ...(index.columnNames || []), index.unique]); - }, - onRename: (source, target) => [ - { - type: 'IndexRename', - tableName: source.tableName, - oldName: target.name, - newName: source.name, - reason: Reason.Rename, - }, - ], - onMissing: (source) => [ - { - type: 'IndexCreate', - index: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'IndexDrop', - indexName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - const sourceUsing = source.using ?? 'btree'; - const targetUsing = target.using ?? 'btree'; - - let reason = ''; - - if (!haveEqualColumns(source.columnNames, target.columnNames)) { - reason = `columns are different (${source.columnNames} vs ${target.columnNames})`; - } else if (source.unique !== target.unique) { - reason = `uniqueness is different (${source.unique} vs ${target.unique})`; - } else if (sourceUsing !== targetUsing) { - reason = `using method is different (${source.using} vs ${target.using})`; - } else if (source.where !== target.where) { - reason = `where clause is different (${source.where} vs ${target.where})`; - } else if (source.expression !== target.expression) { - reason = `expression is different (${source.expression} vs ${target.expression})`; - } - - if (reason) { - return [ - { type: 'IndexDrop', indexName: target.name, reason }, - { type: 'IndexCreate', index: source, reason }, - ]; - } - - return []; - }, -}); diff --git a/server/src/sql-tools/comparers/override.comparer.spec.ts b/server/src/sql-tools/comparers/override.comparer.spec.ts deleted file mode 100644 index dfa6fa4455..0000000000 --- a/server/src/sql-tools/comparers/override.comparer.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { compareOverrides } from 'src/sql-tools/comparers/override.comparer'; -import { DatabaseOverride, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testOverride: DatabaseOverride = { - name: 'test', - value: { type: 'function', name: 'test_func', sql: 'func implementation' }, - synchronize: true, -}; - -describe('compareOverrides', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareOverrides().onExtra(testOverride)).toEqual([ - { - type: 'OverrideDrop', - overrideName: 'test', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareOverrides().onMissing(testOverride)).toEqual([ - { - type: 'OverrideCreate', - override: testOverride, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareOverrides().onCompare(testOverride, testOverride)).toEqual([]); - }); - - it('should drop and recreate when the value changes', () => { - const source: DatabaseOverride = { - name: 'test', - value: { - type: 'function', - name: 'test_func', - sql: 'func implementation', - }, - synchronize: true, - }; - const target: DatabaseOverride = { - name: 'test', - value: { - type: 'function', - name: 'test_func', - sql: 'func implementation2', - }, - synchronize: true, - }; - expect(compareOverrides().onCompare(source, target)).toEqual([ - { - override: source, - type: 'OverrideUpdate', - reason: expect.stringContaining('value is different'), - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/override.comparer.ts b/server/src/sql-tools/comparers/override.comparer.ts deleted file mode 100644 index 999770bf69..0000000000 --- a/server/src/sql-tools/comparers/override.comparer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Comparer, DatabaseOverride, Reason } from 'src/sql-tools/types'; - -export const compareOverrides = (): Comparer => ({ - onMissing: (source) => [ - { - type: 'OverrideCreate', - override: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'OverrideDrop', - overrideName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - if (source.value.name !== target.value.name || source.value.sql !== target.value.sql) { - const sourceValue = JSON.stringify(source.value); - const targetValue = JSON.stringify(target.value); - return [ - { type: 'OverrideUpdate', override: source, reason: `value is different (${sourceValue} vs ${targetValue})` }, - ]; - } - - return []; - }, -}); diff --git a/server/src/sql-tools/comparers/parameter.comparer.spec.ts b/server/src/sql-tools/comparers/parameter.comparer.spec.ts deleted file mode 100644 index 23e6c78118..0000000000 --- a/server/src/sql-tools/comparers/parameter.comparer.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { compareParameters } from 'src/sql-tools/comparers/parameter.comparer'; -import { DatabaseParameter, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testParameter: DatabaseParameter = { - name: 'test', - databaseName: 'immich', - value: 'on', - scope: 'database', - synchronize: true, -}; - -describe('compareParameters', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareParameters().onExtra(testParameter)).toEqual([ - { - type: 'ParameterReset', - databaseName: 'immich', - parameterName: 'test', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareParameters().onMissing(testParameter)).toEqual([ - { - type: 'ParameterSet', - parameter: testParameter, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareParameters().onCompare(testParameter, testParameter)).toEqual([]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/parameter.comparer.ts b/server/src/sql-tools/comparers/parameter.comparer.ts deleted file mode 100644 index 41d0508d70..0000000000 --- a/server/src/sql-tools/comparers/parameter.comparer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Comparer, DatabaseParameter, Reason } from 'src/sql-tools/types'; - -export const compareParameters = (): Comparer => ({ - onMissing: (source) => [ - { - type: 'ParameterSet', - parameter: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'ParameterReset', - databaseName: target.databaseName, - parameterName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: () => { - // TODO - return []; - }, -}); diff --git a/server/src/sql-tools/comparers/table.comparer.spec.ts b/server/src/sql-tools/comparers/table.comparer.spec.ts deleted file mode 100644 index 909db26ea9..0000000000 --- a/server/src/sql-tools/comparers/table.comparer.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { compareTables } from 'src/sql-tools/comparers/table.comparer'; -import { DatabaseTable, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testTable: DatabaseTable = { - name: 'test', - columns: [], - constraints: [], - indexes: [], - triggers: [], - synchronize: true, -}; - -describe('compareParameters', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareTables({}).onExtra(testTable)).toEqual([ - { - type: 'TableDrop', - tableName: 'test', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareTables({}).onMissing(testTable)).toEqual([ - { - type: 'TableCreate', - table: testTable, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareTables({}).onCompare(testTable, testTable)).toEqual([]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/table.comparer.ts b/server/src/sql-tools/comparers/table.comparer.ts deleted file mode 100644 index 6576dce1b1..0000000000 --- a/server/src/sql-tools/comparers/table.comparer.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { compareColumns } from 'src/sql-tools/comparers/column.comparer'; -import { compareConstraints } from 'src/sql-tools/comparers/constraint.comparer'; -import { compareIndexes } from 'src/sql-tools/comparers/index.comparer'; -import { compareTriggers } from 'src/sql-tools/comparers/trigger.comparer'; -import { compare } from 'src/sql-tools/helpers'; -import { Comparer, DatabaseTable, Reason, SchemaDiffOptions } from 'src/sql-tools/types'; - -export const compareTables = (options: SchemaDiffOptions): Comparer => ({ - onMissing: (source) => [ - { - type: 'TableCreate', - table: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'TableDrop', - tableName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - return [ - ...compare(source.columns, target.columns, options.columns, compareColumns()), - ...compare(source.indexes, target.indexes, options.indexes, compareIndexes()), - ...compare(source.constraints, target.constraints, options.constraints, compareConstraints()), - ...compare(source.triggers, target.triggers, options.triggers, compareTriggers()), - ]; - }, -}); diff --git a/server/src/sql-tools/comparers/trigger.comparer.spec.ts b/server/src/sql-tools/comparers/trigger.comparer.spec.ts deleted file mode 100644 index c80b0d2273..0000000000 --- a/server/src/sql-tools/comparers/trigger.comparer.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { compareTriggers } from 'src/sql-tools/comparers/trigger.comparer'; -import { DatabaseTrigger, Reason } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const testTrigger: DatabaseTrigger = { - name: 'test', - tableName: 'table1', - timing: 'before', - actions: ['delete'], - scope: 'row', - functionName: 'my_trigger_function', - synchronize: true, -}; - -describe('compareTriggers', () => { - describe('onExtra', () => { - it('should work', () => { - expect(compareTriggers().onExtra(testTrigger)).toEqual([ - { - type: 'TriggerDrop', - tableName: 'table1', - triggerName: 'test', - reason: Reason.MissingInSource, - }, - ]); - }); - }); - - describe('onMissing', () => { - it('should work', () => { - expect(compareTriggers().onMissing(testTrigger)).toEqual([ - { - type: 'TriggerCreate', - trigger: testTrigger, - reason: Reason.MissingInTarget, - }, - ]); - }); - }); - - describe('onCompare', () => { - it('should work', () => { - expect(compareTriggers().onCompare(testTrigger, testTrigger)).toEqual([]); - }); - - it('should detect a change in function name', () => { - const source: DatabaseTrigger = { ...testTrigger, functionName: 'my_new_name' }; - const target: DatabaseTrigger = { ...testTrigger, functionName: 'my_old_name' }; - const reason = `function is different (my_new_name vs my_old_name)`; - expect(compareTriggers().onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]); - }); - - it('should detect a change in actions', () => { - const source: DatabaseTrigger = { ...testTrigger, actions: ['delete'] }; - const target: DatabaseTrigger = { ...testTrigger, actions: ['delete', 'insert'] }; - const reason = `action is different (delete vs delete,insert)`; - expect(compareTriggers().onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]); - }); - - it('should detect a change in timing', () => { - const source: DatabaseTrigger = { ...testTrigger, timing: 'before' }; - const target: DatabaseTrigger = { ...testTrigger, timing: 'after' }; - const reason = `timing method is different (before vs after)`; - expect(compareTriggers().onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]); - }); - - it('should detect a change in scope', () => { - const source: DatabaseTrigger = { ...testTrigger, scope: 'row' }; - const target: DatabaseTrigger = { ...testTrigger, scope: 'statement' }; - const reason = `scope is different (row vs statement)`; - expect(compareTriggers().onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]); - }); - - it('should detect a change in new table reference', () => { - const source: DatabaseTrigger = { ...testTrigger, referencingNewTableAs: 'new_table' }; - const target: DatabaseTrigger = { ...testTrigger, referencingNewTableAs: undefined }; - const reason = `new table reference is different (new_table vs undefined)`; - expect(compareTriggers().onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]); - }); - - it('should detect a change in old table reference', () => { - const source: DatabaseTrigger = { ...testTrigger, referencingOldTableAs: 'old_table' }; - const target: DatabaseTrigger = { ...testTrigger, referencingOldTableAs: undefined }; - const reason = `old table reference is different (old_table vs undefined)`; - expect(compareTriggers().onCompare(source, target)).toEqual([{ type: 'TriggerCreate', trigger: source, reason }]); - }); - }); -}); diff --git a/server/src/sql-tools/comparers/trigger.comparer.ts b/server/src/sql-tools/comparers/trigger.comparer.ts deleted file mode 100644 index 4ba2d5dba3..0000000000 --- a/server/src/sql-tools/comparers/trigger.comparer.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Comparer, DatabaseTrigger, Reason } from 'src/sql-tools/types'; - -export const compareTriggers = (): Comparer => ({ - onMissing: (source) => [ - { - type: 'TriggerCreate', - trigger: source, - reason: Reason.MissingInTarget, - }, - ], - onExtra: (target) => [ - { - type: 'TriggerDrop', - tableName: target.tableName, - triggerName: target.name, - reason: Reason.MissingInSource, - }, - ], - onCompare: (source, target) => { - let reason = ''; - if (source.functionName !== target.functionName) { - reason = `function is different (${source.functionName} vs ${target.functionName})`; - } else if (source.actions.join(' OR ') !== target.actions.join(' OR ')) { - reason = `action is different (${source.actions} vs ${target.actions})`; - } else if (source.timing !== target.timing) { - reason = `timing method is different (${source.timing} vs ${target.timing})`; - } else if (source.scope !== target.scope) { - reason = `scope is different (${source.scope} vs ${target.scope})`; - } else if (source.referencingNewTableAs !== target.referencingNewTableAs) { - reason = `new table reference is different (${source.referencingNewTableAs} vs ${target.referencingNewTableAs})`; - } else if (source.referencingOldTableAs !== target.referencingOldTableAs) { - reason = `old table reference is different (${source.referencingOldTableAs} vs ${target.referencingOldTableAs})`; - } - - if (reason) { - return [{ type: 'TriggerCreate', trigger: source, reason }]; - } - - return []; - }, -}); diff --git a/server/src/sql-tools/contexts/base-context.ts b/server/src/sql-tools/contexts/base-context.ts deleted file mode 100644 index 0fa7230a00..0000000000 --- a/server/src/sql-tools/contexts/base-context.ts +++ /dev/null @@ -1,104 +0,0 @@ -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, - DatabaseExtension, - DatabaseFunction, - DatabaseOverride, - DatabaseParameter, - DatabaseSchema, - DatabaseTable, -} from 'src/sql-tools/types'; - -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; - overrideTableName: string; - - tables: DatabaseTable[] = []; - functions: DatabaseFunction[] = []; - enums: DatabaseEnum[] = []; - extensions: DatabaseExtension[] = []; - parameters: DatabaseParameter[] = []; - 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) { - return this.tables.find((table) => table.name === name); - } - - warn(context: string, message: string) { - this.warnings.push(`[${context}] ${message}`); - } - - build(): DatabaseSchema { - const overrideMap = new Map(); - for (const override of this.overrides) { - const { type, name } = override.value; - overrideMap.set(asOverrideKey(type, name), override); - } - - for (const func of this.functions) { - func.override = overrideMap.get(asOverrideKey('function', func.name)); - } - - for (const { indexes, triggers } of this.tables) { - for (const index of indexes) { - index.override = overrideMap.get(asOverrideKey('index', index.name)); - } - - for (const trigger of triggers) { - trigger.override = overrideMap.get(asOverrideKey('trigger', trigger.name)); - } - } - - return { - databaseName: this.databaseName, - schemaName: this.schemaName, - tables: this.tables, - functions: this.functions, - enums: this.enums, - extensions: this.extensions, - parameters: this.parameters, - overrides: this.overrides, - warnings: this.warnings, - }; - } -} diff --git a/server/src/sql-tools/contexts/processor-context.ts b/server/src/sql-tools/contexts/processor-context.ts deleted file mode 100644 index 3ab196b0af..0000000000 --- a/server/src/sql-tools/contexts/processor-context.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -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 }; - -export class ProcessorContext extends BaseContext { - constructor(public options: SchemaFromCodeOptions) { - options.createForeignKeyIndexes = options.createForeignKeyIndexes ?? true; - options.overrides = options.overrides ?? false; - super(options); - } - - classToTable: WeakMap = new WeakMap(); - tableToMetadata: WeakMap = new WeakMap(); - - getTableByObject(object: Function) { - return this.classToTable.get(object); - } - - getTableMetadata(table: DatabaseTable) { - const metadata = this.tableToMetadata.get(table); - if (!metadata) { - throw new Error(`Table metadata not found for table: ${table.name}`); - } - return metadata; - } - - addTable(table: DatabaseTable, options: TableOptions, object: Function) { - this.tables.push(table); - this.classToTable.set(object, table); - this.tableToMetadata.set(table, { options, object, methodToColumn: new Map() }); - } - - getColumnByObjectAndPropertyName( - object: object, - propertyName: string | symbol, - ): { table?: DatabaseTable; column?: DatabaseColumn } { - const table = this.getTableByObject(object.constructor); - if (!table) { - return {}; - } - - const tableMetadata = this.tableToMetadata.get(table); - if (!tableMetadata) { - return {}; - } - - const column = tableMetadata.methodToColumn.get(propertyName); - - return { table, column }; - } - - addColumn(table: DatabaseTable, column: DatabaseColumn, options: ColumnOptions, propertyName: string | symbol) { - table.columns.push(column); - const tableMetadata = this.getTableMetadata(table); - tableMetadata.methodToColumn.set(propertyName, column); - } - - warnMissingTable(context: string, object: object, propertyName?: symbol | string) { - const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : ''); - this.warn(context, `Unable to find table (${label})`); - } - - warnMissingColumn(context: string, object: object, propertyName?: symbol | string) { - const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : ''); - this.warn(context, `Unable to find column (${label})`); - } -} diff --git a/server/src/sql-tools/contexts/reader-context.ts b/server/src/sql-tools/contexts/reader-context.ts deleted file mode 100644 index 94f5c82fc1..0000000000 --- a/server/src/sql-tools/contexts/reader-context.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { SchemaFromDatabaseOptions } from 'src/sql-tools/types'; - -export class ReaderContext extends BaseContext { - constructor(public options: SchemaFromDatabaseOptions) { - super(options); - } -} diff --git a/server/src/sql-tools/decorators/after-delete.decorator.ts b/server/src/sql-tools/decorators/after-delete.decorator.ts deleted file mode 100644 index 181bfab6c8..0000000000 --- a/server/src/sql-tools/decorators/after-delete.decorator.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator'; - -export const AfterDeleteTrigger = (options: Omit) => - TriggerFunction({ - timing: 'after', - actions: ['delete'], - ...options, - }); diff --git a/server/src/sql-tools/decorators/after-insert.decorator.ts b/server/src/sql-tools/decorators/after-insert.decorator.ts deleted file mode 100644 index c302a5cebe..0000000000 --- a/server/src/sql-tools/decorators/after-insert.decorator.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator'; - -export const AfterInsertTrigger = (options: Omit) => - TriggerFunction({ - timing: 'after', - actions: ['insert'], - ...options, - }); diff --git a/server/src/sql-tools/decorators/before-update.decorator.ts b/server/src/sql-tools/decorators/before-update.decorator.ts deleted file mode 100644 index 2119e29c9b..0000000000 --- a/server/src/sql-tools/decorators/before-update.decorator.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator'; - -export const BeforeUpdateTrigger = (options: Omit) => - TriggerFunction({ - timing: 'before', - actions: ['update'], - ...options, - }); diff --git a/server/src/sql-tools/decorators/check.decorator.ts b/server/src/sql-tools/decorators/check.decorator.ts deleted file mode 100644 index 56fe1ecc3f..0000000000 --- a/server/src/sql-tools/decorators/check.decorator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { register } from 'src/sql-tools/register'; - -export type CheckOptions = { - name?: string; - expression: string; - synchronize?: boolean; -}; -export const Check = (options: CheckOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'checkConstraint', item: { object, options } }); -}; diff --git a/server/src/sql-tools/decorators/column.decorator.ts b/server/src/sql-tools/decorators/column.decorator.ts deleted file mode 100644 index e5a0eb52f8..0000000000 --- a/server/src/sql-tools/decorators/column.decorator.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { asOptions } from 'src/sql-tools/helpers'; -import { register } from 'src/sql-tools/register'; -import { ColumnStorage, ColumnType, DatabaseEnum } from 'src/sql-tools/types'; - -export type ColumnValue = null | boolean | string | number | Array | object | Date | (() => string); - -export type ColumnBaseOptions = { - name?: string; - primary?: boolean; - type?: ColumnType; - nullable?: boolean; - length?: number; - default?: ColumnValue; - comment?: string; - synchronize?: boolean; - storage?: ColumnStorage; - identity?: boolean; - index?: boolean; - indexName?: string; - unique?: boolean; - uniqueConstraintName?: string; -}; - -export type ColumnOptions = ColumnBaseOptions & { - enum?: DatabaseEnum; - array?: boolean; -}; - -export const Column = (options: string | ColumnOptions = {}): PropertyDecorator => { - return (object: object, propertyName: string | symbol) => - void register({ type: 'column', item: { object, propertyName, options: asOptions(options) } }); -}; diff --git a/server/src/sql-tools/decorators/configuration-parameter.decorator.ts b/server/src/sql-tools/decorators/configuration-parameter.decorator.ts deleted file mode 100644 index 953027d25c..0000000000 --- a/server/src/sql-tools/decorators/configuration-parameter.decorator.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ColumnValue } from 'src/sql-tools/decorators/column.decorator'; -import { register } from 'src/sql-tools/register'; -import { ParameterScope } from 'src/sql-tools/types'; - -export type ConfigurationParameterOptions = { - name: string; - value: ColumnValue; - scope: ParameterScope; - synchronize?: boolean; -}; -export const ConfigurationParameter = (options: ConfigurationParameterOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'configurationParameter', item: { object, options } }); -}; diff --git a/server/src/sql-tools/decorators/create-date-column.decorator.ts b/server/src/sql-tools/decorators/create-date-column.decorator.ts deleted file mode 100644 index 1a3362a614..0000000000 --- a/server/src/sql-tools/decorators/create-date-column.decorator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator'; - -export const CreateDateColumn = (options: ColumnOptions = {}): PropertyDecorator => { - return Column({ - type: 'timestamp with time zone', - default: () => 'now()', - ...options, - }); -}; diff --git a/server/src/sql-tools/decorators/database.decorator.ts b/server/src/sql-tools/decorators/database.decorator.ts deleted file mode 100644 index 17b2460df6..0000000000 --- a/server/src/sql-tools/decorators/database.decorator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { register } from 'src/sql-tools/register'; - -export type DatabaseOptions = { - name?: string; - synchronize?: boolean; -}; -export const Database = (options: DatabaseOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'database', item: { object, options } }); -}; diff --git a/server/src/sql-tools/decorators/delete-date-column.decorator.ts b/server/src/sql-tools/decorators/delete-date-column.decorator.ts deleted file mode 100644 index ca5427c27f..0000000000 --- a/server/src/sql-tools/decorators/delete-date-column.decorator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator'; - -export const DeleteDateColumn = (options: ColumnOptions = {}): PropertyDecorator => { - return Column({ - type: 'timestamp with time zone', - nullable: true, - ...options, - }); -}; diff --git a/server/src/sql-tools/decorators/extension.decorator.ts b/server/src/sql-tools/decorators/extension.decorator.ts deleted file mode 100644 index d431cbfd02..0000000000 --- a/server/src/sql-tools/decorators/extension.decorator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { asOptions } from 'src/sql-tools/helpers'; -import { register } from 'src/sql-tools/register'; - -export type ExtensionOptions = { - name: string; - synchronize?: boolean; -}; -export const Extension = (options: string | ExtensionOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'extension', item: { object, options: asOptions(options) } }); -}; diff --git a/server/src/sql-tools/decorators/extensions.decorator.ts b/server/src/sql-tools/decorators/extensions.decorator.ts deleted file mode 100644 index 724446c5fa..0000000000 --- a/server/src/sql-tools/decorators/extensions.decorator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { asOptions } from 'src/sql-tools/helpers'; -import { register } from 'src/sql-tools/register'; - -export type ExtensionsOptions = { - name: string; - synchronize?: boolean; -}; -export const Extensions = (options: Array): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => { - for (const option of options) { - register({ type: 'extension', item: { object, options: asOptions(option) } }); - } - }; -}; diff --git a/server/src/sql-tools/decorators/foreign-key-column.decorator.ts b/server/src/sql-tools/decorators/foreign-key-column.decorator.ts deleted file mode 100644 index c9c83f010d..0000000000 --- a/server/src/sql-tools/decorators/foreign-key-column.decorator.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import { ForeignKeyAction } from 'src/sql-tools//decorators/foreign-key-constraint.decorator'; -import { ColumnBaseOptions } from 'src/sql-tools/decorators/column.decorator'; -import { register } from 'src/sql-tools/register'; - -export type ForeignKeyColumnOptions = ColumnBaseOptions & { - onUpdate?: ForeignKeyAction; - onDelete?: ForeignKeyAction; - constraintName?: string; -}; - -export const ForeignKeyColumn = (target: () => Function, options: ForeignKeyColumnOptions): PropertyDecorator => { - return (object: object, propertyName: string | symbol) => { - register({ type: 'foreignKeyColumn', item: { object, propertyName, options, target } }); - }; -}; diff --git a/server/src/sql-tools/decorators/foreign-key-constraint.decorator.ts b/server/src/sql-tools/decorators/foreign-key-constraint.decorator.ts deleted file mode 100644 index e5d2f513dc..0000000000 --- a/server/src/sql-tools/decorators/foreign-key-constraint.decorator.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { register } from 'src/sql-tools/register'; - -export type ForeignKeyAction = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION'; - -export type ForeignKeyConstraintOptions = { - name?: string; - index?: boolean; - indexName?: string; - columns: string[]; - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - referenceTable: () => Function; - referenceColumns?: string[]; - onUpdate?: ForeignKeyAction; - onDelete?: ForeignKeyAction; - synchronize?: boolean; -}; - -export const ForeignKeyConstraint = (options: ForeignKeyConstraintOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (target: Function) => { - register({ type: 'foreignKeyConstraint', item: { object: target, options } }); - }; -}; diff --git a/server/src/sql-tools/decorators/generated-column.decorator.ts b/server/src/sql-tools/decorators/generated-column.decorator.ts deleted file mode 100644 index 4338b4146c..0000000000 --- a/server/src/sql-tools/decorators/generated-column.decorator.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Column, ColumnOptions, ColumnValue } from 'src/sql-tools/decorators/column.decorator'; -import { ColumnType } from 'src/sql-tools/types'; - -export type GeneratedColumnStrategy = 'uuid' | 'identity'; - -export type GenerateColumnOptions = Omit & { - strategy?: GeneratedColumnStrategy; -}; - -export const GeneratedColumn = ({ strategy = 'uuid', ...options }: GenerateColumnOptions): PropertyDecorator => { - let columnType: ColumnType | undefined; - let columnDefault: ColumnValue | undefined; - - switch (strategy) { - case 'uuid': { - columnType = 'uuid'; - columnDefault = () => 'uuid_generate_v4()'; - break; - } - - case 'identity': { - columnType = 'integer'; - options.identity = true; - break; - } - - default: { - throw new Error(`Unsupported strategy for @GeneratedColumn ${strategy}`); - } - } - - return Column({ - type: columnType, - default: columnDefault, - ...options, - }); -}; diff --git a/server/src/sql-tools/decorators/index.decorator.ts b/server/src/sql-tools/decorators/index.decorator.ts deleted file mode 100644 index 1b6d38e390..0000000000 --- a/server/src/sql-tools/decorators/index.decorator.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { asOptions } from 'src/sql-tools/helpers'; -import { register } from 'src/sql-tools/register'; - -export type IndexOptions = { - name?: string; - unique?: boolean; - expression?: string; - using?: string; - with?: string; - where?: string; - columns?: string[]; - synchronize?: boolean; -}; -export const Index = (options: string | IndexOptions = {}): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'index', item: { object, options: asOptions(options) } }); -}; diff --git a/server/src/sql-tools/decorators/primary-column.decorator.ts b/server/src/sql-tools/decorators/primary-column.decorator.ts deleted file mode 100644 index e605b4be5d..0000000000 --- a/server/src/sql-tools/decorators/primary-column.decorator.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator'; - -export const PrimaryColumn = (options: Omit = {}) => Column({ ...options, primary: true }); diff --git a/server/src/sql-tools/decorators/primary-generated-column.decorator.ts b/server/src/sql-tools/decorators/primary-generated-column.decorator.ts deleted file mode 100644 index 25e125ebf6..0000000000 --- a/server/src/sql-tools/decorators/primary-generated-column.decorator.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { GenerateColumnOptions, GeneratedColumn } from 'src/sql-tools/decorators/generated-column.decorator'; - -export const PrimaryGeneratedColumn = (options: Omit = {}) => - GeneratedColumn({ ...options, primary: true }); diff --git a/server/src/sql-tools/decorators/table.decorator.ts b/server/src/sql-tools/decorators/table.decorator.ts deleted file mode 100644 index 7ea5882147..0000000000 --- a/server/src/sql-tools/decorators/table.decorator.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { asOptions } from 'src/sql-tools/helpers'; -import { register } from 'src/sql-tools/register'; - -export type TableOptions = { - name?: string; - primaryConstraintName?: string; - synchronize?: boolean; -}; - -/** Table comments here */ -export const Table = (options: string | TableOptions = {}): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'table', item: { object, options: asOptions(options) } }); -}; diff --git a/server/src/sql-tools/decorators/trigger-function.decorator.ts b/server/src/sql-tools/decorators/trigger-function.decorator.ts deleted file mode 100644 index 17016f7946..0000000000 --- a/server/src/sql-tools/decorators/trigger-function.decorator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Trigger, TriggerOptions } from 'src/sql-tools/decorators/trigger.decorator'; -import { DatabaseFunction } from 'src/sql-tools/types'; - -export type TriggerFunctionOptions = Omit & { function: DatabaseFunction }; -export const TriggerFunction = (options: TriggerFunctionOptions) => - Trigger({ - name: options.function.name, - ...options, - functionName: options.function.name, - }); diff --git a/server/src/sql-tools/decorators/trigger.decorator.ts b/server/src/sql-tools/decorators/trigger.decorator.ts deleted file mode 100644 index ce9a5c17f7..0000000000 --- a/server/src/sql-tools/decorators/trigger.decorator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { register } from 'src/sql-tools/register'; -import { TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types'; - -export type TriggerOptions = { - name?: string; - timing: TriggerTiming; - actions: TriggerAction[]; - scope: TriggerScope; - functionName: string; - referencingNewTableAs?: string; - referencingOldTableAs?: string; - when?: string; - synchronize?: boolean; -}; - -export const Trigger = (options: TriggerOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'trigger', item: { object, options } }); -}; diff --git a/server/src/sql-tools/decorators/unique.decorator.ts b/server/src/sql-tools/decorators/unique.decorator.ts deleted file mode 100644 index 1f61fccb6f..0000000000 --- a/server/src/sql-tools/decorators/unique.decorator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { register } from 'src/sql-tools/register'; - -export type UniqueOptions = { - name?: string; - columns: string[]; - synchronize?: boolean; -}; -export const Unique = (options: UniqueOptions): ClassDecorator => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - return (object: Function) => void register({ type: 'uniqueConstraint', item: { object, options } }); -}; diff --git a/server/src/sql-tools/decorators/update-date-column.decorator.ts b/server/src/sql-tools/decorators/update-date-column.decorator.ts deleted file mode 100644 index 68dd50c617..0000000000 --- a/server/src/sql-tools/decorators/update-date-column.decorator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Column, ColumnOptions } from 'src/sql-tools/decorators/column.decorator'; - -export const UpdateDateColumn = (options: ColumnOptions = {}): PropertyDecorator => { - return Column({ - type: 'timestamp with time zone', - default: () => 'now()', - ...options, - }); -}; diff --git a/server/src/sql-tools/helpers.ts b/server/src/sql-tools/helpers.ts deleted file mode 100644 index e0daf8262f..0000000000 --- a/server/src/sql-tools/helpers.ts +++ /dev/null @@ -1,247 +0,0 @@ -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 asOptions = (options: string | T): T => { - if (typeof options === 'string') { - return { name: options } as T; - } - - return options; -}; - -export const sha1 = (value: string) => createHash('sha1').update(value).digest('hex'); - -export const fromColumnValue = (columnValue?: ColumnValue) => { - if (columnValue === undefined) { - return; - } - - if (typeof columnValue === 'function') { - return columnValue() as string; - } - - const value = columnValue; - - if (value === null) { - return value; - } - - if (typeof value === 'number') { - return String(value); - } - - if (typeof value === 'boolean') { - return value ? 'true' : 'false'; - } - - if (value instanceof Date) { - return `'${value.toISOString()}'`; - } - - if (Array.isArray(value)) { - return "'{}'"; - } - - return `'${String(value)}'`; -}; - -export const setIsEqual = (source: Set, target: Set) => - source.size === target.size && [...source].every((x) => target.has(x)); - -export const haveEqualColumns = (sourceColumns?: string[], targetColumns?: string[]) => { - return setIsEqual(new Set(sourceColumns), new Set(targetColumns)); -}; - -export const haveEqualOverrides = (source: T, target: T) => { - if (!source.override || !target.override) { - return false; - } - - const sourceValue = source.override.value; - const targetValue = target.override.value; - - return sourceValue.name === targetValue.name && sourceValue.sql === targetValue.sql; -}; - -export const compare = ( - sources: T[], - targets: T[], - options: IgnoreOptions | undefined, - comparer: Comparer, -) => { - options = options || {}; - const sourceMap = Object.fromEntries(sources.map((table) => [table.name, table])); - const targetMap = Object.fromEntries(targets.map((table) => [table.name, table])); - const items: SchemaDiff[] = []; - - const keys = new Set([...Object.keys(sourceMap), ...Object.keys(targetMap)]); - const missingKeys = new Set(); - const extraKeys = new Set(); - - // common keys - for (const key of keys) { - const source = sourceMap[key]; - const target = targetMap[key]; - - if (isIgnored(source, target, options ?? true)) { - continue; - } - - if (isSynchronizeDisabled(source, target)) { - continue; - } - - if (source && !target) { - 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 = {}; - 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; - } - - 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; -}; - -const isIgnored = ( - source: { synchronize?: boolean } | undefined, - target: { synchronize?: boolean } | undefined, - options: IgnoreOptions, -) => { - if (typeof options === 'boolean') { - return !options; - } - return (options.ignoreExtra && !source) || (options.ignoreMissing && !target); -}; - -const isSynchronizeDisabled = (source?: { synchronize?: boolean }, target?: { synchronize?: boolean }) => { - return source?.synchronize === false || target?.synchronize === false; -}; - -export const isDefaultEqual = (source: DatabaseColumn, target: DatabaseColumn) => { - if (source.default === target.default) { - return true; - } - - if (source.default === undefined || target.default === undefined) { - return false; - } - - if ( - withTypeCast(source.default, getColumnType(source)) === target.default || - withTypeCast(target.default, getColumnType(target)) === source.default - ) { - return true; - } - - return false; -}; - -export const getColumnType = (column: DatabaseColumn) => { - let type = column.enumName || column.type; - if (column.isArray) { - type += `[${column.length ?? ''}]`; - } else if (column.length !== undefined) { - type += `(${column.length})`; - } - - return type; -}; - -const withTypeCast = (value: string, type: string) => { - if (!value.startsWith(`'`)) { - value = `'${value}'`; - } - return `${value}::${type}`; -}; - -export const getColumnModifiers = (column: DatabaseColumn) => { - const modifiers: string[] = []; - - if (!column.nullable) { - modifiers.push('NOT NULL'); - } - - if (column.default) { - modifiers.push(`DEFAULT ${column.default}`); - } - if (column.identity) { - modifiers.push(`GENERATED ALWAYS AS IDENTITY`); - } - - return modifiers.length === 0 ? '' : ' ' + modifiers.join(' '); -}; - -export const asColumnComment = (tableName: string, columnName: string, comment: string): string => { - return `COMMENT ON COLUMN "${tableName}"."${columnName}" IS '${comment}';`; -}; - -export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', '); - -export const asJsonString = (value: unknown): string => { - return `'${escape(JSON.stringify(value))}'::jsonb`; -}; - -const escape = (value: string) => { - return value - .replaceAll("'", "''") - .replaceAll(/[\\]/g, '\\\\') - .replaceAll(/[\b]/g, String.raw`\b`) - .replaceAll(/[\f]/g, String.raw`\f`) - .replaceAll(/[\n]/g, String.raw`\n`) - .replaceAll(/[\r]/g, String.raw`\r`) - .replaceAll(/[\t]/g, String.raw`\t`); -}; - -export const asRenameKey = (values: Array) => - values.map((value) => value ?? '').join('|'); diff --git a/server/src/sql-tools/index.ts b/server/src/sql-tools/index.ts deleted file mode 100644 index 0d3e53df51..0000000000 --- a/server/src/sql-tools/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from 'src/sql-tools/public_api'; diff --git a/server/src/sql-tools/naming/default.naming.ts b/server/src/sql-tools/naming/default.naming.ts deleted file mode 100644 index 807580169d..0000000000 --- a/server/src/sql-tools/naming/default.naming.ts +++ /dev/null @@ -1,50 +0,0 @@ -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}`; - } - } - } -} diff --git a/server/src/sql-tools/naming/hash.naming.ts b/server/src/sql-tools/naming/hash.naming.ts deleted file mode 100644 index 575d0f1239..0000000000 --- a/server/src/sql-tools/naming/hash.naming.ts +++ /dev/null @@ -1,51 +0,0 @@ -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); - } - } - } -} diff --git a/server/src/sql-tools/naming/naming.interface.ts b/server/src/sql-tools/naming/naming.interface.ts deleted file mode 100644 index f331a22c46..0000000000 --- a/server/src/sql-tools/naming/naming.interface.ts +++ /dev/null @@ -1,59 +0,0 @@ -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; -} diff --git a/server/src/sql-tools/processors/check-constraint.processor.ts b/server/src/sql-tools/processors/check-constraint.processor.ts deleted file mode 100644 index 5eba1015bf..0000000000 --- a/server/src/sql-tools/processors/check-constraint.processor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ConstraintType, Processor } from 'src/sql-tools/types'; - -export const processCheckConstraints: Processor = (ctx, items) => { - for (const { - item: { object, options }, - } of items.filter((item) => item.type === 'checkConstraint')) { - const table = ctx.getTableByObject(object); - if (!table) { - ctx.warnMissingTable('@Check', object); - continue; - } - - const tableName = table.name; - - table.constraints.push({ - type: ConstraintType.CHECK, - name: options.name || ctx.getNameFor({ type: 'check', tableName, expression: options.expression }), - tableName, - expression: options.expression, - synchronize: options.synchronize ?? true, - }); - } -}; diff --git a/server/src/sql-tools/processors/column.processor.ts b/server/src/sql-tools/processors/column.processor.ts deleted file mode 100644 index 9b499b380b..0000000000 --- a/server/src/sql-tools/processors/column.processor.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ColumnOptions } from 'src/sql-tools/decorators/column.decorator'; -import { fromColumnValue } from 'src/sql-tools/helpers'; -import { Processor } from 'src/sql-tools/types'; - -export const processColumns: Processor = (ctx, items) => { - for (const { - type, - item: { object, propertyName, options }, - } of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) { - const table = ctx.getTableByObject(object.constructor); - if (!table) { - ctx.warnMissingTable(type === 'column' ? '@Column' : '@ForeignKeyColumn', object, propertyName); - continue; - } - - 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 - continue; - } - - let defaultValue = fromColumnValue(options.default); - let nullable = options.nullable ?? false; - - // map `{ default: null }` to `{ nullable: true }` - if (defaultValue === null) { - nullable = true; - defaultValue = undefined; - } - - const isEnum = !!(options as ColumnOptions).enum; - - ctx.addColumn( - table, - { - name: columnName, - tableName: table.name, - primary: options.primary ?? false, - default: defaultValue, - nullable, - isArray: (options as ColumnOptions).array ?? false, - length: options.length, - type: isEnum ? 'enum' : options.type || 'character varying', - enumName: isEnum ? (options as ColumnOptions).enum!.name : undefined, - comment: options.comment, - storage: options.storage, - identity: options.identity, - synchronize: options.synchronize ?? true, - }, - options, - propertyName, - ); - } -}; diff --git a/server/src/sql-tools/processors/configuration-parameter.processor.ts b/server/src/sql-tools/processors/configuration-parameter.processor.ts deleted file mode 100644 index dbb5cd4636..0000000000 --- a/server/src/sql-tools/processors/configuration-parameter.processor.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { fromColumnValue } from 'src/sql-tools/helpers'; -import { Processor } from 'src/sql-tools/types'; - -export const processConfigurationParameters: Processor = (ctx, items) => { - for (const { - item: { options }, - } of items.filter((item) => item.type === 'configurationParameter')) { - ctx.parameters.push({ - databaseName: ctx.databaseName, - name: options.name, - value: fromColumnValue(options.value), - scope: options.scope, - synchronize: options.synchronize ?? true, - }); - } -}; diff --git a/server/src/sql-tools/processors/database.processor.ts b/server/src/sql-tools/processors/database.processor.ts deleted file mode 100644 index 9f2e847fd6..0000000000 --- a/server/src/sql-tools/processors/database.processor.ts +++ /dev/null @@ -1,9 +0,0 @@ -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 || ctx.getNameFor({ type: 'database', name: object.name }); - } -}; diff --git a/server/src/sql-tools/processors/enum.processor.ts b/server/src/sql-tools/processors/enum.processor.ts deleted file mode 100644 index 1ef65231c9..0000000000 --- a/server/src/sql-tools/processors/enum.processor.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Processor } from 'src/sql-tools/types'; - -export const processEnums: Processor = (ctx, items) => { - for (const { item } of items.filter((item) => item.type === 'enum')) { - // TODO log warnings if enum name is not unique - ctx.enums.push(item); - } -}; diff --git a/server/src/sql-tools/processors/extension.processor.ts b/server/src/sql-tools/processors/extension.processor.ts deleted file mode 100644 index 068c66883c..0000000000 --- a/server/src/sql-tools/processors/extension.processor.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Processor } from 'src/sql-tools/types'; - -export const processExtensions: Processor = (ctx, items) => { - if (ctx.options.extensions === false) { - return; - } - - for (const { - item: { options }, - } of items.filter((item) => item.type === 'extension')) { - ctx.extensions.push({ - name: options.name, - synchronize: options.synchronize ?? true, - }); - } -}; diff --git a/server/src/sql-tools/processors/foreign-key-column.processor.ts b/server/src/sql-tools/processors/foreign-key-column.processor.ts deleted file mode 100644 index 6d147a78eb..0000000000 --- a/server/src/sql-tools/processors/foreign-key-column.processor.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ActionType, ConstraintType, Processor } from 'src/sql-tools/types'; - -export const processForeignKeyColumns: Processor = (ctx, items) => { - for (const { - item: { object, propertyName, options, target }, - } of items.filter((item) => item.type === 'foreignKeyColumn')) { - const { table, column } = ctx.getColumnByObjectAndPropertyName(object, propertyName); - if (!table) { - ctx.warnMissingTable('@ForeignKeyColumn', object); - continue; - } - - if (!column) { - // should be impossible since they are pre-created in `column.processor.ts` - ctx.warnMissingColumn('@ForeignKeyColumn', object, propertyName); - continue; - } - - const referenceTable = ctx.getTableByObject(target()); - if (!referenceTable) { - ctx.warnMissingTable('@ForeignKeyColumn', object, propertyName); - continue; - } - - const columnNames = [column.name]; - const referenceColumns = referenceTable.columns.filter((column) => column.primary); - - // infer FK column type from reference table - if (referenceColumns.length === 1) { - column.type = referenceColumns[0].type; - } - - const referenceTableName = referenceTable.name; - const referenceColumnNames = referenceColumns.map((column) => column.name); - 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, - referenceColumnNames, - onUpdate: options.onUpdate as ActionType, - onDelete: options.onDelete as ActionType, - synchronize: options.synchronize ?? true, - }); - - if (options.unique || options.uniqueConstraintName) { - table.constraints.push({ - name: options.uniqueConstraintName || ctx.getNameFor({ type: 'unique', tableName: table.name, columnNames }), - tableName: table.name, - columnNames, - type: ConstraintType.UNIQUE, - synchronize: options.synchronize ?? true, - }); - } - } -}; diff --git a/server/src/sql-tools/processors/foreign-key-constraint.processor.ts b/server/src/sql-tools/processors/foreign-key-constraint.processor.ts deleted file mode 100644 index 39d7508d11..0000000000 --- a/server/src/sql-tools/processors/foreign-key-constraint.processor.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { ActionType, ConstraintType, Processor } from 'src/sql-tools/types'; - -export const processForeignKeyConstraints: Processor = (ctx, items) => { - for (const { - item: { object, options }, - } of items.filter((item) => item.type === 'foreignKeyConstraint')) { - const table = ctx.getTableByObject(object); - if (!table) { - ctx.warnMissingTable('@ForeignKeyConstraint', { name: 'referenceTable' }); - continue; - } - - const referenceTable = ctx.getTableByObject(options.referenceTable()); - if (!referenceTable) { - const referenceTableName = options.referenceTable()?.name; - ctx.warn( - '@ForeignKeyConstraint.referenceTable', - `Unable to find table` + (referenceTableName ? ` (${referenceTableName})` : ''), - ); - continue; - } - - let missingColumn = false; - - for (const columnName of options.columns) { - if (!table.columns.some(({ name }) => name === columnName)) { - const metadata = ctx.getTableMetadata(table); - ctx.warn('@ForeignKeyConstraint.columns', `Unable to find column (${metadata.object.name}.${columnName})`); - missingColumn = true; - } - } - - for (const columnName of options.referenceColumns || []) { - if (!referenceTable.columns.some(({ name }) => name === columnName)) { - const metadata = ctx.getTableMetadata(referenceTable); - ctx.warn( - '@ForeignKeyConstraint.referenceColumns', - `Unable to find column (${metadata.object.name}.${columnName})`, - ); - missingColumn = true; - } - } - - if (missingColumn) { - continue; - } - - const referenceTableName = referenceTable.name; - const referenceColumnNames = - options.referenceColumns || referenceTable.columns.filter(({ primary }) => primary).map(({ name }) => name); - - 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, - referenceColumnNames, - onUpdate: options.onUpdate as ActionType, - onDelete: options.onDelete as ActionType, - synchronize: options.synchronize ?? true, - }); - - if (options.index === false) { - continue; - } - - 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: indexName, - tableName: table.name, - columnNames: options.columns, - unique: false, - synchronize: options.synchronize ?? true, - }); - } - } -}; diff --git a/server/src/sql-tools/processors/function.processor.ts b/server/src/sql-tools/processors/function.processor.ts deleted file mode 100644 index 9b351b77f7..0000000000 --- a/server/src/sql-tools/processors/function.processor.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Processor } from 'src/sql-tools/types'; - -export const processFunctions: Processor = (ctx, items) => { - if (ctx.options.functions === false) { - return; - } - - for (const { item } of items.filter((item) => item.type === 'function')) { - // TODO log warnings if function name is not unique - ctx.functions.push(item); - } -}; diff --git a/server/src/sql-tools/processors/index.processor.ts b/server/src/sql-tools/processors/index.processor.ts deleted file mode 100644 index 766e83fe8b..0000000000 --- a/server/src/sql-tools/processors/index.processor.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Processor } from 'src/sql-tools/types'; - -export const processIndexes: Processor = (ctx, items) => { - for (const { - item: { object, options }, - } of items.filter((item) => item.type === 'index')) { - const table = ctx.getTableByObject(object); - if (!table) { - ctx.warnMissingTable('@Check', object); - continue; - } - - const indexName = - options.name || - ctx.getNameFor({ - type: 'index', - tableName: table.name, - columnNames: options.columns, - where: options.where, - }); - - table.indexes.push({ - name: indexName, - tableName: table.name, - unique: options.unique ?? false, - expression: options.expression, - using: options.using, - with: options.with, - where: options.where, - columnNames: options.columns, - synchronize: options.synchronize ?? true, - }); - } - - // column indexes - for (const { - type, - item: { object, propertyName, options }, - } of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) { - const { table, column } = ctx.getColumnByObjectAndPropertyName(object, propertyName); - if (!table) { - ctx.warnMissingTable('@Column', object); - continue; - } - - if (!column) { - // should be impossible since they are created in `column.processor.ts` - ctx.warnMissingColumn('@Column', object, propertyName); - continue; - } - - if (options.index === false) { - continue; - } - - const isIndexRequested = - options.indexName || options.index || (type === 'foreignKeyColumn' && ctx.options.createForeignKeyIndexes); - if (!isIndexRequested) { - continue; - } - - 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) { - continue; - } - - const isOnlyPrimaryColumn = options.primary && table.columns.filter(({ primary }) => primary === true).length === 1; - if (isOnlyPrimaryColumn) { - // will have an index created by the primary key constraint - continue; - } - - table.indexes.push({ - name: indexName, - tableName: table.name, - unique: false, - columnNames: [column.name], - synchronize: options.synchronize ?? true, - }); - } -}; diff --git a/server/src/sql-tools/processors/index.ts b/server/src/sql-tools/processors/index.ts deleted file mode 100644 index feb0a82f05..0000000000 --- a/server/src/sql-tools/processors/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { processCheckConstraints } from 'src/sql-tools/processors/check-constraint.processor'; -import { processColumns } from 'src/sql-tools/processors/column.processor'; -import { processConfigurationParameters } from 'src/sql-tools/processors/configuration-parameter.processor'; -import { processDatabases } from 'src/sql-tools/processors/database.processor'; -import { processEnums } from 'src/sql-tools/processors/enum.processor'; -import { processExtensions } from 'src/sql-tools/processors/extension.processor'; -import { processForeignKeyColumns } from 'src/sql-tools/processors/foreign-key-column.processor'; -import { processForeignKeyConstraints } from 'src/sql-tools/processors/foreign-key-constraint.processor'; -import { processFunctions } from 'src/sql-tools/processors/function.processor'; -import { processIndexes } from 'src/sql-tools/processors/index.processor'; -import { processOverrides } from 'src/sql-tools/processors/override.processor'; -import { processPrimaryKeyConstraints } from 'src/sql-tools/processors/primary-key-contraint.processor'; -import { processTables } from 'src/sql-tools/processors/table.processor'; -import { processTriggers } from 'src/sql-tools/processors/trigger.processor'; -import { processUniqueConstraints } from 'src/sql-tools/processors/unique-constraint.processor'; -import { Processor } from 'src/sql-tools/types'; - -export const processors: Processor[] = [ - processDatabases, - processConfigurationParameters, - processEnums, - processExtensions, - processFunctions, - processTables, - processColumns, - processForeignKeyColumns, - processForeignKeyConstraints, - processUniqueConstraints, - processCheckConstraints, - processPrimaryKeyConstraints, - processIndexes, - processTriggers, - processOverrides, -]; diff --git a/server/src/sql-tools/processors/override.processor.ts b/server/src/sql-tools/processors/override.processor.ts deleted file mode 100644 index 67b92fbd40..0000000000 --- a/server/src/sql-tools/processors/override.processor.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { asFunctionCreate } from 'src/sql-tools/transformers/function.transformer'; -import { asIndexCreate } from 'src/sql-tools/transformers/index.transformer'; -import { asTriggerCreate } from 'src/sql-tools/transformers/trigger.transformer'; -import { Processor } from 'src/sql-tools/types'; - -export const processOverrides: Processor = (ctx) => { - if (ctx.options.overrides === false) { - return; - } - - for (const func of ctx.functions) { - if (!func.synchronize) { - continue; - } - - ctx.overrides.push({ - name: `function_${func.name}`, - value: { type: 'function', name: func.name, sql: asFunctionCreate(func) }, - synchronize: true, - }); - } - - for (const { triggers, indexes } of ctx.tables) { - for (const trigger of triggers) { - if (!trigger.synchronize) { - continue; - } - - ctx.overrides.push({ - name: `trigger_${trigger.name}`, - value: { type: 'trigger', name: trigger.name, sql: asTriggerCreate(trigger) }, - synchronize: true, - }); - } - - for (const index of indexes) { - if (!index.synchronize) { - continue; - } - - if (index.expression || index.using || index.with || index.where) { - ctx.overrides.push({ - name: `index_${index.name}`, - value: { type: 'index', name: index.name, sql: asIndexCreate(index) }, - synchronize: true, - }); - } - } - } -}; diff --git a/server/src/sql-tools/processors/primary-key-contraint.processor.ts b/server/src/sql-tools/processors/primary-key-contraint.processor.ts deleted file mode 100644 index 0971bfc337..0000000000 --- a/server/src/sql-tools/processors/primary-key-contraint.processor.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ConstraintType, Processor } from 'src/sql-tools/types'; - -export const processPrimaryKeyConstraints: Processor = (ctx) => { - for (const table of ctx.tables) { - const columnNames: string[] = []; - - for (const column of table.columns) { - if (column.primary) { - columnNames.push(column.name); - } - } - - if (columnNames.length > 0) { - const tableMetadata = ctx.getTableMetadata(table); - table.constraints.push({ - type: ConstraintType.PRIMARY_KEY, - name: - tableMetadata.options.primaryConstraintName || - ctx.getNameFor({ - type: 'primaryKey', - tableName: table.name, - columnNames, - }), - tableName: table.name, - columnNames, - synchronize: tableMetadata.options.synchronize ?? true, - }); - } - } -}; diff --git a/server/src/sql-tools/processors/table.processor.ts b/server/src/sql-tools/processors/table.processor.ts deleted file mode 100644 index 993c9ec45d..0000000000 --- a/server/src/sql-tools/processors/table.processor.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Processor } from 'src/sql-tools/types'; - -export const processTables: Processor = (ctx, items) => { - for (const { - item: { options, object }, - } of items.filter((item) => item.type === 'table')) { - const test = ctx.getTableByObject(object); - if (test) { - throw new Error( - `Table ${test.name} has already been registered. Does ${object.name} have two @Table() decorators?`, - ); - } - - ctx.addTable( - { - name: options.name || ctx.getNameFor({ type: 'table', name: object.name }), - columns: [], - constraints: [], - indexes: [], - triggers: [], - synchronize: options.synchronize ?? true, - }, - options, - object, - ); - } -}; diff --git a/server/src/sql-tools/processors/trigger.processor.ts b/server/src/sql-tools/processors/trigger.processor.ts deleted file mode 100644 index b50b42cc49..0000000000 --- a/server/src/sql-tools/processors/trigger.processor.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Processor } from 'src/sql-tools/types'; - -export const processTriggers: Processor = (ctx, items) => { - for (const { - item: { object, options }, - } of items.filter((item) => item.type === 'trigger')) { - const table = ctx.getTableByObject(object); - if (!table) { - ctx.warnMissingTable('@Trigger', object); - 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: triggerName, - tableName: table.name, - timing: options.timing, - actions: options.actions, - when: options.when, - scope: options.scope, - referencingNewTableAs: options.referencingNewTableAs, - referencingOldTableAs: options.referencingOldTableAs, - functionName: options.functionName, - synchronize: options.synchronize ?? true, - }); - } -}; diff --git a/server/src/sql-tools/processors/unique-constraint.processor.ts b/server/src/sql-tools/processors/unique-constraint.processor.ts deleted file mode 100644 index 0cbfc26a70..0000000000 --- a/server/src/sql-tools/processors/unique-constraint.processor.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ConstraintType, Processor } from 'src/sql-tools/types'; - -export const processUniqueConstraints: Processor = (ctx, items) => { - for (const { - item: { object, options }, - } of items.filter((item) => item.type === 'uniqueConstraint')) { - const table = ctx.getTableByObject(object); - if (!table) { - ctx.warnMissingTable('@Unique', object); - continue; - } - - const tableName = table.name; - const columnNames = options.columns; - - table.constraints.push({ - type: ConstraintType.UNIQUE, - name: options.name || ctx.getNameFor({ type: 'unique', tableName, columnNames }), - tableName, - columnNames, - synchronize: options.synchronize ?? true, - }); - } - - // column level constraints - for (const { - type, - item: { object, propertyName, options }, - } of items.filter((item) => item.type === 'column' || item.type === 'foreignKeyColumn')) { - const { table, column } = ctx.getColumnByObjectAndPropertyName(object, propertyName); - if (!table) { - ctx.warnMissingTable('@Column', object); - continue; - } - - if (!column) { - // should be impossible since they are created in `column.processor.ts` - ctx.warnMissingColumn('@Column', object, propertyName); - continue; - } - - 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: uniqueConstraintName, - tableName: table.name, - columnNames: [column.name], - synchronize: options.synchronize ?? true, - }); - } - } -}; diff --git a/server/src/sql-tools/public_api.ts b/server/src/sql-tools/public_api.ts deleted file mode 100644 index 9e7983383e..0000000000 --- a/server/src/sql-tools/public_api.ts +++ /dev/null @@ -1,31 +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'; -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'; -export { schemaFromCode } from 'src/sql-tools/schema-from-code'; -export { schemaFromDatabase } from 'src/sql-tools/schema-from-database'; -export * from 'src/sql-tools/types'; diff --git a/server/src/sql-tools/readers/column.reader.ts b/server/src/sql-tools/readers/column.reader.ts deleted file mode 100644 index 249bd77f2c..0000000000 --- a/server/src/sql-tools/readers/column.reader.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { sql } from 'kysely'; -import { jsonArrayFrom } from 'kysely/helpers/postgres'; -import { ColumnType, DatabaseColumn, Reader } from 'src/sql-tools/types'; - -export const readColumns: Reader = async (ctx, db) => { - const columns = await db - .selectFrom('information_schema.columns as c') - .leftJoin('information_schema.element_types as o', (join) => - join - .onRef('c.table_catalog', '=', 'o.object_catalog') - .onRef('c.table_schema', '=', 'o.object_schema') - .onRef('c.table_name', '=', 'o.object_name') - .on('o.object_type', '=', sql.lit('TABLE')) - .onRef('c.dtd_identifier', '=', 'o.collection_type_identifier'), - ) - .leftJoin('pg_type as t', (join) => - join.onRef('t.typname', '=', 'c.udt_name').on('c.data_type', '=', sql.lit('USER-DEFINED')), - ) - .leftJoin('pg_enum as e', (join) => join.onRef('e.enumtypid', '=', 't.oid')) - .select([ - 'c.table_name', - 'c.column_name', - - // is ARRAY, USER-DEFINED, or data type - 'c.data_type', - 'c.column_default', - 'c.is_nullable', - 'c.character_maximum_length', - - // number types - 'c.numeric_precision', - 'c.numeric_scale', - - // date types - 'c.datetime_precision', - - // user defined type - 'c.udt_catalog', - 'c.udt_schema', - 'c.udt_name', - - // data type for ARRAYs - 'o.data_type as array_type', - ]) - .where('table_schema', '=', ctx.schemaName) - .execute(); - - const enumRaw = await db - .selectFrom('pg_type') - .innerJoin('pg_namespace', (join) => - join.onRef('pg_namespace.oid', '=', 'pg_type.typnamespace').on('pg_namespace.nspname', '=', ctx.schemaName), - ) - .where('typtype', '=', sql.lit('e')) - .select((eb) => [ - 'pg_type.typname as name', - jsonArrayFrom( - eb.selectFrom('pg_enum as e').select(['e.enumlabel as value']).whereRef('e.enumtypid', '=', 'pg_type.oid'), - ).as('values'), - ]) - .execute(); - - const enums = enumRaw.map((item) => ({ name: item.name, values: item.values.map(({ value }) => value) })); - for (const { name, values } of enums) { - ctx.enums.push({ name, values, synchronize: true }); - } - - const enumMap = Object.fromEntries(enums.map((e) => [e.name, e.values])); - // add columns to tables - for (const column of columns) { - const table = ctx.getTableByName(column.table_name); - if (!table) { - continue; - } - - const columnName = column.column_name; - - 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', - isArray: column.array_type !== null, - numericPrecision: column.numeric_precision ?? undefined, - numericScale: column.numeric_scale ?? undefined, - length: column.character_maximum_length ?? undefined, - default: column.column_default ?? undefined, - synchronize: true, - }; - - const columnLabel = `${table.name}.${columnName}`; - - switch (column.data_type) { - // array types - case 'ARRAY': { - if (!column.array_type) { - ctx.warnings.push(`Unable to find type for ${columnLabel} (ARRAY)`); - continue; - } - item.type = column.array_type as ColumnType; - break; - } - - // enum types - case 'USER-DEFINED': { - if (!enumMap[column.udt_name]) { - ctx.warnings.push(`Unable to find type for ${columnLabel} (ENUM)`); - continue; - } - - item.type = 'enum'; - item.enumName = column.udt_name; - break; - } - } - - table.columns.push(item); - } -}; diff --git a/server/src/sql-tools/readers/comment.reader.ts b/server/src/sql-tools/readers/comment.reader.ts deleted file mode 100644 index 05cc91e7a9..0000000000 --- a/server/src/sql-tools/readers/comment.reader.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Reader } from 'src/sql-tools/types'; - -export const readComments: Reader = async (ctx, db) => { - const comments = await db - .selectFrom('pg_description as d') - .innerJoin('pg_class as c', 'd.objoid', 'c.oid') - .leftJoin('pg_attribute as a', (join) => - join.onRef('a.attrelid', '=', 'c.oid').onRef('a.attnum', '=', 'd.objsubid'), - ) - .select([ - 'c.relname as object_name', - 'c.relkind as object_type', - 'd.description as value', - 'a.attname as column_name', - ]) - .where('d.description', 'is not', null) - .orderBy('object_type') - .orderBy('object_name') - .execute(); - - for (const comment of comments) { - if (comment.object_type === 'r') { - const table = ctx.getTableByName(comment.object_name); - if (!table) { - continue; - } - - if (comment.column_name) { - const column = table.columns.find(({ name }) => name === comment.column_name); - if (column) { - column.comment = comment.value; - } - } - } - } -}; diff --git a/server/src/sql-tools/readers/constraint.reader.ts b/server/src/sql-tools/readers/constraint.reader.ts deleted file mode 100644 index 662c6f414a..0000000000 --- a/server/src/sql-tools/readers/constraint.reader.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { sql } from 'kysely'; -import { ActionType, ConstraintType, Reader } from 'src/sql-tools/types'; - -export const readConstraints: Reader = async (ctx, db) => { - const constraints = await db - .selectFrom('pg_constraint') - .innerJoin('pg_namespace', 'pg_namespace.oid', 'pg_constraint.connamespace') // namespace - .innerJoin('pg_class as source_table', (join) => - join.onRef('source_table.oid', '=', 'pg_constraint.conrelid').on('source_table.relkind', 'in', [ - // ordinary table - sql.lit('r'), - // partitioned table - sql.lit('p'), - // foreign table - sql.lit('f'), - ]), - ) // table - .leftJoin('pg_class as reference_table', 'reference_table.oid', 'pg_constraint.confrelid') // reference table - .select((eb) => [ - 'pg_constraint.contype as constraint_type', - 'pg_constraint.conname as constraint_name', - 'source_table.relname as table_name', - 'reference_table.relname as reference_table_name', - 'pg_constraint.confupdtype as update_action', - 'pg_constraint.confdeltype as delete_action', - // 'pg_constraint.oid as constraint_id', - eb - .selectFrom('pg_attribute') - // matching table for PK, FK, and UQ - .whereRef('pg_attribute.attrelid', '=', 'pg_constraint.conrelid') - .whereRef('pg_attribute.attnum', '=', sql`any("pg_constraint"."conkey")`) - .select((eb) => eb.fn('json_agg', ['pg_attribute.attname']).as('column_name')) - .as('column_names'), - eb - .selectFrom('pg_attribute') - // matching foreign table for FK - .whereRef('pg_attribute.attrelid', '=', 'pg_constraint.confrelid') - .whereRef('pg_attribute.attnum', '=', sql`any("pg_constraint"."confkey")`) - .select((eb) => eb.fn('json_agg', ['pg_attribute.attname']).as('column_name')) - .as('reference_column_names'), - eb.fn('pg_get_constraintdef', ['pg_constraint.oid']).as('expression'), - ]) - .where('pg_namespace.nspname', '=', ctx.schemaName) - .execute(); - - for (const constraint of constraints) { - const table = ctx.getTableByName(constraint.table_name); - if (!table) { - continue; - } - - const constraintName = constraint.constraint_name; - - switch (constraint.constraint_type) { - // primary key constraint - case 'p': { - if (!constraint.column_names) { - ctx.warnings.push(`Skipping CONSTRAINT "${constraintName}", no columns found`); - continue; - } - table.constraints.push({ - type: ConstraintType.PRIMARY_KEY, - name: constraintName, - tableName: constraint.table_name, - columnNames: constraint.column_names, - synchronize: true, - }); - break; - } - - // foreign key constraint - case 'f': { - if (!constraint.column_names || !constraint.reference_table_name || !constraint.reference_column_names) { - ctx.warnings.push( - `Skipping CONSTRAINT "${constraintName}", missing either columns, referenced table, or referenced columns,`, - ); - continue; - } - - table.constraints.push({ - type: ConstraintType.FOREIGN_KEY, - name: constraintName, - tableName: constraint.table_name, - columnNames: constraint.column_names, - referenceTableName: constraint.reference_table_name, - referenceColumnNames: constraint.reference_column_names, - onUpdate: asDatabaseAction(constraint.update_action), - onDelete: asDatabaseAction(constraint.delete_action), - synchronize: true, - }); - break; - } - - // unique constraint - case 'u': { - table.constraints.push({ - type: ConstraintType.UNIQUE, - name: constraintName, - tableName: constraint.table_name, - columnNames: constraint.column_names as string[], - synchronize: true, - }); - break; - } - - // check constraint - case 'c': { - table.constraints.push({ - type: ConstraintType.CHECK, - name: constraint.constraint_name, - tableName: constraint.table_name, - expression: constraint.expression.replace('CHECK ', ''), - synchronize: true, - }); - break; - } - } - } -}; - -const asDatabaseAction = (action: string) => { - switch (action) { - case 'a': { - return ActionType.NO_ACTION; - } - case 'c': { - return ActionType.CASCADE; - } - case 'r': { - return ActionType.RESTRICT; - } - case 'n': { - return ActionType.SET_NULL; - } - case 'd': { - return ActionType.SET_DEFAULT; - } - - default: { - return ActionType.NO_ACTION; - } - } -}; diff --git a/server/src/sql-tools/readers/extension.reader.ts b/server/src/sql-tools/readers/extension.reader.ts deleted file mode 100644 index aa33f4d21e..0000000000 --- a/server/src/sql-tools/readers/extension.reader.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Reader } from 'src/sql-tools/types'; - -export const readExtensions: Reader = async (ctx, db) => { - const extensions = await db - .selectFrom('pg_catalog.pg_extension') - // .innerJoin('pg_namespace', 'pg_namespace.oid', 'pg_catalog.pg_extension.extnamespace') - // .where('pg_namespace.nspname', '=', schemaName) - .select(['extname as name', 'extversion as version']) - .execute(); - - for (const { name } of extensions) { - ctx.extensions.push({ name, synchronize: true }); - } -}; diff --git a/server/src/sql-tools/readers/function.reader.ts b/server/src/sql-tools/readers/function.reader.ts deleted file mode 100644 index 4696747f52..0000000000 --- a/server/src/sql-tools/readers/function.reader.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { sql } from 'kysely'; -import { Reader } from 'src/sql-tools/types'; - -export const readFunctions: Reader = async (ctx, db) => { - const routines = await db - .selectFrom('pg_proc as p') - .innerJoin('pg_namespace', 'pg_namespace.oid', 'p.pronamespace') - .leftJoin('pg_depend as d', (join) => join.onRef('d.objid', '=', 'p.oid').on('d.deptype', '=', sql.lit('e'))) - .where('d.objid', 'is', sql.lit(null)) - .where('p.prokind', '=', sql.lit('f')) - .where('pg_namespace.nspname', '=', ctx.schemaName) - .select((eb) => [ - 'p.proname as name', - eb.fn('pg_get_function_identity_arguments', ['p.oid']).as('arguments'), - eb.fn('pg_get_functiondef', ['p.oid']).as('expression'), - ]) - .execute(); - - for (const { name, expression } of routines) { - ctx.functions.push({ - name, - // TODO read expression from the overrides table - expression, - synchronize: true, - }); - } -}; diff --git a/server/src/sql-tools/readers/index.reader.ts b/server/src/sql-tools/readers/index.reader.ts deleted file mode 100644 index 26b17a0d19..0000000000 --- a/server/src/sql-tools/readers/index.reader.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { sql } from 'kysely'; -import { Reader } from 'src/sql-tools/types'; - -export const readIndexes: Reader = async (ctx, db) => { - const indexes = await db - .selectFrom('pg_index as ix') - // matching index, which has column information - .innerJoin('pg_class as i', 'ix.indexrelid', 'i.oid') - .innerJoin('pg_am as a', 'i.relam', 'a.oid') - // matching table - .innerJoin('pg_class as t', 'ix.indrelid', 't.oid') - // namespace - .innerJoin('pg_namespace', 'pg_namespace.oid', 'i.relnamespace') - // PK and UQ constraints automatically have indexes, so we can ignore those - .leftJoin('pg_constraint', (join) => - join - .onRef('pg_constraint.conindid', '=', 'i.oid') - .on('pg_constraint.contype', 'in', [sql.lit('p'), sql.lit('u')]), - ) - .where('pg_constraint.oid', 'is', null) - .select((eb) => [ - 'i.relname as index_name', - 't.relname as table_name', - 'ix.indisunique as unique', - 'a.amname as using', - eb.fn('pg_get_expr', ['ix.indexprs', 'ix.indrelid']).as('expression'), - eb.fn('pg_get_expr', ['ix.indpred', 'ix.indrelid']).as('where'), - eb - .selectFrom('pg_attribute as a') - .where('t.relkind', '=', sql.lit('r')) - .whereRef('a.attrelid', '=', 't.oid') - // list of columns numbers in the index - .whereRef('a.attnum', '=', sql`any("ix"."indkey")`) - .select((eb) => eb.fn('json_agg', ['a.attname']).as('column_name')) - .as('column_names'), - ]) - .where('pg_namespace.nspname', '=', ctx.schemaName) - .where('ix.indisprimary', '=', sql.lit(false)) - .execute(); - - for (const index of indexes) { - const table = ctx.getTableByName(index.table_name); - if (!table) { - continue; - } - - table.indexes.push({ - name: index.index_name, - tableName: index.table_name, - columnNames: index.column_names ?? undefined, - expression: index.expression ?? undefined, - using: index.using, - where: index.where ?? undefined, - unique: index.unique, - synchronize: true, - }); - } -}; diff --git a/server/src/sql-tools/readers/index.ts b/server/src/sql-tools/readers/index.ts deleted file mode 100644 index 354f99c7ca..0000000000 --- a/server/src/sql-tools/readers/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readColumns } from 'src/sql-tools/readers/column.reader'; -import { readComments } from 'src/sql-tools/readers/comment.reader'; -import { readConstraints } from 'src/sql-tools/readers/constraint.reader'; -import { readExtensions } from 'src/sql-tools/readers/extension.reader'; -import { readFunctions } from 'src/sql-tools/readers/function.reader'; -import { readIndexes } from 'src/sql-tools/readers/index.reader'; -import { readName } from 'src/sql-tools/readers/name.reader'; -import { readOverrides } from 'src/sql-tools/readers/override.reader'; -import { readParameters } from 'src/sql-tools/readers/parameter.reader'; -import { readTables } from 'src/sql-tools/readers/table.reader'; -import { readTriggers } from 'src/sql-tools/readers/trigger.reader'; -import { Reader } from 'src/sql-tools/types'; - -export const readers: Reader[] = [ - readName, - readParameters, - readExtensions, - readFunctions, - readTables, - readColumns, - readIndexes, - readConstraints, - readTriggers, - readComments, - readOverrides, -]; diff --git a/server/src/sql-tools/readers/name.reader.ts b/server/src/sql-tools/readers/name.reader.ts deleted file mode 100644 index de4f1af3a6..0000000000 --- a/server/src/sql-tools/readers/name.reader.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { QueryResult, sql } from 'kysely'; -import { Reader } from 'src/sql-tools/types'; - -export const readName: Reader = async (ctx, db) => { - const result = (await sql`SELECT current_database() as name`.execute(db)) as QueryResult<{ name: string }>; - - ctx.databaseName = result.rows[0].name; -}; diff --git a/server/src/sql-tools/readers/override.reader.ts b/server/src/sql-tools/readers/override.reader.ts deleted file mode 100644 index 34f0004f95..0000000000 --- a/server/src/sql-tools/readers/override.reader.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { sql } from 'kysely'; -import { OverrideType, Reader } from 'src/sql-tools/types'; - -export const readOverrides: Reader = async (ctx, db) => { - try { - const result = await sql - .raw<{ - name: string; - value: { type: OverrideType; name: string; sql: string }; - }>(`SELECT name, value FROM "${ctx.overrideTableName}"`) - .execute(db); - - for (const { name, value } of result.rows) { - ctx.overrides.push({ name, value, synchronize: true }); - } - } catch (error) { - ctx.warn('Overrides', `Error reading override table: ${error}`); - } -}; diff --git a/server/src/sql-tools/readers/parameter.reader.ts b/server/src/sql-tools/readers/parameter.reader.ts deleted file mode 100644 index c5f36591a3..0000000000 --- a/server/src/sql-tools/readers/parameter.reader.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { sql } from 'kysely'; -import { ParameterScope, Reader } from 'src/sql-tools/types'; - -export const readParameters: Reader = async (ctx, db) => { - const parameters = await db - .selectFrom('pg_settings') - .where('source', 'in', [sql.lit('database'), sql.lit('user')]) - .select(['name', 'setting as value', 'source as scope']) - .execute(); - - for (const parameter of parameters) { - ctx.parameters.push({ - name: parameter.name, - value: parameter.value, - databaseName: ctx.databaseName, - scope: parameter.scope as ParameterScope, - synchronize: true, - }); - } -}; diff --git a/server/src/sql-tools/readers/table.reader.ts b/server/src/sql-tools/readers/table.reader.ts deleted file mode 100644 index 4570179bbf..0000000000 --- a/server/src/sql-tools/readers/table.reader.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { sql } from 'kysely'; -import { Reader } from 'src/sql-tools/types'; - -export const readTables: Reader = async (ctx, db) => { - const tables = await db - .selectFrom('information_schema.tables') - .where('table_schema', '=', ctx.schemaName) - .where('table_type', '=', sql.lit('BASE TABLE')) - .selectAll() - .execute(); - - for (const table of tables) { - ctx.tables.push({ - name: table.table_name, - columns: [], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }); - } -}; diff --git a/server/src/sql-tools/readers/trigger.reader.ts b/server/src/sql-tools/readers/trigger.reader.ts deleted file mode 100644 index 92fb1d12bf..0000000000 --- a/server/src/sql-tools/readers/trigger.reader.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Reader, TriggerAction, TriggerScope, TriggerTiming } from 'src/sql-tools/types'; - -export const readTriggers: Reader = async (ctx, db) => { - const triggers = await db - .selectFrom('pg_trigger as t') - .innerJoin('pg_proc as p', 't.tgfoid', 'p.oid') - .innerJoin('pg_namespace as n', 'p.pronamespace', 'n.oid') - .innerJoin('pg_class as c', 't.tgrelid', 'c.oid') - .select((eb) => [ - 't.tgname as name', - 't.tgenabled as enabled', - 't.tgtype as type', - 't.tgconstraint as _constraint', - 't.tgdeferrable as is_deferrable', - 't.tginitdeferred as is_initially_deferred', - 't.tgargs as arguments', - 't.tgoldtable as referencing_old_table_as', - 't.tgnewtable as referencing_new_table_as', - eb.fn('pg_get_expr', ['t.tgqual', 't.tgrelid']).as('when_expression'), - 'p.proname as function_name', - 'c.relname as table_name', - ]) - .where('t.tgisinternal', '=', false) // Exclude internal system triggers - .where('n.nspname', '=', ctx.schemaName) - .execute(); - - // add triggers to tables - for (const trigger of triggers) { - const table = ctx.getTableByName(trigger.table_name); - if (!table) { - continue; - } - - table.triggers.push({ - name: trigger.name, - tableName: trigger.table_name, - functionName: trigger.function_name, - referencingNewTableAs: trigger.referencing_new_table_as ?? undefined, - referencingOldTableAs: trigger.referencing_old_table_as ?? undefined, - when: trigger.when_expression, - synchronize: true, - ...parseTriggerType(trigger.type), - }); - } -}; - -export const hasMask = (input: number, mask: number) => (input & mask) === mask; - -export const parseTriggerType = (type: number) => { - // eslint-disable-next-line unicorn/prefer-math-trunc - const scope: TriggerScope = hasMask(type, 1 << 0) ? 'row' : 'statement'; - - let timing: TriggerTiming = 'after'; - const timingMasks: Array<{ mask: number; value: TriggerTiming }> = [ - { mask: 1 << 1, value: 'before' }, - { mask: 1 << 6, value: 'instead of' }, - ]; - - for (const { mask, value } of timingMasks) { - if (hasMask(type, mask)) { - timing = value; - break; - } - } - - const actions: TriggerAction[] = []; - const actionMasks: Array<{ mask: number; value: TriggerAction }> = [ - { mask: 1 << 2, value: 'insert' }, - { mask: 1 << 3, value: 'delete' }, - { mask: 1 << 4, value: 'update' }, - { mask: 1 << 5, value: 'truncate' }, - ]; - - for (const { mask, value } of actionMasks) { - if (hasMask(type, mask)) { - actions.push(value); - break; - } - } - - if (actions.length === 0) { - throw new Error(`Unable to parse trigger type ${type}`); - } - - return { actions, timing, scope }; -}; diff --git a/server/src/sql-tools/register-enum.ts b/server/src/sql-tools/register-enum.ts deleted file mode 100644 index 5e9b41adcb..0000000000 --- a/server/src/sql-tools/register-enum.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { register } from 'src/sql-tools/register'; -import { DatabaseEnum } from 'src/sql-tools/types'; - -export type EnumOptions = { - name: string; - values: string[]; - synchronize?: boolean; -}; - -export const registerEnum = (options: EnumOptions) => { - const item: DatabaseEnum = { - name: options.name, - values: options.values, - synchronize: options.synchronize ?? true, - }; - - register({ type: 'enum', item }); - - return item; -}; diff --git a/server/src/sql-tools/register-function.ts b/server/src/sql-tools/register-function.ts deleted file mode 100644 index 9f1c84c4fa..0000000000 --- a/server/src/sql-tools/register-function.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { register } from 'src/sql-tools/register'; -import { ColumnType, DatabaseFunction } from 'src/sql-tools/types'; - -export type FunctionOptions = { - name: string; - arguments?: string[]; - returnType: ColumnType | string; - language?: 'SQL' | 'PLPGSQL'; - behavior?: 'immutable' | 'stable' | 'volatile'; - parallel?: 'safe' | 'unsafe' | 'restricted'; - strict?: boolean; - synchronize?: boolean; -} & ({ body: string } | { return: string }); - -export const registerFunction = (options: FunctionOptions) => { - const name = options.name; - const expression = asFunctionExpression(options); - - const item: DatabaseFunction = { - name, - expression, - synchronize: options.synchronize ?? true, - }; - - register({ type: 'function', item }); - - return item; -}; - -const asFunctionExpression = (options: FunctionOptions) => { - const name = options.name; - const sql: string[] = [ - `CREATE OR REPLACE FUNCTION ${name}(${(options.arguments || []).join(', ')})`, - `RETURNS ${options.returnType}`, - ]; - - const flags = [ - options.parallel ? `PARALLEL ${options.parallel.toUpperCase()}` : undefined, - options.strict ? 'STRICT' : undefined, - options.behavior ? options.behavior.toUpperCase() : undefined, - `LANGUAGE ${options.language ?? 'SQL'}`, - ].filter((x) => x !== undefined); - - if (flags.length > 0) { - sql.push(flags.join(' ')); - } - - if ('return' in options) { - sql.push(` RETURN ${options.return}`); - } - - if ('body' in options) { - const body = options.body; - sql.push(...(body.includes('\n') ? [`AS $$`, ' ' + body.trim(), `$$;`] : [`AS $$${body}$$;`])); - } - - return sql.join('\n ').trim(); -}; diff --git a/server/src/sql-tools/register-item.ts b/server/src/sql-tools/register-item.ts deleted file mode 100644 index fede281a1b..0000000000 --- a/server/src/sql-tools/register-item.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import { CheckOptions } from 'src/sql-tools/decorators/check.decorator'; -import { ColumnOptions } from 'src/sql-tools/decorators/column.decorator'; -import { ConfigurationParameterOptions } from 'src/sql-tools/decorators/configuration-parameter.decorator'; -import { DatabaseOptions } from 'src/sql-tools/decorators/database.decorator'; -import { ExtensionOptions } from 'src/sql-tools/decorators/extension.decorator'; -import { ForeignKeyColumnOptions } from 'src/sql-tools/decorators/foreign-key-column.decorator'; -import { ForeignKeyConstraintOptions } from 'src/sql-tools/decorators/foreign-key-constraint.decorator'; -import { IndexOptions } from 'src/sql-tools/decorators/index.decorator'; -import { TableOptions } from 'src/sql-tools/decorators/table.decorator'; -import { TriggerOptions } from 'src/sql-tools/decorators/trigger.decorator'; -import { UniqueOptions } from 'src/sql-tools/decorators/unique.decorator'; -import { DatabaseEnum, DatabaseFunction } from 'src/sql-tools/types'; - -export type ClassBased = { object: Function } & T; -export type PropertyBased = { object: object; propertyName: string | symbol } & T; -export type RegisterItem = - | { type: 'database'; item: ClassBased<{ options: DatabaseOptions }> } - | { type: 'table'; item: ClassBased<{ options: TableOptions }> } - | { type: 'index'; item: ClassBased<{ options: IndexOptions }> } - | { type: 'uniqueConstraint'; item: ClassBased<{ options: UniqueOptions }> } - | { type: 'checkConstraint'; item: ClassBased<{ options: CheckOptions }> } - | { type: 'column'; item: PropertyBased<{ options: ColumnOptions }> } - | { type: 'function'; item: DatabaseFunction } - | { type: 'enum'; item: DatabaseEnum } - | { type: 'trigger'; item: ClassBased<{ options: TriggerOptions }> } - | { type: 'extension'; item: ClassBased<{ options: ExtensionOptions }> } - | { type: 'configurationParameter'; item: ClassBased<{ options: ConfigurationParameterOptions }> } - | { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => Function }> } - | { type: 'foreignKeyConstraint'; item: ClassBased<{ options: ForeignKeyConstraintOptions }> }; -export type RegisterItemType = Extract['item']; diff --git a/server/src/sql-tools/register.ts b/server/src/sql-tools/register.ts deleted file mode 100644 index 4df04c935a..0000000000 --- a/server/src/sql-tools/register.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RegisterItem } from 'src/sql-tools/register-item'; - -const items: RegisterItem[] = []; - -export const register = (item: RegisterItem) => void items.push(item); - -export const getRegisteredItems = () => items; - -export const resetRegisteredItems = () => { - items.length = 0; -}; diff --git a/server/src/sql-tools/schema-diff.spec.ts b/server/src/sql-tools/schema-diff.spec.ts deleted file mode 100644 index f45fb98bd3..0000000000 --- a/server/src/sql-tools/schema-diff.spec.ts +++ /dev/null @@ -1,689 +0,0 @@ -import { schemaDiff } from 'src/sql-tools/schema-diff'; -import { - ActionType, - ColumnType, - ConstraintType, - DatabaseColumn, - DatabaseConstraint, - DatabaseIndex, - DatabaseSchema, - DatabaseTable, -} from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const fromColumn = (column: Partial>): DatabaseSchema => { - const tableName = 'table1'; - - return { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: tableName, - columns: [ - { - name: 'column1', - primary: false, - synchronize: true, - isArray: false, - type: 'character varying', - nullable: false, - ...column, - tableName, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], - }; -}; - -const fromConstraint = (constraint?: DatabaseConstraint): DatabaseSchema => { - const tableName = constraint?.tableName || 'table1'; - - return { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: tableName, - columns: [ - { - name: 'column1', - primary: false, - synchronize: true, - isArray: false, - type: 'character varying', - nullable: false, - tableName, - }, - ], - indexes: [], - triggers: [], - constraints: constraint ? [constraint] : [], - synchronize: true, - }, - ], - warnings: [], - }; -}; - -const fromIndex = (index?: DatabaseIndex): DatabaseSchema => { - const tableName = index?.tableName || 'table1'; - - return { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: tableName, - columns: [ - { - name: 'column1', - primary: false, - synchronize: true, - isArray: false, - type: 'character varying', - nullable: false, - tableName, - }, - ], - indexes: index ? [index] : [], - constraints: [], - triggers: [], - synchronize: true, - }, - ], - warnings: [], - }; -}; - -const newSchema = (schema: { - name?: string; - tables: Array<{ - name: string; - columns?: Array<{ - name: string; - type?: ColumnType; - nullable?: boolean; - isArray?: boolean; - }>; - indexes?: DatabaseIndex[]; - constraints?: DatabaseConstraint[]; - }>; -}): DatabaseSchema => { - const tables: DatabaseTable[] = []; - - for (const table of schema.tables || []) { - const tableName = table.name; - const columns: DatabaseColumn[] = []; - - for (const column of table.columns || []) { - const columnName = column.name; - - columns.push({ - tableName, - name: columnName, - primary: false, - type: column.type || 'character varying', - isArray: column.isArray ?? false, - nullable: column.nullable ?? false, - synchronize: true, - }); - } - - tables.push({ - name: tableName, - columns, - indexes: table.indexes ?? [], - constraints: table.constraints ?? [], - triggers: [], - synchronize: true, - }); - } - - return { - databaseName: 'immich', - schemaName: schema?.name || 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables, - warnings: [], - }; -}; - -describe(schemaDiff.name, () => { - it('should work', () => { - const diff = schemaDiff(newSchema({ tables: [] }), newSchema({ tables: [] })); - expect(diff.items).toEqual([]); - }); - - describe('table', () => { - describe('TableCreate', () => { - it('should find a missing table', () => { - const column: DatabaseColumn = { - type: 'character varying', - tableName: 'table1', - primary: false, - name: 'column1', - isArray: false, - nullable: false, - synchronize: true, - }; - const diff = schemaDiff( - newSchema({ tables: [{ name: 'table1', columns: [column] }] }), - newSchema({ tables: [] }), - ); - - expect(diff.items).toHaveLength(1); - expect(diff.items[0]).toEqual({ - type: 'TableCreate', - table: { - name: 'table1', - columns: [column], - constraints: [], - indexes: [], - triggers: [], - synchronize: true, - }, - reason: 'missing in target', - }); - }); - }); - - describe('TableDrop', () => { - it('should find an extra table', () => { - const diff = schemaDiff( - newSchema({ tables: [] }), - newSchema({ - tables: [{ name: 'table1', columns: [{ name: 'column1' }] }], - }), - { tables: { ignoreExtra: false } }, - ); - - expect(diff.items).toHaveLength(1); - expect(diff.items[0]).toEqual({ - type: 'TableDrop', - tableName: 'table1', - reason: 'missing in source', - }); - }); - }); - - it('should skip identical tables', () => { - const diff = schemaDiff( - newSchema({ - tables: [{ name: 'table1', columns: [{ name: 'column1' }] }], - }), - newSchema({ - tables: [{ name: 'table1', columns: [{ name: 'column1' }] }], - }), - ); - - expect(diff.items).toEqual([]); - }); - }); - - describe('column', () => { - describe('ColumnAdd', () => { - it('should find a new column', () => { - const diff = schemaDiff( - newSchema({ - tables: [ - { - name: 'table1', - columns: [{ name: 'column1' }, { name: 'column2' }], - }, - ], - }), - newSchema({ - tables: [{ name: 'table1', columns: [{ name: 'column1' }] }], - }), - ); - - expect(diff.items).toEqual([ - { - type: 'ColumnAdd', - column: { - tableName: 'table1', - isArray: false, - primary: false, - name: 'column2', - nullable: false, - type: 'character varying', - synchronize: true, - }, - reason: 'missing in target', - }, - ]); - }); - }); - - describe('ColumnDrop', () => { - it('should find an extra column', () => { - const diff = schemaDiff( - newSchema({ - tables: [{ name: 'table1', columns: [{ name: 'column1' }] }], - }), - newSchema({ - tables: [ - { - name: 'table1', - columns: [{ name: 'column1' }, { name: 'column2' }], - }, - ], - }), - ); - - expect(diff.items).toEqual([ - { - type: 'ColumnDrop', - tableName: 'table1', - columnName: 'column2', - reason: 'missing in source', - }, - ]); - }); - }); - - describe('nullable', () => { - it('should make a column nullable', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', nullable: true }), - fromColumn({ name: 'column1', nullable: false }), - ); - - expect(diff.items).toEqual([ - { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { - nullable: true, - }, - reason: 'nullable is different (true vs false)', - }, - ]); - }); - - it('should make a column non-nullable', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', nullable: false }), - fromColumn({ name: 'column1', nullable: true }), - ); - - expect(diff.items).toEqual([ - { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { - nullable: false, - }, - reason: 'nullable is different (false vs true)', - }, - ]); - }); - }); - - describe('default', () => { - it('should set a default value to a function', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', default: 'uuid_generate_v4()' }), - fromColumn({ name: 'column1' }), - ); - - expect(diff.items).toEqual([ - { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { - default: 'uuid_generate_v4()', - }, - reason: 'default is different (uuid_generate_v4() vs undefined)', - }, - ]); - }); - - it('should ignore explicit casts for strings', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', type: 'character varying', default: `''` }), - fromColumn({ name: 'column1', type: 'character varying', default: `''::character varying` }), - ); - - expect(diff.items).toEqual([]); - }); - - it('should ignore explicit casts for numbers', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', type: 'bigint', default: `0` }), - fromColumn({ name: 'column1', type: 'bigint', default: `'0'::bigint` }), - ); - - expect(diff.items).toEqual([]); - }); - - it('should ignore explicit casts for enums', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', type: 'enum', enumName: 'enum1', default: `test` }), - fromColumn({ name: 'column1', type: 'enum', enumName: 'enum1', default: `'test'::enum1` }), - ); - - expect(diff.items).toEqual([]); - }); - - it('should support arrays, ignoring types', () => { - const diff = schemaDiff( - fromColumn({ name: 'column1', type: 'character varying', isArray: true, default: "'{}'" }), - fromColumn({ - name: 'column1', - type: 'character varying', - isArray: true, - default: "'{}'::character varying[]", - }), - ); - - expect(diff.items).toEqual([]); - }); - }); - }); - - describe('constraint', () => { - describe('ConstraintAdd', () => { - it('should detect a new constraint', () => { - const diff = schemaDiff( - fromConstraint({ - name: 'PK_test', - type: ConstraintType.PRIMARY_KEY, - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }), - fromConstraint(), - ); - - expect(diff.items).toEqual([ - { - type: 'ConstraintAdd', - constraint: { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_test', - columnNames: ['id'], - tableName: 'table1', - synchronize: true, - }, - reason: 'missing in target', - }, - ]); - }); - }); - - describe('ConstraintDrop', () => { - it('should detect an extra constraint', () => { - const diff = schemaDiff( - fromConstraint(), - fromConstraint({ - name: 'PK_test', - type: ConstraintType.PRIMARY_KEY, - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }), - ); - - expect(diff.items).toEqual([ - { - type: 'ConstraintDrop', - tableName: 'table1', - constraintName: 'PK_test', - reason: 'missing in source', - }, - ]); - }); - }); - - describe('primary key', () => { - it('should skip identical primary key constraints', () => { - const constraint: DatabaseConstraint = { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }; - - const diff = schemaDiff(fromConstraint({ ...constraint }), fromConstraint({ ...constraint })); - - expect(diff.items).toEqual([]); - }); - }); - - describe('foreign key', () => { - it('should skip identical foreign key constraints', () => { - const constraint: DatabaseConstraint = { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_test', - tableName: 'table1', - columnNames: ['parentId'], - referenceTableName: 'table2', - referenceColumnNames: ['id'], - synchronize: true, - }; - - const diff = schemaDiff(fromConstraint(constraint), fromConstraint(constraint)); - - expect(diff.items).toEqual([]); - }); - - it('should drop and recreate when the column changes', () => { - const constraint: DatabaseConstraint = { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_test', - tableName: 'table1', - columnNames: ['parentId'], - referenceTableName: 'table2', - referenceColumnNames: ['id'], - synchronize: true, - }; - - const diff = schemaDiff( - fromConstraint(constraint), - fromConstraint({ ...constraint, columnNames: ['parentId2'] }), - ); - - expect(diff.items).toEqual([ - { - constraintName: 'FK_test', - reason: 'columns are different (parentId vs parentId2)', - tableName: 'table1', - type: 'ConstraintDrop', - }, - { - constraint: { - columnNames: ['parentId'], - name: 'FK_test', - referenceColumnNames: ['id'], - referenceTableName: 'table2', - synchronize: true, - tableName: 'table1', - type: 'foreign-key', - }, - reason: 'columns are different (parentId vs parentId2)', - type: 'ConstraintAdd', - }, - ]); - }); - - it('should drop and recreate when the ON DELETE action changes', () => { - const constraint: DatabaseConstraint = { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_test', - tableName: 'table1', - columnNames: ['parentId'], - referenceTableName: 'table2', - referenceColumnNames: ['id'], - onDelete: ActionType.CASCADE, - synchronize: true, - }; - - const diff = schemaDiff(fromConstraint(constraint), fromConstraint({ ...constraint, onDelete: undefined })); - - expect(diff.items).toEqual([ - { - constraintName: 'FK_test', - reason: 'ON DELETE action is different (CASCADE vs NO ACTION)', - tableName: 'table1', - type: 'ConstraintDrop', - }, - { - constraint: { - columnNames: ['parentId'], - name: 'FK_test', - referenceColumnNames: ['id'], - referenceTableName: 'table2', - onDelete: ActionType.CASCADE, - synchronize: true, - tableName: 'table1', - type: 'foreign-key', - }, - reason: 'ON DELETE action is different (CASCADE vs NO ACTION)', - type: 'ConstraintAdd', - }, - ]); - }); - }); - - describe('unique', () => { - it('should skip identical unique constraints', () => { - const constraint: DatabaseConstraint = { - type: ConstraintType.UNIQUE, - name: 'UQ_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }; - - const diff = schemaDiff(fromConstraint({ ...constraint }), fromConstraint({ ...constraint })); - - expect(diff.items).toEqual([]); - }); - }); - - describe('check', () => { - it('should skip identical check constraints', () => { - const constraint: DatabaseConstraint = { - type: ConstraintType.CHECK, - name: 'CHK_test', - tableName: 'table1', - expression: 'column1 > 0', - synchronize: true, - }; - - const diff = schemaDiff(fromConstraint({ ...constraint }), fromConstraint({ ...constraint })); - - expect(diff.items).toEqual([]); - }); - }); - }); - - describe('index', () => { - describe('IndexCreate', () => { - it('should detect a new index', () => { - const diff = schemaDiff( - fromIndex({ - name: 'IDX_test', - tableName: 'table1', - columnNames: ['id'], - unique: false, - synchronize: true, - }), - fromIndex(), - ); - - expect(diff.items).toEqual([ - { - type: 'IndexCreate', - index: { - name: 'IDX_test', - columnNames: ['id'], - tableName: 'table1', - unique: false, - synchronize: true, - }, - reason: 'missing in target', - }, - ]); - }); - }); - - describe('IndexDrop', () => { - it('should detect an extra index', () => { - const diff = schemaDiff( - fromIndex(), - fromIndex({ - name: 'IDX_test', - unique: true, - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }), - ); - - expect(diff.items).toEqual([ - { - type: 'IndexDrop', - indexName: 'IDX_test', - reason: 'missing in source', - }, - ]); - }); - }); - - it('should recreate the index if unique changes', () => { - const index: DatabaseIndex = { - name: 'IDX_test', - tableName: 'table1', - columnNames: ['id'], - unique: true, - synchronize: true, - }; - const diff = schemaDiff(fromIndex(index), fromIndex({ ...index, unique: false })); - - expect(diff.items).toEqual([ - { - type: 'IndexDrop', - indexName: 'IDX_test', - reason: 'uniqueness is different (true vs false)', - }, - { - type: 'IndexCreate', - index, - reason: 'uniqueness is different (true vs false)', - }, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/schema-diff.ts b/server/src/sql-tools/schema-diff.ts deleted file mode 100644 index 846210931b..0000000000 --- a/server/src/sql-tools/schema-diff.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { compareEnums } from 'src/sql-tools/comparers/enum.comparer'; -import { compareExtensions } from 'src/sql-tools/comparers/extension.comparer'; -import { compareFunctions } from 'src/sql-tools/comparers/function.comparer'; -import { compareOverrides } from 'src/sql-tools/comparers/override.comparer'; -import { compareParameters } from 'src/sql-tools/comparers/parameter.comparer'; -import { compareTables } from 'src/sql-tools/comparers/table.comparer'; -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { compare } from 'src/sql-tools/helpers'; -import { transformers } from 'src/sql-tools/transformers'; -import { - ConstraintType, - DatabaseSchema, - SchemaDiff, - SchemaDiffOptions, - SchemaDiffToSqlOptions, -} from 'src/sql-tools/types'; - -/** - * Compute the difference between two database schemas - */ -export const schemaDiff = (source: DatabaseSchema, target: DatabaseSchema, options: SchemaDiffOptions = {}) => { - const items = [ - ...compare(source.parameters, target.parameters, options.parameters, compareParameters()), - ...compare(source.extensions, target.extensions, options.extensions, compareExtensions()), - ...compare(source.functions, target.functions, options.functions, compareFunctions()), - ...compare(source.enums, target.enums, options.enums, compareEnums()), - ...compare(source.tables, target.tables, options.tables, compareTables(options)), - ...compare(source.overrides, target.overrides, options.overrides, compareOverrides()), - ]; - - type SchemaName = SchemaDiff['type']; - const itemMap: Record = { - ColumnRename: [], - ConstraintRename: [], - IndexRename: [], - - ExtensionDrop: [], - ExtensionCreate: [], - - ParameterSet: [], - ParameterReset: [], - - FunctionDrop: [], - FunctionCreate: [], - - EnumDrop: [], - EnumCreate: [], - - TriggerDrop: [], - ConstraintDrop: [], - TableDrop: [], - ColumnDrop: [], - ColumnAdd: [], - ColumnAlter: [], - TableCreate: [], - ConstraintAdd: [], - TriggerCreate: [], - - IndexCreate: [], - IndexDrop: [], - - OverrideCreate: [], - OverrideUpdate: [], - OverrideDrop: [], - }; - - for (const item of items) { - itemMap[item.type].push(item); - } - - const constraintAdds = itemMap.ConstraintAdd.filter((item) => item.type === 'ConstraintAdd'); - - const orderedItems = [ - ...itemMap.ExtensionCreate, - ...itemMap.FunctionCreate, - ...itemMap.ParameterSet, - ...itemMap.ParameterReset, - ...itemMap.EnumCreate, - ...itemMap.TriggerDrop, - ...itemMap.IndexDrop, - ...itemMap.ConstraintDrop, - ...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, - ...itemMap.EnumDrop, - ...itemMap.FunctionDrop, - ...itemMap.OverrideCreate, - ...itemMap.OverrideUpdate, - ...itemMap.OverrideDrop, - ]; - - return { - items: orderedItems, - asSql: (options?: SchemaDiffToSqlOptions) => schemaDiffToSql(orderedItems, options), - asHuman: () => schemaDiffToHuman(orderedItems), - }; -}; - -/** - * Convert schema diffs into SQL statements - */ -export const schemaDiffToSql = (items: SchemaDiff[], options: SchemaDiffToSqlOptions = {}): string[] => { - return items.flatMap((item) => asSql(item, options)); -}; - -/** - * Convert schema diff into human readable statements - */ -export const schemaDiffToHuman = (items: SchemaDiff[]): string[] => { - return items.flatMap((item) => asHuman(item)); -}; - -export const asSql = (item: SchemaDiff, options: SchemaDiffToSqlOptions): string[] => { - const ctx = new BaseContext(options); - for (const transform of transformers) { - const result = transform(ctx, item); - if (!result) { - continue; - } - - return asArray(result).map((result) => result + withComments(options.comments, item)); - } - - throw new Error(`Unhandled schema diff type: ${item.type}`); -}; - -export const asHuman = (item: SchemaDiff): string => { - switch (item.type) { - case 'ExtensionCreate': { - return `The extension "${item.extension.name}" is missing and needs to be created`; - } - case 'ExtensionDrop': { - return `The extension "${item.extensionName}" exists but is no longer needed`; - } - case 'FunctionCreate': { - return `The function "${item.function.name}" is missing and needs to be created`; - } - case 'FunctionDrop': { - return `The function "${item.functionName}" exists but should be removed`; - } - case 'TableCreate': { - return `The table "${item.table.name}" is missing and needs to be created`; - } - case 'TableDrop': { - return `The table "${item.tableName}" exists but should be removed`; - } - case 'ColumnAdd': { - return `The column "${item.column.tableName}"."${item.column.name}" is missing and needs to be created`; - } - case 'ColumnRename': { - return `The column "${item.tableName}"."${item.oldName}" was renamed to "${item.tableName}"."${item.newName}"`; - } - case 'ColumnAlter': { - return `The column "${item.tableName}"."${item.columnName}" has changes that need to be applied ${JSON.stringify( - item.changes, - )}`; - } - case 'ColumnDrop': { - return `The column "${item.tableName}"."${item.columnName}" exists but should be removed`; - } - case 'ConstraintAdd': { - return `The constraint "${item.constraint.tableName}"."${item.constraint.name}" (${item.constraint.type}) is missing and needs to be created`; - } - case 'ConstraintRename': { - return `The constraint "${item.tableName}"."${item.oldName}" was renamed to "${item.tableName}"."${item.newName}"`; - } - case 'ConstraintDrop': { - return `The constraint "${item.tableName}"."${item.constraintName}" exists but should be removed`; - } - case 'IndexCreate': { - return `The index "${item.index.tableName}"."${item.index.name}" is missing and needs to be created`; - } - case 'IndexRename': { - return `The index "${item.tableName}"."${item.oldName}" was renamed to "${item.tableName}"."${item.newName}"`; - } - case 'IndexDrop': { - return `The index "${item.indexName}" exists but is no longer needed`; - } - case 'TriggerCreate': { - return `The trigger "${item.trigger.tableName}"."${item.trigger.name}" is missing and needs to be created`; - } - case 'TriggerDrop': { - return `The trigger "${item.tableName}"."${item.triggerName}" exists but is no longer needed`; - } - case 'ParameterSet': { - return `The configuration parameter "${item.parameter.name}" has a different value and needs to be updated to "${item.parameter.value}"`; - } - case 'ParameterReset': { - return `The configuration parameter "${item.parameterName}" is set, but should be reset to the default value`; - } - case 'EnumCreate': { - return `The enum "${item.enum.name}" is missing and needs to be created`; - } - case 'EnumDrop': { - return `The enum "${item.enumName}" exists but is no longer needed`; - } - case 'OverrideCreate': { - return `The override "${item.override.name}" is missing and needs to be created`; - } - case 'OverrideUpdate': { - return `The override "${item.override.name}" needs to be updated`; - } - case 'OverrideDrop': { - return `The override "${item.overrideName}" exists but is no longer needed`; - } - } -}; - -const withComments = (comments: boolean | undefined, item: SchemaDiff): string => { - if (!comments) { - return ''; - } - - return ` -- ${item.reason}`; -}; - -const asArray = (items: T | T[]): T[] => { - if (Array.isArray(items)) { - return items; - } - - return [items]; -}; diff --git a/server/src/sql-tools/schema-from-code.spec.ts b/server/src/sql-tools/schema-from-code.spec.ts deleted file mode 100644 index b0c88d1f57..0000000000 --- a/server/src/sql-tools/schema-from-code.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { readdirSync } from 'node:fs'; -import { join } from 'node:path'; -import { schemaFromCode } from 'src/sql-tools/schema-from-code'; -import { SchemaFromCodeOptions } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const importModule = async (filePath: string) => { - const module = await import(filePath); - const options: SchemaFromCodeOptions = module.options; - - return { module, options }; -}; - -describe(schemaFromCode.name, () => { - it('should work', () => { - expect(schemaFromCode({ reset: true })).toEqual({ - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [], - warnings: [], - }); - }); - - describe('test files', () => { - const errorStubs = readdirSync('test/sql-tools/errors', { withFileTypes: true }); - for (const file of errorStubs) { - const filePath = join(file.parentPath, file.name); - it(filePath, async () => { - const { module, options } = await importModule(filePath); - - expect(module.message).toBeDefined(); - expect(() => schemaFromCode({ ...options, reset: true })).toThrowError(module.message); - }); - } - - const stubs = readdirSync('test/sql-tools', { withFileTypes: true }); - for (const file of stubs) { - if (file.isDirectory()) { - continue; - } - - const filePath = join(file.parentPath, file.name); - it(filePath, async () => { - const { module, options } = await importModule(filePath); - - expect(module.description).toBeDefined(); - expect(module.schema).toBeDefined(); - expect(schemaFromCode({ ...options, reset: true }), module.description).toEqual(module.schema); - }); - } - }); -}); diff --git a/server/src/sql-tools/schema-from-code.ts b/server/src/sql-tools/schema-from-code.ts deleted file mode 100644 index 2e19f414e4..0000000000 --- a/server/src/sql-tools/schema-from-code.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { ProcessorContext } from 'src/sql-tools/contexts/processor-context'; -import { processors } from 'src/sql-tools/processors'; -import { getRegisteredItems, resetRegisteredItems } from 'src/sql-tools/register'; -import { ConstraintType, SchemaFromCodeOptions } from 'src/sql-tools/types'; - -/** - * Load schema from code (decorators, etc) - */ -export const schemaFromCode = (options: SchemaFromCodeOptions = {}) => { - try { - const ctx = new ProcessorContext(options); - const items = getRegisteredItems(); - - for (const processor of processors) { - processor(ctx, items); - } - - if (ctx.options.overrides) { - ctx.tables.push({ - name: ctx.overrideTableName, - columns: [ - { - name: 'name', - tableName: ctx.overrideTableName, - primary: true, - type: 'character varying', - nullable: false, - isArray: false, - synchronize: true, - }, - { - name: 'value', - tableName: ctx.overrideTableName, - primary: false, - type: 'jsonb', - nullable: false, - isArray: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: `${ctx.overrideTableName}_pkey`, - tableName: ctx.overrideTableName, - columnNames: ['name'], - synchronize: true, - }, - ], - synchronize: true, - }); - } - - return ctx.build(); - } finally { - if (options.reset) { - resetRegisteredItems(); - } - } -}; diff --git a/server/src/sql-tools/schema-from-database.ts b/server/src/sql-tools/schema-from-database.ts deleted file mode 100644 index ee34e9dd8d..0000000000 --- a/server/src/sql-tools/schema-from-database.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Kysely } from 'kysely'; -import { PostgresJSDialect } from 'kysely-postgres-js'; -import { Sql } from 'postgres'; -import { ReaderContext } from 'src/sql-tools/contexts/reader-context'; -import { readers } from 'src/sql-tools/readers'; -import { DatabaseSchema, PostgresDB, SchemaFromDatabaseOptions } from 'src/sql-tools/types'; - -export type DatabaseLike = Sql | Kysely; - -const isKysely = (db: DatabaseLike): db is Kysely => db instanceof Kysely; - -/** - * Load schema from a database url - */ -export const schemaFromDatabase = async ( - database: DatabaseLike, - options: SchemaFromDatabaseOptions = {}, -): Promise => { - const db = isKysely(database) - ? (database as Kysely) - : new Kysely({ dialect: new PostgresJSDialect({ postgres: database }) }); - const ctx = new ReaderContext(options); - - try { - for (const reader of readers) { - await reader(ctx, db); - } - - return ctx.build(); - } finally { - // only close the connection it we created it - if (!isKysely(database)) { - await db.destroy(); - } - } -}; diff --git a/server/src/sql-tools/transformers/column.transformer.spec.ts b/server/src/sql-tools/transformers/column.transformer.spec.ts deleted file mode 100644 index 6828e2a72d..0000000000 --- a/server/src/sql-tools/transformers/column.transformer.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformColumns } from 'src/sql-tools/transformers/column.transformer'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -describe(transformColumns.name, () => { - describe('ColumnAdd', () => { - it('should work', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAdd', - column: { - name: 'column1', - tableName: 'table1', - primary: false, - type: 'character varying', - nullable: false, - isArray: false, - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD "column1" character varying NOT NULL;'); - }); - - it('should add a nullable column', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAdd', - column: { - name: 'column1', - tableName: 'table1', - primary: false, - type: 'character varying', - nullable: true, - isArray: false, - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD "column1" character varying;'); - }); - - it('should add a column with an enum type', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAdd', - column: { - name: 'column1', - tableName: 'table1', - primary: false, - type: 'character varying', - enumName: 'table1_column1_enum', - nullable: true, - isArray: false, - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD "column1" table1_column1_enum;'); - }); - - it('should add a column that is an array type', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAdd', - column: { - name: 'column1', - tableName: 'table1', - primary: false, - type: 'boolean', - nullable: true, - isArray: true, - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD "column1" boolean[];'); - }); - }); - - describe('ColumnAlter', () => { - it('should make a column nullable', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { nullable: true }, - reason: 'unknown', - }), - ).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" DROP NOT NULL;`]); - }); - - it('should make a column non-nullable', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { nullable: false }, - reason: 'unknown', - }), - ).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" SET NOT NULL;`]); - }); - - it('should update the default value', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { default: 'uuid_generate_v4()' }, - reason: 'unknown', - }), - ).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" SET DEFAULT uuid_generate_v4();`]); - }); - - it('should update the default value to NULL', () => { - expect( - transformColumns(ctx, { - type: 'ColumnAlter', - tableName: 'table1', - columnName: 'column1', - changes: { - default: 'NULL', - }, - reason: 'unknown', - }), - ).toEqual([`ALTER TABLE "table1" ALTER COLUMN "column1" SET DEFAULT NULL;`]); - }); - }); - - describe('ColumnDrop', () => { - it('should work', () => { - expect( - transformColumns(ctx, { - type: 'ColumnDrop', - tableName: 'table1', - columnName: 'column1', - reason: 'unknown', - }), - ).toEqual(`ALTER TABLE "table1" DROP COLUMN "column1";`); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/column.transformer.ts b/server/src/sql-tools/transformers/column.transformer.ts deleted file mode 100644 index ffa565e533..0000000000 --- a/server/src/sql-tools/transformers/column.transformer.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers'; -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { ColumnChanges, DatabaseColumn } from 'src/sql-tools/types'; - -export const transformColumns: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'ColumnAdd': { - return asColumnAdd(item.column); - } - - case 'ColumnAlter': { - 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 `ALTER TABLE "${item.tableName}" DROP COLUMN "${item.columnName}";`; - } - - default: { - return false; - } - } -}; - -const asColumnAdd = (column: DatabaseColumn): string => { - return ( - `ALTER TABLE "${column.tableName}" ADD "${column.name}" ${getColumnType(column)}` + getColumnModifiers(column) + ';' - ); -}; - -export const asColumnAlter = (tableName: string, columnName: string, changes: ColumnChanges): string[] => { - const base = `ALTER TABLE "${tableName}" ALTER COLUMN "${columnName}"`; - const items: string[] = []; - if (changes.nullable !== undefined) { - items.push(changes.nullable ? `${base} DROP NOT NULL;` : `${base} SET NOT NULL;`); - } - - if (changes.default !== undefined) { - items.push(`${base} SET DEFAULT ${changes.default};`); - } - - if (changes.storage !== undefined) { - items.push(`${base} SET STORAGE ${changes.storage.toUpperCase()};`); - } - - if (changes.comment !== undefined) { - items.push(asColumnComment(tableName, columnName, changes.comment)); - } - - return items; -}; diff --git a/server/src/sql-tools/transformers/constraint.transformer.spec.ts b/server/src/sql-tools/transformers/constraint.transformer.spec.ts deleted file mode 100644 index 6e512afdca..0000000000 --- a/server/src/sql-tools/transformers/constraint.transformer.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformConstraints } from 'src/sql-tools/transformers/constraint.transformer'; -import { ConstraintType } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -describe(transformConstraints.name, () => { - describe('ConstraintAdd', () => { - describe('primary keys', () => { - it('should work', () => { - expect( - transformConstraints(ctx, { - type: 'ConstraintAdd', - constraint: { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD CONSTRAINT "PK_test" PRIMARY KEY ("id");'); - }); - }); - - describe('foreign keys', () => { - it('should work', () => { - expect( - transformConstraints(ctx, { - type: 'ConstraintAdd', - constraint: { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_test', - tableName: 'table1', - columnNames: ['parentId'], - referenceColumnNames: ['id'], - referenceTableName: 'table2', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual( - 'ALTER TABLE "table1" ADD CONSTRAINT "FK_test" FOREIGN KEY ("parentId") REFERENCES "table2" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;', - ); - }); - }); - - describe('unique', () => { - it('should work', () => { - expect( - transformConstraints(ctx, { - type: 'ConstraintAdd', - constraint: { - type: ConstraintType.UNIQUE, - name: 'UQ_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD CONSTRAINT "UQ_test" UNIQUE ("id");'); - }); - }); - - describe('check', () => { - it('should work', () => { - expect( - transformConstraints(ctx, { - type: 'ConstraintAdd', - constraint: { - type: ConstraintType.CHECK, - name: 'CHK_test', - tableName: 'table1', - expression: '"id" IS NOT NULL', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('ALTER TABLE "table1" ADD CONSTRAINT "CHK_test" CHECK ("id" IS NOT NULL);'); - }); - }); - }); - - describe('ConstraintDrop', () => { - it('should work', () => { - expect( - transformConstraints(ctx, { - type: 'ConstraintDrop', - tableName: 'table1', - constraintName: 'PK_test', - reason: 'unknown', - }), - ).toEqual(`ALTER TABLE "table1" DROP CONSTRAINT "PK_test";`); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/constraint.transformer.ts b/server/src/sql-tools/transformers/constraint.transformer.ts deleted file mode 100644 index 94421e56fa..0000000000 --- a/server/src/sql-tools/transformers/constraint.transformer.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { asColumnList } from 'src/sql-tools/helpers'; -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { ActionType, ConstraintType, DatabaseConstraint } from 'src/sql-tools/types'; - -export const transformConstraints: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'ConstraintAdd': { - 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 `ALTER TABLE "${item.tableName}" DROP CONSTRAINT "${item.constraintName}";`; - } - default: { - return false; - } - } -}; - -const withAction = (constraint: { onDelete?: ActionType; onUpdate?: ActionType }) => - ` ON UPDATE ${constraint.onUpdate ?? ActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? ActionType.NO_ACTION}`; - -export const asConstraintBody = (constraint: DatabaseConstraint): string => { - const base = `CONSTRAINT "${constraint.name}"`; - - switch (constraint.type) { - case ConstraintType.PRIMARY_KEY: { - const columnNames = asColumnList(constraint.columnNames); - return `${base} PRIMARY KEY (${columnNames})`; - } - - case ConstraintType.FOREIGN_KEY: { - const columnNames = asColumnList(constraint.columnNames); - const referenceColumnNames = asColumnList(constraint.referenceColumnNames); - return ( - `${base} FOREIGN KEY (${columnNames}) REFERENCES "${constraint.referenceTableName}" (${referenceColumnNames})` + - withAction(constraint) - ); - } - - case ConstraintType.UNIQUE: { - const columnNames = asColumnList(constraint.columnNames); - return `${base} UNIQUE (${columnNames})`; - } - - case ConstraintType.CHECK: { - return `${base} CHECK (${constraint.expression})`; - } - - default: { - throw new Error(`Unknown constraint type: ${(constraint as any).type}`); - } - } -}; diff --git a/server/src/sql-tools/transformers/enum.transformer.ts b/server/src/sql-tools/transformers/enum.transformer.ts deleted file mode 100644 index cd7bddc2d2..0000000000 --- a/server/src/sql-tools/transformers/enum.transformer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseEnum } from 'src/sql-tools/types'; - -export const transformEnums: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'EnumCreate': { - return asEnumCreate(item.enum); - } - - case 'EnumDrop': { - return asEnumDrop(item.enumName); - } - - default: { - return false; - } - } -}; - -const asEnumCreate = ({ name, values }: DatabaseEnum): string => { - return `CREATE TYPE "${name}" AS ENUM (${values.map((value) => `'${value}'`)});`; -}; - -const asEnumDrop = (enumName: string): string => { - return `DROP TYPE "${enumName}";`; -}; diff --git a/server/src/sql-tools/transformers/extension.transformer.spec.ts b/server/src/sql-tools/transformers/extension.transformer.spec.ts deleted file mode 100644 index 2ab0402875..0000000000 --- a/server/src/sql-tools/transformers/extension.transformer.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformExtensions } from 'src/sql-tools/transformers/extension.transformer'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -describe(transformExtensions.name, () => { - describe('ExtensionDrop', () => { - it('should work', () => { - expect( - transformExtensions(ctx, { - type: 'ExtensionDrop', - extensionName: 'cube', - reason: 'unknown', - }), - ).toEqual(`DROP EXTENSION "cube";`); - }); - }); - - describe('ExtensionCreate', () => { - it('should work', () => { - expect( - transformExtensions(ctx, { - type: 'ExtensionCreate', - extension: { - name: 'cube', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual(`CREATE EXTENSION IF NOT EXISTS "cube";`); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/extension.transformer.ts b/server/src/sql-tools/transformers/extension.transformer.ts deleted file mode 100644 index 26e76c1157..0000000000 --- a/server/src/sql-tools/transformers/extension.transformer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseExtension } from 'src/sql-tools/types'; - -export const transformExtensions: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'ExtensionCreate': { - return asExtensionCreate(item.extension); - } - - case 'ExtensionDrop': { - return asExtensionDrop(item.extensionName); - } - - default: { - return false; - } - } -}; - -const asExtensionCreate = (extension: DatabaseExtension): string => { - return `CREATE EXTENSION IF NOT EXISTS "${extension.name}";`; -}; - -const asExtensionDrop = (extensionName: string): string => { - return `DROP EXTENSION "${extensionName}";`; -}; diff --git a/server/src/sql-tools/transformers/function.transformer.spec.ts b/server/src/sql-tools/transformers/function.transformer.spec.ts deleted file mode 100644 index 5b0ba71c7d..0000000000 --- a/server/src/sql-tools/transformers/function.transformer.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformFunctions } from 'src/sql-tools/transformers/function.transformer'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -describe(transformFunctions.name, () => { - describe('FunctionDrop', () => { - it('should work', () => { - expect( - transformFunctions(ctx, { - type: 'FunctionDrop', - functionName: 'test_func', - reason: 'unknown', - }), - ).toEqual(`DROP FUNCTION test_func;`); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/function.transformer.ts b/server/src/sql-tools/transformers/function.transformer.ts deleted file mode 100644 index 42a56cbe13..0000000000 --- a/server/src/sql-tools/transformers/function.transformer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseFunction } from 'src/sql-tools/types'; - -export const transformFunctions: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'FunctionCreate': { - return asFunctionCreate(item.function); - } - - case 'FunctionDrop': { - return asFunctionDrop(item.functionName); - } - - default: { - return false; - } - } -}; - -export const asFunctionCreate = (func: DatabaseFunction): string => { - return func.expression; -}; - -const asFunctionDrop = (functionName: string): string => { - return `DROP FUNCTION ${functionName};`; -}; diff --git a/server/src/sql-tools/transformers/index.transformer.spec.ts b/server/src/sql-tools/transformers/index.transformer.spec.ts deleted file mode 100644 index c9656463bf..0000000000 --- a/server/src/sql-tools/transformers/index.transformer.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformIndexes } from 'src/sql-tools/transformers/index.transformer'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -describe(transformIndexes.name, () => { - describe('IndexCreate', () => { - it('should work', () => { - expect( - transformIndexes(ctx, { - type: 'IndexCreate', - index: { - name: 'IDX_test', - tableName: 'table1', - columnNames: ['column1'], - unique: false, - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('CREATE INDEX "IDX_test" ON "table1" ("column1");'); - }); - - it('should create an unique index', () => { - expect( - transformIndexes(ctx, { - type: 'IndexCreate', - index: { - name: 'IDX_test', - tableName: 'table1', - columnNames: ['column1'], - unique: true, - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('CREATE UNIQUE INDEX "IDX_test" ON "table1" ("column1");'); - }); - - it('should create an index with a custom expression', () => { - expect( - transformIndexes(ctx, { - type: 'IndexCreate', - index: { - name: 'IDX_test', - tableName: 'table1', - unique: false, - expression: '"id" IS NOT NULL', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id" IS NOT NULL);'); - }); - - it('should create an index with a where clause', () => { - expect( - transformIndexes(ctx, { - type: 'IndexCreate', - index: { - name: 'IDX_test', - tableName: 'table1', - columnNames: ['id'], - unique: false, - where: '("id" IS NOT NULL)', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id") WHERE ("id" IS NOT NULL);'); - }); - - it('should create an index with a custom expression', () => { - expect( - transformIndexes(ctx, { - type: 'IndexCreate', - index: { - name: 'IDX_test', - tableName: 'table1', - unique: false, - using: 'gin', - expression: '"id" IS NOT NULL', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual('CREATE INDEX "IDX_test" ON "table1" USING gin ("id" IS NOT NULL);'); - }); - }); - - describe('IndexDrop', () => { - it('should work', () => { - expect( - transformIndexes(ctx, { - type: 'IndexDrop', - indexName: 'IDX_test', - reason: 'unknown', - }), - ).toEqual(`DROP INDEX "IDX_test";`); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/index.transformer.ts b/server/src/sql-tools/transformers/index.transformer.ts deleted file mode 100644 index acd65140ee..0000000000 --- a/server/src/sql-tools/transformers/index.transformer.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { asColumnList } from 'src/sql-tools/helpers'; -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseIndex } from 'src/sql-tools/types'; - -export const transformIndexes: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'IndexCreate': { - return asIndexCreate(item.index); - } - - case 'IndexRename': { - return `ALTER INDEX "${item.oldName}" RENAME TO "${item.newName}";`; - } - - case 'IndexDrop': { - return `DROP INDEX "${item.indexName}";`; - } - - default: { - return false; - } - } -}; - -export const asIndexCreate = (index: DatabaseIndex): string => { - let sql = `CREATE`; - - if (index.unique) { - sql += ' UNIQUE'; - } - - sql += ` INDEX "${index.name}" ON "${index.tableName}"`; - - if (index.columnNames) { - const columnNames = asColumnList(index.columnNames); - sql += ` (${columnNames})`; - } - - if (index.using && index.using !== 'btree') { - sql += ` USING ${index.using}`; - } - - if (index.expression) { - sql += ` (${index.expression})`; - } - - if (index.with) { - sql += ` WITH (${index.with})`; - } - - if (index.where) { - sql += ` WHERE ${index.where}`; - } - - return sql + ';'; -}; diff --git a/server/src/sql-tools/transformers/index.ts b/server/src/sql-tools/transformers/index.ts deleted file mode 100644 index 395d69f2e2..0000000000 --- a/server/src/sql-tools/transformers/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { transformColumns } from 'src/sql-tools/transformers/column.transformer'; -import { transformConstraints } from 'src/sql-tools/transformers/constraint.transformer'; -import { transformEnums } from 'src/sql-tools/transformers/enum.transformer'; -import { transformExtensions } from 'src/sql-tools/transformers/extension.transformer'; -import { transformFunctions } from 'src/sql-tools/transformers/function.transformer'; -import { transformIndexes } from 'src/sql-tools/transformers/index.transformer'; -import { transformOverrides } from 'src/sql-tools/transformers/override.transformer'; -import { transformParameters } from 'src/sql-tools/transformers/parameter.transformer'; -import { transformTables } from 'src/sql-tools/transformers/table.transformer'; -import { transformTriggers } from 'src/sql-tools/transformers/trigger.transformer'; -import { SqlTransformer } from 'src/sql-tools/transformers/types'; - -export const transformers: SqlTransformer[] = [ - transformColumns, - transformConstraints, - transformEnums, - transformExtensions, - transformFunctions, - transformIndexes, - transformParameters, - transformTables, - transformTriggers, - transformOverrides, -]; diff --git a/server/src/sql-tools/transformers/override.transformer.ts b/server/src/sql-tools/transformers/override.transformer.ts deleted file mode 100644 index 1e2e981128..0000000000 --- a/server/src/sql-tools/transformers/override.transformer.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { asJsonString } from 'src/sql-tools/helpers'; -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseOverride } from 'src/sql-tools/types'; - -export const transformOverrides: SqlTransformer = (ctx, item) => { - const tableName = ctx.overrideTableName; - - switch (item.type) { - case 'OverrideCreate': { - return asOverrideCreate(tableName, item.override); - } - - case 'OverrideUpdate': { - return asOverrideUpdate(tableName, item.override); - } - - case 'OverrideDrop': { - return asOverrideDrop(tableName, item.overrideName); - } - - default: { - return false; - } - } -}; - -export const asOverrideCreate = (tableName: string, override: DatabaseOverride): string => { - return `INSERT INTO "${tableName}" ("name", "value") VALUES ('${override.name}', ${asJsonString(override.value)});`; -}; - -export const asOverrideUpdate = (tableName: string, override: DatabaseOverride): string => { - return `UPDATE "${tableName}" SET "value" = ${asJsonString(override.value)} WHERE "name" = '${override.name}';`; -}; - -export const asOverrideDrop = (tableName: string, overrideName: string): string => { - return `DELETE FROM "${tableName}" WHERE "name" = '${overrideName}';`; -}; diff --git a/server/src/sql-tools/transformers/parameter.transformer.ts b/server/src/sql-tools/transformers/parameter.transformer.ts deleted file mode 100644 index d23472f991..0000000000 --- a/server/src/sql-tools/transformers/parameter.transformer.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseParameter } from 'src/sql-tools/types'; - -export const transformParameters: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'ParameterSet': { - return asParameterSet(item.parameter); - } - - case 'ParameterReset': { - return asParameterReset(item.databaseName, item.parameterName); - } - - default: { - return false; - } - } -}; - -const asParameterSet = (parameter: DatabaseParameter): string => { - let sql = ''; - if (parameter.scope === 'database') { - sql += `ALTER DATABASE "${parameter.databaseName}" `; - } - - sql += `SET ${parameter.name} TO ${parameter.value}`; - - return sql; -}; - -const asParameterReset = (databaseName: string, parameterName: string): string => { - return `ALTER DATABASE "${databaseName}" RESET "${parameterName}"`; -}; diff --git a/server/src/sql-tools/transformers/table.transformer.spec.ts b/server/src/sql-tools/transformers/table.transformer.spec.ts deleted file mode 100644 index 0d89fcd278..0000000000 --- a/server/src/sql-tools/transformers/table.transformer.spec.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformTables } from 'src/sql-tools/transformers/table.transformer'; -import { ConstraintType, DatabaseTable } from 'src/sql-tools/types'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -const table1: DatabaseTable = { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - primary: true, - type: 'character varying', - nullable: true, - isArray: false, - synchronize: true, - }, - { - name: 'column2', - primary: false, - tableName: 'table1', - type: 'character varying', - nullable: true, - isArray: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'index1', - tableName: 'table1', - columnNames: ['column2'], - unique: false, - synchronize: true, - }, - ], - constraints: [ - { - name: 'constraint1', - tableName: 'table1', - columnNames: ['column1'], - type: ConstraintType.PRIMARY_KEY, - synchronize: true, - }, - { - name: 'constraint2', - tableName: 'table1', - columnNames: ['column1'], - type: ConstraintType.FOREIGN_KEY, - referenceTableName: 'table2', - referenceColumnNames: ['parentId'], - synchronize: true, - }, - { - name: 'constraint3', - tableName: 'table1', - columnNames: ['column1'], - type: ConstraintType.UNIQUE, - synchronize: true, - }, - ], - triggers: [], - synchronize: true, -}; - -describe(transformTables.name, () => { - describe('TableDrop', () => { - it('should work', () => { - expect( - transformTables(ctx, { - type: 'TableDrop', - tableName: 'table1', - reason: 'unknown', - }), - ).toEqual(`DROP TABLE "table1";`); - }); - }); - - describe('TableCreate', () => { - it('should work', () => { - expect( - transformTables(ctx, { - type: 'TableCreate', - table: table1, - reason: 'unknown', - }), - ).toEqual([ - `CREATE TABLE "table1" ( - "column1" character varying, - "column2" character varying, - CONSTRAINT "constraint1" PRIMARY KEY ("column1"), - CONSTRAINT "constraint2" FOREIGN KEY ("column1") REFERENCES "table2" ("parentId") ON UPDATE NO ACTION ON DELETE NO ACTION, - CONSTRAINT "constraint3" UNIQUE ("column1") -);`, - `CREATE INDEX "index1" ON "table1" ("column2");`, - ]); - }); - - it('should handle a non-nullable column', () => { - expect( - transformTables(ctx, { - type: 'TableCreate', - table: { - name: 'table1', - columns: [ - { - tableName: 'table1', - primary: false, - name: 'column1', - type: 'character varying', - isArray: false, - nullable: false, - synchronize: true, - }, - ], - indexes: [], - constraints: [], - triggers: [], - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual([ - `CREATE TABLE "table1" ( - "column1" character varying NOT NULL -);`, - ]); - }); - - it('should handle a default value', () => { - expect( - transformTables(ctx, { - type: 'TableCreate', - table: { - name: 'table1', - columns: [ - { - tableName: 'table1', - name: 'column1', - primary: false, - type: 'character varying', - isArray: false, - nullable: true, - default: 'uuid_generate_v4()', - synchronize: true, - }, - ], - indexes: [], - constraints: [], - triggers: [], - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual([ - `CREATE TABLE "table1" ( - "column1" character varying DEFAULT uuid_generate_v4() -);`, - ]); - }); - - it('should handle a string with a fixed length', () => { - expect( - transformTables(ctx, { - type: 'TableCreate', - table: { - name: 'table1', - columns: [ - { - tableName: 'table1', - primary: false, - name: 'column1', - type: 'character varying', - length: 2, - isArray: false, - nullable: true, - synchronize: true, - }, - ], - indexes: [], - constraints: [], - triggers: [], - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual([ - `CREATE TABLE "table1" ( - "column1" character varying(2) -);`, - ]); - }); - - it('should handle an array type', () => { - expect( - transformTables(ctx, { - type: 'TableCreate', - table: { - name: 'table1', - columns: [ - { - tableName: 'table1', - primary: false, - name: 'column1', - type: 'character varying', - isArray: true, - nullable: true, - synchronize: true, - }, - ], - indexes: [], - constraints: [], - triggers: [], - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual([ - `CREATE TABLE "table1" ( - "column1" character varying[] -);`, - ]); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/table.transformer.ts b/server/src/sql-tools/transformers/table.transformer.ts deleted file mode 100644 index a81bfc25aa..0000000000 --- a/server/src/sql-tools/transformers/table.transformer.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers'; -import { asColumnAlter } from 'src/sql-tools/transformers/column.transformer'; -import { asConstraintBody } from 'src/sql-tools/transformers/constraint.transformer'; -import { asIndexCreate } from 'src/sql-tools/transformers/index.transformer'; -import { asTriggerCreate } from 'src/sql-tools/transformers/trigger.transformer'; -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseTable } from 'src/sql-tools/types'; - -export const transformTables: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'TableCreate': { - return asTableCreate(item.table); - } - - case 'TableDrop': { - return asTableDrop(item.tableName); - } - - default: { - return false; - } - } -}; - -const asTableCreate = (table: DatabaseTable) => { - const tableName = table.name; - - const items: string[] = []; - for (const column of table.columns) { - items.push(`"${column.name}" ${getColumnType(column)}${getColumnModifiers(column)}`); - } - - for (const constraint of table.constraints) { - items.push(asConstraintBody(constraint)); - } - - const sql = [`CREATE TABLE "${tableName}" (\n ${items.join(',\n ')}\n);`]; - - for (const column of table.columns) { - if (column.comment) { - sql.push(asColumnComment(tableName, column.name, column.comment)); - } - - if (column.storage) { - sql.push(...asColumnAlter(tableName, column.name, { storage: column.storage })); - } - } - - for (const index of table.indexes) { - sql.push(asIndexCreate(index)); - } - - for (const trigger of table.triggers) { - sql.push(asTriggerCreate(trigger)); - } - - return sql; -}; - -const asTableDrop = (tableName: string) => { - return `DROP TABLE "${tableName}";`; -}; diff --git a/server/src/sql-tools/transformers/trigger.transformer.spec.ts b/server/src/sql-tools/transformers/trigger.transformer.spec.ts deleted file mode 100644 index f6ba889c29..0000000000 --- a/server/src/sql-tools/transformers/trigger.transformer.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { transformTriggers } from 'src/sql-tools/transformers/trigger.transformer'; -import { describe, expect, it } from 'vitest'; - -const ctx = new BaseContext({}); - -describe(transformTriggers.name, () => { - describe('TriggerCreate', () => { - it('should work', () => { - expect( - transformTriggers(ctx, { - type: 'TriggerCreate', - trigger: { - name: 'trigger1', - tableName: 'table1', - timing: 'before', - actions: ['update'], - scope: 'row', - functionName: 'function1', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual( - `CREATE OR REPLACE TRIGGER "trigger1" - BEFORE UPDATE ON "table1" - FOR EACH ROW - EXECUTE FUNCTION function1();`, - ); - }); - - it('should work with multiple actions', () => { - expect( - transformTriggers(ctx, { - type: 'TriggerCreate', - trigger: { - name: 'trigger1', - tableName: 'table1', - timing: 'before', - actions: ['update', 'delete'], - scope: 'row', - functionName: 'function1', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual( - `CREATE OR REPLACE TRIGGER "trigger1" - BEFORE UPDATE OR DELETE ON "table1" - FOR EACH ROW - EXECUTE FUNCTION function1();`, - ); - }); - - it('should work with old/new reference table aliases', () => { - expect( - transformTriggers(ctx, { - type: 'TriggerCreate', - trigger: { - name: 'trigger1', - tableName: 'table1', - timing: 'before', - actions: ['update'], - referencingNewTableAs: 'new', - referencingOldTableAs: 'old', - scope: 'row', - functionName: 'function1', - synchronize: true, - }, - reason: 'unknown', - }), - ).toEqual( - `CREATE OR REPLACE TRIGGER "trigger1" - BEFORE UPDATE ON "table1" - REFERENCING OLD TABLE AS "old" NEW TABLE AS "new" - FOR EACH ROW - EXECUTE FUNCTION function1();`, - ); - }); - }); - - describe('TriggerDrop', () => { - it('should work', () => { - expect( - transformTriggers(ctx, { - type: 'TriggerDrop', - tableName: 'table1', - triggerName: 'trigger1', - reason: 'unknown', - }), - ).toEqual(`DROP TRIGGER "trigger1" ON "table1";`); - }); - }); -}); diff --git a/server/src/sql-tools/transformers/trigger.transformer.ts b/server/src/sql-tools/transformers/trigger.transformer.ts deleted file mode 100644 index fca557abfc..0000000000 --- a/server/src/sql-tools/transformers/trigger.transformer.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { SqlTransformer } from 'src/sql-tools/transformers/types'; -import { DatabaseTrigger } from 'src/sql-tools/types'; - -export const transformTriggers: SqlTransformer = (ctx, item) => { - switch (item.type) { - case 'TriggerCreate': { - return asTriggerCreate(item.trigger); - } - - case 'TriggerDrop': { - return asTriggerDrop(item.tableName, item.triggerName); - } - - default: { - return false; - } - } -}; - -export const asTriggerCreate = (trigger: DatabaseTrigger): string => { - const sql: string[] = [ - `CREATE OR REPLACE TRIGGER "${trigger.name}"`, - `${trigger.timing.toUpperCase()} ${trigger.actions.map((action) => action.toUpperCase()).join(' OR ')} ON "${trigger.tableName}"`, - ]; - - if (trigger.referencingOldTableAs || trigger.referencingNewTableAs) { - let statement = `REFERENCING`; - if (trigger.referencingOldTableAs) { - statement += ` OLD TABLE AS "${trigger.referencingOldTableAs}"`; - } - if (trigger.referencingNewTableAs) { - statement += ` NEW TABLE AS "${trigger.referencingNewTableAs}"`; - } - sql.push(statement); - } - - if (trigger.scope) { - sql.push(`FOR EACH ${trigger.scope.toUpperCase()}`); - } - - if (trigger.when) { - sql.push(`WHEN (${trigger.when})`); - } - - sql.push(`EXECUTE FUNCTION ${trigger.functionName}();`); - - return sql.join('\n '); -}; - -export const asTriggerDrop = (tableName: string, triggerName: string): string => { - return `DROP TRIGGER "${triggerName}" ON "${tableName}";`; -}; diff --git a/server/src/sql-tools/transformers/types.ts b/server/src/sql-tools/transformers/types.ts deleted file mode 100644 index 96cbe4d918..0000000000 --- a/server/src/sql-tools/transformers/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { BaseContext } from 'src/sql-tools/contexts/base-context'; -import { SchemaDiff } from 'src/sql-tools/types'; - -export type SqlTransformer = (ctx: BaseContext, item: SchemaDiff) => string | string[] | false; diff --git a/server/src/sql-tools/types.ts b/server/src/sql-tools/types.ts deleted file mode 100644 index 9d93a79ff1..0000000000 --- a/server/src/sql-tools/types.ts +++ /dev/null @@ -1,538 +0,0 @@ -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 & { - /** automatically create indexes on foreign key columns */ - createForeignKeyIndexes?: boolean; - reset?: boolean; - - functions?: boolean; - extensions?: boolean; - parameters?: boolean; - overrides?: boolean; -}; - -export type SchemaFromDatabaseOptions = BaseContextOptions; - -export type SchemaDiffToSqlOptions = BaseContextOptions & { - comments?: boolean; -}; - -export type SchemaDiffOptions = BaseContextOptions & { - tables?: IgnoreOptions; - columns?: IgnoreOptions; - indexes?: IgnoreOptions; - triggers?: IgnoreOptions; - constraints?: IgnoreOptions; - functions?: IgnoreOptions; - enums?: IgnoreOptions; - extensions?: IgnoreOptions; - parameters?: IgnoreOptions; - overrides?: IgnoreOptions; -}; - -export type IgnoreOptions = - | boolean - | { - ignoreExtra?: boolean; - ignoreMissing?: boolean; - }; - -export type Processor = (ctx: ProcessorContext, items: RegisterItem[]) => void; -export type Reader = (ctx: ReaderContext, db: DatabaseClient) => Promise; - -export type PostgresDB = { - pg_am: { - oid: number; - amname: string; - amhandler: string; - amtype: string; - }; - - pg_attribute: { - attrelid: number; - attname: string; - attnum: number; - atttypeid: number; - attstattarget: number; - attstatarget: number; - aanum: number; - }; - - pg_class: { - oid: number; - relname: string; - relkind: string; - relnamespace: string; - reltype: string; - relowner: string; - relam: string; - relfilenode: string; - reltablespace: string; - relpages: number; - reltuples: number; - relallvisible: number; - reltoastrelid: string; - relhasindex: PostgresYesOrNo; - relisshared: PostgresYesOrNo; - relpersistence: string; - }; - - pg_constraint: { - oid: number; - conname: string; - conrelid: string; - contype: string; - connamespace: string; - conkey: number[]; - confkey: number[]; - confrelid: string; - confupdtype: string; - confdeltype: string; - confmatchtype: number; - condeferrable: PostgresYesOrNo; - condeferred: PostgresYesOrNo; - convalidated: PostgresYesOrNo; - conindid: number; - }; - - pg_description: { - objoid: string; - classoid: string; - objsubid: number; - description: string; - }; - - pg_trigger: { - oid: string; - tgisinternal: boolean; - tginitdeferred: boolean; - tgdeferrable: boolean; - tgrelid: string; - tgfoid: string; - tgname: string; - tgenabled: string; - tgtype: number; - tgconstraint: string; - tgdeferred: boolean; - tgargs: Buffer; - tgoldtable: string; - tgnewtable: string; - tgqual: string; - }; - - 'pg_catalog.pg_extension': { - oid: string; - extname: string; - extowner: string; - extnamespace: string; - extrelocatable: boolean; - extversion: string; - extconfig: string[]; - extcondition: string[]; - }; - - pg_enum: { - oid: string; - enumtypid: string; - enumsortorder: number; - enumlabel: string; - }; - - pg_index: { - indexrelid: string; - indrelid: string; - indisready: boolean; - indexprs: string | null; - indpred: string | null; - indkey: number[]; - indisprimary: boolean; - indisunique: boolean; - }; - - pg_indexes: { - schemaname: string; - tablename: string; - indexname: string; - tablespace: string | null; - indexrelid: string; - indexdef: string; - }; - - pg_namespace: { - oid: number; - nspname: string; - nspowner: number; - nspacl: string[]; - }; - - pg_type: { - oid: string; - typname: string; - typnamespace: string; - typowner: string; - typtype: string; - typcategory: string; - typarray: string; - }; - - pg_depend: { - objid: string; - deptype: string; - }; - - pg_proc: { - oid: string; - proname: string; - pronamespace: string; - prokind: string; - }; - - pg_settings: { - name: string; - setting: string; - unit: string | null; - category: string; - short_desc: string | null; - extra_desc: string | null; - context: string; - vartype: string; - source: string; - min_val: string | null; - max_val: string | null; - enumvals: string[] | null; - boot_val: string | null; - reset_val: string | null; - sourcefile: string | null; - sourceline: number | null; - pending_restart: PostgresYesOrNo; - }; - - 'information_schema.tables': { - table_catalog: string; - table_schema: string; - table_name: string; - table_type: 'VIEW' | 'BASE TABLE' | string; - is_insertable_info: PostgresYesOrNo; - is_typed: PostgresYesOrNo; - commit_action: string | null; - }; - - 'information_schema.columns': { - table_catalog: string; - table_schema: string; - table_name: string; - column_name: string; - ordinal_position: number; - column_default: string | null; - is_nullable: PostgresYesOrNo; - data_type: string; - dtd_identifier: string; - character_maximum_length: number | null; - character_octet_length: number | null; - numeric_precision: number | null; - numeric_precision_radix: number | null; - numeric_scale: number | null; - datetime_precision: number | null; - interval_type: string | null; - interval_precision: number | null; - udt_catalog: string; - udt_schema: string; - udt_name: string; - maximum_cardinality: number | null; - is_updatable: PostgresYesOrNo; - }; - - 'information_schema.element_types': { - object_catalog: string; - object_schema: string; - object_name: string; - object_type: string; - collection_type_identifier: string; - data_type: string; - }; - - 'information_schema.routines': { - specific_catalog: string; - specific_schema: string; - specific_name: string; - routine_catalog: string; - routine_schema: string; - routine_name: string; - routine_type: string; - data_type: string; - type_udt_catalog: string; - type_udt_schema: string; - type_udt_name: string; - dtd_identifier: string; - routine_body: string; - routine_definition: string; - external_name: string; - external_language: string; - is_deterministic: PostgresYesOrNo; - security_type: string; - }; -}; - -type PostgresYesOrNo = 'YES' | 'NO'; - -export type DatabaseClient = Kysely; - -export enum ConstraintType { - PRIMARY_KEY = 'primary-key', - FOREIGN_KEY = 'foreign-key', - UNIQUE = 'unique', - CHECK = 'check', -} - -export enum ActionType { - NO_ACTION = 'NO ACTION', - RESTRICT = 'RESTRICT', - CASCADE = 'CASCADE', - SET_NULL = 'SET NULL', - SET_DEFAULT = 'SET DEFAULT', -} - -export type ColumnStorage = 'default' | 'external' | 'extended' | 'main'; - -export type ColumnType = - | 'bigint' - | 'boolean' - | 'bytea' - | 'character' - | 'character varying' - | 'date' - | 'double precision' - | 'integer' - | 'jsonb' - | 'polygon' - | 'text' - | 'time' - | 'time with time zone' - | 'time without time zone' - | 'timestamp' - | 'timestamp with time zone' - | 'timestamp without time zone' - | 'uuid' - | 'vector' - | 'enum' - | 'serial' - | 'real'; - -export type DatabaseSchema = { - databaseName: string; - schemaName: string; - functions: DatabaseFunction[]; - enums: DatabaseEnum[]; - tables: DatabaseTable[]; - extensions: DatabaseExtension[]; - parameters: DatabaseParameter[]; - overrides: DatabaseOverride[]; - warnings: string[]; -}; - -export type DatabaseParameter = { - name: string; - databaseName: string; - value: string | number | null | undefined; - scope: ParameterScope; - synchronize: boolean; -}; - -export type ParameterScope = 'database' | 'user'; - -export type DatabaseOverride = { - name: string; - value: { name: string; type: OverrideType; sql: string }; - synchronize: boolean; -}; - -export type OverrideType = 'function' | 'index' | 'trigger'; - -export type DatabaseEnum = { - name: string; - values: string[]; - synchronize: boolean; -}; - -export type DatabaseFunction = { - name: string; - expression: string; - synchronize: boolean; - override?: DatabaseOverride; -}; - -export type DatabaseExtension = { - name: string; - synchronize: boolean; -}; - -export type DatabaseTable = { - name: string; - columns: DatabaseColumn[]; - indexes: DatabaseIndex[]; - constraints: DatabaseConstraint[]; - triggers: DatabaseTrigger[]; - synchronize: boolean; -}; - -export type DatabaseConstraint = - | DatabasePrimaryKeyConstraint - | DatabaseForeignKeyConstraint - | DatabaseUniqueConstraint - | DatabaseCheckConstraint; - -export type DatabaseColumn = { - primary: boolean; - name: string; - tableName: string; - comment?: string; - - type: ColumnType; - nullable: boolean; - isArray: boolean; - synchronize: boolean; - - default?: string; - length?: number; - storage?: ColumnStorage; - identity?: boolean; - - // enum values - enumName?: string; - - // numeric types - numericPrecision?: number; - numericScale?: number; -}; - -export type ColumnChanges = { - nullable?: boolean; - default?: string; - comment?: string; - storage?: ColumnStorage; -}; - -type ColumBasedConstraint = { - name: string; - tableName: string; - columnNames: string[]; -}; - -export type DatabasePrimaryKeyConstraint = ColumBasedConstraint & { - type: ConstraintType.PRIMARY_KEY; - synchronize: boolean; -}; - -export type DatabaseUniqueConstraint = ColumBasedConstraint & { - type: ConstraintType.UNIQUE; - synchronize: boolean; -}; - -export type DatabaseForeignKeyConstraint = ColumBasedConstraint & { - type: ConstraintType.FOREIGN_KEY; - referenceTableName: string; - referenceColumnNames: string[]; - onUpdate?: ActionType; - onDelete?: ActionType; - synchronize: boolean; -}; - -export type DatabaseCheckConstraint = { - type: ConstraintType.CHECK; - name: string; - tableName: string; - expression: string; - synchronize: boolean; -}; - -export type DatabaseTrigger = { - name: string; - tableName: string; - timing: TriggerTiming; - actions: TriggerAction[]; - scope: TriggerScope; - referencingNewTableAs?: string; - referencingOldTableAs?: string; - when?: string; - functionName: string; - override?: DatabaseOverride; - synchronize: boolean; -}; -export type TriggerTiming = 'before' | 'after' | 'instead of'; -export type TriggerAction = 'insert' | 'update' | 'delete' | 'truncate'; -export type TriggerScope = 'row' | 'statement'; - -export type DatabaseIndex = { - name: string; - tableName: string; - columnNames?: string[]; - expression?: string; - unique: boolean; - using?: string; - with?: string; - where?: string; - override?: DatabaseOverride; - synchronize: boolean; -}; - -export type SchemaDiff = { reason: string } & ( - | { type: 'ExtensionCreate'; extension: DatabaseExtension } - | { type: 'ExtensionDrop'; extensionName: string } - | { type: 'FunctionCreate'; function: DatabaseFunction } - | { type: 'FunctionDrop'; functionName: 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 } - | { type: 'ParameterSet'; parameter: DatabaseParameter } - | { type: 'ParameterReset'; databaseName: string; parameterName: string } - | { type: 'EnumCreate'; enum: DatabaseEnum } - | { type: 'EnumDrop'; enumName: string } - | { type: 'OverrideCreate'; override: DatabaseOverride } - | { type: 'OverrideUpdate'; override: DatabaseOverride } - | { type: 'OverrideDrop'; overrideName: string } -); - -export type CompareFunction = (source: T, target: T) => SchemaDiff[]; -export type Comparer = { - onMissing: (source: T) => SchemaDiff[]; - onExtra: (target: T) => SchemaDiff[]; - onCompare: CompareFunction; - /** 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; -export type Generated = - T extends KyselyColumnType - ? KyselyColumnType - : KyselyColumnType; -export type Int8 = KyselyColumnType; diff --git a/server/src/types.ts b/server/src/types.ts index 3e9ea25957..8cf128f497 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -8,7 +8,6 @@ import { SetMaintenanceModeDto } from 'src/dtos/maintenance.dto'; import { AssetOrder, AssetType, - DatabaseSslMode, ExifOrientation, ImageFormat, JobName, @@ -393,23 +392,6 @@ export type JobItem = export type VectorExtension = (typeof VECTOR_EXTENSIONS)[number]; -export type DatabaseConnectionURL = { - connectionType: 'url'; - url: string; -}; - -export type DatabaseConnectionParts = { - connectionType: 'parts'; - host: string; - port: number; - username: string; - password: string; - database: string; - ssl?: DatabaseSslMode; -}; - -export type DatabaseConnectionParams = DatabaseConnectionURL | DatabaseConnectionParts; - export interface ExtensionVersion { name: VectorExtension; availableVersion: string | null; diff --git a/server/src/utils/database.spec.ts b/server/src/utils/database.spec.ts deleted file mode 100644 index 4c6a82ad8f..0000000000 --- a/server/src/utils/database.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { asPostgresConnectionConfig } from 'src/utils/database'; - -describe('database utils', () => { - describe('asPostgresConnectionConfig', () => { - it('should handle sslmode=require', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=require', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=prefer', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=prefer', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=verify-ca', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-ca', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=verify-full', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-full', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=no-verify', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=no-verify', - }), - ).toMatchObject({ ssl: { rejectUnauthorized: false } }); - }); - - it('should handle ssl=true', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?ssl=true', - }), - ).toMatchObject({ ssl: true }); - }); - - it('should reject invalid ssl', () => { - expect(() => - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?ssl=invalid', - }), - ).toThrowError('Invalid ssl option'); - }); - - it('should handle socket: URLs', () => { - expect( - asPostgresConnectionConfig({ connectionType: 'url', url: 'socket:/run/postgresql?db=database1' }), - ).toMatchObject({ host: '/run/postgresql', database: 'database1' }); - }); - - it('should handle sockets in postgres: URLs', () => { - expect( - asPostgresConnectionConfig({ connectionType: 'url', url: 'postgres:///database2?host=/path/to/socket' }), - ).toMatchObject({ - host: '/path/to/socket', - database: 'database2', - }); - }); - }); -}); diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 9ae15fd7d5..4dd0c9b302 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -1,3 +1,4 @@ +import { createPostgres, DatabaseConnectionParams } from '@immich/sql-tools'; import { AliasedRawBuilder, DeduplicateJoinsPlugin, @@ -14,90 +15,24 @@ import { } from 'kysely'; import { PostgresJSDialect } from 'kysely-postgres-js'; import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; -import { parse } from 'pg-connection-string'; -import postgres, { Notice, PostgresError } from 'postgres'; +import { Notice, PostgresError } from 'postgres'; import { columns, Exif, lockableProperties, LockableProperty, Person } from 'src/database'; import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum'; +import { AssetFileType, AssetVisibility, DatabaseExtension } from 'src/enum'; import { AssetSearchBuilderOptions } from 'src/repositories/search.repository'; import { DB } from 'src/schema'; -import { DatabaseConnectionParams, VectorExtension } from 'src/types'; - -type Ssl = 'require' | 'allow' | 'prefer' | 'verify-full' | boolean | object; - -const isValidSsl = (ssl?: string | boolean | object): ssl is Ssl => - typeof ssl !== 'string' || ssl === 'require' || ssl === 'allow' || ssl === 'prefer' || ssl === 'verify-full'; - -export const asPostgresConnectionConfig = (params: DatabaseConnectionParams) => { - if (params.connectionType === 'parts') { - return { - host: params.host, - port: params.port, - username: params.username, - password: params.password, - database: params.database, - ssl: params.ssl === DatabaseSslMode.Disable ? false : params.ssl, - }; - } - - const { host, port, user, password, database, ...rest } = parse(params.url); - let ssl: Ssl | undefined; - if (rest.ssl) { - if (!isValidSsl(rest.ssl)) { - throw new Error(`Invalid ssl option: ${rest.ssl}`); - } - ssl = rest.ssl; - } - - return { - host: host ?? undefined, - port: port ? Number(port) : undefined, - username: user, - password, - database: database ?? undefined, - ssl, - }; -}; - -export const getKyselyConfig = ( - params: DatabaseConnectionParams, - options: Partial>> = {}, -): KyselyConfig => { - const config = asPostgresConnectionConfig(params); +import { VectorExtension } from 'src/types'; +export const getKyselyConfig = (connection: DatabaseConnectionParams): KyselyConfig => { return { dialect: new PostgresJSDialect({ - postgres: postgres({ - onnotice: (notice: Notice) => { + postgres: createPostgres({ + connection, + onNotice: (notice: Notice) => { if (notice['severity'] !== 'NOTICE') { console.warn('Postgres notice:', notice); } }, - max: 10, - types: { - date: { - to: 1184, - from: [1082, 1114, 1184], - serialize: (x: Date | string) => (x instanceof Date ? x.toISOString() : x), - parse: (x: string) => new Date(x), - }, - bigint: { - to: 20, - from: [20, 1700], - parse: (value: string) => Number.parseInt(value), - serialize: (value: number) => value.toString(), - }, - }, - connection: { - TimeZone: 'UTC', - }, - host: config.host, - port: config.port, - username: config.username, - password: config.password, - database: config.database, - ssl: config.ssl, - ...options, }), }), log(event) { diff --git a/server/test/sql-tools/check-constraint-default-name.stub.ts b/server/test/sql-tools/check-constraint-default-name.stub.ts deleted file mode 100644 index 1cb7c0644a..0000000000 --- a/server/test/sql-tools/check-constraint-default-name.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Check, Column, ConstraintType, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -@Check({ expression: '1=1' }) -export class Table1 { - @Column({ type: 'uuid' }) - id!: string; -} - -export const description = 'should create a check constraint with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.CHECK, - name: 'CHK_8d2ecfd49b984941f6b2589799', - tableName: 'table1', - expression: '1=1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/check-constraint-override-name.stub.ts b/server/test/sql-tools/check-constraint-override-name.stub.ts deleted file mode 100644 index 3752dcfb22..0000000000 --- a/server/test/sql-tools/check-constraint-override-name.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Check, Column, ConstraintType, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -@Check({ name: 'CHK_test', expression: '1=1' }) -export class Table1 { - @Column({ type: 'uuid' }) - id!: string; -} - -export const description = 'should create a check constraint with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.CHECK, - name: 'CHK_test', - tableName: 'table1', - expression: '1=1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-create-date.stub.ts b/server/test/sql-tools/column-create-date.stub.ts deleted file mode 100644 index db5add2a12..0000000000 --- a/server/test/sql-tools/column-create-date.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { CreateDateColumn, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @CreateDateColumn() - createdAt!: string; -} - -export const description = 'should register a table with an created at date column'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'createdAt', - tableName: 'table1', - type: 'timestamp with time zone', - default: 'now()', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-array.stub.ts b/server/test/sql-tools/column-default-array.stub.ts deleted file mode 100644 index b5e9b7d04a..0000000000 --- a/server/test/sql-tools/column-default-array.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'character varying', array: true, default: [] }) - column1!: string[]; -} - -export const description = 'should register a table with a column with a default value (array)'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: true, - primary: false, - synchronize: true, - default: "'{}'", - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-boolean.stub.ts b/server/test/sql-tools/column-default-boolean.stub.ts deleted file mode 100644 index 6454333599..0000000000 --- a/server/test/sql-tools/column-default-boolean.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'boolean', default: true }) - column1!: boolean; -} - -export const description = 'should register a table with a column with a default value (boolean)'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'boolean', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - default: 'true', - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-date.stub.ts b/server/test/sql-tools/column-default-date.stub.ts deleted file mode 100644 index 70f4d520f9..0000000000 --- a/server/test/sql-tools/column-default-date.stub.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -const date = new Date(2023, 0, 1); - -@Table() -export class Table1 { - @Column({ type: 'character varying', default: date }) - column1!: string; -} - -export const description = 'should register a table with a column with a default value (date)'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - default: "'2023-01-01T00:00:00.000Z'", - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-function.stub.ts b/server/test/sql-tools/column-default-function.stub.ts deleted file mode 100644 index 1066a9af21..0000000000 --- a/server/test/sql-tools/column-default-function.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'character varying', default: () => 'now()' }) - column1!: string; -} - -export const description = 'should register a table with a column with a default function'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - default: 'now()', - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-null.stub.ts b/server/test/sql-tools/column-default-null.stub.ts deleted file mode 100644 index b517ca5a96..0000000000 --- a/server/test/sql-tools/column-default-null.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'character varying', default: null }) - column1!: string; -} - -export const description = 'should register a nullable column from a default of null'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: true, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-number.stub.ts b/server/test/sql-tools/column-default-number.stub.ts deleted file mode 100644 index 7954f2498b..0000000000 --- a/server/test/sql-tools/column-default-number.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'integer', default: 0 }) - column1!: string; -} - -export const description = 'should register a table with a column with a default value (number)'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'integer', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - default: '0', - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-default-string.stub.ts b/server/test/sql-tools/column-default-string.stub.ts deleted file mode 100644 index 0d0a18a0eb..0000000000 --- a/server/test/sql-tools/column-default-string.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'character varying', default: 'foo' }) - column1!: string; -} - -export const description = 'should register a table with a column with a default value (string)'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - default: "'foo'", - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-delete-date.stub.ts b/server/test/sql-tools/column-delete-date.stub.ts deleted file mode 100644 index de494ad16e..0000000000 --- a/server/test/sql-tools/column-delete-date.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { DatabaseSchema, DeleteDateColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @DeleteDateColumn() - deletedAt!: string; -} - -export const description = 'should register a table with a deleted at date column'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'deletedAt', - tableName: 'table1', - type: 'timestamp with time zone', - nullable: true, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-enum-type.stub.ts b/server/test/sql-tools/column-enum-type.stub.ts deleted file mode 100644 index 563835d720..0000000000 --- a/server/test/sql-tools/column-enum-type.stub.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Column, DatabaseSchema, registerEnum, Table } from 'src/sql-tools'; - -enum Test { - Foo = 'foo', - Bar = 'bar', -} - -const test_enum = registerEnum({ name: 'test_enum', values: Object.values(Test) }); - -@Table() -export class Table1 { - @Column({ enum: test_enum }) - column1!: string; -} - -export const description = 'should accept an enum type'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [ - { - name: 'test_enum', - values: ['foo', 'bar'], - synchronize: true, - }, - ], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'enum', - enumName: 'test_enum', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-generated-identity.ts b/server/test/sql-tools/column-generated-identity.ts deleted file mode 100644 index 29f7ba969a..0000000000 --- a/server/test/sql-tools/column-generated-identity.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ConstraintType, DatabaseSchema, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryGeneratedColumn({ strategy: 'identity' }) - column1!: string; -} - -export const description = 'should register a table with a generated identity column'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'integer', - identity: true, - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_50c4f9905061b1e506d38a2a380', - tableName: 'table1', - columnNames: ['column1'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-generated-uuid.stub.ts b/server/test/sql-tools/column-generated-uuid.stub.ts deleted file mode 100644 index 0d4d78a84f..0000000000 --- a/server/test/sql-tools/column-generated-uuid.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ConstraintType, DatabaseSchema, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryGeneratedColumn({ strategy: 'uuid' }) - column1!: string; -} - -export const description = 'should register a table with a primary generated uuid column'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'uuid', - default: 'uuid_generate_v4()', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_50c4f9905061b1e506d38a2a380', - tableName: 'table1', - columnNames: ['column1'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-index-name-default.ts b/server/test/sql-tools/column-index-name-default.ts deleted file mode 100644 index ea1fb17fb4..0000000000 --- a/server/test/sql-tools/column-index-name-default.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ index: true }) - column1!: string; -} - -export const description = 'should create a column with an index'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_50c4f9905061b1e506d38a2a38', - columnNames: ['column1'], - tableName: 'table1', - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-index-name.ts b/server/test/sql-tools/column-index-name.ts deleted file mode 100644 index 2a37469600..0000000000 --- a/server/test/sql-tools/column-index-name.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ indexName: 'IDX_test' }) - column1!: string; -} - -export const description = 'should create a column with an index if a name is provided'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_test', - columnNames: ['column1'], - tableName: 'table1', - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-inferred-nullable.stub.ts b/server/test/sql-tools/column-inferred-nullable.stub.ts deleted file mode 100644 index 50810291d3..0000000000 --- a/server/test/sql-tools/column-inferred-nullable.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ default: null }) - column1!: string; -} - -export const description = 'should infer nullable from the default value'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: true, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-name-default.stub.ts b/server/test/sql-tools/column-name-default.stub.ts deleted file mode 100644 index 57e15fc8b6..0000000000 --- a/server/test/sql-tools/column-name-default.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column() - column1!: string; -} - -export const description = 'should register a table with a column with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-name-override.stub.ts b/server/test/sql-tools/column-name-override.stub.ts deleted file mode 100644 index 8741162735..0000000000 --- a/server/test/sql-tools/column-name-override.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ name: 'column-1' }) - column1!: string; -} - -export const description = 'should register a table with a column with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column-1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-name-string.stub.ts b/server/test/sql-tools/column-name-string.stub.ts deleted file mode 100644 index e4a60f51b9..0000000000 --- a/server/test/sql-tools/column-name-string.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column('column-1') - column1!: string; -} - -export const description = 'should register a table with a column with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column-1', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-nullable.stub.ts b/server/test/sql-tools/column-nullable.stub.ts deleted file mode 100644 index 31c72fe97c..0000000000 --- a/server/test/sql-tools/column-nullable.stub.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ nullable: true }) - column1!: string; -} - -export const description = 'should set nullable correctly'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: true, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-string-length.stub.ts b/server/test/sql-tools/column-string-length.stub.ts deleted file mode 100644 index a04cfbd117..0000000000 --- a/server/test/sql-tools/column-string-length.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ length: 2 }) - column1!: string; -} - -export const description = 'should use create a string column with a fixed length'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - length: 2, - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-unique-constraint-name-default.stub.ts b/server/test/sql-tools/column-unique-constraint-name-default.stub.ts deleted file mode 100644 index 076a93bf57..0000000000 --- a/server/test/sql-tools/column-unique-constraint-name-default.stub.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'uuid', unique: true }) - id!: string; -} - -export const description = 'should create a unique key constraint with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.UNIQUE, - name: 'UQ_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-unique-constraint-name-override.stub.ts b/server/test/sql-tools/column-unique-constraint-name-override.stub.ts deleted file mode 100644 index d4c3d5bb6a..0000000000 --- a/server/test/sql-tools/column-unique-constraint-name-override.stub.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column({ type: 'uuid', unique: true, uniqueConstraintName: 'UQ_test' }) - id!: string; -} - -export const description = 'should create a unique key constraint with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.UNIQUE, - name: 'UQ_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/column-update-date.stub.ts b/server/test/sql-tools/column-update-date.stub.ts deleted file mode 100644 index dfa09888c0..0000000000 --- a/server/test/sql-tools/column-update-date.stub.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DatabaseSchema, Table, UpdateDateColumn } from 'src/sql-tools'; - -@Table() -export class Table1 { - @UpdateDateColumn() - updatedAt!: string; -} - -export const description = 'should register a table with an updated at date column'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'updatedAt', - tableName: 'table1', - type: 'timestamp with time zone', - default: 'now()', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/errors/table-duplicate-decorator.stub.ts b/server/test/sql-tools/errors/table-duplicate-decorator.stub.ts deleted file mode 100644 index 3b7a8781b9..0000000000 --- a/server/test/sql-tools/errors/table-duplicate-decorator.stub.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Table } from 'src/sql-tools'; - -@Table({ name: 'table-1' }) -@Table({ name: 'table-2' }) -export class Table1 {} - -export const message = 'Table table-2 has already been registered'; diff --git a/server/test/sql-tools/foreign-key-constraint-column-order.stub.ts b/server/test/sql-tools/foreign-key-constraint-column-order.stub.ts deleted file mode 100644 index 2523701e49..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-column-order.stub.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id1!: string; - - @PrimaryColumn({ type: 'uuid' }) - id2!: string; -} - -@Table() -@ForeignKeyConstraint({ - columns: ['parentId1', 'parentId2'], - referenceTable: () => Table1, - referenceColumns: ['id2', 'id1'], -}) -export class Table2 { - @Column({ type: 'uuid' }) - parentId1!: string; - - @Column({ type: 'uuid' }) - parentId2!: string; -} - -export const description = 'should create a foreign key constraint to the target table'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id1', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - { - name: 'id2', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_e457e8b1301b7bc06ef78188ee4', - tableName: 'table1', - columnNames: ['id1', 'id2'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId1', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - { - name: 'parentId2', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_aed36d04470eba20161aa8b1dc', - tableName: 'table2', - columnNames: ['parentId1', 'parentId2'], - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_aed36d04470eba20161aa8b1dc6', - tableName: 'table2', - columnNames: ['parentId1', 'parentId2'], - referenceColumnNames: ['id2', 'id1'], - referenceTableName: 'table1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/foreign-key-constraint-missing-column.stub.ts b/server/test/sql-tools/foreign-key-constraint-missing-column.stub.ts deleted file mode 100644 index dcd957676a..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-missing-column.stub.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -@Table() -@ForeignKeyConstraint({ columns: ['parentId2'], referenceTable: () => Table1 }) -export class Table2 { - @Column({ type: 'uuid' }) - parentId!: string; -} - -export const description = 'should warn against missing column in foreign key constraint'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: ['[@ForeignKeyConstraint.columns] Unable to find column (Table2.parentId2)'], -}; diff --git a/server/test/sql-tools/foreign-key-constraint-missing-reference-column.stub.ts b/server/test/sql-tools/foreign-key-constraint-missing-reference-column.stub.ts deleted file mode 100644 index 238f4174f3..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-missing-reference-column.stub.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -@Table() -@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1, referenceColumns: ['foo'] }) -export class Table2 { - @Column({ type: 'uuid' }) - parentId!: string; -} - -export const description = 'should warn against missing reference column in foreign key constraint'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: ['[@ForeignKeyConstraint.referenceColumns] Unable to find column (Table1.foo)'], -}; diff --git a/server/test/sql-tools/foreign-key-constraint-missing-reference-table.stub.ts b/server/test/sql-tools/foreign-key-constraint-missing-reference-table.stub.ts deleted file mode 100644 index c6d6fd5b09..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-missing-reference-table.stub.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Column, DatabaseSchema, ForeignKeyConstraint, Table } from 'src/sql-tools'; - -class Foo {} - -@Table() -@ForeignKeyConstraint({ - columns: ['parentId'], - referenceTable: () => Foo, -}) -export class Table1 { - @Column() - parentId!: string; -} - -export const description = 'should warn against missing reference table'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'parentId', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: ['[@ForeignKeyConstraint.referenceTable] Unable to find table (Foo)'], -}; diff --git a/server/test/sql-tools/foreign-key-constraint-multiple-columns.stub.ts b/server/test/sql-tools/foreign-key-constraint-multiple-columns.stub.ts deleted file mode 100644 index a86611bb50..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-multiple-columns.stub.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id1!: string; - - @PrimaryColumn({ type: 'uuid' }) - id2!: string; -} - -@Table() -@ForeignKeyConstraint({ columns: ['parentId1', 'parentId2'], referenceTable: () => Table1 }) -export class Table2 { - @Column({ type: 'uuid' }) - parentId1!: string; - - @Column({ type: 'uuid' }) - parentId2!: string; -} - -export const description = 'should create a foreign key constraint to the target table'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id1', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - { - name: 'id2', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_e457e8b1301b7bc06ef78188ee4', - tableName: 'table1', - columnNames: ['id1', 'id2'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId1', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - { - name: 'parentId2', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_aed36d04470eba20161aa8b1dc', - tableName: 'table2', - columnNames: ['parentId1', 'parentId2'], - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_aed36d04470eba20161aa8b1dc6', - tableName: 'table2', - columnNames: ['parentId1', 'parentId2'], - referenceColumnNames: ['id1', 'id2'], - referenceTableName: 'table1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/foreign-key-constraint-no-index.stub.ts b/server/test/sql-tools/foreign-key-constraint-no-index.stub.ts deleted file mode 100644 index 8bb436c9ac..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-no-index.stub.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -@Table() -@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1, index: false }) -export class Table2 { - @Column({ type: 'uuid' }) - parentId!: string; -} - -export const description = 'should create a foreign key constraint to the target table without an index'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_3fcca5cc563abf256fc346e3ff4', - tableName: 'table2', - columnNames: ['parentId'], - referenceColumnNames: ['id'], - referenceTableName: 'table1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/foreign-key-constraint-no-primary.stub.ts b/server/test/sql-tools/foreign-key-constraint-no-primary.stub.ts deleted file mode 100644 index 6680b13b91..0000000000 --- a/server/test/sql-tools/foreign-key-constraint-no-primary.stub.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @Column() - foo!: string; -} - -@Table() -@ForeignKeyConstraint({ - columns: ['bar'], - referenceTable: () => Table1, - referenceColumns: ['foo'], -}) -export class Table2 { - @Column() - bar!: string; -} - -export const description = 'should create a foreign key constraint to the target table without a primary key'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'foo', - tableName: 'table1', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'bar', - tableName: 'table2', - type: 'character varying', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_7d9c784c98d12365d198d52e4e', - tableName: 'table2', - columnNames: ['bar'], - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_7d9c784c98d12365d198d52e4e6', - tableName: 'table2', - columnNames: ['bar'], - referenceTableName: 'table1', - referenceColumnNames: ['foo'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/foreign-key-constraint.stub.ts b/server/test/sql-tools/foreign-key-constraint.stub.ts deleted file mode 100644 index 518c5aa6bb..0000000000 --- a/server/test/sql-tools/foreign-key-constraint.stub.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, ForeignKeyConstraint, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -@Table() -@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1 }) -export class Table2 { - @Column({ type: 'uuid' }) - parentId!: string; -} - -export const description = 'should create a foreign key constraint to the target table'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_3fcca5cc563abf256fc346e3ff', - tableName: 'table2', - columnNames: ['parentId'], - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_3fcca5cc563abf256fc346e3ff4', - tableName: 'table2', - columnNames: ['parentId'], - referenceColumnNames: ['id'], - referenceTableName: 'table1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/foreign-key-inferred-type.stub.ts b/server/test/sql-tools/foreign-key-inferred-type.stub.ts deleted file mode 100644 index 33f1c2dfde..0000000000 --- a/server/test/sql-tools/foreign-key-inferred-type.stub.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ConstraintType, DatabaseSchema, ForeignKeyColumn, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -@Table() -export class Table2 { - @ForeignKeyColumn(() => Table1, {}) - parentId!: string; -} - -export const description = 'should infer the column type from the reference column'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_3fcca5cc563abf256fc346e3ff', - tableName: 'table2', - columnNames: ['parentId'], - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_3fcca5cc563abf256fc346e3ff4', - tableName: 'table2', - columnNames: ['parentId'], - referenceColumnNames: ['id'], - referenceTableName: 'table1', - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/foreign-key-with-unique-constraint.stub.ts b/server/test/sql-tools/foreign-key-with-unique-constraint.stub.ts deleted file mode 100644 index 288f7c6698..0000000000 --- a/server/test/sql-tools/foreign-key-with-unique-constraint.stub.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ConstraintType, DatabaseSchema, ForeignKeyColumn, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -@Table() -export class Table2 { - @ForeignKeyColumn(() => Table1, { unique: true }) - parentId!: string; -} - -export const description = 'should create a foreign key constraint with a unique constraint'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - { - name: 'table2', - columns: [ - { - name: 'parentId', - tableName: 'table2', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_3fcca5cc563abf256fc346e3ff', - tableName: 'table2', - columnNames: ['parentId'], - unique: false, - synchronize: true, - }, - ], - triggers: [], - constraints: [ - { - type: ConstraintType.FOREIGN_KEY, - name: 'FK_3fcca5cc563abf256fc346e3ff4', - tableName: 'table2', - columnNames: ['parentId'], - referenceColumnNames: ['id'], - referenceTableName: 'table1', - synchronize: true, - }, - { - type: ConstraintType.UNIQUE, - name: 'UQ_3fcca5cc563abf256fc346e3ff4', - tableName: 'table2', - columnNames: ['parentId'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/index-name-default.stub.ts b/server/test/sql-tools/index-name-default.stub.ts deleted file mode 100644 index 1918106eaa..0000000000 --- a/server/test/sql-tools/index-name-default.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Column, DatabaseSchema, Index, Table } from 'src/sql-tools'; - -@Table() -@Index({ columns: ['id'] }) -export class Table1 { - @Column({ type: 'uuid' }) - id!: string; -} - -export const description = 'should create an index with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_b249cc64cf63b8a22557cdc853', - tableName: 'table1', - unique: false, - columnNames: ['id'], - synchronize: true, - }, - ], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/index-name-override.stub.ts b/server/test/sql-tools/index-name-override.stub.ts deleted file mode 100644 index a48dc6e6d6..0000000000 --- a/server/test/sql-tools/index-name-override.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Column, DatabaseSchema, Index, Table } from 'src/sql-tools'; - -@Table() -@Index({ name: 'IDX_test', columns: ['id'] }) -export class Table1 { - @Column({ type: 'uuid' }) - id!: string; -} - -export const description = 'should create an index with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_test', - tableName: 'table1', - unique: false, - columnNames: ['id'], - synchronize: true, - }, - ], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/index-with-expression.ts b/server/test/sql-tools/index-with-expression.ts deleted file mode 100644 index 07755b7f96..0000000000 --- a/server/test/sql-tools/index-with-expression.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Column, DatabaseSchema, Index, Table } from 'src/sql-tools'; - -@Table() -@Index({ expression: '"id" IS NOT NULL' }) -export class Table1 { - @Column({ nullable: true }) - column1!: string; -} - -export const description = 'should create an index based off of an expression'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: true, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_376788d186160c4faa5aaaef63', - tableName: 'table1', - unique: false, - expression: '"id" IS NOT NULL', - synchronize: true, - }, - ], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/index-with-where.stub.ts b/server/test/sql-tools/index-with-where.stub.ts deleted file mode 100644 index 86a4a3089d..0000000000 --- a/server/test/sql-tools/index-with-where.stub.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Column, DatabaseSchema, Index, Table } from 'src/sql-tools'; - -@Table() -@Index({ columns: ['id'], where: '"id" IS NOT NULL' }) -export class Table1 { - @Column({ nullable: true }) - column1!: string; -} - -export const description = 'should create an index with a where clause'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'column1', - tableName: 'table1', - type: 'character varying', - nullable: true, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [ - { - name: 'IDX_9f4e073964c0395f51f9b39900', - tableName: 'table1', - unique: false, - columnNames: ['id'], - where: '"id" IS NOT NULL', - synchronize: true, - }, - ], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/primary-key-constraint-name-default.stub.ts b/server/test/sql-tools/primary-key-constraint-name-default.stub.ts deleted file mode 100644 index 7edfd6ff36..0000000000 --- a/server/test/sql-tools/primary-key-constraint-name-default.stub.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ConstraintType, DatabaseSchema, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table() -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -export const description = 'should add a primary key constraint to the table with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/primary-key-constraint-name-override.stub.ts b/server/test/sql-tools/primary-key-constraint-name-override.stub.ts deleted file mode 100644 index ce1f2a096c..0000000000 --- a/server/test/sql-tools/primary-key-constraint-name-override.stub.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ConstraintType, DatabaseSchema, PrimaryColumn, Table } from 'src/sql-tools'; - -@Table({ primaryConstraintName: 'PK_test' }) -export class Table1 { - @PrimaryColumn({ type: 'uuid' }) - id!: string; -} - -export const description = 'should add a primary key constraint to the table with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: true, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.PRIMARY_KEY, - name: 'PK_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/table-name-default.stub.ts b/server/test/sql-tools/table-name-default.stub.ts deleted file mode 100644 index 4384944364..0000000000 --- a/server/test/sql-tools/table-name-default.stub.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DatabaseSchema, Table } from 'src/sql-tools'; - -@Table() -export class Table1 {} - -export const description = 'should register a table with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/table-name-override.stub.ts b/server/test/sql-tools/table-name-override.stub.ts deleted file mode 100644 index 5bccc429d0..0000000000 --- a/server/test/sql-tools/table-name-override.stub.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DatabaseSchema, Table } from 'src/sql-tools'; - -@Table({ name: 'table-1' }) -export class Table1 {} - -export const description = 'should register a table with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table-1', - columns: [], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/table-name-string-option.stub.ts b/server/test/sql-tools/table-name-string-option.stub.ts deleted file mode 100644 index f394699172..0000000000 --- a/server/test/sql-tools/table-name-string-option.stub.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DatabaseSchema, Table } from 'src/sql-tools'; - -@Table('table-1') -export class Table1 {} - -export const description = 'should register a table with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table-1', - columns: [], - indexes: [], - triggers: [], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/trigger-after-delete.stub.ts b/server/test/sql-tools/trigger-after-delete.stub.ts deleted file mode 100644 index dcceaf25ce..0000000000 --- a/server/test/sql-tools/trigger-after-delete.stub.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AfterDeleteTrigger, DatabaseSchema, registerFunction, Table } from 'src/sql-tools'; - -const test_fn = registerFunction({ - name: 'test_fn', - body: 'SELECT 1;', - returnType: 'character varying', -}); - -@Table() -@AfterDeleteTrigger({ - name: 'my_trigger', - function: test_fn, - scope: 'row', -}) -export class Table1 {} - -export const description = 'should create a trigger'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [expect.any(Object)], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [], - indexes: [], - triggers: [ - { - name: 'my_trigger', - functionName: 'test_fn', - tableName: 'table1', - timing: 'after', - scope: 'row', - actions: ['delete'], - synchronize: true, - }, - ], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/trigger-before-update.stub.ts b/server/test/sql-tools/trigger-before-update.stub.ts deleted file mode 100644 index 6bf6afc721..0000000000 --- a/server/test/sql-tools/trigger-before-update.stub.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { BeforeUpdateTrigger, DatabaseSchema, registerFunction, Table } from 'src/sql-tools'; - -const test_fn = registerFunction({ - name: 'test_fn', - body: 'SELECT 1;', - returnType: 'character varying', -}); - -@Table() -@BeforeUpdateTrigger({ - name: 'my_trigger', - function: test_fn, - scope: 'row', -}) -export class Table1 {} - -export const description = 'should create a trigger '; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [expect.any(Object)], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [], - indexes: [], - triggers: [ - { - name: 'my_trigger', - functionName: 'test_fn', - tableName: 'table1', - timing: 'before', - scope: 'row', - actions: ['update'], - synchronize: true, - }, - ], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/trigger-name-default.stub.ts b/server/test/sql-tools/trigger-name-default.stub.ts deleted file mode 100644 index 382389bcf7..0000000000 --- a/server/test/sql-tools/trigger-name-default.stub.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { DatabaseSchema, Table, Trigger } from 'src/sql-tools'; - -@Table() -@Trigger({ - timing: 'before', - actions: ['insert'], - scope: 'row', - functionName: 'function1', -}) -export class Table1 {} - -export const description = 'should register a trigger with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [], - indexes: [], - triggers: [ - { - name: 'TR_ca71832b10b77ed600ef05df631', - tableName: 'table1', - functionName: 'function1', - actions: ['insert'], - scope: 'row', - timing: 'before', - synchronize: true, - }, - ], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/trigger-name-override.stub.ts b/server/test/sql-tools/trigger-name-override.stub.ts deleted file mode 100644 index 33c4da6b67..0000000000 --- a/server/test/sql-tools/trigger-name-override.stub.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { DatabaseSchema, Table, Trigger } from 'src/sql-tools'; - -@Table() -@Trigger({ - name: 'trigger1', - timing: 'before', - actions: ['insert'], - scope: 'row', - functionName: 'function1', -}) -export class Table1 {} - -export const description = 'should a trigger with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [], - indexes: [], - triggers: [ - { - name: 'trigger1', - tableName: 'table1', - functionName: 'function1', - actions: ['insert'], - scope: 'row', - timing: 'before', - synchronize: true, - }, - ], - constraints: [], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/unique-constraint-name-default.stub.ts b/server/test/sql-tools/unique-constraint-name-default.stub.ts deleted file mode 100644 index 90fbe09224..0000000000 --- a/server/test/sql-tools/unique-constraint-name-default.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, Table, Unique } from 'src/sql-tools'; - -@Table() -@Unique({ columns: ['id'] }) -export class Table1 { - @Column({ type: 'uuid' }) - id!: string; -} - -export const description = 'should add a unique constraint to the table with a default name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.UNIQUE, - name: 'UQ_b249cc64cf63b8a22557cdc8537', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/sql-tools/unique-constraint-name-override.stub.ts b/server/test/sql-tools/unique-constraint-name-override.stub.ts deleted file mode 100644 index 3da7584c0c..0000000000 --- a/server/test/sql-tools/unique-constraint-name-override.stub.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Column, ConstraintType, DatabaseSchema, Table, Unique } from 'src/sql-tools'; - -@Table() -@Unique({ name: 'UQ_test', columns: ['id'] }) -export class Table1 { - @Column({ type: 'uuid' }) - id!: string; -} - -export const description = 'should add a unique constraint to the table with a specific name'; -export const schema: DatabaseSchema = { - databaseName: 'postgres', - schemaName: 'public', - functions: [], - enums: [], - extensions: [], - parameters: [], - overrides: [], - tables: [ - { - name: 'table1', - columns: [ - { - name: 'id', - tableName: 'table1', - type: 'uuid', - nullable: false, - isArray: false, - primary: false, - synchronize: true, - }, - ], - indexes: [], - triggers: [], - constraints: [ - { - type: ConstraintType.UNIQUE, - name: 'UQ_test', - tableName: 'table1', - columnNames: ['id'], - synchronize: true, - }, - ], - synchronize: true, - }, - ], - warnings: [], -}; diff --git a/server/test/utils.ts b/server/test/utils.ts index c2a83c52ae..b3e47b2b7e 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -1,3 +1,4 @@ +import { createPostgres, DatabaseConnectionParams } from '@immich/sql-tools'; import { CallHandler, ExecutionContext, Provider, ValidationPipe } from '@nestjs/common'; import { APP_GUARD, APP_PIPE } from '@nestjs/core'; import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils'; @@ -9,7 +10,6 @@ import multer from 'multer'; import { ChildProcessWithoutNullStreams } from 'node:child_process'; import { Duplex, Readable, Writable } from 'node:stream'; import { PNG } from 'pngjs'; -import postgres from 'postgres'; import { UploadFieldName } from 'src/dtos/asset-media.dto'; import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor'; import { AuthGuard } from 'src/middleware/auth.guard'; @@ -70,7 +70,7 @@ import { DB } from 'src/schema'; import { AuthService } from 'src/services/auth.service'; import { BaseService } from 'src/services/base.service'; import { RepositoryInterface } from 'src/types'; -import { asPostgresConnectionConfig, getKyselyConfig } from 'src/utils/database'; +import { getKyselyConfig } from 'src/utils/database'; import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; @@ -445,13 +445,8 @@ const withDatabase = (url: string, name: string) => url.replace(`/${templateName export const getKyselyDB = async (suffix?: string): Promise> => { const testUrl = process.env.IMMICH_TEST_POSTGRES_URL!; - const sql = postgres({ - ...asPostgresConnectionConfig({ - connectionType: 'url', - url: withDatabase(testUrl, 'postgres'), - }), - max: 1, - }); + const connection = { connectionType: 'url', url: withDatabase(testUrl, 'postgres') } as DatabaseConnectionParams; + const sql = createPostgres({ maxConnections: 1, connection }); const randomSuffix = Math.random().toString(36).slice(2, 7); const dbName = `immich_${suffix ?? randomSuffix}`; diff --git a/server/tsconfig.json b/server/tsconfig.json index e12b614f0d..fcb0ea2a97 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "node16", + "module": "node20", "strict": true, "declaration": true, "removeComments": true,