[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 === PROMPT === Creating CB_ORDS handler package ===
CREATE OR REPLACE PACKAGE cb_ords_handler_pkg AUTHID DEFINER AS CREATE OR REPLACE PACKAGE cb_ords_handler_pkg AUTHID DEFINER AS
PROCEDURE set_vpd_context(p_authorization IN VARCHAR2); PROCEDURE set_vpd_context(p_authorization IN VARCHAR2);
FUNCTION set_vpd_context_sql(p_authorization IN VARCHAR2) RETURN NUMBER;
PROCEDURE clear_vpd_context; PROCEDURE clear_vpd_context;
END; 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); admin.cb_agent_ctx_pkg.set_user_by_bearer(v_bearer_key);
END; 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 PROCEDURE clear_vpd_context AS
BEGIN BEGIN
admin.cb_agent_ctx_pkg.clear_user; admin.cb_agent_ctx_pkg.clear_user;
@@ -79,51 +86,21 @@ BEGIN
p_module_name => 'cb.agent.security', p_module_name => 'cb.agent.security',
p_pattern => 'vpd/documents', p_pattern => 'vpd/documents',
p_method => 'POST', p_method => 'POST',
p_source_type => ORDS.source_type_plsql, p_source_type => ORDS.source_type_query,
p_source => q'! p_source => q'!
DECLARE WITH vpd_ctx AS (
v_rows_json CLOB; SELECT cb_ords_handler_pkg.set_vpd_context_sql(:auth_header) AS applied
v_offset NUMBER := 1; FROM dual
BEGIN )
cb_ords_handler_pkg.set_vpd_context(:auth_header); SELECT d.doc_id,
d.title,
SELECT JSON_ARRAYAGG( d.owner_emp_no,
JSON_OBJECT( d.dept_code,
'doc_id' VALUE doc_id, d.contents
'title' VALUE title, FROM admin.cb_v_search_documents d
'owner_emp_no' VALUE owner_emp_no, CROSS JOIN vpd_ctx
'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;
!', !',
p_items_per_page => 0 p_items_per_page => 25
); );
ORDS.DEFINE_PARAMETER( ORDS.DEFINE_PARAMETER(

View File

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