mirror of
https://github.com/immich-app/immich.git
synced 2026-07-14 09:00:59 +00:00
chore: migrate to sql-tools library (#26400)
Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
parent
a469d350be
commit
a07d7b0c82
231 changed files with 209 additions and 9151 deletions
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
|
|
@ -343,6 +343,9 @@ importers:
|
||||||
'@extism/extism':
|
'@extism/extism':
|
||||||
specifier: 2.0.0-rc13
|
specifier: 2.0.0-rc13
|
||||||
version: 2.0.0-rc13
|
version: 2.0.0-rc13
|
||||||
|
'@immich/sql-tools':
|
||||||
|
specifier: ^0.2.0
|
||||||
|
version: 0.2.0
|
||||||
'@nestjs/bullmq':
|
'@nestjs/bullmq':
|
||||||
specifier: ^11.0.1
|
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)
|
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':
|
'@immich/justified-layout-wasm@0.4.3':
|
||||||
resolution: {integrity: sha512-fpcQ7zPhP3Cp1bEXhONVYSUeIANa2uzaQFGKufUZQo5FO7aFT77szTVChhlCy4XaVy5R4ZvgSkA/1TJmeORz7Q==}
|
resolution: {integrity: sha512-fpcQ7zPhP3Cp1bEXhONVYSUeIANa2uzaQFGKufUZQo5FO7aFT77szTVChhlCy4XaVy5R4ZvgSkA/1TJmeORz7Q==}
|
||||||
|
|
||||||
|
'@immich/sql-tools@0.2.0':
|
||||||
|
resolution: {integrity: sha512-AH0GRIUYrckNKuid5uO33vgRbGaznhRtArdQ91K310A1oUFjaoNzOaZyZhXwEmft3WYeC1bx4fdgUeois2QH5A==}
|
||||||
|
|
||||||
'@immich/svelte-markdown-preprocess@0.2.1':
|
'@immich/svelte-markdown-preprocess@0.2.1':
|
||||||
resolution: {integrity: sha512-mbr/g75lO8Zh+ELCuYrZP0XB4gf2UbK8rJcGYMYxFJJzMMunV+sm9FqtV1dbwW2dpXzCZGz1XPCEZ6oo526TbA==}
|
resolution: {integrity: sha512-mbr/g75lO8Zh+ELCuYrZP0XB4gf2UbK8rJcGYMYxFJJzMMunV+sm9FqtV1dbwW2dpXzCZGz1XPCEZ6oo526TbA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -8291,6 +8297,10 @@ packages:
|
||||||
postgres:
|
postgres:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
kysely@0.28.11:
|
||||||
|
resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
kysely@0.28.2:
|
kysely@0.28.2:
|
||||||
resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==}
|
resolution: {integrity: sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
@ -14812,6 +14822,13 @@ snapshots:
|
||||||
|
|
||||||
'@immich/justified-layout-wasm@0.4.3': {}
|
'@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)':
|
'@immich/svelte-markdown-preprocess@0.2.1(svelte@5.51.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
front-matter: 4.0.2
|
front-matter: 4.0.2
|
||||||
|
|
@ -20822,12 +20839,20 @@ snapshots:
|
||||||
type-is: 2.0.1
|
type-is: 2.0.1
|
||||||
vary: 1.1.2
|
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):
|
kysely-postgres-js@3.0.0(kysely@0.28.2)(postgres@3.4.8):
|
||||||
dependencies:
|
dependencies:
|
||||||
kysely: 0.28.2
|
kysely: 0.28.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
postgres: 3.4.8
|
postgres: 3.4.8
|
||||||
|
|
||||||
|
kysely@0.28.11: {}
|
||||||
|
|
||||||
kysely@0.28.2: {}
|
kysely@0.28.2: {}
|
||||||
|
|
||||||
langium@3.3.1:
|
langium@3.3.1:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@extism/extism": "2.0.0-rc13",
|
"@extism/extism": "2.0.0-rc13",
|
||||||
|
"@immich/sql-tools": "^0.2.0",
|
||||||
"@nestjs/bullmq": "^11.0.1",
|
"@nestjs/bullmq": "^11.0.1",
|
||||||
"@nestjs/common": "^11.0.4",
|
"@nestjs/common": "^11.0.4",
|
||||||
"@nestjs/core": "^11.0.4",
|
"@nestjs/core": "^11.0.4",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
process.env.DB_URL = process.env.DB_URL || 'postgres://postgres:postgres@localhost:5432/immich';
|
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 { Kysely, sql } from 'kysely';
|
||||||
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
||||||
import { basename, dirname, extname, join } from 'node:path';
|
import { basename, dirname, extname, join } from 'node:path';
|
||||||
import postgres from 'postgres';
|
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import 'src/schema';
|
import 'src/schema';
|
||||||
import { schemaDiff, schemaFromCode, schemaFromDatabase } from 'src/sql-tools';
|
import { getKyselyConfig } from 'src/utils/database';
|
||||||
import { asPostgresConnectionConfig, getKyselyConfig } from 'src/utils/database';
|
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const command = process.argv[2];
|
const command = process.argv[2];
|
||||||
|
|
@ -130,10 +129,9 @@ const create = (path: string, up: string[], down: string[]) => {
|
||||||
const compare = async () => {
|
const compare = async () => {
|
||||||
const configRepository = new ConfigRepository();
|
const configRepository = new ConfigRepository();
|
||||||
const { database } = configRepository.getEnv();
|
const { database } = configRepository.getEnv();
|
||||||
const db = postgres(asPostgresConnectionConfig(database.config));
|
|
||||||
|
|
||||||
const source = schemaFromCode({ overrides: true, namingStrategy: 'default' });
|
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'));
|
console.log(source.warnings.join('\n'));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { asHuman } from '@immich/sql-tools';
|
||||||
import { Command, CommandRunner } from 'nest-commander';
|
import { Command, CommandRunner } from 'nest-commander';
|
||||||
import { ErrorMessages } from 'src/constants';
|
import { ErrorMessages } from 'src/constants';
|
||||||
import { CliService } from 'src/services/cli.service';
|
import { CliService } from 'src/services/cli.service';
|
||||||
import { asHuman } from 'src/sql-tools/schema-diff';
|
|
||||||
|
|
||||||
@Command({
|
@Command({
|
||||||
name: 'schema-check',
|
name: 'schema-check',
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
import { BeforeUpdateTrigger, Column, ColumnOptions } from '@immich/sql-tools';
|
||||||
import { SetMetadata, applyDecorators } from '@nestjs/common';
|
import { SetMetadata, applyDecorators } from '@nestjs/common';
|
||||||
import { ApiOperation, ApiOperationOptions, ApiProperty, ApiPropertyOptions, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiOperationOptions, ApiProperty, ApiPropertyOptions, ApiTags } from '@nestjs/swagger';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ApiCustomExtension, ApiTag, ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum';
|
import { ApiCustomExtension, ApiTag, ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum';
|
||||||
import { EmitEvent } from 'src/repositories/event.repository';
|
import { EmitEvent } from 'src/repositories/event.repository';
|
||||||
import { immich_uuid_v7, updated_at } from 'src/schema/functions';
|
import { immich_uuid_v7, updated_at } from 'src/schema/functions';
|
||||||
import { BeforeUpdateTrigger, Column, ColumnOptions } from 'src/sql-tools';
|
|
||||||
import { setUnion } from 'src/utils/set';
|
import { setUnion } from 'src/utils/set';
|
||||||
|
|
||||||
const GeneratedUuidV7Column = (options: Omit<ColumnOptions, 'type' | 'default' | 'nullable'> = {}) =>
|
const GeneratedUuidV7Column = (options: Omit<ColumnOptions, 'type' | 'default' | 'nullable'> = {}) =>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
import { Transform, Type } from 'class-transformer';
|
import { Transform, Type } from 'class-transformer';
|
||||||
import { IsEnum, IsInt, IsString, Matches } from 'class-validator';
|
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';
|
import { IsIPRange, Optional, ValidateBoolean } from 'src/validation';
|
||||||
|
|
||||||
|
// TODO import from sql-tools once the swagger plugin supports external enums
|
||||||
|
enum DatabaseSslMode {
|
||||||
|
Disable = 'disable',
|
||||||
|
Allow = 'allow',
|
||||||
|
Prefer = 'prefer',
|
||||||
|
Require = 'require',
|
||||||
|
VerifyFull = 'verify-full',
|
||||||
|
}
|
||||||
|
|
||||||
export class EnvDto {
|
export class EnvDto {
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Optional()
|
@Optional()
|
||||||
|
|
|
||||||
|
|
@ -821,14 +821,6 @@ export enum OAuthTokenEndpointAuthMethod {
|
||||||
ClientSecretBasic = 'client_secret_basic',
|
ClientSecretBasic = 'client_secret_basic',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DatabaseSslMode {
|
|
||||||
Disable = 'disable',
|
|
||||||
Allow = 'allow',
|
|
||||||
Prefer = 'prefer',
|
|
||||||
Require = 'require',
|
|
||||||
VerifyFull = 'verify-full',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AssetVisibility {
|
export enum AssetVisibility {
|
||||||
Archive = 'archive',
|
Archive = 'archive',
|
||||||
Timeline = 'timeline',
|
Timeline = 'timeline',
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,9 @@ class Workers {
|
||||||
try {
|
try {
|
||||||
const value = await systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode);
|
const value = await systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode);
|
||||||
return value?.isMaintenanceMode || false;
|
return value?.isMaintenanceMode || false;
|
||||||
} catch (error) {
|
} catch (error: Error | any) {
|
||||||
// Table doesn't exist (migrations haven't run yet)
|
// Table doesn't exist (migrations haven't run yet)
|
||||||
if (error instanceof PostgresError && error.code === '42P01') {
|
if ((error as PostgresError).code === '42P01') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { DatabaseConnectionParams } from '@immich/sql-tools';
|
||||||
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
||||||
import { Inject, Injectable, Optional } from '@nestjs/common';
|
import { Inject, Injectable, Optional } from '@nestjs/common';
|
||||||
import { QueueOptions } from 'bullmq';
|
import { QueueOptions } from 'bullmq';
|
||||||
|
|
@ -21,7 +22,7 @@ import {
|
||||||
LogLevel,
|
LogLevel,
|
||||||
QueueName,
|
QueueName,
|
||||||
} from 'src/enum';
|
} from 'src/enum';
|
||||||
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
|
import { VectorExtension } from 'src/types';
|
||||||
import { setDifference } from 'src/utils/set';
|
import { setDifference } from 'src/utils/set';
|
||||||
|
|
||||||
export interface EnvData {
|
export interface EnvData {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { schemaDiff, schemaFromCode, schemaFromDatabase } from '@immich/sql-tools';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import AsyncLock from 'async-lock';
|
import AsyncLock from 'async-lock';
|
||||||
import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely';
|
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 { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import 'src/schema'; // make sure all schema definitions are imported for schemaFromCode
|
import 'src/schema'; // make sure all schema definitions are imported for schemaFromCode
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { schemaDiff, schemaFromCode, schemaFromDatabase } from 'src/sql-tools';
|
|
||||||
import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types';
|
import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types';
|
||||||
import { vectorIndexQuery } from 'src/utils/database';
|
import { vectorIndexQuery } from 'src/utils/database';
|
||||||
import { isValidInteger } from 'src/validation';
|
import { isValidInteger } from 'src/validation';
|
||||||
|
|
@ -289,7 +289,8 @@ export class DatabaseRepository {
|
||||||
|
|
||||||
async getSchemaDrift() {
|
async getSchemaDrift() {
|
||||||
const source = schemaFromCode({ overrides: true, namingStrategy: 'default' });
|
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, {
|
const drift = schemaDiff(source, target, {
|
||||||
tables: { ignoreExtra: true },
|
tables: { ignoreExtra: true },
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { registerEnum } from '@immich/sql-tools';
|
||||||
import { AssetStatus, AssetVisibility, SourceType } from 'src/enum';
|
import { AssetStatus, AssetVisibility, SourceType } from 'src/enum';
|
||||||
import { registerEnum } from 'src/sql-tools';
|
|
||||||
|
|
||||||
export const assets_status_enum = registerEnum({
|
export const assets_status_enum = registerEnum({
|
||||||
name: 'assets_status_enum',
|
name: 'assets_status_enum',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { registerFunction } from 'src/sql-tools';
|
import { registerFunction } from '@immich/sql-tools';
|
||||||
|
|
||||||
export const immich_uuid_v7 = registerFunction({
|
export const immich_uuid_v7 = registerFunction({
|
||||||
name: 'immich_uuid_v7',
|
name: 'immich_uuid_v7',
|
||||||
|
|
|
||||||
|
|
@ -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 { asset_face_source_type, asset_visibility_enum, assets_status_enum } from 'src/schema/enums';
|
||||||
import {
|
import {
|
||||||
album_delete_audit,
|
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 { UserTable } from 'src/schema/tables/user.table';
|
||||||
import { VersionHistoryTable } from 'src/schema/tables/version-history.table';
|
import { VersionHistoryTable } from 'src/schema/tables/version-history.table';
|
||||||
import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.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'])
|
@Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql'])
|
||||||
@Database({ name: 'immich' })
|
@Database({ name: 'immich' })
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
Check,
|
Check,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -15,7 +10,12 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('activity')
|
||||||
@UpdatedAtTrigger('activity_updatedAt')
|
@UpdatedAtTrigger('activity_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||||
import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('album_asset_audit')
|
@Table('album_asset_audit')
|
||||||
export class AlbumAssetAuditTable {
|
export class AlbumAssetAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +6,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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' })
|
@Table({ name: 'album_asset' })
|
||||||
@UpdatedAtTrigger('album_asset_updatedAt')
|
@UpdatedAtTrigger('album_asset_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('album_audit')
|
@Table('album_audit')
|
||||||
export class AlbumAuditTable {
|
export class AlbumAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('album_user_audit')
|
@Table('album_user_audit')
|
||||||
export class AlbumUserAuditTable {
|
export class AlbumUserAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
AfterInsertTrigger,
|
AfterInsertTrigger,
|
||||||
|
|
@ -13,7 +8,12 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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' })
|
@Table({ name: 'album_user' })
|
||||||
// Pre-existing indices from original album <--> user ManyToMany mapping
|
// Pre-existing indices from original album <--> user ManyToMany mapping
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -14,7 +9,12 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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' })
|
@Table({ name: 'album' })
|
||||||
@UpdatedAtTrigger('album_updatedAt')
|
@UpdatedAtTrigger('album_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
|
||||||
import { Permission } from 'src/enum';
|
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +7,10 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('api_key')
|
||||||
@UpdatedAtTrigger('api_key_updatedAt')
|
@UpdatedAtTrigger('api_key_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('asset_audit')
|
@Table('asset_audit')
|
||||||
export class AssetAuditTable {
|
export class AssetAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
AfterInsertTrigger,
|
AfterInsertTrigger,
|
||||||
|
|
@ -10,7 +7,10 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Table,
|
Table,
|
||||||
Unique,
|
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')
|
@Table('asset_edit')
|
||||||
@AfterInsertTrigger({ scope: 'statement', function: asset_edit_insert, referencingNewTableAs: 'inserted_edit' })
|
@AfterInsertTrigger({ scope: 'statement', function: asset_edit_insert, referencingNewTableAs: 'inserted_edit' })
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { Column, ForeignKeyColumn, Generated, Int8, Table, Timestamp, UpdateDateColumn } from '@immich/sql-tools';
|
||||||
import { LockableProperty } from 'src/database';
|
import { LockableProperty } from 'src/database';
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { Column, ForeignKeyColumn, Generated, Int8, Table, Timestamp, UpdateDateColumn } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('asset_exif')
|
@Table('asset_exif')
|
||||||
@UpdatedAtTrigger('asset_exif_updatedAt')
|
@UpdatedAtTrigger('asset_exif_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('asset_face_audit')
|
@Table('asset_face_audit')
|
||||||
export class AssetFaceAuditTable {
|
export class AssetFaceAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -15,7 +9,13 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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' })
|
@Table({ name: 'asset_face' })
|
||||||
@UpdatedAtTrigger('asset_face_updatedAt')
|
@UpdatedAtTrigger('asset_face_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
|
||||||
import { AssetFileType } from 'src/enum';
|
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -11,7 +8,10 @@ import {
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Unique,
|
Unique,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('asset_file')
|
||||||
@Unique({ columns: ['assetId', 'type', 'isEdited'] })
|
@Unique({ columns: ['assetId', 'type', 'isEdited'] })
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, ForeignKeyColumn, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { Column, ForeignKeyColumn, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('asset_job_status')
|
@Table('asset_job_status')
|
||||||
export class AssetJobStatusTable {
|
export class AssetJobStatusTable {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('asset_metadata_audit')
|
@Table('asset_metadata_audit')
|
||||||
export class AssetMetadataAuditTable {
|
export class AssetMetadataAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -11,7 +7,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@UpdatedAtTrigger('asset_metadata_updated_at')
|
||||||
@Table('asset_metadata')
|
@Table('asset_metadata')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from '@immich/sql-tools';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('asset_ocr')
|
@Table('asset_ocr')
|
||||||
export class AssetOcrTable {
|
export class AssetOcrTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -17,7 +10,14 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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';
|
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database';
|
||||||
|
|
||||||
@Table('asset')
|
@Table('asset')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Index, PrimaryColumn, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { DatabaseAction, EntityType } from 'src/enum';
|
import { DatabaseAction, EntityType } from 'src/enum';
|
||||||
import { Column, CreateDateColumn, Generated, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('audit')
|
@Table('audit')
|
||||||
@Index({ columns: ['ownerId', 'createdAt'] })
|
@Index({ columns: ['ownerId', 'createdAt'] })
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, ForeignKeyColumn, Index, Table } from '@immich/sql-tools';
|
||||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||||
import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table({ name: 'face_search' })
|
@Table({ name: 'face_search' })
|
||||||
@Index({
|
@Index({
|
||||||
|
|
|
||||||
|
|
@ -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' })
|
@Table({ name: 'geodata_places', primaryConstraintName: 'geodata_places_pkey' })
|
||||||
@Index({
|
@Index({
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +8,9 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('library')
|
||||||
@UpdatedAtTrigger('library_updatedAt')
|
@UpdatedAtTrigger('library_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||||
import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('memory_asset_audit')
|
@Table('memory_asset_audit')
|
||||||
export class MemoryAssetAuditTable {
|
export class MemoryAssetAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +6,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('memory_asset')
|
||||||
@UpdatedAtTrigger('memory_asset_updatedAt')
|
@UpdatedAtTrigger('memory_asset_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('memory_audit')
|
@Table('memory_audit')
|
||||||
export class MemoryAuditTable {
|
export class MemoryAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -13,7 +9,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('memory')
|
||||||
@UpdatedAtTrigger('memory_updatedAt')
|
@UpdatedAtTrigger('memory_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, Generated, PrimaryGeneratedColumn, Table, Unique } from '@immich/sql-tools';
|
||||||
import { PathType } from 'src/enum';
|
import { PathType } from 'src/enum';
|
||||||
import { Column, Generated, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('move_history')
|
@Table('move_history')
|
||||||
// path lock (per entity)
|
// path lock (per entity)
|
||||||
|
|
|
||||||
|
|
@ -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' })
|
@Table({ name: 'naturalearth_countries', primaryConstraintName: 'naturalearth_countries_pkey' })
|
||||||
export class NaturalEarthCountriesTable {
|
export class NaturalEarthCountriesTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -11,7 +8,10 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('notification')
|
||||||
@UpdatedAtTrigger('notification_updatedAt')
|
@UpdatedAtTrigger('notification_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, ForeignKeyColumn, Index, Table } from '@immich/sql-tools';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('ocr_search')
|
@Table('ocr_search')
|
||||||
@Index({
|
@Index({
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('partner_audit')
|
@Table('partner_audit')
|
||||||
export class PartnerAuditTable {
|
export class PartnerAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -10,7 +7,10 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('partner')
|
||||||
@UpdatedAtTrigger('partner_updatedAt')
|
@UpdatedAtTrigger('partner_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('person_audit')
|
@Table('person_audit')
|
||||||
export class PersonAuditTable {
|
export class PersonAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Check,
|
Check,
|
||||||
|
|
@ -13,7 +9,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('person')
|
||||||
@UpdatedAtTrigger('person_updatedAt')
|
@UpdatedAtTrigger('person_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { PluginContext } from 'src/enum';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -9,7 +8,8 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'src/sql-tools';
|
} from '@immich/sql-tools';
|
||||||
|
import { PluginContext } from 'src/enum';
|
||||||
import type { JSONSchema } from 'src/types/plugin-schema.types';
|
import type { JSONSchema } from 'src/types/plugin-schema.types';
|
||||||
|
|
||||||
@Table('plugin')
|
@Table('plugin')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -9,7 +7,9 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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' })
|
@Table({ name: 'session' })
|
||||||
@UpdatedAtTrigger('session_updatedAt')
|
@UpdatedAtTrigger('session_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { ForeignKeyColumn, Table } from '@immich/sql-tools';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||||
import { ForeignKeyColumn, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('shared_link_asset')
|
@Table('shared_link_asset')
|
||||||
export class SharedLinkAssetTable {
|
export class SharedLinkAssetTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -9,7 +6,10 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
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')
|
@Table('shared_link')
|
||||||
export class SharedLinkTable {
|
export class SharedLinkTable {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, ForeignKeyColumn, Index, Table } from '@immich/sql-tools';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table({ name: 'smart_search' })
|
@Table({ name: 'smart_search' })
|
||||||
@Index({
|
@Index({
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('stack_audit')
|
@Table('stack_audit')
|
||||||
export class StackAuditTable {
|
export class StackAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -11,7 +7,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('stack')
|
||||||
@UpdatedAtTrigger('stack_updatedAt')
|
@UpdatedAtTrigger('stack_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
|
||||||
import { SyncEntityType } from 'src/enum';
|
|
||||||
import { SessionTable } from 'src/schema/tables/session.table';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +7,10 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('session_sync_checkpoint')
|
||||||
@UpdatedAtTrigger('session_sync_checkpoint_updatedAt')
|
@UpdatedAtTrigger('session_sync_checkpoint_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, PrimaryColumn, Table } from '@immich/sql-tools';
|
||||||
import { SystemMetadataKey } from 'src/enum';
|
import { SystemMetadataKey } from 'src/enum';
|
||||||
import { Column, PrimaryColumn, Table } from 'src/sql-tools';
|
|
||||||
import { SystemMetadata } from 'src/types';
|
import { SystemMetadata } from 'src/types';
|
||||||
|
|
||||||
@Table('system_metadata')
|
@Table('system_metadata')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { ForeignKeyColumn, Index, Table } from '@immich/sql-tools';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { TagTable } from 'src/schema/tables/tag.table';
|
import { TagTable } from 'src/schema/tables/tag.table';
|
||||||
import { ForeignKeyColumn, Index, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Index({ columns: ['assetId', 'tagId'] })
|
@Index({ columns: ['assetId', 'tagId'] })
|
||||||
@Table('tag_asset')
|
@Table('tag_asset')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { ForeignKeyColumn, Table } from '@immich/sql-tools';
|
||||||
import { TagTable } from 'src/schema/tables/tag.table';
|
import { TagTable } from 'src/schema/tables/tag.table';
|
||||||
import { ForeignKeyColumn, Table } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('tag_closure')
|
@Table('tag_closure')
|
||||||
export class TagClosureTable {
|
export class TagClosureTable {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +8,9 @@ import {
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Unique,
|
Unique,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('tag')
|
||||||
@UpdatedAtTrigger('tag_updatedAt')
|
@UpdatedAtTrigger('tag_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('user_audit')
|
@Table('user_audit')
|
||||||
export class UserAuditTable {
|
export class UserAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Column, CreateDateColumn, Generated, Table, Timestamp } from '@immich/sql-tools';
|
||||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
import { UserMetadataKey } from 'src/enum';
|
import { UserMetadataKey } from 'src/enum';
|
||||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
|
||||||
|
|
||||||
@Table('user_metadata_audit')
|
@Table('user_metadata_audit')
|
||||||
export class UserMetadataAuditTable {
|
export class UserMetadataAuditTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -11,7 +7,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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';
|
import { UserMetadata, UserMetadataItem } from 'src/types';
|
||||||
|
|
||||||
@UpdatedAtTrigger('user_metadata_updated_at')
|
@UpdatedAtTrigger('user_metadata_updated_at')
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
|
@ -13,7 +9,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
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')
|
@Table('user')
|
||||||
@UpdatedAtTrigger('user_updatedAt')
|
@UpdatedAtTrigger('user_updatedAt')
|
||||||
|
|
|
||||||
|
|
@ -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')
|
@Table('version_history')
|
||||||
export class VersionHistoryTable {
|
export class VersionHistoryTable {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
|
@ -10,7 +7,10 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
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';
|
import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types';
|
||||||
|
|
||||||
@Table('workflow')
|
@Table('workflow')
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { schemaDiff } from '@immich/sql-tools';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { isAbsolute, join } from 'node:path';
|
import { isAbsolute, join } from 'node:path';
|
||||||
import { SALT_ROUNDS } from 'src/constants';
|
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 { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||||
import { MaintenanceAction, SystemMetadataKey } from 'src/enum';
|
import { MaintenanceAction, SystemMetadataKey } from 'src/enum';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { schemaDiff } from 'src/sql-tools';
|
|
||||||
import { createMaintenanceLoginUrl, generateMaintenanceSecret } from 'src/utils/maintenance';
|
import { createMaintenanceLoginUrl, generateMaintenanceSecret } from 'src/utils/maintenance';
|
||||||
import { getExternalDomain } from 'src/utils/misc';
|
import { getExternalDomain } from 'src/utils/misc';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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<DatabaseColumn>;
|
|
||||||
|
|
||||||
const dropAndRecreateColumn = (source: DatabaseColumn, target: DatabaseColumn, reason: string): SchemaDiff[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: 'ColumnDrop',
|
|
||||||
tableName: target.tableName,
|
|
||||||
columnName: target.name,
|
|
||||||
reason,
|
|
||||||
},
|
|
||||||
{ type: 'ColumnAdd', column: source, reason },
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
@ -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,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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<DatabaseConstraint> => ({
|
|
||||||
getRenameKey: (constraint) => {
|
|
||||||
switch (constraint.type) {
|
|
||||||
case ConstraintType.PRIMARY_KEY:
|
|
||||||
case ConstraintType.UNIQUE: {
|
|
||||||
return asRenameKey([constraint.type, constraint.tableName, ...constraint.columnNames.toSorted()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ConstraintType.FOREIGN_KEY: {
|
|
||||||
return asRenameKey([
|
|
||||||
constraint.type,
|
|
||||||
constraint.tableName,
|
|
||||||
...constraint.columnNames.toSorted(),
|
|
||||||
constraint.referenceTableName,
|
|
||||||
...constraint.referenceColumnNames.toSorted(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
case ConstraintType.CHECK: {
|
|
||||||
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<DatabasePrimaryKeyConstraint> = (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<DatabaseForeignKeyConstraint> = (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<DatabaseUniqueConstraint> = (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<DatabaseCheckConstraint> = (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 },
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
@ -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)',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { Comparer, DatabaseEnum, Reason } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const compareEnums = (): Comparer<DatabaseEnum> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { Comparer, DatabaseExtension, Reason } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const compareExtensions = (): Comparer<DatabaseExtension> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { Comparer, DatabaseFunction, Reason } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const compareFunctions = (): Comparer<DatabaseFunction> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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)',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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<DatabaseIndex> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import { Comparer, DatabaseOverride, Reason } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const compareOverrides = (): Comparer<DatabaseOverride> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { Comparer, DatabaseParameter, Reason } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const compareParameters = (): Comparer<DatabaseParameter> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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<DatabaseTable> => ({
|
|
||||||
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()),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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 }]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { Comparer, DatabaseTrigger, Reason } from 'src/sql-tools/types';
|
|
||||||
|
|
||||||
export const compareTriggers = (): Comparer<DatabaseTrigger> => ({
|
|
||||||
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 [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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<string, DatabaseOverride>();
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<string | symbol, DatabaseColumn> };
|
|
||||||
|
|
||||||
export class ProcessorContext extends BaseContext {
|
|
||||||
constructor(public options: SchemaFromCodeOptions) {
|
|
||||||
options.createForeignKeyIndexes = options.createForeignKeyIndexes ?? true;
|
|
||||||
options.overrides = options.overrides ?? false;
|
|
||||||
super(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
classToTable: WeakMap<Function, DatabaseTable> = new WeakMap();
|
|
||||||
tableToMetadata: WeakMap<DatabaseTable, TableMetadata> = 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})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator';
|
|
||||||
|
|
||||||
export const AfterDeleteTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
|
||||||
TriggerFunction({
|
|
||||||
timing: 'after',
|
|
||||||
actions: ['delete'],
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator';
|
|
||||||
|
|
||||||
export const AfterInsertTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
|
||||||
TriggerFunction({
|
|
||||||
timing: 'after',
|
|
||||||
actions: ['insert'],
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { TriggerFunction, TriggerFunctionOptions } from 'src/sql-tools/decorators/trigger-function.decorator';
|
|
||||||
|
|
||||||
export const BeforeUpdateTrigger = (options: Omit<TriggerFunctionOptions, 'timing' | 'actions'>) =>
|
|
||||||
TriggerFunction({
|
|
||||||
timing: 'before',
|
|
||||||
actions: ['update'],
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
@ -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 } });
|
|
||||||
};
|
|
||||||
|
|
@ -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<unknown> | 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) } });
|
|
||||||
};
|
|
||||||
|
|
@ -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 } });
|
|
||||||
};
|
|
||||||
|
|
@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -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 } });
|
|
||||||
};
|
|
||||||
|
|
@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -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) } });
|
|
||||||
};
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue