-- 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;