콘텐츠로 이동

(KO) CUBRID 읽기 경로 — 저장 프로시저 호출이 끝에서 끝까지 어떻게 실행되는가 (JavaSP/PL/CSQL과 임베디드 SQL 콜백)

이 문서가 따라가는 것은 단 한 번의 호출이다 — CALL my_sp('arg'), 여기서 my_sp은 본문에서 서버 측 JDBC 드라이버로 SELECT * FROM t WHERE x > 10 임베디드 SELECT를 발행하는 JavaSP다. JDBC 클라이언트가 CallableStatement.execute을 호출한 순간부터 마지막 결과가 애플리케이션에 돌아올 때까지가 추적 대상이다. 여정은 CUBRID broker 데몬으로 들어가는 TCP 바이트 스트림에서 시작해, Unix 도메인의 SCM_RIGHTS 파일 디스크립터 전달로 idle CAS 워커에 넘겨 지고, 표준 CSS 프레이밍을 거쳐 cub_server의 요청 핸들러 안에 도착하고, CALL 문에 대한 컴파일 파이프라인을 걸어, PL 패밀리 (cubrid-pl-javasp.md, cubrid-pl-plcsql.md)에 들어간다. PL 패밀리 는 호출을 SP_CODE_INVOKE 봉투로 묶어 cub_pl JVM에 보내고, JVM 안의 TargetMethod.invoke()가 리플렉션 디스패치를 수행해 SP 본문이 실행된다. 본문이 임베디드 SELECT을 발행하면 PL 서버 브리지 (cubrid-pl-server-bridge.md)가 각 콜백을 METHOD_CALLBACK_* 오피코드를 담은 SP_CODE_INTERNAL_JDBC로 감싸, 원래 호출이 도착했 던 같은 소켓으로 cub_server에 다시 보낸다. 서버의 cubpl::executor::response_callback_command가 그 봉투를 callback_prepare / callback_execute / callback_fetch로 라우팅하면, 표준 컴파일·실행 파이프라인을 재귀적으로 다시 들어간다. 결과 행은 JVM으로 흘러 돌아오고, SP 본문이 끝나면 SP 반환값이 SP_CODE_RESULT로 돌아오며, 나머지 귀로 구간은 cubrid-rpath-select.md의 반환 경로와 같다.

예시를 PL/CSQL이 아닌 JavaSP로 잡은 까닭은 리플렉션 디스패치 단계가 구체적이고 눈에 보이기 때문이다. PL/CSQL 변종도 같은 여정을 따라 가되, 리플렉션 호출 자리에 PlcsqlCompilerMain.compileInner / 컴파일된 클래스 디스패치가 들어간다. 이 rpath는 둘 모두를 다루며, PL/CSQL 전용 갈래는 따로 표시한다.

임베디드 SELECT 예시는 의도적으로 작다. SP 경로에서 가장 큰 영향을 미치는 단일 메커니즘(서버↔JVM 콜백 채널)을 join, ordering, aggregate 의 짐을 더하지 않은 채 드러내려는 선택이다. 이 경로 의 갈래 (재귀 SP 호출, JavaSP와 PL/CSQL의 컴파일 시점 의미 검사 차이, holdable 결과 셋, OID 머터리얼라이즈 콜백)는 끝의 다루지 않은 항목 절에 한 줄짜리 포인터로 정리해 두었다.

1단계 — 클라이언트에서 broker로 (CallableStatement)

섹션 제목: “1단계 — 클라이언트에서 broker로 (CallableStatement)”

여정은 JDBC 클라이언트 프로세스에서 시작한다. 애플리케이션이 CallableStatement.execute("CALL my_sp(?)")을 호출하면 JDBC 드라이버 가 그 호출을 CCI 요청으로 감싸 TCP 소켓으로 cub_broker에 내려 보낸다. broker 입장에서 CALL 문은 다른 SQL과 구별되지 않는다 — 같은 dispatch_thr_fSCM_RIGHTS로 idle CAS에 파일 디스크립 터를 넘기고, 그 시점부터는 JDBC 드라이버와 CAS가 직접 대화한다. broker의 receiver/dispatch/CAS 워커 형태와 SCM_RIGHTS fd 핸드오프 는 cubrid-broker.md §Process topology 참조. 이 여정의 나머지 구간에서 broker는 핫 패스에 있지 않다.

