modified: backend/src/controllers/schemaController.ts

modified:   frontend/src/pages/DatabaseSchema.tsx
This commit is contained in:
2026-01-28 00:04:01 +03:00
parent 4fb92470ce
commit c5b4799dcb
2 changed files with 112 additions and 71 deletions

View File

@@ -31,7 +31,7 @@ interface SchemaData {
updated_at: string;
}
// Parse PostgreSQL schema
// Parse PostgreSQL schema - optimized with bulk queries
async function parsePostgresSchema(databaseId: string): Promise<SchemaData> {
const startTime = Date.now();
console.log(`[Schema] Starting schema parse for database ${databaseId}`);
@@ -41,10 +41,8 @@ async function parsePostgresSchema(databaseId: string): Promise<SchemaData> {
throw new Error('Database not found or not active');
}
console.log(`[Schema] Fetching tables list...`);
const tablesStartTime = Date.now();
// Get all tables with comments via pg_catalog
// Get all tables with comments
console.log(`[Schema] Fetching tables...`);
const tablesResult = await pool.query(`
SELECT
t.table_schema,
@@ -57,68 +55,95 @@ async function parsePostgresSchema(databaseId: string): Promise<SchemaData> {
AND t.table_type = 'BASE TABLE'
ORDER BY t.table_schema, t.table_name
`);
console.log(`[Schema] Found ${tablesResult.rows.length} tables in ${Date.now() - startTime}ms`);
console.log(`[Schema] Found ${tablesResult.rows.length} tables in ${Date.now() - tablesStartTime}ms`);
const tables: TableInfo[] = [];
let processedCount = 0;
for (const table of tablesResult.rows) {
processedCount++;
if (processedCount % 50 === 0 || processedCount === tablesResult.rows.length) {
console.log(`[Schema] Processing table ${processedCount}/${tablesResult.rows.length}: ${table.table_schema}.${table.table_name}`);
}
// Get columns for each table
const columnsResult = await pool.query(`
SELECT
c.column_name,
c.data_type,
c.is_nullable,
c.column_default,
CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as is_primary,
pg_catalog.col_description(pc.oid, c.ordinal_position) as column_comment
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_class pc ON pc.relname = c.table_name
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = pc.relnamespace AND pn.nspname = c.table_schema
LEFT JOIN (
SELECT ku.column_name, ku.table_schema, ku.table_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage ku
ON tc.constraint_name = ku.constraint_name
AND tc.table_schema = ku.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
) pk ON c.column_name = pk.column_name
AND c.table_schema = pk.table_schema
AND c.table_name = pk.table_name
WHERE c.table_schema = $1 AND c.table_name = $2
ORDER BY c.ordinal_position
`, [table.table_schema, table.table_name]);
// Get foreign keys
const fkResult = await pool.query(`
SELECT
kcu.column_name,
ccu.table_name AS references_table,
ccu.column_name AS references_column,
tc.constraint_name
// Get ALL columns in one query
console.log(`[Schema] Fetching all columns...`);
const columnsStartTime = Date.now();
const allColumnsResult = await pool.query(`
SELECT
c.table_schema,
c.table_name,
c.column_name,
c.data_type,
c.is_nullable,
c.column_default,
c.ordinal_position,
CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END as is_primary,
pg_catalog.col_description(pc.oid, c.ordinal_position) as column_comment
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_class pc ON pc.relname = c.table_name
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = pc.relnamespace AND pn.nspname = c.table_schema
LEFT JOIN (
SELECT ku.column_name, ku.table_schema, ku.table_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = $1
AND tc.table_name = $2
`, [table.table_schema, table.table_name]);
JOIN information_schema.key_column_usage ku
ON tc.constraint_name = ku.constraint_name
AND tc.table_schema = ku.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
) pk ON c.column_name = pk.column_name
AND c.table_schema = pk.table_schema
AND c.table_name = pk.table_name
WHERE c.table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY c.table_schema, c.table_name, c.ordinal_position
`);
console.log(`[Schema] Found ${allColumnsResult.rows.length} columns in ${Date.now() - columnsStartTime}ms`);
tables.push({
// Get ALL foreign keys in one query
console.log(`[Schema] Fetching all foreign keys...`);
const fkStartTime = Date.now();
const allFkResult = await pool.query(`
SELECT
tc.table_schema,
tc.table_name,
kcu.column_name,
ccu.table_name AS references_table,
ccu.column_name AS references_column,
tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
`);
console.log(`[Schema] Found ${allFkResult.rows.length} foreign keys in ${Date.now() - fkStartTime}ms`);
// Group columns by table
const columnsByTable = new Map<string, any[]>();
for (const col of allColumnsResult.rows) {
const key = `${col.table_schema}.${col.table_name}`;
if (!columnsByTable.has(key)) {
columnsByTable.set(key, []);
}
columnsByTable.get(key)!.push(col);
}
// Group foreign keys by table
const fkByTable = new Map<string, any[]>();
for (const fk of allFkResult.rows) {
const key = `${fk.table_schema}.${fk.table_name}`;
if (!fkByTable.has(key)) {
fkByTable.set(key, []);
}
fkByTable.get(key)!.push(fk);
}
// Build tables array
console.log(`[Schema] Building schema structure...`);
const tables: TableInfo[] = tablesResult.rows.map(table => {
const key = `${table.table_schema}.${table.table_name}`;
const columns = columnsByTable.get(key) || [];
const fks = fkByTable.get(key) || [];
return {
name: table.table_name,
schema: table.table_schema,
comment: table.table_comment,
columns: columnsResult.rows.map(col => ({
columns: columns.map(col => ({
name: col.column_name,
type: col.data_type,
nullable: col.is_nullable === 'YES',
@@ -126,17 +151,17 @@ async function parsePostgresSchema(databaseId: string): Promise<SchemaData> {
is_primary: col.is_primary,
comment: col.column_comment,
})),
foreign_keys: fkResult.rows.map(fk => ({
foreign_keys: fks.map(fk => ({
column: fk.column_name,
references_table: fk.references_table,
references_column: fk.references_column,
constraint_name: fk.constraint_name,
})),
});
}
};
});
const totalTime = Date.now() - startTime;
console.log(`[Schema] Completed! ${tables.length} tables, ${tables.reduce((acc, t) => acc + t.columns.length, 0)} columns, ${tables.reduce((acc, t) => acc + t.foreign_keys.length, 0)} FKs in ${totalTime}ms`);
console.log(`[Schema] Completed! ${tables.length} tables, ${allColumnsResult.rows.length} columns, ${allFkResult.rows.length} FKs in ${totalTime}ms`);
return {
tables,