refactor: derive legacy schema compat from feature specs
This commit is contained in:
@@ -7,13 +7,13 @@ export interface AccountTokenSchemaInspector {
|
|||||||
execute(sqlText: string): Promise<void>;
|
execute(sqlText: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountTokenColumnCompatibilitySpec = {
|
export type AccountTokenColumnCompatibilitySpec = {
|
||||||
table: 'account_tokens';
|
table: 'account_tokens';
|
||||||
column: string;
|
column: string;
|
||||||
addSql: Record<AccountTokenSchemaDialect, string>;
|
addSql: Record<AccountTokenSchemaDialect, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS: AccountTokenColumnCompatibilitySpec[] = [
|
export const ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS: AccountTokenColumnCompatibilitySpec[] = [
|
||||||
{
|
{
|
||||||
table: 'account_tokens',
|
table: 'account_tokens',
|
||||||
column: 'token_group',
|
column: 'token_group',
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
function readSource(relativePath: string): string {
|
||||||
|
return readFileSync(new URL(relativePath, import.meta.url), 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('legacySchemaCompat architecture boundaries', () => {
|
||||||
|
it('derives feature-owned legacy mutations from compatibility specs instead of hardcoding a second full whitelist', () => {
|
||||||
|
const source = readSource('./legacySchemaCompat.ts');
|
||||||
|
|
||||||
|
expect(source).toContain('ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS');
|
||||||
|
expect(source).toContain('PROXY_FILE_COLUMN_COMPATIBILITY_SPECS');
|
||||||
|
expect(source).toContain('ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS');
|
||||||
|
expect(source).toContain('SITE_COLUMN_COMPATIBILITY_SPECS');
|
||||||
|
expect(source).toContain('SITE_TABLE_COMPATIBILITY_SPECS');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
|
ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS,
|
||||||
ensureAccountTokenSchemaCompatibility,
|
ensureAccountTokenSchemaCompatibility,
|
||||||
type AccountTokenSchemaInspector,
|
type AccountTokenSchemaInspector,
|
||||||
} from './accountTokenSchemaCompatibility.js';
|
} from './accountTokenSchemaCompatibility.js';
|
||||||
import {
|
import {
|
||||||
ensureProxyFileSchemaCompatibility,
|
ensureProxyFileSchemaCompatibility,
|
||||||
|
PROXY_FILE_COLUMN_COMPATIBILITY_SPECS,
|
||||||
|
PROXY_FILE_INDEX_COMPATIBILITY_SPECS,
|
||||||
|
PROXY_FILE_TABLE_COMPATIBILITY_SPECS,
|
||||||
type ProxyFileSchemaInspector,
|
type ProxyFileSchemaInspector,
|
||||||
} from './proxyFileSchemaCompatibility.js';
|
} from './proxyFileSchemaCompatibility.js';
|
||||||
import {
|
import {
|
||||||
ensureRouteGroupingSchemaCompatibility,
|
ensureRouteGroupingSchemaCompatibility,
|
||||||
|
ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS,
|
||||||
|
ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS,
|
||||||
type RouteGroupingSchemaInspector,
|
type RouteGroupingSchemaInspector,
|
||||||
} from './routeGroupingSchemaCompatibility.js';
|
} from './routeGroupingSchemaCompatibility.js';
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +23,8 @@ import {
|
|||||||
} from './sharedIndexSchemaCompatibility.js';
|
} from './sharedIndexSchemaCompatibility.js';
|
||||||
import {
|
import {
|
||||||
ensureSiteSchemaCompatibility,
|
ensureSiteSchemaCompatibility,
|
||||||
|
SITE_COLUMN_COMPATIBILITY_SPECS,
|
||||||
|
SITE_TABLE_COMPATIBILITY_SPECS,
|
||||||
type SiteSchemaInspector,
|
type SiteSchemaInspector,
|
||||||
} from './siteSchemaCompatibility.js';
|
} from './siteSchemaCompatibility.js';
|
||||||
|
|
||||||
@@ -29,36 +37,16 @@ export interface LegacySchemaCompatInspector extends
|
|||||||
AccountTokenSchemaInspector,
|
AccountTokenSchemaInspector,
|
||||||
SharedIndexSchemaInspector {}
|
SharedIndexSchemaInspector {}
|
||||||
|
|
||||||
const LEGACY_COMPAT_TABLES = new Set([
|
const BOOTSTRAP_OWNED_LEGACY_TABLES = [
|
||||||
'account_tokens',
|
'account_tokens',
|
||||||
'token_model_availability',
|
'token_model_availability',
|
||||||
'route_group_sources',
|
|
||||||
'proxy_video_tasks',
|
'proxy_video_tasks',
|
||||||
'proxy_files',
|
|
||||||
'downstream_api_keys',
|
'downstream_api_keys',
|
||||||
'site_disabled_models',
|
];
|
||||||
]);
|
|
||||||
|
|
||||||
const LEGACY_COMPAT_COLUMNS = new Set([
|
const BOOTSTRAP_OWNED_LEGACY_COLUMNS = [
|
||||||
'sites.status',
|
'sites.status',
|
||||||
'sites.proxy_url',
|
|
||||||
'sites.use_system_proxy',
|
|
||||||
'sites.custom_headers',
|
|
||||||
'sites.external_checkin_url',
|
|
||||||
'sites.global_weight',
|
|
||||||
'account_tokens.token_group',
|
|
||||||
'account_tokens.value_status',
|
|
||||||
'token_routes.display_name',
|
|
||||||
'token_routes.display_icon',
|
|
||||||
'token_routes.route_mode',
|
|
||||||
'token_routes.decision_snapshot',
|
|
||||||
'token_routes.decision_refreshed_at',
|
|
||||||
'token_routes.routing_strategy',
|
|
||||||
'route_channels.token_id',
|
'route_channels.token_id',
|
||||||
'route_channels.source_model',
|
|
||||||
'route_channels.last_selected_at',
|
|
||||||
'route_channels.consecutive_fail_count',
|
|
||||||
'route_channels.cooldown_level',
|
|
||||||
'proxy_video_tasks.status_snapshot',
|
'proxy_video_tasks.status_snapshot',
|
||||||
'proxy_video_tasks.upstream_response_meta',
|
'proxy_video_tasks.upstream_response_meta',
|
||||||
'proxy_video_tasks.last_upstream_status',
|
'proxy_video_tasks.last_upstream_status',
|
||||||
@@ -71,16 +59,12 @@ const LEGACY_COMPAT_COLUMNS = new Set([
|
|||||||
'proxy_logs.client_app_name',
|
'proxy_logs.client_app_name',
|
||||||
'proxy_logs.client_confidence',
|
'proxy_logs.client_confidence',
|
||||||
'proxy_logs.downstream_api_key_id',
|
'proxy_logs.downstream_api_key_id',
|
||||||
]);
|
];
|
||||||
|
|
||||||
const LEGACY_COMPAT_INDEXES = new Set([
|
const BOOTSTRAP_OWNED_LEGACY_INDEXES = [
|
||||||
'site_disabled_models_site_model_unique',
|
|
||||||
'site_disabled_models_site_id_idx',
|
|
||||||
'token_model_availability_token_model_unique',
|
'token_model_availability_token_model_unique',
|
||||||
'proxy_video_tasks_public_id_unique',
|
'proxy_video_tasks_public_id_unique',
|
||||||
'proxy_video_tasks_upstream_video_id_idx',
|
'proxy_video_tasks_upstream_video_id_idx',
|
||||||
'proxy_files_public_id_unique',
|
|
||||||
'proxy_files_owner_lookup_idx',
|
|
||||||
'downstream_api_keys_key_unique',
|
'downstream_api_keys_key_unique',
|
||||||
'downstream_api_keys_name_idx',
|
'downstream_api_keys_name_idx',
|
||||||
'downstream_api_keys_enabled_idx',
|
'downstream_api_keys_enabled_idx',
|
||||||
@@ -88,23 +72,53 @@ const LEGACY_COMPAT_INDEXES = new Set([
|
|||||||
'proxy_logs_client_app_id_created_at_idx',
|
'proxy_logs_client_app_id_created_at_idx',
|
||||||
'proxy_logs_client_family_created_at_idx',
|
'proxy_logs_client_family_created_at_idx',
|
||||||
'proxy_logs_downstream_api_key_created_at_idx',
|
'proxy_logs_downstream_api_key_created_at_idx',
|
||||||
'route_group_sources_group_source_unique',
|
];
|
||||||
'route_group_sources_source_route_id_idx',
|
|
||||||
...SHARED_INDEX_COMPATIBILITY_SPECS.map((spec) => spec.indexName),
|
|
||||||
]);
|
|
||||||
|
|
||||||
function normalizeSqlText(sqlText: string): string {
|
function normalizeSqlText(sqlText: string): string {
|
||||||
return sqlText.trim().replace(/\s+/g, ' ').toLowerCase();
|
return sqlText.trim().replace(/\s+/g, ' ').toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEGACY_COMPAT_UPDATES = new Set([
|
function extractIndexName(sqlText: string): string | null {
|
||||||
'UPDATE sites SET use_system_proxy = 0 WHERE use_system_proxy IS NULL;',
|
const match = normalizeSqlText(sqlText).match(
|
||||||
'UPDATE `sites` SET `use_system_proxy` = FALSE WHERE `use_system_proxy` IS NULL',
|
/^create (?:unique )?index(?: if not exists)? [`"]?([a-z0-9_]+)[`"]?/i,
|
||||||
'UPDATE "sites" SET "use_system_proxy" = FALSE WHERE "use_system_proxy" IS NULL',
|
);
|
||||||
'UPDATE sites SET global_weight = 1 WHERE global_weight IS NULL OR global_weight <= 0;',
|
return match?.[1] ?? null;
|
||||||
'UPDATE `sites` SET `global_weight` = 1 WHERE `global_weight` IS NULL OR `global_weight` <= 0',
|
}
|
||||||
'UPDATE "sites" SET "global_weight" = 1 WHERE "global_weight" IS NULL OR "global_weight" <= 0',
|
|
||||||
].map((sqlText) => normalizeSqlText(sqlText)));
|
const LEGACY_COMPAT_TABLES = new Set([
|
||||||
|
...SITE_TABLE_COMPATIBILITY_SPECS.map((spec) => spec.table),
|
||||||
|
...ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS.map((spec) => spec.table),
|
||||||
|
...PROXY_FILE_TABLE_COMPATIBILITY_SPECS.map((spec) => spec.table),
|
||||||
|
...BOOTSTRAP_OWNED_LEGACY_TABLES,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const LEGACY_COMPAT_COLUMNS = new Set([
|
||||||
|
...SITE_COLUMN_COMPATIBILITY_SPECS.map((spec) => `sites.${spec.column}`),
|
||||||
|
...ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS.map((spec) => `${spec.table}.${spec.column}`),
|
||||||
|
...ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS.map((spec) => `${spec.table}.${spec.column}`),
|
||||||
|
...PROXY_FILE_COLUMN_COMPATIBILITY_SPECS.map((spec) => `${spec.table}.${spec.column}`),
|
||||||
|
...BOOTSTRAP_OWNED_LEGACY_COLUMNS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const LEGACY_COMPAT_INDEXES = new Set([
|
||||||
|
...SITE_TABLE_COMPATIBILITY_SPECS.flatMap((spec) => spec.postCreateSql ? Object.values(spec.postCreateSql) : [])
|
||||||
|
.flat()
|
||||||
|
.map((sqlText) => extractIndexName(sqlText))
|
||||||
|
.filter((indexName): indexName is string => Boolean(indexName)),
|
||||||
|
...ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS.flatMap((spec) => Object.values(spec.createSql))
|
||||||
|
.flat()
|
||||||
|
.map((sqlText) => extractIndexName(sqlText))
|
||||||
|
.filter((indexName): indexName is string => Boolean(indexName)),
|
||||||
|
...PROXY_FILE_INDEX_COMPATIBILITY_SPECS.map((spec) => spec.indexName),
|
||||||
|
...BOOTSTRAP_OWNED_LEGACY_INDEXES,
|
||||||
|
...SHARED_INDEX_COMPATIBILITY_SPECS.map((spec) => spec.indexName),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const LEGACY_COMPAT_UPDATES = new Set(
|
||||||
|
SITE_COLUMN_COMPATIBILITY_SPECS
|
||||||
|
.flatMap((spec) => spec.normalizeSql ? Object.values(spec.normalizeSql) : [])
|
||||||
|
.map((sqlText) => normalizeSqlText(sqlText)),
|
||||||
|
);
|
||||||
|
|
||||||
export function classifyLegacyCompatMutation(sqlText: string): LegacySchemaCompatClassification {
|
export function classifyLegacyCompatMutation(sqlText: string): LegacySchemaCompatClassification {
|
||||||
const normalized = normalizeSqlText(sqlText);
|
const normalized = normalizeSqlText(sqlText);
|
||||||
|
|||||||
@@ -8,18 +8,30 @@ export interface ProxyFileSchemaInspector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProxyFileColumnCompatibilitySpec = {
|
type ProxyFileColumnCompatibilitySpec = {
|
||||||
|
table: 'proxy_files';
|
||||||
column: string;
|
column: string;
|
||||||
addSql: Record<ProxyFileSchemaDialect, string>;
|
addSql: Record<ProxyFileSchemaDialect, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CREATE_TABLE_SQL: Record<ProxyFileSchemaDialect, string> = {
|
export type ProxyFileTableCompatibilitySpec = {
|
||||||
sqlite: 'CREATE TABLE IF NOT EXISTS "proxy_files" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "public_id" TEXT NOT NULL, "owner_type" TEXT NOT NULL, "owner_id" TEXT NOT NULL, "filename" TEXT NOT NULL, "mime_type" TEXT NOT NULL, "purpose" TEXT, "byte_size" INTEGER NOT NULL, "sha256" TEXT NOT NULL, "content_base64" TEXT NOT NULL, "created_at" TEXT, "updated_at" TEXT, "deleted_at" TEXT)',
|
table: 'proxy_files';
|
||||||
postgres: 'CREATE TABLE IF NOT EXISTS "proxy_files" ("id" SERIAL PRIMARY KEY, "public_id" TEXT NOT NULL, "owner_type" TEXT NOT NULL, "owner_id" TEXT NOT NULL, "filename" TEXT NOT NULL, "mime_type" TEXT NOT NULL, "purpose" TEXT, "byte_size" INTEGER NOT NULL, "sha256" TEXT NOT NULL, "content_base64" TEXT NOT NULL, "created_at" TEXT, "updated_at" TEXT, "deleted_at" TEXT)',
|
createSql: Record<ProxyFileSchemaDialect, string>;
|
||||||
mysql: 'CREATE TABLE IF NOT EXISTS `proxy_files` (`id` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, `public_id` VARCHAR(191) NOT NULL, `owner_type` VARCHAR(64) NOT NULL, `owner_id` VARCHAR(191) NOT NULL, `filename` TEXT NOT NULL, `mime_type` VARCHAR(191) NOT NULL, `purpose` TEXT NULL, `byte_size` INTEGER NOT NULL, `sha256` VARCHAR(191) NOT NULL, `content_base64` LONGTEXT NOT NULL, `created_at` TEXT NULL, `updated_at` TEXT NULL, `deleted_at` TEXT NULL)',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
export const PROXY_FILE_TABLE_COMPATIBILITY_SPECS: ProxyFileTableCompatibilitySpec[] = [
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
|
createSql: {
|
||||||
|
sqlite: 'CREATE TABLE IF NOT EXISTS "proxy_files" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "public_id" TEXT NOT NULL, "owner_type" TEXT NOT NULL, "owner_id" TEXT NOT NULL, "filename" TEXT NOT NULL, "mime_type" TEXT NOT NULL, "purpose" TEXT, "byte_size" INTEGER NOT NULL, "sha256" TEXT NOT NULL, "content_base64" TEXT NOT NULL, "created_at" TEXT, "updated_at" TEXT, "deleted_at" TEXT)',
|
||||||
|
postgres: 'CREATE TABLE IF NOT EXISTS "proxy_files" ("id" SERIAL PRIMARY KEY, "public_id" TEXT NOT NULL, "owner_type" TEXT NOT NULL, "owner_id" TEXT NOT NULL, "filename" TEXT NOT NULL, "mime_type" TEXT NOT NULL, "purpose" TEXT, "byte_size" INTEGER NOT NULL, "sha256" TEXT NOT NULL, "content_base64" TEXT NOT NULL, "created_at" TEXT, "updated_at" TEXT, "deleted_at" TEXT)',
|
||||||
|
mysql: 'CREATE TABLE IF NOT EXISTS `proxy_files` (`id` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, `public_id` VARCHAR(191) NOT NULL, `owner_type` VARCHAR(64) NOT NULL, `owner_id` VARCHAR(191) NOT NULL, `filename` TEXT NOT NULL, `mime_type` VARCHAR(191) NOT NULL, `purpose` TEXT NULL, `byte_size` INTEGER NOT NULL, `sha256` VARCHAR(191) NOT NULL, `content_base64` LONGTEXT NOT NULL, `created_at` TEXT NULL, `updated_at` TEXT NULL, `deleted_at` TEXT NULL)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PROXY_FILE_COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
||||||
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'public_id',
|
column: 'public_id',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "public_id" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "public_id" TEXT',
|
||||||
@@ -28,6 +40,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'owner_type',
|
column: 'owner_type',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "owner_type" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "owner_type" TEXT',
|
||||||
@@ -36,6 +49,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'owner_id',
|
column: 'owner_id',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "owner_id" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "owner_id" TEXT',
|
||||||
@@ -44,6 +58,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'filename',
|
column: 'filename',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "filename" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "filename" TEXT',
|
||||||
@@ -52,6 +67,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'mime_type',
|
column: 'mime_type',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "mime_type" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "mime_type" TEXT',
|
||||||
@@ -60,6 +76,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'purpose',
|
column: 'purpose',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "purpose" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "purpose" TEXT',
|
||||||
@@ -68,6 +85,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'byte_size',
|
column: 'byte_size',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "byte_size" INTEGER',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "byte_size" INTEGER',
|
||||||
@@ -76,6 +94,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'sha256',
|
column: 'sha256',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "sha256" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "sha256" TEXT',
|
||||||
@@ -84,6 +103,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'content_base64',
|
column: 'content_base64',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "content_base64" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "content_base64" TEXT',
|
||||||
@@ -92,6 +112,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "created_at" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "created_at" TEXT',
|
||||||
@@ -100,6 +121,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'updated_at',
|
column: 'updated_at',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "updated_at" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "updated_at" TEXT',
|
||||||
@@ -108,6 +130,7 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
column: 'deleted_at',
|
column: 'deleted_at',
|
||||||
addSql: {
|
addSql: {
|
||||||
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "deleted_at" TEXT',
|
sqlite: 'ALTER TABLE "proxy_files" ADD COLUMN "deleted_at" TEXT',
|
||||||
@@ -117,21 +140,33 @@ const COLUMN_COMPATIBILITY_SPECS: ProxyFileColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const CREATE_INDEX_SQL: Record<ProxyFileSchemaDialect, string[]> = {
|
export type ProxyFileIndexCompatibilitySpec = {
|
||||||
sqlite: [
|
table: 'proxy_files';
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "proxy_files_public_id_unique" ON "proxy_files" ("public_id")',
|
indexName: string;
|
||||||
'CREATE INDEX IF NOT EXISTS "proxy_files_owner_lookup_idx" ON "proxy_files" ("owner_type", "owner_id", "deleted_at")',
|
createSql: Record<ProxyFileSchemaDialect, string>;
|
||||||
],
|
|
||||||
postgres: [
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS "proxy_files_public_id_unique" ON "proxy_files" ("public_id")',
|
|
||||||
'CREATE INDEX IF NOT EXISTS "proxy_files_owner_lookup_idx" ON "proxy_files" ("owner_type", "owner_id", "deleted_at")',
|
|
||||||
],
|
|
||||||
mysql: [
|
|
||||||
'CREATE UNIQUE INDEX `proxy_files_public_id_unique` ON `proxy_files` (`public_id`(191))',
|
|
||||||
'CREATE INDEX `proxy_files_owner_lookup_idx` ON `proxy_files` (`owner_type`(64), `owner_id`(191), `deleted_at`(191))',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PROXY_FILE_INDEX_COMPATIBILITY_SPECS: ProxyFileIndexCompatibilitySpec[] = [
|
||||||
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
|
indexName: 'proxy_files_public_id_unique',
|
||||||
|
createSql: {
|
||||||
|
sqlite: 'CREATE UNIQUE INDEX IF NOT EXISTS "proxy_files_public_id_unique" ON "proxy_files" ("public_id")',
|
||||||
|
postgres: 'CREATE UNIQUE INDEX IF NOT EXISTS "proxy_files_public_id_unique" ON "proxy_files" ("public_id")',
|
||||||
|
mysql: 'CREATE UNIQUE INDEX `proxy_files_public_id_unique` ON `proxy_files` (`public_id`(191))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
table: 'proxy_files',
|
||||||
|
indexName: 'proxy_files_owner_lookup_idx',
|
||||||
|
createSql: {
|
||||||
|
sqlite: 'CREATE INDEX IF NOT EXISTS "proxy_files_owner_lookup_idx" ON "proxy_files" ("owner_type", "owner_id", "deleted_at")',
|
||||||
|
postgres: 'CREATE INDEX IF NOT EXISTS "proxy_files_owner_lookup_idx" ON "proxy_files" ("owner_type", "owner_id", "deleted_at")',
|
||||||
|
mysql: 'CREATE INDEX `proxy_files_owner_lookup_idx` ON `proxy_files` (`owner_type`(64), `owner_id`(191), `deleted_at`(191))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function normalizeSchemaErrorMessage(error: unknown): string {
|
function normalizeSchemaErrorMessage(error: unknown): string {
|
||||||
if (typeof error === 'object' && error && 'message' in error) {
|
if (typeof error === 'object' && error && 'message' in error) {
|
||||||
return String((error as { message?: unknown }).message || '');
|
return String((error as { message?: unknown }).message || '');
|
||||||
@@ -155,16 +190,17 @@ async function executeIgnoreDuplicate(inspector: ProxyFileSchemaInspector, sqlTe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureProxyFileSchemaCompatibility(inspector: ProxyFileSchemaInspector): Promise<void> {
|
export async function ensureProxyFileSchemaCompatibility(inspector: ProxyFileSchemaInspector): Promise<void> {
|
||||||
if (!await inspector.tableExists('proxy_files')) {
|
const [tableSpec] = PROXY_FILE_TABLE_COMPATIBILITY_SPECS;
|
||||||
await executeIgnoreDuplicate(inspector, CREATE_TABLE_SQL[inspector.dialect]);
|
if (!await inspector.tableExists(tableSpec.table)) {
|
||||||
|
await executeIgnoreDuplicate(inspector, tableSpec.createSql[inspector.dialect]);
|
||||||
} else {
|
} else {
|
||||||
for (const spec of COLUMN_COMPATIBILITY_SPECS) {
|
for (const spec of PROXY_FILE_COLUMN_COMPATIBILITY_SPECS) {
|
||||||
if (await inspector.columnExists('proxy_files', spec.column)) continue;
|
if (await inspector.columnExists(spec.table, spec.column)) continue;
|
||||||
await executeIgnoreDuplicate(inspector, spec.addSql[inspector.dialect]);
|
await executeIgnoreDuplicate(inspector, spec.addSql[inspector.dialect]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const sqlText of CREATE_INDEX_SQL[inspector.dialect]) {
|
for (const spec of PROXY_FILE_INDEX_COMPATIBILITY_SPECS) {
|
||||||
await executeIgnoreDuplicate(inspector, sqlText);
|
await executeIgnoreDuplicate(inspector, spec.createSql[inspector.dialect]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ export interface RouteGroupingSchemaInspector {
|
|||||||
execute(sqlText: string): Promise<void>;
|
execute(sqlText: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteGroupingColumnCompatibilitySpec = {
|
export type RouteGroupingColumnCompatibilitySpec = {
|
||||||
table: 'token_routes' | 'route_channels';
|
table: 'token_routes' | 'route_channels';
|
||||||
column: string;
|
column: string;
|
||||||
addSql: Record<RouteGroupingSchemaDialect, string>;
|
addSql: Record<RouteGroupingSchemaDialect, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RouteGroupingTableCompatibilitySpec = {
|
export type RouteGroupingTableCompatibilitySpec = {
|
||||||
table: 'route_group_sources';
|
table: 'route_group_sources';
|
||||||
createSql: Record<RouteGroupingSchemaDialect, string[]>;
|
createSql: Record<RouteGroupingSchemaDialect, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS: RouteGroupingColumnCompatibilitySpec[] = [
|
export const ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS: RouteGroupingColumnCompatibilitySpec[] = [
|
||||||
{
|
{
|
||||||
table: 'token_routes',
|
table: 'token_routes',
|
||||||
column: 'display_name',
|
column: 'display_name',
|
||||||
@@ -111,7 +111,7 @@ const ROUTE_GROUPING_COLUMN_COMPATIBILITY_SPECS: RouteGroupingColumnCompatibilit
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS: RouteGroupingTableCompatibilitySpec[] = [
|
export const ROUTE_GROUPING_TABLE_COMPATIBILITY_SPECS: RouteGroupingTableCompatibilitySpec[] = [
|
||||||
{
|
{
|
||||||
table: 'route_group_sources',
|
table: 'route_group_sources',
|
||||||
createSql: {
|
createSql: {
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ export interface SiteSchemaInspector {
|
|||||||
execute(sqlText: string): Promise<void>;
|
execute(sqlText: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SiteColumnCompatibilitySpec = {
|
export type SiteColumnCompatibilitySpec = {
|
||||||
column: string;
|
column: string;
|
||||||
addSql: Record<SiteSchemaDialect, string>;
|
addSql: Record<SiteSchemaDialect, string>;
|
||||||
normalizeSql?: Record<SiteSchemaDialect, string>;
|
normalizeSql?: Record<SiteSchemaDialect, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SiteTableCompatibilitySpec = {
|
export type SiteTableCompatibilitySpec = {
|
||||||
table: string;
|
table: string;
|
||||||
createSql: Record<SiteSchemaDialect, string>;
|
createSql: Record<SiteSchemaDialect, string>;
|
||||||
postCreateSql?: Record<SiteSchemaDialect, string[]>;
|
postCreateSql?: Record<SiteSchemaDialect, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SITE_COLUMN_COMPATIBILITY_SPECS: SiteColumnCompatibilitySpec[] = [
|
export const SITE_COLUMN_COMPATIBILITY_SPECS: SiteColumnCompatibilitySpec[] = [
|
||||||
{
|
{
|
||||||
column: 'proxy_url',
|
column: 'proxy_url',
|
||||||
addSql: {
|
addSql: {
|
||||||
@@ -72,7 +72,7 @@ const SITE_COLUMN_COMPATIBILITY_SPECS: SiteColumnCompatibilitySpec[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const SITE_TABLE_COMPATIBILITY_SPECS: SiteTableCompatibilitySpec[] = [
|
export const SITE_TABLE_COMPATIBILITY_SPECS: SiteTableCompatibilitySpec[] = [
|
||||||
{
|
{
|
||||||
table: 'site_disabled_models',
|
table: 'site_disabled_models',
|
||||||
createSql: {
|
createSql: {
|
||||||
|
|||||||
Reference in New Issue
Block a user