CAS는 JDBC 측 소켓에서 SQL 텍스트를 받아 ux_prepare / ux_execute 을 거치고, 이들은 임베디드 db_* API (db_open_bufferdb_compile_statement_localdb_execute_statement_local)을 호출한다. SQL이 리터럴 텍스트이므로 이 CALLux_execute 안에서 prepare와 execute가 연달아 발화한다. 이 어댑테이션의 정설은 cubrid-dbi-cci.md. CAS는 네트워크 관점에서 CUBRID 클라이언트다 — JDBC 측 소켓 옆에 서버 측 CSS 소켓을 함께 쥐고 있고, 컴파일·실행 작업을 표준 NET_SERVER_* 프레이밍으로 cub_server에 보낸다. 프레이밍은 cubrid-network-protocol.md에 정리되어 있다.

2단계 — 서버 측 요청 진입과 세션 바인딩

섹션 제목: “2단계 — 서버 측 요청 진입과 세션 바인딩”

연결 수락과 세션 바인딩은 평범한 SELECT 경로와 동일하다 — 자세한 내용은 cubrid-rpath-select.md 2단계 참조. 간단히 말하면, cub_master가 새 연결을 Unix 도메인 소켓으로 cub_server에 넘기고, epoll 기반 cubconn::connection::worker이 CSS 프레임드 패킷을 읽어 net_Requests[]로 디코딩된 요청을 디스패치한다. 우리의 CALL 에서 관련 오피코드는 NET_SERVER_QM_QUERY_PREPARE이다(Statement.execute 경로의 통합 prepare+execute). SESSION_STATE 조회, 트랜잭션 바인딩 (TDES), THREAD_ENTRY 셋업이 모두 여기서 일어난다 — cubrid-server-session.mdcubrid-transaction.md이 정설이다.

트랜잭션 바인딩은 평범한 SELECT 경로보다 SP 경로에서 더 무겁게 들어온다. SP 호출은 기본적으로 호출자의 트랜잭션에 합류하기 때문 이다 (JavaSP은 호출자가 연 같은 TDES 안에서 돈다). SP의 transaction_control 플래그가 켜져 있다면 SP 자신이 브리지로 COMMIT이나 ROLLBACK을 발행할 수도 있다. PL/CSQL은 항상 transaction_control = true이고, JavaSP은 SP 정의의 플래그를 따른다. invoke_java::transaction_control 필드는 cubrid-pl-javasp.md §Wire protocol 참조.

쿼리 매니저가 원시 SQL 텍스트를 컴파일 프런트엔드에 넘긴다. 렉서 / Bison 파서 / 의미 검사 / 재작성 / 옵티마이저 / XASL 생성기 / XASL 캐시 여정은 어떤 SQL 문이라도 같다 — 각 단계의 정설은 cubrid-parser.md, cubrid-semantic-check.md, cubrid-query-rewrite.md, cubrid-query-optimizer.md, cubrid-xasl-generator.md, cubrid-xasl-cache.md이다.

CALL 문이 SELECT과 다른 점은 두 가지다.

  • 파스 트리 모양. 루트는 PT_SELECT이 아니라 PT_METHOD_CALL (혹은 SP-call 서브타입을 가진 PT_FUNCTION)이다. 이름 해석은 my_sp을 테이블 카탈로그가 아니라 _db_stored_procedure 카탈로그 (cubrid-pl-javasp.md §Catalog rows 참조)에서 찾는다. 누락된 SP는 옵티마이저가 돌기 전에 의미 검사 단계에서 거절된다.
  • XASL 모양. XASL 루트는 BUILDLIST_PROC이 아니라 DO_PROC이다. DO_PROC은 단일 프로시저 호출과 패킹된 인자 표현식을 들고 있고, 실행기의 블록별 이터레이터는 호출을 한 번 돌리고 반환값을 거둔다. 호출 자체에는 행 스트림 출력이 없지만 SP 본문의 임베디드 쿼리는 콜백마다 결과 셋을 만들어 낼 수 있다 (아래 7단계 참조).

