Tibero tbPSM — PL/SQL 호환 절차형 언어 완전 해설

Tibero의 절차형 언어 tbPSM의 블록 구조, 변수·커서·예외 처리, 프로시저·함수·패키지·트리거 작성법을 Oracle PL/SQL과 비교하며 설명합니다.

· 6 min read · PALDYN Team

지난 글에서 Tibero TAC의 클러스터 구조를 살펴봤다. 이번에는 Tibero에서 복잡한 비즈니스 로직을 DB 내부에 구현하는 데 사용하는 절차형 언어 tbPSM(Tibero Procedural SQL Module) 을 다룬다.

tbPSM이란

tbPSM은 Oracle PL/SQL을 기반으로 설계된 Tibero의 절차형 확장 언어다. SQL과 달리 조건 분기, 반복, 예외 처리, 변수 선언 등 절차형 프로그래밍 요소를 포함한다. Oracle 환경에서 작성된 PL/SQL 코드의 대부분을 재작성 없이 또는 최소한의 수정으로 실행할 수 있다.

블록 구조

tbPSM의 기본 단위는 블록(block) 이다. 익명 블록(anonymous block), 프로시저, 함수, 패키지 모두 동일한 구조를 갖는다.

tbPSM 블록 구조

-- 기본 블록 구조
DECLARE
    -- 변수, 커서, 타입, 예외 선언 (생략 가능)
    v_emp_count  NUMBER;
    v_dept_name  VARCHAR2(50);
BEGIN
    -- 실행부 (필수)
    SELECT COUNT(*), d.dept_name
    INTO   v_emp_count, v_dept_name
    FROM   employees e
    JOIN   departments d ON e.dept_id = d.dept_id
    WHERE  d.dept_id = 10
    GROUP  BY d.dept_name;

    DBMS_OUTPUT.PUT_LINE(v_dept_name || ': ' || v_emp_count || '명');
EXCEPTION
    -- 예외 처리 (생략 가능)
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('해당 부서 없음');
    WHEN OTHERS THEN
        RAISE_APPLICATION_ERROR(-20001, SQLERRM);
END;
/

저장 객체 유형

tbPSM 저장 객체 유형

프로시저(Procedure)

반환값 없이 DML 수행이나 부작용(side effect) 처리에 사용한다.

CREATE OR REPLACE PROCEDURE transfer_funds(
    p_from_id  IN  accounts.account_id%TYPE,
    p_to_id    IN  accounts.account_id%TYPE,
    p_amount   IN  NUMBER,
    p_result   OUT VARCHAR2
) AS
    v_from_bal NUMBER;
BEGIN
    SELECT balance INTO v_from_bal
    FROM   accounts WHERE account_id = p_from_id
    FOR UPDATE;  -- 행 잠금

    IF v_from_bal < p_amount THEN
        p_result := 'INSUFFICIENT_FUNDS';
        RETURN;
    END IF;

    UPDATE accounts SET balance = balance - p_amount
    WHERE  account_id = p_from_id;

    UPDATE accounts SET balance = balance + p_amount
    WHERE  account_id = p_to_id;

    COMMIT;
    p_result := 'OK';
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        p_result := 'ERROR: ' || SQLERRM;
END;
/

-- 호출
DECLARE
    v_res VARCHAR2(100);
BEGIN
    transfer_funds(1001, 1002, 50000, v_res);
    DBMS_OUTPUT.PUT_LINE(v_res);
END;
/

함수(Function)

값을 반환하며 SQL 쿼리 내에서 직접 호출할 수 있다.

CREATE OR REPLACE FUNCTION get_dept_budget(
    p_dept_id IN departments.dept_id%TYPE
) RETURN NUMBER
AS
    v_total NUMBER := 0;
BEGIN
    SELECT NVL(SUM(salary), 0)
    INTO   v_total
    FROM   employees
    WHERE  dept_id = p_dept_id;

    RETURN v_total;
END;
/

-- SQL 쿼리 내 직접 호출
SELECT dept_id, dept_name,
       get_dept_budget(dept_id) AS total_salary
