refs #734: add validated annotation PL/SQL API

This commit is contained in:
devmrko
2026-07-24 16:03:50 +09:00
parent 7175460314
commit 745c091113
2 changed files with 190 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
-- SGMP PoC: validated table/view and column annotation API
-- Issue: #734
CREATE OR REPLACE FUNCTION sgmp_set_annotation(
p_schema_name IN VARCHAR2,
p_target_kind IN VARCHAR2,
p_object_name IN VARCHAR2,
p_column_name IN VARCHAR2 DEFAULT NULL,
p_change_text IN VARCHAR2 DEFAULT NULL,
p_annotation_name IN VARCHAR2 DEFAULT 'AI_GUIDANCE'
) RETURN VARCHAR2
AUTHID DEFINER
IS
l_schema_name VARCHAR2(128);
l_object_name VARCHAR2(128);
l_column_name VARCHAR2(128);
l_annotation_name VARCHAR2(128);
l_target_kind VARCHAR2(20);
l_object_type VARCHAR2(30);
l_exists PLS_INTEGER := 0;
l_action VARCHAR2(20);
l_sql VARCHAR2(32767);
FUNCTION simple_name(p_value VARCHAR2, p_label VARCHAR2) RETURN VARCHAR2 IS
l_value VARCHAR2(128) := UPPER(TRIM(p_value));
BEGIN
IF l_value IS NULL OR NOT REGEXP_LIKE(l_value, '^[A-Z][A-Z0-9_$#]{0,127}$') THEN
RAISE_APPLICATION_ERROR(-20001, p_label || ' has an invalid format.');
END IF;
RETURN l_value;
END;
FUNCTION qname(p_value VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN DBMS_ASSERT.ENQUOTE_NAME(p_value, FALSE);
END;
FUNCTION literal(p_value VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN DBMS_ASSERT.ENQUOTE_LITERAL(p_value);
END;
BEGIN
l_schema_name := simple_name(p_schema_name, 'schema_name');
l_object_name := simple_name(p_object_name, 'object_name');
l_target_kind := UPPER(TRIM(p_target_kind));
IF l_target_kind NOT IN ('TABLE', 'COLUMN') THEN
RAISE_APPLICATION_ERROR(-20002, 'target_kind must be TABLE or COLUMN.');
END IF;
l_annotation_name := simple_name(p_annotation_name, 'annotation_name');
IF p_change_text IS NULL OR LENGTH(p_change_text) = 0 THEN
RAISE_APPLICATION_ERROR(-20003, 'change_text must not be empty.');
END IF;
IF LENGTH(p_change_text) > 4000 THEN
RAISE_APPLICATION_ERROR(-20004, 'change_text must be 4000 characters or less.');
END IF;
BEGIN
SELECT object_type
INTO l_object_type
FROM all_objects
WHERE owner = l_schema_name
AND object_name = l_object_name
AND object_type IN ('TABLE', 'VIEW')
AND ROWNUM = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20005, 'TABLE or VIEW object was not found.');
END;
IF l_target_kind = 'COLUMN' THEN
IF p_column_name IS NULL THEN
RAISE_APPLICATION_ERROR(-20006, 'column_name is required for COLUMN target.');
END IF;
IF l_object_type = 'VIEW' THEN
RAISE_APPLICATION_ERROR(-20007, 'VIEW column annotations cannot be altered by Oracle.');
END IF;
l_column_name := simple_name(p_column_name, 'column_name');
BEGIN
SELECT 1 INTO l_exists
FROM all_tab_columns
WHERE owner = l_schema_name
AND table_name = l_object_name
AND column_name = l_column_name
AND ROWNUM = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20008, 'column_name does not exist on the table.');
END;
ELSIF p_column_name IS NOT NULL THEN
RAISE_APPLICATION_ERROR(-20009, 'column_name is not allowed for TABLE target.');
END IF;
SELECT COUNT(*)
INTO l_exists
FROM all_annotations_usage
WHERE annotation_owner = l_schema_name
AND object_name = l_object_name
AND object_type = l_object_type
AND annotation_name = l_annotation_name
AND (l_target_kind = 'TABLE' AND column_name IS NULL
OR l_target_kind = 'COLUMN' AND column_name = l_column_name);
IF l_exists > 0 THEN
IF l_target_kind = 'COLUMN' THEN
l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
|| ' MODIFY ' || qname(l_column_name) || ' ANNOTATIONS (DROP '
|| qname(l_annotation_name) || ')';
ELSIF l_object_type = 'VIEW' THEN
l_sql := 'ALTER VIEW ' || qname(l_schema_name) || '.' || qname(l_object_name)
|| ' ANNOTATIONS (DROP ' || qname(l_annotation_name) || ')';
ELSE
l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
|| ' ANNOTATIONS (DROP ' || qname(l_annotation_name) || ')';
END IF;
EXECUTE IMMEDIATE l_sql;
l_action := 'REPLACED';
ELSE
l_action := 'ADDED';
END IF;
IF l_target_kind = 'COLUMN' THEN
l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
|| ' MODIFY ' || qname(l_column_name) || ' ANNOTATIONS (ADD '
|| qname(l_annotation_name) || ' ' || literal(p_change_text) || ')';
ELSIF l_object_type = 'VIEW' THEN
l_sql := 'ALTER VIEW ' || qname(l_schema_name) || '.' || qname(l_object_name)
|| ' ANNOTATIONS (ADD ' || qname(l_annotation_name) || ' '
|| literal(p_change_text) || ')';
ELSE
l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
|| ' ANNOTATIONS (ADD ' || qname(l_annotation_name) || ' '
|| literal(p_change_text) || ')';
END IF;
EXECUTE IMMEDIATE l_sql;
RETURN l_action || ': ' || l_schema_name || '.' || l_object_name
|| CASE WHEN l_column_name IS NULL THEN '' ELSE '.' || l_column_name END
|| ' [' || l_annotation_name || ']';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE BETWEEN -20099 AND -20000 THEN
RAISE;
END IF;
RAISE_APPLICATION_ERROR(-20099, 'annotation change failed: ' || SQLERRM);
END;
/
SHOW ERRORS FUNCTION sgmp_set_annotation;

