phase 2.2 테이블 타입관리 쿼리로 변경 완료

This commit is contained in:
kjs 2025-09-30 18:01:57 +09:00
parent f9f31c7bd3
commit c8c05f1c0d
1 changed files with 286 additions and 335 deletions

View File

@ -1,4 +1,4 @@
import prisma from "../config/database"; import { query, queryOne, transaction } from "../database/db";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { cache, CacheKeys } from "../utils/cache"; import { cache, CacheKeys } from "../utils/cache";
import { import {
@ -28,13 +28,14 @@ export class TableManagementService {
): Promise<{ isCodeType: boolean; codeCategory?: string }> { ): Promise<{ isCodeType: boolean; codeCategory?: string }> {
try { try {
// column_labels 테이블에서 해당 컬럼의 web_type이 'code'인지 확인 // column_labels 테이블에서 해당 컬럼의 web_type이 'code'인지 확인
const result = await prisma.$queryRaw` const result = await query(
SELECT web_type, code_category `SELECT web_type, code_category
FROM column_labels FROM column_labels
WHERE table_name = ${tableName} WHERE table_name = $1
AND column_name = ${columnName} AND column_name = $2
AND web_type = 'code' AND web_type = 'code'`,
`; [tableName, columnName]
);
if (Array.isArray(result) && result.length > 0) { if (Array.isArray(result) && result.length > 0) {
const row = result[0] as any; const row = result[0] as any;
@ -70,8 +71,8 @@ export class TableManagementService {
} }
// information_schema는 여전히 $queryRaw 사용 // information_schema는 여전히 $queryRaw 사용
const rawTables = await prisma.$queryRaw<any[]>` const rawTables = await query<any>(
SELECT `SELECT
t.table_name as "tableName", t.table_name as "tableName",
COALESCE(tl.table_label, t.table_name) as "displayName", COALESCE(tl.table_label, t.table_name) as "displayName",
COALESCE(tl.description, '') as "description", COALESCE(tl.description, '') as "description",
@ -83,8 +84,8 @@ export class TableManagementService {
AND t.table_type = 'BASE TABLE' AND t.table_type = 'BASE TABLE'
AND t.table_name NOT LIKE 'pg_%' AND t.table_name NOT LIKE 'pg_%'
AND t.table_name NOT LIKE 'sql_%' AND t.table_name NOT LIKE 'sql_%'
ORDER BY t.table_name ORDER BY t.table_name`
`; );
// BigInt를 Number로 변환하여 JSON 직렬화 문제 해결 // BigInt를 Number로 변환하여 JSON 직렬화 문제 해결
const tables: TableInfo[] = rawTables.map((table) => ({ const tables: TableInfo[] = rawTables.map((table) => ({
@ -147,11 +148,12 @@ export class TableManagementService {
// 전체 컬럼 수 조회 (캐시 확인) // 전체 컬럼 수 조회 (캐시 확인)
let total = cache.get<number>(countCacheKey); let total = cache.get<number>(countCacheKey);
if (!total) { if (!total) {
const totalResult = await prisma.$queryRaw<[{ count: bigint }]>` const totalResult = await query<{ count: bigint }>(
SELECT COUNT(*) as count `SELECT COUNT(*) as count
FROM information_schema.columns c FROM information_schema.columns c
WHERE c.table_name = ${tableName} WHERE c.table_name = $1`,
`; [tableName]
);
total = Number(totalResult[0].count); total = Number(totalResult[0].count);
// 컬럼 수는 자주 변하지 않으므로 30분 캐시 // 컬럼 수는 자주 변하지 않으므로 30분 캐시
cache.set(countCacheKey, total, 30 * 60 * 1000); cache.set(countCacheKey, total, 30 * 60 * 1000);
@ -159,8 +161,8 @@ export class TableManagementService {
// 페이지네이션 적용한 컬럼 조회 // 페이지네이션 적용한 컬럼 조회
const offset = (page - 1) * size; const offset = (page - 1) * size;
const rawColumns = await prisma.$queryRaw<any[]>` const rawColumns = await query<any>(
SELECT `SELECT
c.column_name as "columnName", c.column_name as "columnName",
COALESCE(cl.column_label, c.column_name) as "displayName", COALESCE(cl.column_label, c.column_name) as "displayName",
c.data_type as "dataType", c.data_type as "dataType",
@ -195,12 +197,13 @@ export class TableManagementService {
ON tc.constraint_name = kcu.constraint_name ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY' WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_name = ${tableName} AND tc.table_name = $1
) pk ON c.column_name = pk.column_name AND c.table_name = pk.table_name ) pk ON c.column_name = pk.column_name AND c.table_name = pk.table_name
WHERE c.table_name = ${tableName} WHERE c.table_name = $1
ORDER BY c.ordinal_position ORDER BY c.ordinal_position
LIMIT ${size} OFFSET ${offset} LIMIT $2 OFFSET $3`,
`; [tableName, size, offset]
);
// BigInt를 Number로 변환하여 JSON 직렬화 문제 해결 // BigInt를 Number로 변환하여 JSON 직렬화 문제 해결
const columns: ColumnTypeInfo[] = rawColumns.map((column) => ({ const columns: ColumnTypeInfo[] = rawColumns.map((column) => ({
@ -251,15 +254,12 @@ export class TableManagementService {
try { try {
logger.info(`테이블 라벨 자동 추가 시작: ${tableName}`); logger.info(`테이블 라벨 자동 추가 시작: ${tableName}`);
await prisma.table_labels.upsert({ await query(
where: { table_name: tableName }, `INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
update: {}, // 이미 존재하면 변경하지 않음 VALUES ($1, $2, $3, NOW(), NOW())
create: { ON CONFLICT (table_name) DO NOTHING`,
table_name: tableName, [tableName, tableName, ""]
table_label: tableName, );
description: "",
},
});
logger.info(`테이블 라벨 자동 추가 완료: ${tableName}`); logger.info(`테이블 라벨 자동 추가 완료: ${tableName}`);
} catch (error) { } catch (error) {
@ -282,15 +282,16 @@ export class TableManagementService {
logger.info(`테이블 라벨 업데이트 시작: ${tableName}`); logger.info(`테이블 라벨 업데이트 시작: ${tableName}`);
// table_labels 테이블에 UPSERT // table_labels 테이블에 UPSERT
await prisma.$executeRaw` await query(
INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) `INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES (${tableName}, ${displayName}, ${description || ""}, NOW(), NOW()) VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (table_name) ON CONFLICT (table_name)
DO UPDATE SET DO UPDATE SET
table_label = EXCLUDED.table_label, table_label = EXCLUDED.table_label,
description = EXCLUDED.description, description = EXCLUDED.description,
updated_date = NOW() updated_date = NOW()`,
`; [tableName, displayName, description || ""]
);
// 캐시 무효화 // 캐시 무효화
cache.delete(CacheKeys.TABLE_LIST); cache.delete(CacheKeys.TABLE_LIST);
@ -320,43 +321,40 @@ export class TableManagementService {
await this.insertTableIfNotExists(tableName); await this.insertTableIfNotExists(tableName);
// column_labels 업데이트 또는 생성 // column_labels 업데이트 또는 생성
await prisma.column_labels.upsert({ await query(
where: { `INSERT INTO column_labels (
table_name_column_name: { table_name, column_name, column_label, input_type, detail_settings,
table_name: tableName, code_category, code_value, reference_table, reference_column,
column_name: columnName, display_column, display_order, is_visible, created_date, updated_date
}, ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW())
}, ON CONFLICT (table_name, column_name)
update: { DO UPDATE SET
column_label: settings.columnLabel, column_label = EXCLUDED.column_label,
input_type: settings.inputType, input_type = EXCLUDED.input_type,
detail_settings: settings.detailSettings, detail_settings = EXCLUDED.detail_settings,
code_category: settings.codeCategory, code_category = EXCLUDED.code_category,
code_value: settings.codeValue, code_value = EXCLUDED.code_value,
reference_table: settings.referenceTable, reference_table = EXCLUDED.reference_table,
reference_column: settings.referenceColumn, reference_column = EXCLUDED.reference_column,
display_column: settings.displayColumn, // 🎯 Entity 조인에서 표시할 컬럼명 display_column = EXCLUDED.display_column,
display_order: settings.displayOrder || 0, display_order = EXCLUDED.display_order,
is_visible: is_visible = EXCLUDED.is_visible,
settings.isVisible !== undefined ? settings.isVisible : true, updated_date = NOW()`,
updated_date: new Date(), [
}, tableName,
create: { columnName,
table_name: tableName, settings.columnLabel,
column_name: columnName, settings.inputType,
column_label: settings.columnLabel, settings.detailSettings,
input_type: settings.inputType, settings.codeCategory,
detail_settings: settings.detailSettings, settings.codeValue,
code_category: settings.codeCategory, settings.referenceTable,
code_value: settings.codeValue, settings.referenceColumn,
reference_table: settings.referenceTable, settings.displayColumn,
reference_column: settings.referenceColumn, settings.displayOrder || 0,
display_column: settings.displayColumn, // 🎯 Entity 조인에서 표시할 컬럼명 settings.isVisible !== undefined ? settings.isVisible : true,
display_order: settings.displayOrder || 0, ]
is_visible: );
settings.isVisible !== undefined ? settings.isVisible : true,
},
});
// 캐시 무효화 - 해당 테이블의 컬럼 캐시 삭제 // 캐시 무효화 - 해당 테이블의 컬럼 캐시 삭제
cache.deleteByPattern(`table_columns:${tableName}:`); cache.deleteByPattern(`table_columns:${tableName}:`);
@ -387,8 +385,8 @@ export class TableManagementService {
`전체 컬럼 설정 일괄 업데이트 시작: ${tableName}, ${columnSettings.length}` `전체 컬럼 설정 일괄 업데이트 시작: ${tableName}, ${columnSettings.length}`
); );
// Prisma 트랜잭션 사용 // Raw Query 트랜잭션 사용
await prisma.$transaction(async (tx) => { await transaction(async (client) => {
// 테이블이 table_labels에 없으면 자동 추가 // 테이블이 table_labels에 없으면 자동 추가
await this.insertTableIfNotExists(tableName); await this.insertTableIfNotExists(tableName);
@ -434,16 +432,18 @@ export class TableManagementService {
try { try {
logger.info(`테이블 라벨 정보 조회 시작: ${tableName}`); logger.info(`테이블 라벨 정보 조회 시작: ${tableName}`);
const tableLabel = await prisma.table_labels.findUnique({ const tableLabel = await queryOne<{
where: { table_name: tableName }, table_name: string;
select: { table_label: string | null;
table_name: true, description: string | null;
table_label: true, created_date: Date | null;
description: true, updated_date: Date | null;
created_date: true, }>(
updated_date: true, `SELECT table_name, table_label, description, created_date, updated_date
}, FROM table_labels
}); WHERE table_name = $1`,
[tableName]
);
if (!tableLabel) { if (!tableLabel) {
return null; return null;
@ -478,31 +478,30 @@ export class TableManagementService {
try { try {
logger.info(`컬럼 라벨 정보 조회 시작: ${tableName}.${columnName}`); logger.info(`컬럼 라벨 정보 조회 시작: ${tableName}.${columnName}`);
const columnLabel = await prisma.column_labels.findUnique({ const columnLabel = await queryOne<{
where: { id: number;
table_name_column_name: { table_name: string;
table_name: tableName, column_name: string;
column_name: columnName, column_label: string | null;
}, web_type: string | null;
}, detail_settings: any;
select: { description: string | null;
id: true, display_order: number | null;
table_name: true, is_visible: boolean | null;
column_name: true, code_category: string | null;
column_label: true, code_value: string | null;
web_type: true, reference_table: string | null;
detail_settings: true, reference_column: string | null;
description: true, created_date: Date | null;
display_order: true, updated_date: Date | null;
is_visible: true, }>(
code_category: true, `SELECT id, table_name, column_name, column_label, web_type, detail_settings,
code_value: true, description, display_order, is_visible, code_category, code_value,
reference_table: true, reference_table, reference_column, created_date, updated_date
reference_column: true, FROM column_labels
created_date: true, WHERE table_name = $1 AND column_name = $2`,
updated_date: true, [tableName, columnName]
}, );
});
if (!columnLabel) { if (!columnLabel) {
return null; return null;
@ -563,57 +562,28 @@ export class TableManagementService {
...detailSettings, ...detailSettings,
}; };
// column_labels 테이블에 해당 컬럼이 있는지 확인 // column_labels UPSERT로 업데이트 또는 생성
const existingColumn = await prisma.column_labels.findFirst({ await query(
where: { `INSERT INTO column_labels (
table_name: tableName, table_name, column_name, web_type, detail_settings, input_type, created_date, updated_date
column_name: columnName, ) VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
}, ON CONFLICT (table_name, column_name)
}); DO UPDATE SET
web_type = EXCLUDED.web_type,
if (existingColumn) { detail_settings = EXCLUDED.detail_settings,
// 기존 컬럼 라벨 업데이트 input_type = COALESCE(EXCLUDED.input_type, column_labels.input_type),
const updateData: any = { updated_date = NOW()`,
web_type: webType, [
detail_settings: JSON.stringify(finalDetailSettings), tableName,
updated_date: new Date(), columnName,
}; webType,
JSON.stringify(finalDetailSettings),
if (inputType) { inputType || null,
updateData.input_type = inputType; ]
} );
logger.info(
await prisma.column_labels.update({ `컬럼 웹 타입 설정 완료: ${tableName}.${columnName} = ${webType}`
where: { );
id: existingColumn.id,
},
data: updateData,
});
logger.info(
`컬럼 웹 타입 업데이트 완료: ${tableName}.${columnName} = ${webType}`
);
} else {
// 새로운 컬럼 라벨 생성
const createData: any = {
table_name: tableName,
column_name: columnName,
web_type: webType,
detail_settings: JSON.stringify(finalDetailSettings),
created_date: new Date(),
updated_date: new Date(),
};
if (inputType) {
createData.input_type = inputType;
}
await prisma.column_labels.create({
data: createData,
});
logger.info(
`컬럼 라벨 생성 및 웹 타입 설정 완료: ${tableName}.${columnName} = ${webType}`
);
}
} catch (error) { } catch (error) {
logger.error( logger.error(
`컬럼 웹 타입 설정 중 오류 발생: ${tableName}.${columnName}`, `컬럼 웹 타입 설정 중 오류 발생: ${tableName}.${columnName}`,
@ -650,20 +620,18 @@ export class TableManagementService {
}; };
// table_type_columns 테이블에서 업데이트 // table_type_columns 테이블에서 업데이트
await prisma.$executeRaw` await query(
INSERT INTO table_type_columns ( `INSERT INTO table_type_columns (
table_name, column_name, input_type, detail_settings, table_name, column_name, input_type, detail_settings,
is_nullable, display_order, created_date, updated_date is_nullable, display_order, created_date, updated_date
) VALUES ( ) VALUES ($1, $2, $3, $4, 'Y', 0, now(), now())
${tableName}, ${columnName}, ${inputType}, ${JSON.stringify(finalDetailSettings)},
'Y', 0, now(), now()
)
ON CONFLICT (table_name, column_name) ON CONFLICT (table_name, column_name)
DO UPDATE SET DO UPDATE SET
input_type = ${inputType}, input_type = EXCLUDED.input_type,
detail_settings = ${JSON.stringify(finalDetailSettings)}, detail_settings = EXCLUDED.detail_settings,
updated_date = now(); updated_date = now()`,
`; [tableName, columnName, inputType, JSON.stringify(finalDetailSettings)]
);
logger.info( logger.info(
`컬럼 입력 타입 설정 완료: ${tableName}.${columnName} = ${inputType}` `컬럼 입력 타입 설정 완료: ${tableName}.${columnName} = ${inputType}`
@ -911,27 +879,24 @@ export class TableManagementService {
); );
// 🎯 컬럼명을 doc_type으로 사용하여 파일 구분 // 🎯 컬럼명을 doc_type으로 사용하여 파일 구분
const fileInfos = await prisma.attach_file_info.findMany({ const fileInfos = await query<{
where: { objid: string;
target_objid: String(targetObjid), real_file_name: string;
doc_type: columnName, // 컬럼명으로 파일 구분 file_size: number;
status: "ACTIVE", file_ext: string;
}, file_path: string;
select: { doc_type: string;
objid: true, doc_type_name: string;
real_file_name: true, regdate: Date;
file_size: true, writer: string;
file_ext: true, }>(
file_path: true, `SELECT objid, real_file_name, file_size, file_ext, file_path,
doc_type: true, doc_type, doc_type_name, regdate, writer
doc_type_name: true, FROM attach_file_info
regdate: true, WHERE target_objid = $1 AND doc_type = $2 AND status = 'ACTIVE'
writer: true, ORDER BY regdate DESC`,
}, [String(targetObjid), columnName]
orderBy: { );
regdate: "desc",
},
});
// 파일 정보 포맷팅 // 파일 정보 포맷팅
return fileInfos.map((fileInfo) => ({ return fileInfos.map((fileInfo) => ({
@ -956,23 +921,24 @@ export class TableManagementService {
*/ */
private async getFileInfoByPath(filePath: string): Promise<any | null> { private async getFileInfoByPath(filePath: string): Promise<any | null> {
try { try {
const fileInfo = await prisma.attach_file_info.findFirst({ const fileInfo = await queryOne<{
where: { objid: string;
file_path: filePath, real_file_name: string;
status: "ACTIVE", file_size: number;
}, file_ext: string;
select: { file_path: string;
objid: true, doc_type: string;
real_file_name: true, doc_type_name: string;
file_size: true, regdate: Date;
file_ext: true, writer: string;
file_path: true, }>(
doc_type: true, `SELECT objid, real_file_name, file_size, file_ext, file_path,
doc_type_name: true, doc_type, doc_type_name, regdate, writer
regdate: true, FROM attach_file_info
writer: true, WHERE file_path = $1 AND status = 'ACTIVE'
}, LIMIT 1`,
}); [filePath]
);
if (!fileInfo) { if (!fileInfo) {
return null; return null;
@ -1000,17 +966,14 @@ export class TableManagementService {
*/ */
private async getFileTypeColumns(tableName: string): Promise<string[]> { private async getFileTypeColumns(tableName: string): Promise<string[]> {
try { try {
const fileColumns = await prisma.column_labels.findMany({ const fileColumns = await query<{ column_name: string }>(
where: { `SELECT column_name
table_name: tableName, FROM column_labels
web_type: "file", WHERE table_name = $1 AND web_type = 'file'`,
}, [tableName]
select: { );
column_name: true,
},
});
const columnNames = fileColumns.map((col: any) => col.column_name); const columnNames = fileColumns.map((col) => col.column_name);
logger.info(`파일 타입 컬럼 감지: ${tableName}`, columnNames); logger.info(`파일 타입 컬럼 감지: ${tableName}`, columnNames);
return columnNames; return columnNames;
} catch (error) { } catch (error) {
@ -1379,19 +1342,19 @@ export class TableManagementService {
displayColumn?: string; displayColumn?: string;
} | null> { } | null> {
try { try {
const result = await prisma.column_labels.findFirst({ const result = await queryOne<{
where: { web_type: string | null;
table_name: tableName, code_category: string | null;
column_name: columnName, reference_table: string | null;
}, reference_column: string | null;
select: { display_column: string | null;
web_type: true, }>(
code_category: true, `SELECT web_type, code_category, reference_table, reference_column, display_column
reference_table: true, FROM column_labels
reference_column: true, WHERE table_name = $1 AND column_name = $2
display_column: true, LIMIT 1`,
}, [tableName, columnName]
}); );
if (!result) { if (!result) {
return null; return null;
@ -1535,10 +1498,7 @@ export class TableManagementService {
// 전체 개수 조회 // 전체 개수 조회
const countQuery = `SELECT COUNT(*) as count FROM ${safeTableName} ${whereClause}`; const countQuery = `SELECT COUNT(*) as count FROM ${safeTableName} ${whereClause}`;
const countResult = await prisma.$queryRawUnsafe<any[]>( const countResult = await query<any>(countQuery, ...searchValues);
countQuery,
...searchValues
);
const total = parseInt(countResult[0].count); const total = parseInt(countResult[0].count);
// 데이터 조회 // 데이터 조회
@ -1549,12 +1509,7 @@ export class TableManagementService {
LIMIT $${paramIndex} OFFSET $${paramIndex + 1} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`; `;
let data = await prisma.$queryRawUnsafe<any[]>( let data = await query<any>(dataQuery, ...searchValues, size, offset);
dataQuery,
...searchValues,
size,
offset
);
// 🎯 파일 컬럼이 있으면 파일 정보 보강 // 🎯 파일 컬럼이 있으면 파일 정보 보강
if (fileColumns.length > 0) { if (fileColumns.length > 0) {
@ -1699,10 +1654,9 @@ export class TableManagementService {
ORDER BY ordinal_position ORDER BY ordinal_position
`; `;
const columnInfoResult = (await prisma.$queryRawUnsafe( const columnInfoResult = (await query(columnInfoQuery, [
columnInfoQuery, tableName,
tableName ])) as any[];
)) as any[];
const columnTypeMap = new Map<string, string>(); const columnTypeMap = new Map<string, string>();
columnInfoResult.forEach((col: any) => { columnInfoResult.forEach((col: any) => {
@ -1759,15 +1713,15 @@ export class TableManagementService {
.join(", "); .join(", ");
const columnNames = columns.map((col) => `"${col}"`).join(", "); const columnNames = columns.map((col) => `"${col}"`).join(", ");
const query = ` const insertQuery = `
INSERT INTO "${tableName}" (${columnNames}) INSERT INTO "${tableName}" (${columnNames})
VALUES (${placeholders}) VALUES (${placeholders})
`; `;
logger.info(`실행할 쿼리: ${query}`); logger.info(`실행할 쿼리: ${insertQuery}`);
logger.info(`쿼리 파라미터:`, values); logger.info(`쿼리 파라미터:`, values);
await prisma.$queryRawUnsafe(query, ...values); await query(insertQuery, values);
logger.info(`테이블 데이터 추가 완료: ${tableName}`); logger.info(`테이블 데이터 추가 완료: ${tableName}`);
} catch (error) { } catch (error) {
@ -1800,10 +1754,9 @@ export class TableManagementService {
ORDER BY c.ordinal_position ORDER BY c.ordinal_position
`; `;
const columnInfoResult = (await prisma.$queryRawUnsafe( const columnInfoResult = (await query(columnInfoQuery, [
columnInfoQuery, tableName,
tableName ])) as any[];
)) as any[];
const columnTypeMap = new Map<string, string>(); const columnTypeMap = new Map<string, string>();
const primaryKeys: string[] = []; const primaryKeys: string[] = [];
@ -1866,7 +1819,7 @@ export class TableManagementService {
} }
// UPDATE 쿼리 생성 // UPDATE 쿼리 생성
const query = ` const updateQuery = `
UPDATE "${tableName}" UPDATE "${tableName}"
SET ${setConditions.join(", ")} SET ${setConditions.join(", ")}
WHERE ${whereConditions.join(" AND ")} WHERE ${whereConditions.join(" AND ")}
@ -1874,10 +1827,10 @@ export class TableManagementService {
const allValues = [...setValues, ...whereValues]; const allValues = [...setValues, ...whereValues];
logger.info(`실행할 UPDATE 쿼리: ${query}`); logger.info(`실행할 UPDATE 쿼리: ${updateQuery}`);
logger.info(`쿼리 파라미터:`, allValues); logger.info(`쿼리 파라미터:`, allValues);
const result = await prisma.$queryRawUnsafe(query, ...allValues); const result = await query(updateQuery, allValues);
logger.info(`테이블 데이터 수정 완료: ${tableName}`, result); logger.info(`테이블 데이터 수정 완료: ${tableName}`, result);
} catch (error) { } catch (error) {
@ -1946,9 +1899,10 @@ export class TableManagementService {
ORDER BY kcu.ordinal_position ORDER BY kcu.ordinal_position
`; `;
const primaryKeys = await prisma.$queryRawUnsafe< const primaryKeys = await query<{ column_name: string }>(
{ column_name: string }[] primaryKeyQuery,
>(primaryKeyQuery, tableName); [tableName]
);
if (primaryKeys.length === 0) { if (primaryKeys.length === 0) {
// 기본 키가 없는 경우, 모든 컬럼으로 삭제 조건 생성 // 기본 키가 없는 경우, 모든 컬럼으로 삭제 조건 생성
@ -1965,7 +1919,7 @@ export class TableManagementService {
const deleteQuery = `DELETE FROM "${tableName}" WHERE ${conditions}`; const deleteQuery = `DELETE FROM "${tableName}" WHERE ${conditions}`;
const result = await prisma.$queryRawUnsafe(deleteQuery, ...values); const result = await query(deleteQuery, values);
deletedCount += Number(result); deletedCount += Number(result);
} }
} else { } else {
@ -1987,7 +1941,7 @@ export class TableManagementService {
const deleteQuery = `DELETE FROM "${tableName}" WHERE ${conditions}`; const deleteQuery = `DELETE FROM "${tableName}" WHERE ${conditions}`;
const result = await prisma.$queryRawUnsafe(deleteQuery, ...values); const result = await query(deleteQuery, values);
deletedCount += Number(result); deletedCount += Number(result);
} }
} }
@ -2269,8 +2223,8 @@ export class TableManagementService {
// 병렬 실행 // 병렬 실행
const [dataResult, countResult] = await Promise.all([ const [dataResult, countResult] = await Promise.all([
prisma.$queryRawUnsafe(dataQuery), query(dataQuery),
prisma.$queryRawUnsafe(countQuery), query(countQuery),
]); ]);
const data = Array.isArray(dataResult) ? dataResult : []; const data = Array.isArray(dataResult) ? dataResult : [];
@ -2642,17 +2596,16 @@ export class TableManagementService {
data: Array<{ column_name: string; data_type: string }>; data: Array<{ column_name: string; data_type: string }>;
}> { }> {
try { try {
const columns = await prisma.$queryRaw< const columns = await query<{
Array<{ column_name: string;
column_name: string; data_type: string;
data_type: string; }>(
}> `SELECT column_name, data_type
>` FROM information_schema.columns
SELECT column_name, data_type WHERE table_name = $1
FROM information_schema.columns ORDER BY ordinal_position`,
WHERE table_name = ${tableName} [tableName]
ORDER BY ordinal_position );
`;
return { data: columns }; return { data: columns };
} catch (error) { } catch (error) {
@ -2687,45 +2640,40 @@ export class TableManagementService {
try { try {
logger.info(`컬럼 라벨 업데이트: ${tableName}.${columnName}`); logger.info(`컬럼 라벨 업데이트: ${tableName}.${columnName}`);
await prisma.column_labels.upsert({ await query(
where: { `INSERT INTO column_labels (
table_name_column_name: { table_name, column_name, column_label, web_type, detail_settings,
table_name: tableName, description, display_order, is_visible, code_category, code_value,
column_name: columnName, reference_table, reference_column, created_date, updated_date
}, ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW())
}, ON CONFLICT (table_name, column_name)
update: { DO UPDATE SET
column_label: updates.columnLabel, column_label = EXCLUDED.column_label,
web_type: updates.webType, web_type = EXCLUDED.web_type,
detail_settings: updates.detailSettings, detail_settings = EXCLUDED.detail_settings,
description: updates.description, description = EXCLUDED.description,
display_order: updates.displayOrder, display_order = EXCLUDED.display_order,
is_visible: updates.isVisible, is_visible = EXCLUDED.is_visible,
code_category: updates.codeCategory, code_category = EXCLUDED.code_category,
code_value: updates.codeValue, code_value = EXCLUDED.code_value,
reference_table: updates.referenceTable, reference_table = EXCLUDED.reference_table,
reference_column: updates.referenceColumn, reference_column = EXCLUDED.reference_column,
// display_column: updates.displayColumn, // 🎯 새로 추가 (임시 주석) updated_date = NOW()`,
updated_date: new Date(), [
}, tableName,
create: { columnName,
table_name: tableName, updates.columnLabel || columnName,
column_name: columnName, updates.webType || "text",
column_label: updates.columnLabel || columnName, updates.detailSettings,
web_type: updates.webType || "text", updates.description,
detail_settings: updates.detailSettings, updates.displayOrder || 0,
description: updates.description, updates.isVisible !== false,
display_order: updates.displayOrder || 0, updates.codeCategory,
is_visible: updates.isVisible !== false, updates.codeValue,
code_category: updates.codeCategory, updates.referenceTable,
code_value: updates.codeValue, updates.referenceColumn,
reference_table: updates.referenceTable, ]
reference_column: updates.referenceColumn, );
// display_column: updates.displayColumn, // 🎯 새로 추가 (임시 주석)
created_date: new Date(),
updated_date: new Date(),
},
});
logger.info(`컬럼 라벨 업데이트 완료: ${tableName}.${columnName}`); logger.info(`컬럼 라벨 업데이트 완료: ${tableName}.${columnName}`);
} catch (error) { } catch (error) {
@ -2949,8 +2897,8 @@ export class TableManagementService {
try { try {
logger.info(`테이블 스키마 정보 조회: ${tableName}`); logger.info(`테이블 스키마 정보 조회: ${tableName}`);
const rawColumns = await prisma.$queryRaw<any[]>` const rawColumns = await query<any>(
SELECT `SELECT
column_name as "columnName", column_name as "columnName",
column_name as "displayName", column_name as "displayName",
data_type as "dataType", data_type as "dataType",
@ -2963,15 +2911,16 @@ export class TableManagementService {
CASE CASE
WHEN column_name IN ( WHEN column_name IN (
SELECT column_name FROM information_schema.key_column_usage SELECT column_name FROM information_schema.key_column_usage
WHERE table_name = ${tableName} AND constraint_name LIKE '%_pkey' WHERE table_name = $1 AND constraint_name LIKE '%_pkey'
) THEN true ) THEN true
ELSE false ELSE false
END as "isPrimaryKey" END as "isPrimaryKey"
FROM information_schema.columns FROM information_schema.columns
WHERE table_name = ${tableName} WHERE table_name = $1
AND table_schema = 'public' AND table_schema = 'public'
ORDER BY ordinal_position ORDER BY ordinal_position`,
`; [tableName]
);
const columns: ColumnTypeInfo[] = rawColumns.map((col) => ({ const columns: ColumnTypeInfo[] = rawColumns.map((col) => ({
tableName: tableName, tableName: tableName,
@ -3012,14 +2961,15 @@ export class TableManagementService {
try { try {
logger.info(`테이블 존재 여부 확인: ${tableName}`); logger.info(`테이블 존재 여부 확인: ${tableName}`);
const result = await prisma.$queryRaw<any[]>` const result = await query<any>(
SELECT EXISTS ( `SELECT EXISTS (
SELECT 1 FROM information_schema.tables SELECT 1 FROM information_schema.tables
WHERE table_name = ${tableName} WHERE table_name = $1
AND table_schema = 'public' AND table_schema = 'public'
AND table_type = 'BASE TABLE' AND table_type = 'BASE TABLE'
) as "exists" ) as "exists"`,
`; [tableName]
);
const exists = result[0]?.exists || false; const exists = result[0]?.exists || false;
logger.info(`테이블 존재 여부: ${tableName} = ${exists}`); logger.info(`테이블 존재 여부: ${tableName} = ${exists}`);
@ -3038,8 +2988,8 @@ export class TableManagementService {
logger.info(`컬럼 입력타입 정보 조회: ${tableName}`); logger.info(`컬럼 입력타입 정보 조회: ${tableName}`);
// table_type_columns에서 입력타입 정보 조회 // table_type_columns에서 입력타입 정보 조회
const rawInputTypes = await prisma.$queryRaw<any[]>` const rawInputTypes = await query<any>(
SELECT `SELECT
ttc.column_name as "columnName", ttc.column_name as "columnName",
ttc.column_name as "displayName", ttc.column_name as "displayName",
COALESCE(ttc.input_type, 'text') as "inputType", COALESCE(ttc.input_type, 'text') as "inputType",
@ -3049,9 +2999,10 @@ export class TableManagementService {
FROM table_type_columns ttc FROM table_type_columns ttc
LEFT JOIN information_schema.columns ic LEFT JOIN information_schema.columns ic
ON ttc.table_name = ic.table_name AND ttc.column_name = ic.column_name ON ttc.table_name = ic.table_name AND ttc.column_name = ic.column_name
WHERE ttc.table_name = ${tableName} WHERE ttc.table_name = $1
ORDER BY ttc.display_order, ttc.column_name ORDER BY ttc.display_order, ttc.column_name`,
`; [tableName]
);
const inputTypes: ColumnTypeInfo[] = rawInputTypes.map((col) => ({ const inputTypes: ColumnTypeInfo[] = rawInputTypes.map((col) => ({
tableName: tableName, tableName: tableName,
@ -3099,7 +3050,7 @@ export class TableManagementService {
logger.info("데이터베이스 연결 상태 확인"); logger.info("데이터베이스 연결 상태 확인");
// 간단한 쿼리로 연결 테스트 // 간단한 쿼리로 연결 테스트
const result = await prisma.$queryRaw<any[]>`SELECT 1 as "test"`; const result = await query<any>(`SELECT 1 as "test"`);
if (result && result.length > 0) { if (result && result.length > 0) {
logger.info("데이터베이스 연결 성공"); logger.info("데이터베이스 연결 성공");