XASL 캐시 키는 SP 시그니처로 줄어들기 때문에, 같은 CALL my_sp(?)을 다시 호출하면 parse / 의미 검사 / optimize / XASL을 건너뛰고 곧장 실행으로 들어간다. 첫 호출만 전체 컴파일 비용을 치른다.

4단계 — 실행기가 DO_PROC에 진입해 SP를 해석한다

섹션 제목: “4단계 — 실행기가 DO_PROC에 진입해 SP를 해석한다”

qexec_execute_mainblock_internalswitch (xasl->type)DO_PROC에 닿으면 호출별 리졸버로 디스패치한다. 리졸버는 의미 검사 가 채워 둔 카탈로그 행을 걸어가며 다음을 추출한다 — 언어 태그 (SP_LANG_JAVA 또는 SP_LANG_PLCSQL), 타깃 클래스·타깃 메서드 문자열 (JavaSP 한정), 인자 디스크립터 리스트 (인자별 mode + DB type), 그리고 transaction_control 플래그. 산출물은 cubpl::pl_signature 하나와 cub_pl로 보낼 인자 값 벡터다.

다음으로 실행기는 PL 패밀리에 본격 진입한다. 호출별 cubpl::executor 객체가 라운드트립을 구동하는 모습은 cubrid-pl-javasp.md §C++ session and executor에 적혀 있다.

  1. executor::fetch_args_peek()DO_PROC XASL 값 디스크립터에서 인자 벡터를 채운다.
  2. executor::request_invoke_command() — 시그니처와 인자를 invoke_java 페이로드에 패킹하고, 글로벌 PL_CONNECTION_POOL (사전에 cub_pl에 열어 둔 N 개의 UDS 또는 TCP 소켓 중 하나)에서 연결을 가져와, SP_CODE_INVOKE과 페이로드를 차례로 쓴다.
  3. executor::response_invoke_command(value) — 같은 연결에서 응답을 읽는 루프에 들어간다. 각 프레임은 SP_CODE_RESULT(최종 반환값), SP_CODE_ERROR, 또는 SP_CODE_INTERNAL_JDBC(콜백 요청 — 7단계에서 처리) 중 하나다.

연결 풀, 연결, 그리고 풀→connection_view RAII는 cubrid-rpath-select.md의 평범한 소켓 추상과 같고, 반대편에 JDBC 클라이언트 대신 cub_pl이 앉아 있을 뿐이다.

5단계 — JVM 디스패치 (ListenerThread → ExecuteThread)

섹션 제목: “5단계 — JVM 디스패치 (ListenerThread → ExecuteThread)”

연결의 JVM 쪽 끝에서 cub_plListenerThread (pl_engine/pl_server/.../ListenerThread.java)는 단일 accept 루프다. 수락된 UDS 또는 TCP 소켓마다 ExecuteThread 하나를 spawn한 뒤 park한다. ExecuteThread는 자기 소켓에서 한 번에 한 프레임씩 읽고 RequestCode별로 디스패치한다.

  • RequestCode.UTIL_PING / UTIL_BOOTSTRAP → 생존 신호 / sysparm 셋업 (부트 시점에 처리되며 호출 경로에는 안 잡힌다).
  • RequestCode.INVOKE_SP → SP 호출 진입점 — processStoredProcedure().
  • RequestCode.COMPILE → PL/CSQL 컴파일 (CREATE PROCEDURE 시점 한정, 호출 경로 아님).
  • RequestCode.DESTROY → 세션 해제.

