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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user