feat: Enhance dynamic form service to handle VIEW tables

- Introduced a new method `resolveBaseTable` to determine the original table name for VIEWs, allowing for seamless data operations.
- Updated existing methods (`saveFormData`, `updateFormDataPartial`, `updateFormData`, and `deleteFormData`) to utilize `resolveBaseTable`, ensuring that operations are performed on the correct base table.
- Improved logging to provide clearer insights into the operations being performed, including handling of original table names when dealing with VIEWs.
This commit is contained in:
kjs
2026-02-27 13:00:22 +09:00
parent c1f7f27005
commit 8bfc2ba4f5
2 changed files with 95 additions and 21 deletions

View File

@@ -210,19 +210,62 @@ export class DynamicFormService {
}
}
/**
* VIEW인 경우 원본(base) 테이블명을 반환, 일반 테이블이면 그대로 반환
*/
async resolveBaseTable(tableName: string): Promise<string> {
try {
const result = await query<{ table_type: string }>(
`SELECT table_type FROM information_schema.tables
WHERE table_name = $1 AND table_schema = 'public'`,
[tableName]
);
if (result.length === 0 || result[0].table_type !== 'VIEW') {
return tableName;
}
// VIEW의 FROM 절에서 첫 번째 테이블을 추출
const viewDef = await query<{ view_definition: string }>(
`SELECT view_definition FROM information_schema.views
WHERE table_name = $1 AND table_schema = 'public'`,
[tableName]
);
if (viewDef.length > 0) {
const definition = viewDef[0].view_definition;
// PostgreSQL은 뷰 정의를 "FROM (테이블명 별칭 LEFT JOIN ...)" 형태로 저장
const fromMatch = definition.match(/FROM\s+\(?(?:public\.)?(\w+)\s/i);
if (fromMatch) {
const baseTable = fromMatch[1];
console.log(`🔄 VIEW ${tableName} → 원본 테이블 ${baseTable} 으로 전환`);
return baseTable;
}
}
return tableName;
} catch (error) {
console.error(`❌ VIEW 원본 테이블 조회 실패:`, error);
return tableName;
}
}
/**
* 폼 데이터 저장 (실제 테이블에 직접 저장)
*/
async saveFormData(
screenId: number,
tableName: string,
tableNameInput: string,
data: Record<string, any>,
ipAddress?: string
): Promise<FormDataResult> {
// VIEW인 경우 원본 테이블로 전환
const tableName = await this.resolveBaseTable(tableNameInput);
try {
console.log("💾 서비스: 실제 테이블에 폼 데이터 저장 시작:", {
screenId,
tableName,
originalTable: tableNameInput !== tableName ? tableNameInput : undefined,
data,
});
@@ -813,14 +856,17 @@ export class DynamicFormService {
*/
async updateFormDataPartial(
id: string | number, // 🔧 UUID 문자열도 지원
tableName: string,
tableNameInput: string,
originalData: Record<string, any>,
newData: Record<string, any>
): Promise<PartialUpdateResult> {
// VIEW인 경우 원본 테이블로 전환
const tableName = await this.resolveBaseTable(tableNameInput);
try {
console.log("🔄 서비스: 부분 업데이트 시작:", {
id,
tableName,
originalTable: tableNameInput !== tableName ? tableNameInput : undefined,
originalData,
newData,
});
@@ -1008,13 +1054,16 @@ export class DynamicFormService {
*/
async updateFormData(
id: string | number,
tableName: string,
tableNameInput: string,
data: Record<string, any>
): Promise<FormDataResult> {
// VIEW인 경우 원본 테이블로 전환
const tableName = await this.resolveBaseTable(tableNameInput);
try {
console.log("🔄 서비스: 실제 테이블에서 폼 데이터 업데이트 시작:", {
id,
tableName,
originalTable: tableNameInput !== tableName ? tableNameInput : undefined,
data,
});
@@ -1212,9 +1261,13 @@ export class DynamicFormService {
screenId?: number
): Promise<void> {
try {
// VIEW인 경우 원본 테이블로 전환 (VIEW에는 기본키가 없으므로)
const actualTable = await this.resolveBaseTable(tableName);
console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", {
id,
tableName,
tableName: actualTable,
originalTable: tableName !== actualTable ? tableName : undefined,
});
// 1. 먼저 테이블의 기본키 컬럼명과 데이터 타입을 동적으로 조회
@@ -1232,15 +1285,15 @@ export class DynamicFormService {
`;
console.log("🔍 기본키 조회 SQL:", primaryKeyQuery);
console.log("🔍 테이블명:", tableName);
console.log("🔍 테이블명:", actualTable);
const primaryKeyResult = await query<{
column_name: string;
data_type: string;
}>(primaryKeyQuery, [tableName]);
}>(primaryKeyQuery, [actualTable]);
if (!primaryKeyResult || primaryKeyResult.length === 0) {
throw new Error(`테이블 ${tableName}의 기본키를 찾을 수 없습니다.`);
throw new Error(`테이블 ${actualTable}의 기본키를 찾을 수 없습니다.`);
}
const primaryKeyInfo = primaryKeyResult[0];
@@ -1272,7 +1325,7 @@ export class DynamicFormService {
// 3. 동적으로 발견된 기본키와 타입 캐스팅을 사용한 DELETE SQL 생성
const deleteQuery = `
DELETE FROM ${tableName}
DELETE FROM ${actualTable}
WHERE ${primaryKeyColumn} = $1${typeCastSuffix}
RETURNING *
`;
@@ -1292,7 +1345,7 @@ export class DynamicFormService {
// 삭제된 행이 없으면 레코드를 찾을 수 없는 것
if (!result || !Array.isArray(result) || result.length === 0) {
throw new Error(`테이블 ${tableName}에서 ID '${id}'에 해당하는 레코드를 찾을 수 없습니다.`);
throw new Error(`테이블 ${actualTable}에서 ID '${id}'에 해당하는 레코드를 찾을 수 없습니다.`);
}
console.log("✅ 서비스: 실제 테이블 삭제 성공:", result);