우리의 CALL에서 프레임의 오피코드는 INVOKE_SP다. processStoredProcedure()이 하는 일은 다음과 같다.

  1. PrepareArgs.readArgs()invoke_java의 인자 디스크립터를 따라 타입 강제 변환을 적용해 와이어 프레임의 인자 벡터를 자바 Value 객체로 푼다.
  2. makeStoredProcedure() — 언어 태그를 보고 JavaSP과 PL/CSQL 디스패치 중 어디로 갈지 아는 StoredProcedure 인스턴스를 돌려 준다.
  3. StoredProcedure.invoke() — 실제 사용자 코드 디스패치.

JavaSP의 경우, invoke()TargetMethod을 만들거나 캐시에서 가져온다. TargetMethodgetMethod()은 리플렉션 (Class.getMethod(methodName, argTypes))으로 사용자 메서드를 풀고, 이때 사용하는 클래스로더 계층은 cubrid-pl-javasp.md §JavaSP-specific: reflective dispatch and classloaders에 정리되어 있다 — ContextClassLoader$CUBRID_DATABASES/<db>/java/에서 JAR을 찾고, 세션별 격리 계층으로 SessionClassLoader이 그 위에 얹힌다. Method 객체가 손에 들어오면 Method.invoke(target, args)이 사용자 코드를 돌린다.

PL/CSQL은 invoke()이 인-프로세스 컴파일된 클래스로 라우팅된다. JVM은 CREATE PROCEDURE 시점에 PlcsqlCompilerMain이 인-프로세스 javax.tools.JavaCompiler로 만들어 둔 프로시저별 Class 객체를 들고 있다. 컴파일된 JAR이 _db_stored_procedure_code.ocode에 Base64로 저장되었다가 JVM이 필요할 때 다시 로드되는 흐름은 cubrid-pl-plcsql.md §Compilation pipeline at CREATE PROCEDURE time이 설명한다. 거기서부터의 디스패치도 마찬가지로 리플렉션이다.

어느 쪽이든 사용자 코드는 JVM 안 ExecuteThread의 스레드 위에서 돈다. 그 스레드는 cub_pl 소유이며 SP 호출 하나에 그 호출이 끝날 때까지 묶여 있다. 재귀 SP 호출 (자바 SP가 다른 SP을 호출)은 새로운 execution_stack 엔트리를 잡고 별도 ExecuteThread로 처리되지만 세션은 공유한다 — cubrid-pl-javasp.md §“Server-side JDBC back-channel” 참조.

6단계 — SP 본문 실행, 임베디드 SELECT 발행

섹션 제목: “6단계 — SP 본문 실행, 임베디드 SELECT 발행”

사용자의 자바 코드는 다음과 같이 생겼다.

public static int my_sp(String arg) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:default:connection:");
PreparedStatement ps = conn.prepareStatement("SELECT * FROM t WHERE x > ?");
ps.setInt(1, 10);
ResultSet rs = ps.executeQuery();
int count = 0;
while (rs.next()) count++;
rs.close();
ps.close();
return count;
}

jdbc:default:connection: URL은 서버 측 JDBC 드라이버 (CUBRIDServerSideDriver)을 가리키는 잘 알려진 단축 URL이며, JVM 부트 시점에 DriverManager에 등록된다. 그 URL로 getConnection을 호출하면 새 TCP 소켓을 열지 않는 CUBRIDServerSideConnection이 돌아온다 — 이미 ExecuteThread이 읽고 있는 그 소켓을 그대로 재사용한다. 이것이 cubrid-pl-server-bridge.md이 부르는 Path B다. 즉 기존 채널 위에서 cub_plcub_server 콜백을 SP_CODE_INTERNAL_JDBC 봉투로 실어 나르는 모던 PL 브리지다.

