(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_f이 SCM_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_buffer → db_compile_statement_local →
db_execute_statement_local)을 호출한다. SQL이 리터럴 텍스트이므로
이 CALL은 ux_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.md와 cubrid-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 참조.
3단계 — CALL 문 컴파일
섹션 제목: “3단계 — CALL 문 컴파일”쿼리 매니저가 원시 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_internal의 switch (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에 적혀 있다.
executor::fetch_args_peek()—DO_PROCXASL 값 디스크립터에서 인자 벡터를 채운다.executor::request_invoke_command()— 시그니처와 인자를invoke_java페이로드에 패킹하고, 글로벌PL_CONNECTION_POOL(사전에cub_pl에 열어 둔 N 개의 UDS 또는 TCP 소켓 중 하나)에서 연결을 가져와,SP_CODE_INVOKE과 페이로드를 차례로 쓴다.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_pl의 ListenerThread
(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()이 하는 일은 다음과 같다.
PrepareArgs.readArgs()—invoke_java의 인자 디스크립터를 따라 타입 강제 변환을 적용해 와이어 프레임의 인자 벡터를 자바Value객체로 푼다.makeStoredProcedure()— 언어 태그를 보고 JavaSP과 PL/CSQL 디스패치 중 어디로 갈지 아는StoredProcedure인스턴스를 돌려 준다.StoredProcedure.invoke()— 실제 사용자 코드 디스패치.
JavaSP의 경우, invoke()이 TargetMethod을 만들거나 캐시에서
가져온다. TargetMethod의 getMethod()은 리플렉션
(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_pl→cub_server 콜백을 SP_CODE_INTERNAL_JDBC 봉투로
실어 나르는 모던 PL 브리지다.
prepareStatement(...)은 CUBRIDServerSidePreparedStatement을 만들
지만 아직 서버와 통신하지 않는다 — SQL을 로컬에 캐시할 뿐이다.
실제 라운드트립은 executeQuery()에서 시작한다. 그 호출은 다음을
한다.
- 외부 코드
SP_CODE_INTERNAL_JDBC+ 내부 오피코드METHOD_CALLBACK_QUERY_PREPARE, SQL 텍스트, 호스트 변수 버퍼를 하나의 프레임에 패킹. - 그 프레임을 소켓에 쓴다.
- 응답 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.cppswitch (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<?>로딩은ContextClassLoader→ServerClassLoader→ bootstrap을 걸어가고,SpSecurityManager이checkExit(System.exit차단)과checkLink(사용자 컨텍스트의 네이티브 라이브러리 로드 차단)을 검사한다.cubrid-pl-javasp.md§“JavaSP-specific: reflective dispatch and classloaders” 참조. - SP 호출 시점의 인증과 grant. SP-invoke 카탈로그 행은
owner와is_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.md과cubrid-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.md—callback_execute아래의 재귀 스캔 디스패치 (7단계).cubrid-list-file.md— 반환값 튜플 머터리얼라이즈 (9단계).cubrid-mvcc.md— 재귀 임베디드 쿼리 실행 도중 가시성 검사 (7단계).cubrid-rpath-select.md—callback_execute이 다시 들어가는 재귀 평범한-SELECT 파이프라인 (7단계).
이 시리즈를 완성하는 인접 rpath 문서.
cubrid-rpath-select.md— 평범한 SELECT의 끝에서 끝.cubrid-rpath-write.md— INSERT + COMMIT의 끝에서 끝.cubrid-rpath-recovery.md— 복구 워크의 끝에서 끝.