[Developer] #424 use ORDS query handlers for object reads

This commit is contained in:
devmrko
2026-06-25 14:56:53 +09:00
parent 75d4f08a20
commit bea010adff
2 changed files with 58 additions and 94 deletions

View File

@@ -20,6 +20,7 @@ SET FEEDBACK ON
PROMPT === Creating CB_ORDS handler package ===
CREATE OR REPLACE PACKAGE cb_ords_handler_pkg AUTHID DEFINER AS
PROCEDURE set_vpd_context(p_authorization IN VARCHAR2);
FUNCTION set_vpd_context_sql(p_authorization IN VARCHAR2) RETURN NUMBER;
PROCEDURE clear_vpd_context;
END;
/
@@ -45,6 +46,12 @@ CREATE OR REPLACE PACKAGE BODY cb_ords_handler_pkg AS
admin.cb_agent_ctx_pkg.set_user_by_bearer(v_bearer_key);
END;
FUNCTION set_vpd_context_sql(p_authorization IN VARCHAR2) RETURN NUMBER AS
BEGIN
set_vpd_context(p_authorization);
RETURN 1;
END;
PROCEDURE clear_vpd_context AS
BEGIN
admin.cb_agent_ctx_pkg.clear_user;
@@ -79,51 +86,21 @@ BEGIN
p_module_name => 'cb.agent.security',
p_pattern => 'vpd/documents',
p_method => 'POST',
p_source_type => ORDS.source_type_plsql,
p_source_type => ORDS.source_type_query,
p_source => q'!
DECLARE
v_rows_json CLOB;
v_offset NUMBER := 1;
BEGIN
cb_ords_handler_pkg.set_vpd_context(:auth_header);
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'doc_id' VALUE doc_id,
'title' VALUE title,
'owner_emp_no' VALUE owner_emp_no,
'dept_code' VALUE dept_code,
'contents' VALUE contents
RETURNING VARCHAR2(32767)
)
ORDER BY doc_id
RETURNING CLOB
)
INTO v_rows_json
FROM admin.cb_v_search_documents;
IF v_rows_json IS NULL THEN
v_rows_json := '[]';
END IF;
:status_code := 200;
OWA_UTIL.MIME_HEADER('application/json', TRUE);
HTP.P('{"rows":');
WHILE v_offset <= DBMS_LOB.GETLENGTH(v_rows_json) LOOP
HTP.P(DBMS_LOB.SUBSTR(v_rows_json, 32767, v_offset));
v_offset := v_offset + 32767;
END LOOP;
HTP.P('}');
cb_ords_handler_pkg.clear_vpd_context;
EXCEPTION
WHEN OTHERS THEN
cb_ords_handler_pkg.clear_vpd_context;
:status_code := 403;
OWA_UTIL.MIME_HEADER('application/json', TRUE);
HTP.P(JSON_OBJECT('error' VALUE SQLERRM RETURNING VARCHAR2(4000)));
END;
WITH vpd_ctx AS (
SELECT cb_ords_handler_pkg.set_vpd_context_sql(:auth_header) AS applied
FROM dual
)
SELECT d.doc_id,
d.title,
d.owner_emp_no,
d.dept_code,
d.contents
FROM admin.cb_v_search_documents d
CROSS JOIN vpd_ctx
!',
p_items_per_page => 0
p_items_per_page => 25
);
ORDS.DEFINE_PARAMETER(

View File

@@ -99,8 +99,8 @@ public class OrdsMetadataService {
throw new AppException("수정할 ORDS Handler를 찾을 수 없습니다.");
}
String sourceType = handler.sourceType() == null ? "" : handler.sourceType().toLowerCase(Locale.ROOT);
if (!sourceType.contains("plsql")) {
throw new AppException("현재는 PL/SQL ORDS Handler만 수정할 수 있습니다: " + handler.sourceType());
if (!sourceType.contains("plsql") && !sourceType.contains("query")) {
throw new AppException("현재는 PL/SQL/SQL Query ORDS Handler만 수정할 수 있습니다: " + handler.sourceType());
}
String currentUser = jdbcTemplate.queryForObject("SELECT USER FROM dual", String.class);
if (currentUser == null || !currentUser.equalsIgnoreCase(handler.parsingSchema())) {
@@ -108,18 +108,31 @@ public class OrdsMetadataService {
+ currentUser + ", Handler parsing schema: " + handler.parsingSchema());
}
jdbcTemplate.update("""
DECLARE
v_source_type VARCHAR2(30);
BEGIN
IF ? = 'QUERY' THEN
v_source_type := ORDS.source_type_query;
ELSE
v_source_type := ORDS.source_type_plsql;
END IF;
ORDS.DEFINE_HANDLER(
p_module_name => ?,
p_pattern => ?,
p_method => ?,
p_source_type => ORDS.source_type_plsql,
p_source_type => v_source_type,
p_source => ?,
p_items_per_page => 0
);
COMMIT;
END;
""", handler.moduleName(), handler.template(), handler.method(), command.source());
""",
handlerSourceTypeCode(handler.sourceType()),
handler.moduleName(),
handler.template(),
handler.method(),
command.source());
}
@Transactional
@@ -163,9 +176,9 @@ public class OrdsMetadataService {
p_module_name => ?,
p_pattern => ?,
p_method => 'POST',
p_source_type => ORDS.source_type_plsql,
p_source_type => ORDS.source_type_query,
p_source => ?,
p_items_per_page => 0
p_items_per_page => 25
);
ORDS.DEFINE_PARAMETER(
p_module_name => ?,
@@ -200,54 +213,28 @@ public class OrdsMetadataService {
}
private String objectQuerySource(ProtectedObject object, List<String> columns) {
String jsonEntries = columns.stream()
.map(column -> "'" + column.toLowerCase(Locale.ROOT) + "' VALUE " + column)
String selectColumns = columns.stream()
.map(column -> "o." + column)
.reduce((left, right) -> left + ",\n " + right)
.orElseThrow();
String selectColumns = String.join(", ", columns);
return """
DECLARE
v_limit NUMBER := LEAST(GREATEST(NVL(:row_limit, 50), 1), 500);
v_rows_json CLOB;
v_offset NUMBER := 1;
BEGIN
cb_ords_handler_pkg.set_vpd_context(:auth_header);
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
%s
RETURNING VARCHAR2(32767)
WITH vpd_ctx AS (
SELECT cb_ords_handler_pkg.set_vpd_context_sql(:auth_header) AS applied
FROM dual
)
RETURNING CLOB
)
INTO v_rows_json
FROM (
SELECT %s
FROM %s.%s
WHERE ROWNUM <= v_limit
);
FROM %s.%s o
CROSS JOIN vpd_ctx
WHERE ROWNUM <= LEAST(GREATEST(NVL(:row_limit, 50), 1), 500)
""".formatted(selectColumns, object.owner(), object.objectName());
}
IF v_rows_json IS NULL THEN
v_rows_json := '[]';
END IF;
:status_code := 200;
OWA_UTIL.MIME_HEADER('application/json', TRUE);
HTP.P('{"rows":');
WHILE v_offset <= DBMS_LOB.GETLENGTH(v_rows_json) LOOP
HTP.P(DBMS_LOB.SUBSTR(v_rows_json, 32767, v_offset));
v_offset := v_offset + 32767;
END LOOP;
HTP.P('}');
cb_ords_handler_pkg.clear_vpd_context;
EXCEPTION
WHEN OTHERS THEN
cb_ords_handler_pkg.clear_vpd_context;
:status_code := 403;
OWA_UTIL.MIME_HEADER('application/json', TRUE);
HTP.P(JSON_OBJECT('error' VALUE SQLERRM RETURNING VARCHAR2(4000)));
END;
""".formatted(jsonEntries, selectColumns, object.owner(), object.objectName());
private String handlerSourceTypeCode(String sourceType) {
String normalized = sourceType == null ? "" : sourceType.toLowerCase(Locale.ROOT);
if (normalized.contains("query")) {
return "QUERY";
}
return "PLSQL";
}
private void requireIdentifier(String value, String label) {