prepareStatement(...)CUBRIDServerSidePreparedStatement을 만들 지만 아직 서버와 통신하지 않는다 — SQL을 로컬에 캐시할 뿐이다. 실제 라운드트립은 executeQuery()에서 시작한다. 그 호출은 다음을 한다.

  1. 외부 코드 SP_CODE_INTERNAL_JDBC + 내부 오피코드 METHOD_CALLBACK_QUERY_PREPARE, SQL 텍스트, 호스트 변수 버퍼를 하나의 프레임에 패킹.
  2. 그 프레임을 소켓에 쓴다.
  3. 응답 read에서 블로킹.

JVM 스레드가 블록된 동안, 같은 연결에서 읽기로 블록되어 있던 cub_server 워커 스레드 — executor::response_invoke_command에 갇혀 있던 — 가 깨어난다.

7단계 — 서버 측 콜백 디스패치

섹션 제목: “7단계 — 서버 측 콜백 디스패치”

이어지는 동작의 정설 문서는 cubrid-pl-server-bridge.md §Path B — cub_pl → server다. 서버 워커 스레드는 SP_CODE_INTERNAL_JDBC 봉투를 읽고 executor::response_callback_command()을 호출한다. 함수가 내부 오피코드를 풀어 디스패치한다.

// executor::response_callback_command — pl_executor.cpp
switch (code) {
case METHOD_CALLBACK_QUERY_PREPARE:
error_code = callback_prepare (thread_ref, unpacker); break;
case METHOD_CALLBACK_QUERY_EXECUTE:
error_code = callback_execute (thread_ref, unpacker); break;
case METHOD_CALLBACK_FETCH:
error_code = callback_fetch (thread_ref, unpacker); break;
case METHOD_CALLBACK_OID_GET:
error_code = callback_oid_get (thread_ref, unpacker); break;
case METHOD_CALLBACK_END_TRANSACTION:
error_code = callback_end_transaction (thread_ref, unpacker); break;
/* ... twelve handlers total ... */
}

우리의 executeQuery에서 JVM은 사실 프레임을 차례로 보낸다 — METHOD_CALLBACK_QUERY_PREPARE(SQL 컴파일)과 METHOD_CALLBACK_QUERY_EXECUTE(실행). 서버 측 JDBC 드라이버가 표준 JDBC 라이프사이클을 따르기 때문이다. callback_prepare은 임베디드 SELECT을 위 3단계와 같은 컴파일 파이프라인으로 돌린다 — cubrid-parser.md, cubrid-semantic-check.md, cubrid-query-rewrite.md, cubrid-query-optimizer.md, cubrid-xasl-generator.md, cubrid-xasl-cache.md 모두 재귀적으로 다시 발화한다. XASL 캐시는 같은 SP의 두 번째 호출에서, 그리고 SP가 같은 SQL을 반복해 발행하면 같은 SP 안에서도 보통 적중한다. prepare은 서버 측 prepared-statement 핸들 ID를 돌려준다.

callback_execute은 prepared statement을 돌려 쿼리 ID와 결과 행의 첫 배치를 돌려준다. 이후 행에 대한 커서 유지는 callback_fetch(배치마다 한 프레임)이 담당한다. pl_executor.cpp의 핸들러 구현은 평범한 SELECT 경로가 쓰는 같은 쿼리 매니저 기계로 들어간다 — xqmgr_execute_query이 여기서도 발화한다. 결국 콜백 경로는 SP 안에서 cubrid-rpath-select.md의 4~11단계를 사실상 다시 들어가는 셈이다.

재귀에는 가드가 걸려 있다. cubrid-pl-server-bridge.md §Recursion guard에 정확히 적혀 있다. tran_get_libcas_depth()(Path A) 와 PL 세션의 m_stack_map 크기(Path B) 둘 다 METHOD_MAX_RECURSION_DEPTH = 15과 비교한다. 다른 SP을 호출하고 그 SP가 또 다른 SP를 호출하는 식으로 15을 넘는 SP는 ER_SP_TOO_MANY_NESTED_CALL로 거절된다. 우리의 임베디드 SELECT은 다른 SP로 재귀하지 않으므로 깊이는 1에 머문다.

