diff --git a/sql/adb/29_agent_ords_vector_search_ords.sql b/sql/adb/29_agent_ords_vector_search_ords.sql index 865fd7f..9f1b409 100644 --- a/sql/adb/29_agent_ords_vector_search_ords.sql +++ b/sql/adb/29_agent_ords_vector_search_ords.sql @@ -48,19 +48,24 @@ BEGIN p_source => q'! DECLARE v_rows SYS_REFCURSOR; + v_body_text CLOB; v_embedding_text VARCHAR2(32767); BEGIN cb_ords_handler_pkg.set_vpd_context(:auth_header); + -- ORDS exposes :body_text as a stream bind. Read it exactly once; referring + -- to the stream bind twice causes ORA-17270 (Duplicate stream parameter). + v_body_text := :body_text; + -- JSON_QUERY accepts a numeric JSON array. The JSON_VALUE fallback also -- permits clients that send the vector as the text "[0.1,0.2,...]". v_embedding_text := JSON_QUERY( - :body_text, + v_body_text, '$.embedding' RETURNING VARCHAR2(32767) ); IF v_embedding_text IS NULL THEN v_embedding_text := JSON_VALUE( - :body_text, + v_body_text, '$.embedding' RETURNING VARCHAR2(32767) ); END IF; diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java b/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java index e743cd6..7f66fa6 100644 --- a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java +++ b/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java @@ -126,6 +126,19 @@ class GuidedFlowTemplateTest { .contains("검색 기술 상세 보기"); } + @Test + void vectorOrdsHandlerReadsBodyStreamOnlyOnce() throws IOException { + String sql = Files.readString(Path.of("sql/adb/29_agent_ords_vector_search_ords.sql")); + + assertThat(sql) + .contains("v_body_text CLOB") + .contains("v_body_text := :body_text") + .contains("Duplicate stream parameter") + .doesNotContain("JSON_QUERY(\n :body_text") + .doesNotContain("JSON_VALUE(\n :body_text"); + assertThat(sql.split("v_body_text := :body_text", -1)).hasSize(2); + } + private String template(String relativePath) throws IOException { return Files.readString(Path.of("src/main/resources/templates").resolve(relativePath)); }