feat(테이블타입): 컬럼 단건 DROP + CreateTableModal flex 레이아웃 수정 (#22)
Build & Deploy to K8s / build-and-deploy (push) Successful in 13m20s

johngreen → main: 컬럼 단건 DROP 기능 + CreateTableModal 다이얼로그 flex 레이아웃 수정
This commit was merged in pull request #22.
This commit is contained in:
2026-05-19 06:02:51 +00:00
6 changed files with 248 additions and 16 deletions
@@ -91,6 +91,32 @@ public class DdlController {
return ResponseEntity.status(400).body(ApiResponse.error((String) result.get("message")));
}
/**
* DELETE /api/ddl/tables/{tableName}/columns/{columnName} - 컬럼 삭제
*/
@DeleteMapping("/tables/{tableName}/columns/{columnName}")
public ResponseEntity<ApiResponse<?>> dropColumn(
@PathVariable String tableName,
@PathVariable String columnName,
@RequestAttribute("company_code") String companyCode,
@RequestAttribute("user_id") String userId) {
if (!isSuperAdmin(companyCode)) {
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
}
Map<String, Object> result = ddlService.dropColumn(tableName, columnName, companyCode, userId);
if (Boolean.TRUE.equals(result.get("success"))) {
return ResponseEntity.ok(ApiResponse.success(Map.of(
"table_name", result.get("table_name"),
"column_name", result.get("column_name"),
"executed_query", result.get("executed_query")
), (String) result.get("message")));
}
return ResponseEntity.status(400).body(ApiResponse.error((String) result.get("message")));
}
/**
* DELETE /api/ddl/tables/{tableName} - 테이블 삭제
*/
@@ -226,6 +226,79 @@ public class DdlService extends BaseService {
}
}
// ─────────────────────────────────────────────────────────────────────────
// DROP COLUMN (DBeaver 방식: FK 등 위반은 Postgres 가 던지는 에러를 그대로 노출)
// ─────────────────────────────────────────────────────────────────────────
public Map<String, Object> dropColumn(String tableName, String columnName,
String companyCode, String userId) {
// 1. 시스템 테이블 보호
if (SYSTEM_TABLES.contains(tableName.toLowerCase())) {
String errorMsg = "'" + tableName + "'은 시스템 테이블이므로 컬럼을 삭제할 수 없습니다.";
logDdlOperation(userId, companyCode, "DROP_COLUMN", tableName,
"SYSTEM_TABLE_PROTECTED", false, errorMsg);
return Map.of("success", false, "message", errorMsg, "error_code", "SYSTEM_TABLE_PROTECTED");
}
// 2. 예약 컬럼 보호 (id / created_date / updated_date / company_code / writer)
if (RESERVED_COLUMNS.contains(columnName.toLowerCase()) || "writer".equalsIgnoreCase(columnName)) {
String errorMsg = "'" + columnName + "'은 시스템 예약 컬럼이므로 삭제할 수 없습니다.";
logDdlOperation(userId, companyCode, "DROP_COLUMN", tableName,
"RESERVED_COLUMN_PROTECTED", false, errorMsg);
return Map.of("success", false, "message", errorMsg, "error_code", "RESERVED_COLUMN_PROTECTED");
}
// 3. 테이블/컬럼 존재 여부
if (!tableExists(tableName)) {
String errorMsg = "테이블 '" + tableName + "'이 존재하지 않습니다.";
logDdlOperation(userId, companyCode, "DROP_COLUMN", tableName, "TABLE_NOT_FOUND", false, errorMsg);
return Map.of("success", false, "message", errorMsg, "error_code", "TABLE_NOT_FOUND");
}
if (!columnExists(tableName, columnName)) {
String errorMsg = "컬럼 '" + columnName + "'이 존재하지 않습니다.";
logDdlOperation(userId, companyCode, "DROP_COLUMN", tableName, "COLUMN_NOT_FOUND", false, errorMsg);
return Map.of("success", false, "message", errorMsg, "error_code", "COLUMN_NOT_FOUND");
}
// 4. DDL 실행 — CASCADE 안 붙임 → FK 참조 있으면 Postgres 가 거부 (DBeaver 와 동일)
String ddlQuery = "ALTER TABLE \"" + sanitize(tableName) + "\" DROP COLUMN \"" + sanitize(columnName) + "\"";
try {
transactionTemplate.execute(status -> {
jdbcTemplate.execute(ddlQuery);
// 컬럼 메타 청소
jdbcTemplate.update(
"DELETE FROM table_type_columns WHERE table_name = ? AND column_name = ?",
tableName, columnName);
jdbcTemplate.update(
"DELETE FROM column_labels WHERE table_name = ? AND column_name = ?",
tableName, columnName);
return null;
});
logDdlOperation(userId, companyCode, "DROP_COLUMN", tableName, ddlQuery, true, null);
log.info("컬럼 삭제 성공: {}.{}, 사용자: {}", tableName, columnName, userId);
return Map.of(
"success", true,
"message", "컬럼 '" + columnName + "'이 성공적으로 삭제되었습니다.",
"table_name", tableName,
"column_name", columnName,
"executed_query", ddlQuery
);
} catch (Exception e) {
String rawMsg = e.getMessage() != null ? e.getMessage() : "";
String guidance = rawMsg.toLowerCase().contains("depend") || rawMsg.toLowerCase().contains("foreign key")
? " (다른 테이블에서 외래키로 참조 중인 컬럼은 삭제할 수 없습니다)"
: "";
String errorMsg = "컬럼 삭제 실패: " + rawMsg + guidance;
logDdlOperation(userId, companyCode, "DROP_COLUMN", tableName,
"FAILED: " + rawMsg, false, errorMsg);
log.error("컬럼 삭제 실패: {}.{}, 사용자: {}, 오류: {}", tableName, columnName, userId, rawMsg, e);
return Map.of("success", false, "message", errorMsg, "error_code", "EXECUTION_FAILED");
}
}
// ─────────────────────────────────────────────────────────────────────────
// VALIDATE
// ─────────────────────────────────────────────────────────────────────────