prepare / execute / fetch의 패킹된 와이어 구조는 src/method/method_struct_query.{cpp,hpp}, method_struct_value.{cpp,hpp} 그리고 OID 패밀리는 method_struct_oid_info.{cpp,hpp}에 산다. 같은 패킹 구조가 더 오래 된 Path A(서버→CAS, legacy C-method scan)에서도 쓰인다. 공유 패밀리는 cubrid-pl-server-bridge.md §Packed wire structures 참조.

8단계 — 결과 행이 JVM으로 흘러 돌아온다

섹션 제목: “8단계 — 결과 행이 JVM으로 흘러 돌아온다”

callback_execute의 핸들러는 쿼리 ID, 컬럼 메타데이터, 첫 행 배치를 담은 METHOD_RESPONSE_SUCCESS 페이로드를 패킹하고 m_stack->send_data_to_java(blk)을 호출해 같은 연결로 응답을 실어 보낸다. 6단계 이후 블록되어 있던 JVM 스레드가 깨어난다.

JVM은 응답을 CUBRIDServerSideResultSet으로 풀고 배치별 행 버퍼를 받침으로 깐 뒤, 사용자 코드에 executeQuery()ResultSet으로 돌려준다. 사용자 입장에서 JDBC API의 거동은 평범한 외부 클라이언트 연결과 똑같다 — rs.next()이 행 단위로 진행하고, 필요한 시점에 METHOD_CALLBACK_FETCH 콜백 라운드트립으로 다음 배치를 받아 온다. 버퍼 배치를 다 쓴 next()은 또 한 번의 콜백 라운드트립을 발생시킨다. 작은 SP은 한 번의 교환으로 끝나기도 하고, 큰 SP은 여러 번을 거친다.

우리의 예시에서 SP 본문은 행 수를 세서 카운트를 로컬에 누적할 뿐 이다. 루프가 다 빠져 나오면 rs.close()METHOD_CALLBACK_CURSOR_CLOSE를 발사하고, ps.close()이 핸들별 정리를 트리거하고, SP 본문의 return count;이 SP의 반환값이 된다.

9단계 — SP_CODE_RESULT과 서버 측 핸드오프

섹션 제목: “9단계 — SP_CODE_RESULT과 서버 측 핸드오프”

JVM의 processStoredProcedure()은 반환값을 SP_CODE_RESULT 외부 코드와 패킹된 Value을 담은 와이어 프레임으로 패킹해 소켓에 쓴다. 계속 executor::response_invoke_command의 read 루프 안에 있던 cub_server 워커 스레드가 마지막으로 한 번 깨어나, SP_CODE_RESULT 을 보고 반환값을 executor의 출력 DB_VALUE로 푼 뒤 루프를 빠져나온다.

request_invoke_command은 연결을 풀에 돌려주고 executor::execute() 호출이 반환된다. qexec_execute_mainblock_internal로 돌아오면 DO_PROC 블록은 SP의 반환값을 XASL 출력으로 세팅하고 실행기는 다음 블록으로 넘어간다 (우리의 경우 다음 블록은 없다 — CALL이 문 전체 다). XASL이 닫히고, 단일 반환값 튜플을 실어 나른 list-file이 finalize되고, 클라이언트로 돌아가는 여정이 시작된다.

10단계 — 결과가 클라이언트로 돌아온다

섹션 제목: “10단계 — 결과가 클라이언트로 돌아온다”

JDBC 애플리케이션으로 가는 귀로는 출국 여정의 역순이며, cubrid-rpath-select.md 11단계와 한 가지 차이만 빼면 동일하다. CALL의 결과 셋 디스크립터는 단일 컬럼·단일 행 스키마(SP의 반환값 하나)이므로 네트워크 트래픽이 작다. CAS는 행을 JDBC 측 소켓으로 JDBC 드라이버에 전달하고, 드라이버는 그것을 CallableStatement.getInt(1) (또는 동급 메서드)로 애플리케이션에 넘긴다.

broker는 다시 핫 패스에 있지 않다 — 1단계의 SCM_RIGHTS 덕분에 JDBC 클라이언트와 CAS는 이미 직접 대화 중이다.

