From 06d5069566ee9cde0c85adbe2047ae1aba5bda32 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 30 Dec 2025 10:54:06 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/table-type-sql-guide.mdc | 230 ++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 3 deletions(-) diff --git a/.cursor/rules/table-type-sql-guide.mdc b/.cursor/rules/table-type-sql-guide.mdc index 501c3218..3c53c537 100644 --- a/.cursor/rules/table-type-sql-guide.mdc +++ b/.cursor/rules/table-type-sql-guide.mdc @@ -335,9 +335,222 @@ ON CONFLICT (table_name, column_name) DO UPDATE SET column_label = EXCLUDED.colu --- -## 7. 체크리스트 +## 7. 로그 테이블 생성 (선택사항) -테이블 생성/수정 시 반드시 확인할 사항: +변경 이력 추적이 필요한 테이블에는 로그 테이블을 생성할 수 있습니다. + +### 7.1 로그 테이블 DDL 템플릿 + +```sql +-- 로그 테이블 생성 +CREATE TABLE 테이블명_log ( + log_id SERIAL PRIMARY KEY, + operation_type VARCHAR(10) NOT NULL, -- INSERT/UPDATE/DELETE + original_id VARCHAR(100), -- 원본 테이블 PK 값 + changed_column VARCHAR(100), -- 변경된 컬럼명 + old_value TEXT, -- 변경 전 값 + new_value TEXT, -- 변경 후 값 + changed_by VARCHAR(50), -- 변경자 ID + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 변경 시각 + ip_address VARCHAR(50), -- 변경 요청 IP + user_agent TEXT, -- User Agent + full_row_before JSONB, -- 변경 전 전체 행 + full_row_after JSONB -- 변경 후 전체 행 +); + +-- 인덱스 생성 +CREATE INDEX idx_테이블명_log_original_id ON 테이블명_log(original_id); +CREATE INDEX idx_테이블명_log_changed_at ON 테이블명_log(changed_at); +CREATE INDEX idx_테이블명_log_operation ON 테이블명_log(operation_type); + +-- 코멘트 추가 +COMMENT ON TABLE 테이블명_log IS '테이블명 테이블 변경 이력'; +``` + +### 7.2 트리거 함수 DDL 템플릿 + +```sql +CREATE OR REPLACE FUNCTION 테이블명_log_trigger_func() +RETURNS TRIGGER AS $$ +DECLARE + v_column_name TEXT; + v_old_value TEXT; + v_new_value TEXT; + v_user_id VARCHAR(50); + v_ip_address VARCHAR(50); +BEGIN + v_user_id := current_setting('app.user_id', TRUE); + v_ip_address := current_setting('app.ip_address', TRUE); + + IF (TG_OP = 'INSERT') THEN + INSERT INTO 테이블명_log (operation_type, original_id, changed_by, ip_address, full_row_after) + VALUES ('INSERT', NEW.id, v_user_id, v_ip_address, row_to_json(NEW)::jsonb); + RETURN NEW; + + ELSIF (TG_OP = 'UPDATE') THEN + FOR v_column_name IN + SELECT column_name + FROM information_schema.columns + WHERE table_name = '테이블명' + AND table_schema = 'public' + LOOP + EXECUTE format('SELECT ($1).%I::TEXT, ($2).%I::TEXT', v_column_name, v_column_name) + INTO v_old_value, v_new_value + USING OLD, NEW; + + IF v_old_value IS DISTINCT FROM v_new_value THEN + INSERT INTO 테이블명_log ( + operation_type, original_id, changed_column, old_value, new_value, + changed_by, ip_address, full_row_before, full_row_after + ) + VALUES ( + 'UPDATE', NEW.id, v_column_name, v_old_value, v_new_value, + v_user_id, v_ip_address, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb + ); + END IF; + END LOOP; + RETURN NEW; + + ELSIF (TG_OP = 'DELETE') THEN + INSERT INTO 테이블명_log (operation_type, original_id, changed_by, ip_address, full_row_before) + VALUES ('DELETE', OLD.id, v_user_id, v_ip_address, row_to_json(OLD)::jsonb); + RETURN OLD; + END IF; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +``` + +### 7.3 트리거 DDL 템플릿 + +```sql +CREATE TRIGGER 테이블명_audit_trigger +AFTER INSERT OR UPDATE OR DELETE ON 테이블명 +FOR EACH ROW EXECUTE FUNCTION 테이블명_log_trigger_func(); +``` + +### 7.4 로그 설정 등록 + +```sql +INSERT INTO table_log_config ( + original_table_name, log_table_name, trigger_name, + trigger_function_name, is_active, created_by, created_at +) VALUES ( + '테이블명', '테이블명_log', '테이블명_audit_trigger', + '테이블명_log_trigger_func', 'Y', '생성자ID', now() +); +``` + +### 7.5 table_labels에 use_log_table 플래그 설정 + +```sql +UPDATE table_labels +SET use_log_table = 'Y', updated_date = now() +WHERE table_name = '테이블명'; +``` + +### 7.6 전체 예시: order_info 로그 테이블 생성 + +```sql +-- Step 1: 로그 테이블 생성 +CREATE TABLE order_info_log ( + log_id SERIAL PRIMARY KEY, + operation_type VARCHAR(10) NOT NULL, + original_id VARCHAR(100), + changed_column VARCHAR(100), + old_value TEXT, + new_value TEXT, + changed_by VARCHAR(50), + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + ip_address VARCHAR(50), + user_agent TEXT, + full_row_before JSONB, + full_row_after JSONB +); + +CREATE INDEX idx_order_info_log_original_id ON order_info_log(original_id); +CREATE INDEX idx_order_info_log_changed_at ON order_info_log(changed_at); +CREATE INDEX idx_order_info_log_operation ON order_info_log(operation_type); + +COMMENT ON TABLE order_info_log IS 'order_info 테이블 변경 이력'; + +-- Step 2: 트리거 함수 생성 +CREATE OR REPLACE FUNCTION order_info_log_trigger_func() +RETURNS TRIGGER AS $$ +DECLARE + v_column_name TEXT; + v_old_value TEXT; + v_new_value TEXT; + v_user_id VARCHAR(50); + v_ip_address VARCHAR(50); +BEGIN + v_user_id := current_setting('app.user_id', TRUE); + v_ip_address := current_setting('app.ip_address', TRUE); + + IF (TG_OP = 'INSERT') THEN + INSERT INTO order_info_log (operation_type, original_id, changed_by, ip_address, full_row_after) + VALUES ('INSERT', NEW.id, v_user_id, v_ip_address, row_to_json(NEW)::jsonb); + RETURN NEW; + ELSIF (TG_OP = 'UPDATE') THEN + FOR v_column_name IN + SELECT column_name FROM information_schema.columns + WHERE table_name = 'order_info' AND table_schema = 'public' + LOOP + EXECUTE format('SELECT ($1).%I::TEXT, ($2).%I::TEXT', v_column_name, v_column_name) + INTO v_old_value, v_new_value USING OLD, NEW; + IF v_old_value IS DISTINCT FROM v_new_value THEN + INSERT INTO order_info_log (operation_type, original_id, changed_column, old_value, new_value, changed_by, ip_address, full_row_before, full_row_after) + VALUES ('UPDATE', NEW.id, v_column_name, v_old_value, v_new_value, v_user_id, v_ip_address, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb); + END IF; + END LOOP; + RETURN NEW; + ELSIF (TG_OP = 'DELETE') THEN + INSERT INTO order_info_log (operation_type, original_id, changed_by, ip_address, full_row_before) + VALUES ('DELETE', OLD.id, v_user_id, v_ip_address, row_to_json(OLD)::jsonb); + RETURN OLD; + END IF; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- Step 3: 트리거 생성 +CREATE TRIGGER order_info_audit_trigger +AFTER INSERT OR UPDATE OR DELETE ON order_info +FOR EACH ROW EXECUTE FUNCTION order_info_log_trigger_func(); + +-- Step 4: 로그 설정 등록 +INSERT INTO table_log_config (original_table_name, log_table_name, trigger_name, trigger_function_name, is_active, created_by, created_at) +VALUES ('order_info', 'order_info_log', 'order_info_audit_trigger', 'order_info_log_trigger_func', 'Y', 'system', now()); + +-- Step 5: table_labels 플래그 업데이트 +UPDATE table_labels SET use_log_table = 'Y', updated_date = now() WHERE table_name = 'order_info'; +``` + +### 7.7 로그 테이블 삭제 + +```sql +-- 트리거 삭제 +DROP TRIGGER IF EXISTS 테이블명_audit_trigger ON 테이블명; + +-- 트리거 함수 삭제 +DROP FUNCTION IF EXISTS 테이블명_log_trigger_func(); + +-- 로그 테이블 삭제 +DROP TABLE IF EXISTS 테이블명_log; + +-- 로그 설정 삭제 +DELETE FROM table_log_config WHERE original_table_name = '테이블명'; + +-- table_labels 플래그 업데이트 +UPDATE table_labels SET use_log_table = 'N', updated_date = now() WHERE table_name = '테이블명'; +``` + +--- + +## 8. 체크리스트 + +### 테이블 생성/수정 시 반드시 확인할 사항: - [ ] DDL에 기본 5개 컬럼 포함 (id, created_date, updated_date, writer, company_code) - [ ] 모든 비즈니스 컬럼은 `VARCHAR(500)` 타입 사용 @@ -349,9 +562,18 @@ ON CONFLICT (table_name, column_name) DO UPDATE SET column_label = EXCLUDED.colu - [ ] code/entity 타입은 detail_settings에 참조 정보 포함 - [ ] ON CONFLICT 절로 중복 시 UPDATE 처리 +### 로그 테이블 생성 시 확인할 사항 (선택): + +- [ ] 로그 테이블 생성 (`테이블명_log`) +- [ ] 인덱스 3개 생성 (original_id, changed_at, operation_type) +- [ ] 트리거 함수 생성 (`테이블명_log_trigger_func`) +- [ ] 트리거 생성 (`테이블명_audit_trigger`) +- [ ] `table_log_config`에 로그 설정 등록 +- [ ] `table_labels.use_log_table = 'Y'` 업데이트 + --- -## 8. 금지 사항 +## 9. 금지 사항 1. **DB 타입 직접 지정 금지**: NUMBER, INTEGER, DATE 등 DB 타입 직접 사용 금지 2. **VARCHAR 길이 변경 금지**: 반드시 `VARCHAR(500)` 사용 @@ -364,5 +586,7 @@ ON CONFLICT (table_name, column_name) DO UPDATE SET column_label = EXCLUDED.colu ## 참조 파일 - `backend-node/src/services/ddlExecutionService.ts`: DDL 실행 서비스 +- `backend-node/src/services/tableManagementService.ts`: 로그 테이블 생성 서비스 - `backend-node/src/types/ddl.ts`: DDL 타입 정의 - `backend-node/src/controllers/ddlController.ts`: DDL API 컨트롤러 +- `backend-node/src/controllers/tableManagementController.ts`: 로그 테이블 API 컨트롤러