펜타시스템/입사 후 첫 프로젝트 공부 내용

발견한 CUBRID 문법 오류 : DML과 DDL+DML의 차이

dev-lee 2026. 9. 15. 09:19

1. CUBRID 트리거 문법 정정

검증 없이 추측으로 작성했던 트리거 구문을 CUBRID 공식 매뉴얼 예제와 대조해 바로잡은 기록임.

1.1 추측으로 잘못 작성했던 부분

  • 행 값 참조 — 바뀐 행의 컬럼을 테이블명.컬럼명(예: test_dmy_table.mng_no) 형태로 참조할 수 있다고 가정했으나 틀린 문법이었음
  • 현재시각 함수 — SYS_DATETIME으로 작성했으나 CUBRID에는 해당 이름의 함수가 없음
  • 공통 원인 — 다른 DBMS(오라클의 :NEW, MySQL의 NEW.)의 감각을 그대로 옮겼고, 매뉴얼 확인을 뒤로 미룬 것이 원인이다

1.2 매뉴얼 기준 정답

CREATE TABLE update_logs(event_code INTEGER, score VARCHAR(10), dt DATETIME);

CREATE TRIGGER example
BEFORE UPDATE ON history(score)
EXECUTE INSERT INTO update_logs VALUES (obj.event_code, obj.score, SYSDATETIME);
  • obj — 트리거를 발동시킨 그 한 행을 가리키는 고정 예약어. 테이블명이 아니라 항상 obj로 씀
  • SYSDATETIME — 언더스코어 없는 형태가 정식 함수명이다

1.3 obj 예약어가 도입된 배경

  • 모호함이 없는 구조 — 트리거는 ON 절에서 감시 대상 테이블을 하나로 확정하므로, obj가 가리킬 대상은 언제나 "지금 이 트리거를 발동시킨 딱 한 행"으로 유일하게 결정됨
  • 테이블명을 쓸 수 없는 이유 — 테이블명을 그대로 상관 참조(correlation name)로 허용하면 "테이블 전체(여러 행의 집합)"라는 원래 의미와 "지금 이 한 행"이라는 의미가 같은 문자열에 겹쳐 파서가 구분할 수 없게 됨
  • 해법 — 실제 테이블명이 될 수 없는 별도 예약어를 도입해 모호함을 원천 차단한 것이다. 다른 언어의 this / self와 같은 역할

상관 참조 이름이 예약어인 DBMS는 대부분 같은 이유를 공유함 — 집합을 가리키는 이름과 원소를 가리키는 이름을 문법 차원에서 분리한 것이다.


2. 수정된 트리거 DDL

감사 로그 테이블에 INSERT/UPDATE 이력을 남기는 실제 적용 구문임.

2.1 최종 구문

CREATE TRIGGER test_ins_trigger
AFTER INSERT ON NCSC_INTEG.test_dmy_table
EXECUTE INSERT INTO NCSC_INTEG.test_trigger_table (tbl_nm, pk_vl, trig_type_cd, chg_dt)
VALUES ('test_dmy_table', obj.mng_no, 'i', SYSDATETIME)
COMMENT 'trigger for my study';

CREATE TRIGGER test_upt_trigger
AFTER UPDATE ON NCSC_INTEG.test_dmy_table
EXECUTE INSERT INTO NCSC_INTEG.test_trigger_table (tbl_nm, pk_vl, trig_type_cd, chg_dt)
VALUES ('test_dmy_table', obj.mng_no, 'u', SYSDATETIME)
COMMENT 'trigger for my study';

2.2 스키마 접두사 규칙

위치 스키마 접두사  비고
ON 절의 대상 테이블 허용 NCSC_INTEG.test_dmy_table
INSERT INTO 대상 테이블 허용 NCSC_INTEG.test_trigger_table
VALUES 안의 obj.컬럼명 불가 obj는 고정 문법이므로 스키마를 붙이면 안 됨