다이어그램 — 전체 파이프라인

섹션 제목: “다이어그램 — 전체 파이프라인”
sequenceDiagram
  participant JDBC as JDBC client
  participant CAS as cub_cas
  participant SRV as cub_server
  participant JVM as cub_pl JVM (ExecuteThread)
  participant USER as user SP body

  JDBC->>CAS: TCP via SCM_RIGHTS handoff (CAS owns fd)
  CAS->>SRV: NET_SERVER_QM_QUERY_PREPARE ("CALL my_sp(?)")
  SRV->>SRV: parse / semantic-check / rewrite / optimize / XASL
  SRV->>SRV: DO_PROC dispatch; resolve SP catalog row
  SRV->>JVM: SP_CODE_INVOKE (invoke_java payload)
  JVM->>JVM: ListenerThread.accept; new ExecuteThread
  JVM->>JVM: TargetMethod.invoke (reflective)
  JVM->>USER: Method.invoke(...)
  USER->>JVM: jdbc:default:connection: prepareStatement
  USER->>JVM: ps.executeQuery()
  JVM-->>SRV: SP_CODE_INTERNAL_JDBC + METHOD_CALLBACK_QUERY_PREPARE
  SRV->>SRV: callback_prepare → compile pipeline (recursive)
  SRV-->>JVM: METHOD_RESPONSE_SUCCESS (handle id)
  JVM-->>SRV: SP_CODE_INTERNAL_JDBC + METHOD_CALLBACK_QUERY_EXECUTE
  SRV->>SRV: callback_execute → executor (recursive into rpath-select)
  SRV-->>JVM: METHOD_RESPONSE_SUCCESS (rows batch)
  JVM->>USER: ResultSet.next() loop
  USER->>JVM: rs.close(); return count
  JVM->>SRV: SP_CODE_RESULT (return value)
  SRV->>SRV: DO_PROC writes return value to XASL output
  SRV->>CAS: result row (single-column, single-row)
  CAS->>JDBC: CallableStatement.getInt(1)
  • PL/CSQL 전용 갈래. PL/CSQL이 CREATE PROCEDURE 도중 임베디드 SQL을 검증하려고 발사하는 컴파일 시점 GET_SQL_SEMANTICS / GET_GLOBAL_SEMANTICS 콜백. 그쪽은 Path B(cub_pl→server)이 아니라 Path A(server→CAS) 위를 달린다. cubrid-pl-server-bridge.md §Compile-time bridge for PL/CSQL embedded SQL과 cubrid-pl-plcsql.md §Asking the C side for global semantics 참조.
  • SCAN_TYPE으로서의 method scan. legacy 서버 측 SCAN_TYPE_METHOD (Path A의 주된 트리거 — 쿼리 도중 행별 인자로 C 빌트인을 호출)은 별도로 정리되어 있다. cubrid-pl-server-bridge.md §Method scan operator와 cubrid-scan-manager.md §S_METHOD 참조.
  • 재귀 SP 호출. SP가 다른 SP을 호출하면 새 execution_stack 엔트리와 새 ExecuteThread을 받지만 세션은 공유한다. 재귀 한도는 15 프레임. cubrid-pl-javasp.md §Server-side JDBC back-channel 참조.
  • COMMIT을 가로지르는 holdable 커서. SP 본문이 열고 SP 반환 전에 닫지 않은 서버 측 커서는 holdable로 표시되어 있으면 세션의 holdable 리스트로 이주한다. cubrid-cursor.md §Holdability 참조.
  • OID 머터리얼라이즈 콜백. SP 본문이 SQL이 아니라 OID로 개별 객체를 읽으면 METHOD_CALLBACK_OID_GET / _CMD / COLLECTION 콜백을 발행한다. 같은 채널, 다른 오피코드. cubrid-pl-server-bridge.md §Path B와 cubrid-class-object.md §Workspace OID materialisation 참조.
  • JVM 크래시 복구. 호출 중 cub_pl이 죽으면 server_monitor 데몬이 감지하고 auto_restart_server = on이면 JVM이 재시작된다. 진행 중이던 호출은 ER_SP_EXECUTE_ERROR로 돌아오고 호출자의 트랜잭션은 롤백할 수 있다. cubrid-master-process.md §server_monitor — the C++ supervisor과 cubrid-pl-javasp.md §Startup FSM 참조.
  • JavaSP 클래스로더 계층과 보안 매니저. my_sp의 구현 클래스에 대한 Class<?> 로딩은 ContextClassLoaderServerClassLoader → bootstrap을 걸어가고, SpSecurityManagercheckExit(System.exit 차단)과 checkLink(사용자 컨텍스트의 네이티브 라이브러리 로드 차단)을 검사한다. cubrid-pl-javasp.md §“JavaSP-specific: reflective dispatch and classloaders” 참조.
  • SP 호출 시점의 인증과 grant. SP-invoke 카탈로그 행은 owneris_definer_rights 플래그를 들고 있다. 호출 지점 인증 검사는 실행기가 DO_PROC에 진입하기 전 의미 검사 도중에 일어난다. 인증 모델은 cubrid-authentication.md, SP 측 행은 cubrid-pl-javasp.md §Catalog rows 참조.
  • SP의 트랜잭션 제어. transaction_control = true인 SP은 본문 안에서 commit() / rollback()을 호출할 수 있고, 이것이 METHOD_CALLBACK_END_TRANSACTION을 발사한다. 이는 호출자의 트랜잭션을 커밋하거나 abort시키며, 그에 따른 모든 가시성 영향을 동반한다. cubrid-pl-server-bridge.mdcubrid-transaction.md 참조.