FROM   departments
ORDER  BY total_salary DESC;

패키지(Package)

관련 프로시저와 함수를 하나로 묶어 모듈화한다. 명세(spec)본체(body) 로 분리된다.

-- 패키지 명세 (인터페이스 선언)
CREATE OR REPLACE PACKAGE emp_mgr AS
    g_default_dept  NUMBER := 10;  -- 패키지 전역 변수

    PROCEDURE hire(p_name VARCHAR2, p_salary NUMBER);
    FUNCTION  get_headcount(p_dept NUMBER) RETURN NUMBER;
END emp_mgr;
/

-- 패키지 본체 (구현)
CREATE OR REPLACE PACKAGE BODY emp_mgr AS
    PROCEDURE hire(p_name VARCHAR2, p_salary NUMBER) AS
    BEGIN
        INSERT INTO employees(name, salary, dept_id)
        VALUES(p_name, p_salary, g_default_dept);
        COMMIT;
    END hire;

    FUNCTION get_headcount(p_dept NUMBER) RETURN NUMBER AS
        v_cnt NUMBER;
    BEGIN
        SELECT COUNT(*) INTO v_cnt
        FROM   employees WHERE dept_id = p_dept;
        RETURN v_cnt;
    END get_headcount;
END emp_mgr;
/

-- 호출
BEGIN
    emp_mgr.hire('홍길동', 4500000);
    DBMS_OUTPUT.PUT_LINE(emp_mgr.get_headcount(10));
END;
/

트리거(Trigger)

DML 이벤트 발생 시 자동 실행된다. :OLD:NEW 의사 레코드로 변경 전후 값에 접근한다.

CREATE OR REPLACE TRIGGER trg_emp_audit
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    INSERT INTO emp_audit_log(
        emp_id, old_salary, new_salary, changed_at, changed_by
    ) VALUES (
        :OLD.employee_id,
        :OLD.salary,
        :NEW.salary,
        SYSDATE,
        SYS_CONTEXT('USERENV', 'SESSION_USER')
    );
END;
/

예외 처리

tbPSM의 예외는 사전 정의 예외사용자 정의 예외로 나뉜다.

DECLARE
    e_duplicate EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_duplicate, -1);  -- ORA-00001 (unique 위반)
BEGIN
    INSERT INTO departments(dept_id, dept_name)
    VALUES(10, '영업부');
EXCEPTION
    WHEN e_duplicate THEN
        DBMS_OUTPUT.PUT_LINE('이미 존재하는 부서 ID');
    WHEN NO_DATA_FOUND THEN
        NULL;  -- 무시
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('오류 코드: ' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('오류 메시지: ' || SQLERRM);
        RAISE;  -- 상위 블록으로 재전파
END;
/

Oracle PL/SQL과의 차이점

Oracle에서 Tibero로 이전할 때 주의해야 할 차이점이다.

항목Oracle PL/SQLTibero tbPSM
기본 문법PL/SQL거의 동일
UTL_FILE지원지원 (일부 제한)
DBMS_JOB지원tb_job으로 대체 권장
DBMS_CRYPTO지원기능 일부 차이
파이프라인 함수지원지원
컬렉션 타입TABLE OF, VARRAY동일 지원
-- Oracle에서 마이그레이션 시 검증 쿼리
SELECT object_name, object_type, status
FROM   dba_objects
WHERE  object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'TRIGGER')
  AND  status = 'INVALID'
  AND  owner  = 'APP_SCHEMA';
-- 컴파일 실패 객체 목록 확인 후 개별 수정

tbPSM은 Oracle PL/SQL을 쓰던 개발자가 거의 동일한 방식으로 코드를 작성할 수 있다. 이전 시에는 내장 패키지(DBMS_, UTL_) 사용 여부를 가장 먼저 점검하고, 비호환 패키지는 대안 구현으로 교체해야 한다.


지난 글: Tibero Active Cluster(TAC) 구조

다음 글: Oracle에서 Tibero로 마이그레이션


읽어주셔서 감사합니다. 😊