View File

@@ -0,0 +1,42 @@
# SGMP annotation 관리 API (#734)
> 상태: Approved
> 구현: `sql/adb/76_sgmp_annotation_api.sql`
> 목적: 백오피스와 운영 스크립트가 동일한 검증 규칙으로 테이블/뷰 annotation을 추가·수정하도록 한다.
## 입력 계약
`SGMP_SET_ANNOTATION`은 스키마, 대상 종류(`TABLE` 또는 `COLUMN`), 테이블/뷰 이름, 선택적 컬럼 이름, annotation 값, 선택적 annotation 이름을 받는다. annotation 이름 기본값은 `AI_GUIDANCE`이며, 이름을 명시하면 그 이름을 사용한다.
컬럼 이름은 `COLUMN` 대상에서만 필수다. 값은 4,000자 이내이며 빈 값은 허용하지 않는다. 객체와 컬럼은 `ALL_OBJECTS`/`ALL_TAB_COLUMNS`에서 확인하고 식별자는 `DBMS_ASSERT`로 제한한다.
## 동작
1. 대상 객체가 TABLE 또는 VIEW인지 확인한다.
2. VIEW의 COLUMN 대상은 Oracle에서 변경할 수 없으므로 명확한 오류로 거부한다.
3. `ALL_ANNOTATIONS_USAGE`에서 동일 annotation의 존재 여부를 확인한다.
4. 기존 값이 있으면 DROP 후 ADD, 없으면 ADD만 수행한다.
5. 수행 결과(`ADDED` 또는 `REPLACED`)와 정규화된 대상을 반환한다.
DDL은 Oracle의 implicit commit 특성이 있으므로 호출자는 별도 트랜잭션으로 간주하지 않는다. 이 함수는 게임명·prefix·특정 테이블 정책을 하드코딩하지 않으며, 입력 객체의 존재와 Oracle 문법만 검증한다.
## 호출 예
```sql
SELECT SGMP_SET_ANNOTATION(
'SGMP_POC', 'TABLE', 'CZN_COMN_USER_MST', NULL,
'AU는 최신 BASE_DT에서 AU_FLAG=1이고 EXPT_USER_YN=N인 활성 사용자 수다.',
'AI_GUIDANCE'
) FROM dual;
SELECT SGMP_SET_ANNOTATION(
'SGMP_POC', 'COLUMN', 'CZN_COMN_USER_MST', 'AU_FLAG',
'AU 집계용 활성 사용자 플래그(1=활성).', 'BUSINESS_DEFINITION'
) FROM dual;
```
## 테스트 기준
- TABLE 신규 annotation은 `ADDED`를 반환한다.
- 같은 이름을 다시 호출하면 기존 값을 교체하고 `REPLACED`를 반환한다.
- 존재하지 않는 객체/컬럼, 잘못된 대상 종류, VIEW의 COLUMN 대상은 오류를 반환한다.