2.3 남은 검증 항목

  • 함수명 일괄 교체 — 기존 설계에서 SYS_DATETIME으로 작성한 지점(chg_dt, job_proc_dt 등) 전체를 SYSDATETIME으로 교체 검토 필요
  • 트리거 밖 동작 확인 — NiFi ExecuteSQL의 UPDATE 문처럼 트리거 외부의 일반 SQL에서도 SYSDATETIME이 동일하게 동작하는지 테스트 필요함

3. DDL에 내장된 DML의 동작 차이

이번 삽질에서 문법보다 더 크게 남은 것은 "같은 DML이라도 어디에 놓이느냐에 따라 이름 해석 시점이 달라진다"는 점이다.

3.1 독립 DML과 내장 DML

  • 전체 구조 — CREATE TRIGGER 문 전체는 객체를 정의하는 DDL이고, 그 안의 EXECUTE INSERT INTO ... VALUES (...)는 DML이 DDL 정의 안에 내장된 형태임
  • 독립 DML — 실행하는 바로 그 순간의 세션/계정을 기준으로 이름 해석과 권한 체크가 즉석에서 이루어짐(동적)
  • 내장 DML — 트리거가 정의되는 CREATE TRIGGER 실행 시점의 계정 기준으로 이름 해석과 권한이 영구히 고정됨(정적). 이후 누가 트리거를 발동시키든 정의 당시 고정된 대로만 동작한다

3.2 정의자 권한

  • definer's rights — 객체를 정의한 계정의 권한으로 실행되는 방식이며, 트리거뿐 아니라 뷰와 저장 프로시저도 대부분 같은 원리를 따름
  • 실행자 권한과의 대비 — 호출자 기준으로 매번 해석하는 invoker's rights 방식과 반대 개념이다
구분 이름  해석 시점 권한 기준
독립 DML 실행 시점 실행 계정
트리거 내장 DML 정의 시점 정의 계정

3.3 실제로 겪은 증상

  • 증상 — dba 계정으로 트리거를 생성하자 트리거 안의 테이블 참조가 dba 스키마 기준으로 고정됨. 실제 감시/참조 대상은 NCSC_INTEG 소유 테이블인데도 계속 dba 기준으로 해석되며 실패했음
  • 효과 없던 시도 — 스키마 접두사를 SQL 문법 차원에서 아무리 바꿔도 해결되지 않음
  • 해결 — 정의하는 계정 자체를 바꾸는 것(NCSC_INTEG/admin으로 재로그인 후 재생성)만이 유일한 해법이었다

에러가 SQL 문법이 아니라 "누가 정의했는가"에서 비롯될 수 있음 — 구문을 계속 고쳐도 증상이 그대로면 세션 계정을 먼저 의심해야 함.

3.4 이렇게 설계된 이유

  • 반복 발동되는 객체 — 트리거는 한 번 정의되고 이후 수많은 다른 주체(예: Python 생성기, NiFi 프로세서, 운영자 콘솔)에 의해 반복 발동됨
  • 발동 시점 권한의 위험 — 매번 발동시키는 주체의 권한에 따라 동작이 달라지면 결과가 예측 불가능해지고, 권한 상승 경로가 열릴 수 있음
  • 일관성 확보 — 발동 시점이 아니라 정의 시점의 권한으로 고정시켜 언제나 동일하게 동작하도록 설계된 것이다

요약

항목  잘못 알던 것 실제
행 값 참조 테이블명.컬럼명 obj.컬럼명 (고정 예약어)
현재시각 함수 SYS_DATETIME SYSDATETIME
스키마 접두사 모든 위치에 사용 가능 obj.컬럼명에는 사용 불가
권한 기준 트리거를 발동시킨 계정 트리거를 정의한 계정
스키마 오류 해결법 SQL 구문 수정 정의 계정 변경 후 재생성

트리거는 DDL로 박제된 DML임 — 문법은 매뉴얼에서 확인하고, 동작 범위는 정의하는 계정에서 확인해야 한다.