feat: Implement company-specific NOT NULL constraint validation for table data

- Added validation for NOT NULL constraints in the add and edit table data functions, ensuring that required fields are not empty based on company-specific settings.
- Enhanced the toggleColumnNullable function to check for existing NULL values before changing the NOT NULL status, providing appropriate error messages.
- Introduced a new service method to validate NOT NULL constraints against company-specific configurations, improving data integrity in a multi-tenancy environment.
This commit is contained in:
kjs
2026-02-24 18:40:36 +09:00
parent cb4fa2aaba
commit 3ca511924e
2 changed files with 160 additions and 19 deletions

View File

@@ -921,6 +921,24 @@ export async function addTableData(
}
}
// 회사별 NOT NULL 소프트 제약조건 검증
const notNullViolations = await tableManagementService.validateNotNullConstraints(
tableName,
data,
companyCode || "*"
);
if (notNullViolations.length > 0) {
res.status(400).json({
success: false,
message: `필수 항목이 비어있습니다: ${notNullViolations.join(", ")}`,
error: {
code: "NOT_NULL_VIOLATION",
details: notNullViolations,
},
});
return;
}
// 데이터 추가
await tableManagementService.addTableData(tableName, data);
@@ -1003,6 +1021,25 @@ export async function editTableData(
}
const tableManagementService = new TableManagementService();
const companyCode = req.user?.companyCode || "*";
// 회사별 NOT NULL 소프트 제약조건 검증 (수정 데이터 대상)
const notNullViolations = await tableManagementService.validateNotNullConstraints(
tableName,
updatedData,
companyCode
);
if (notNullViolations.length > 0) {
res.status(400).json({
success: false,
message: `필수 항목이 비어있습니다: ${notNullViolations.join(", ")}`,
error: {
code: "NOT_NULL_VIOLATION",
details: notNullViolations,
},
});
return;
}
// 데이터 수정
await tableManagementService.editTableData(
@@ -2652,8 +2689,11 @@ export async function toggleTableIndex(
}
/**
* NOT NULL 토글
* NOT NULL 토글 (회사별 소프트 제약조건)
* PUT /api/table-management/tables/:tableName/columns/:columnName/nullable
*
* DB 레벨 ALTER TABLE 대신 table_type_columns.is_nullable을 회사별로 관리한다.
* 멀티테넌시 환경에서 회사 A는 NOT NULL, 회사 B는 NULL 허용이 가능하다.
*/
export async function toggleColumnNullable(
req: AuthenticatedRequest,
@@ -2662,6 +2702,7 @@ export async function toggleColumnNullable(
try {
const { tableName, columnName } = req.params;
const { nullable } = req.body;
const companyCode = req.user?.companyCode || "*";
if (!tableName || !columnName || typeof nullable !== "boolean") {
res.status(400).json({
@@ -2671,18 +2712,54 @@ export async function toggleColumnNullable(
return;
}
if (nullable) {
// NOT NULL 해제
const sql = `ALTER TABLE "public"."${tableName}" ALTER COLUMN "${columnName}" DROP NOT NULL`;
logger.info(`NOT NULL 해제: ${sql}`);
await query(sql);
} else {
// NOT NULL 설정
const sql = `ALTER TABLE "public"."${tableName}" ALTER COLUMN "${columnName}" SET NOT NULL`;
logger.info(`NOT NULL 설정: ${sql}`);
await query(sql);
// is_nullable 값: 'Y' = NULL 허용, 'N' = NOT NULL
const isNullableValue = nullable ? "Y" : "N";
if (!nullable) {
// NOT NULL 설정 전 - 해당 회사의 기존 데이터에 NULL이 있는지 확인
const hasCompanyCode = await query<{ column_name: string }>(
`SELECT column_name FROM information_schema.columns
WHERE table_name = $1 AND column_name = 'company_code'`,
[tableName]
);
if (hasCompanyCode.length > 0) {
const nullCheckQuery = companyCode === "*"
? `SELECT COUNT(*) as null_count FROM "${tableName}" WHERE "${columnName}" IS NULL`
: `SELECT COUNT(*) as null_count FROM "${tableName}" WHERE "${columnName}" IS NULL AND company_code = $1`;
const nullCheckParams = companyCode === "*" ? [] : [companyCode];
const nullCheckResult = await query<{ null_count: string }>(nullCheckQuery, nullCheckParams);
const nullCount = parseInt(nullCheckResult[0]?.null_count || "0", 10);
if (nullCount > 0) {
logger.warn(`NOT NULL 설정 불가 - 해당 회사에 NULL 데이터 존재: ${tableName}.${columnName}`, {
companyCode,
nullCount,
});
res.status(400).json({
success: false,
message: `현재 회사 데이터에 NULL 값이 ${nullCount}건 존재합니다. NULL 데이터를 먼저 정리해주세요.`,
});
return;
}
}
}
// table_type_columns에 회사별 is_nullable 설정 UPSERT
await query(
`INSERT INTO table_type_columns (table_name, column_name, is_nullable, company_code, created_date, updated_date)
VALUES ($1, $2, $3, $4, NOW(), NOW())
ON CONFLICT (table_name, column_name, company_code)
DO UPDATE SET is_nullable = $3, updated_date = NOW()`,
[tableName, columnName, isNullableValue, companyCode]
);
logger.info(`NOT NULL 소프트 제약조건 변경: ${tableName}.${columnName} → is_nullable=${isNullableValue}`, {
companyCode,
});
res.status(200).json({
success: true,
message: nullable
@@ -2692,14 +2769,9 @@ export async function toggleColumnNullable(
} catch (error: any) {
logger.error("NOT NULL 토글 오류:", error);
// NULL 데이터가 있는 컬럼에 NOT NULL 설정 시 안내
const errorMsg = error.message?.includes("contains null values")
? "해당 컬럼에 NULL 값이 있어 NOT NULL 설정이 불가합니다. NULL 데이터를 먼저 정리해주세요."
: "NOT NULL 설정 중 오류가 발생했습니다.";
res.status(500).json({
success: false,
message: errorMsg,
message: "NOT NULL 설정 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}