이 rpath는 다음 상세 문서를 (여정에 등장하는 순서대로) 합성한다.

  • cubrid-broker.md — broker/CAS 프로세스 모델과 SCM_RIGHTS 핸드오프 (1, 10단계).
  • cubrid-dbi-cci.md — CAS 측 db_* 어댑테이션 (1단계).
  • cubrid-network-protocol.md — CSS 프레이밍과 NET_SERVER_* 오피코드 분류 (1, 10단계).
  • cubrid-server-session.md, cubrid-transaction.md — 세션 조회와 TDES 바인딩 (2단계).
  • cubrid-parser.md, cubrid-semantic-check.md, cubrid-query-rewrite.md, cubrid-query-optimizer.md, cubrid-xasl-generator.md, cubrid-xasl-cache.md — 컴파일 파이프라인 (3, 7단계).
  • cubrid-query-executor.md — DO_PROC 디스패치 (4단계).
  • cubrid-pl-javasp.md — 프로세스 토폴로지, 와이어 프로토콜, C++ 세션·실행기, JVM 디스패치, 클래스로더 계층 (4, 5, 6단계).
  • cubrid-pl-plcsql.md — PL/CSQL 컴파일 산출물 (5단계, PL/CSQL 갈래).
  • cubrid-pl-server-bridge.md — PL↔서버 콜백 채널, 오피코드 분류, 재귀 가드, 패킹된 와이어 구조 (6, 7, 8단계).
  • cubrid-scan-manager.mdcallback_execute 아래의 재귀 스캔 디스패치 (7단계).
  • cubrid-list-file.md — 반환값 튜플 머터리얼라이즈 (9단계).
  • cubrid-mvcc.md — 재귀 임베디드 쿼리 실행 도중 가시성 검사 (7단계).
  • cubrid-rpath-select.mdcallback_execute이 다시 들어가는 재귀 평범한-SELECT 파이프라인 (7단계).

이 시리즈를 완성하는 인접 rpath 문서.

  • cubrid-rpath-select.md — 평범한 SELECT의 끝에서 끝.
  • cubrid-rpath-write.md — INSERT + COMMIT의 끝에서 끝.
  • cubrid-rpath-recovery.md — 복구 워크의 끝에서 끝.