(KO) CUBRID DBI와 CCI — 클라이언트 API 표면, 구문 수명 주기, 와이어 드라이버 파사드
목차
학술적 배경
섹션 제목: “학술적 배경”관계형 엔진에 이르는 경로는 세 겹의 동심원 API로 이루어진다. DBMS
클라이언트 드라이버 코드에서 혼란이 생기는 가장 흔한 원인은 이 세
겹을 하나로 뭉뚱그려 보는 데 있다. 가장 바깥 층은 언어 바인딩이다.
JDBC, Python DB-API, ODBC, REST 프론트엔드가 여기 속한다. 중간 층은
호출 수준 인터페이스(CLI)다. X/Open이 정의한 의미에서, 모든 연산이
함수 호출(SQLPrepare, SQLBindParameter, SQLExecute, SQLFetch,
SQLFreeStmt)로 표현되고 불투명 핸들을 소유하며 상태 코드를 반환하는
절차형 C 표면이다. 가장 안쪽 층은 내장 SQL 층이다. Silberschatz 외
Database System Concepts (5장 §Embedded SQL)가 설명하듯이, 호스트
언어 전처리기가 EXEC SQL SELECT ...를 일련의 CLI 호출로 변환하는
방식이다. 오라클 OCI, PostgreSQL libpq, MySQL libmysqlclient, SQLite 등
모든 현대 엔진이 CLI 층을 구현하고 그 위에 언어 바인딩을 얹는 구조를
택한다.
그 위에 세 가지 독립적인 설계 결정이 결과 C API의 모양을 거의 결정한다.
-
세션을 핸들로 삼을 것인가, 구문을 핸들로 삼을 것인가. ODBC는
HENV,HDBC,HSTMT를 별도로 둔다. PostgreSQL의libpq는 연결과 구문을 합쳐서,PQexec호출 하나가PGconn에 대한 일회성 텍스트 실행이 된다. Oracle OCI는OCIStmt를 독립적인 구문 객체로 주고 나중에 서비스 컨텍스트와 연결한다. CUBRID는 ODBC에 가장 가깝다.DB_SESSION *이 구문 핸들이고, 연결은db_restart가 채우는 모듈 스코프 전역 변수(db_Connect_status,db_Database_name)에 암묵적으로 산다. 한 프로세스에DB_SESSION객체가 여러 개 있을 수 있지만, 한 번에 하나의 데이터베이스에만 연결된다는 점이다. -
컴파일 후 실행할 것인가, 준비와 실행을 합칠 것인가. CLI는 일회성 텍스트 경로(
PQexec,mysql_query), 준비/실행 분리 형태 (SQLPrepare/SQLExecute), 또는 둘 다를 제공할 수 있다. CUBRID는 내부적으로 분리 형태를 택했다.db_compile_statement_local과db_execute_statement_local이 별개의 호출이며, 합쳐진 형태는 래퍼 (db_open_buffer_and_compile_first_statement+ 실행 루프 indb_compile_and_execute_queries_internal)로 노출된다. 이 분리 덕분에 JDBC 클라이언트가PreparedStatement를 사용할 때 브로커의 CAS 층이 컴파일된DB_SESSION하나를 여러ux_execute호출에 걸쳐 재사용할 수 있다. -
커서를 반복자로 볼 것인가, 임의 접근 뷰로 볼 것인가. 결과 집합 API는 앞 방향 전용(
cursor_next_tuple, 스트림 끝) 이거나,seek_tuple,prev_tuple,last_tuple을 갖춘 임의 접근 방식이다. CUBRID는 임의 접근을 선택했다. 서버 측 QFILE 목록 파일이 이를 뒷받침한다.T_SELECT타입의DB_QUERY_RESULT마다CURSOR_ID와QFILE_LIST_ID를 갖고 ODBC 스타일의 스크롤 가능한 커서 표면 전체를 지원한다.
이 세 가지를 명시하고 나면, CUBRID DBI의 나머지는 (세션=구문 핸들,
컴파일/실행 분리, 스크롤 가능 커서) 모서리를 선택했을 때 자연스럽게
따라 나오는 결과로 읽힌다. 브로커의 CCI 드라이버는 첫 번째 결정을
뒤집는다. CAS별 핸들(T_SRV_HANDLE)을 외부로 노출해 JDBC 클라이언트
가 연결 하나에 여러 구문 핸들을 쥘 수 있게 한다. 하지만 나머지 두
결정은 그대로 유지한다. 아래에 놓인 실행기가 동일한 db_* 코어이기
때문이다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”교과서적 CLI 층 아래로 내려오면, 모든 주요 클라이언트/서버 DBMS가 비슷한 한 줌의 패턴을 들고 있다. 이 패턴들은 X/Open 명세에 나오는 것이 아니라, 추상 API와 실제 소스 사이의 빈자리를 메우는 엔지니어링 어휘다.
파서 컨텍스트를 겸하는 구문 객체
섹션 제목: “파서 컨텍스트를 겸하는 구문 객체”모든 CLI는 준비와 실행 사이에 파싱된 AST, 호스트 변수 배열, 컬럼 타입
목록, 서버 측 계획 핸들을 어딘가에 보관해야 한다. CUBRID의 DB_SESSION은
parser(PARSER_CONTEXT), statements(PT_NODE ** 파스 트리 배열),
type_list(구문별 컬럼 설명), stage(구문별 1바이트 상태 기계 배열)를
함께 담는다. ;으로 구분된 여러 구문을 DB_SESSION 하나가 처리할 수
있다. parser_parse_string_with_escapes가 배열을 반환하고 dimension이
개수를 기록하기 때문이다. Oracle의 OCIStmt가 가장 가까운 유사물이다.
PostgreSQL의 PGresult는 순수하게 결과만 담으며 준비된 구문은 서버에
산다.
구문당 1바이트를 쓰는 네 단계 상태 기계
섹션 제목: “구문당 1바이트를 쓰는 네 단계 상태 기계”CLI는 이중 실행, 이중 컴파일, 컴파일 전 실행을 막아야 한다.
db_vdb.c는 이를 구문당 1바이트로 강제한다. 그 바이트는
StatementInitialStage(0), StatementCompiledStage,
StatementPreparedStage, StatementExecutedStage 값을 갖는다.
db_compile_statement_local은 초기 → 컴파일됨 → 준비됨으로 전진시킨다.
준비됨 단계는 do_prepare_statement가 XASL 캐시에서 statement->xasl_id
를 채웠다는 뜻이다. db_execute_and_keep_statement_local은 준비됨 →
실행됨으로 전진시킨다. 이 바이트는 모든 진입점에서 확인된다. 예를 들어,
session->stage[stmt_ndx] < StatementPreparedStage이면 실행기가 컴파일러
를 다시 돌린다. CUBRID가 준비됨 단계를 별도 API 호출로 노출하지는 않지만
상태 기계는 내부에서 작동한다.
타입 태그를 가진 별도의 결과 집합 객체
섹션 제목: “타입 태그를 가진 별도의 결과 집합 객체”구문이 실행되고 나면, 컬럼 목록과 커서가 구문 핸들(DB_SESSION)을 닫기
전에 결과를 다 소비하지 않을 수 있기 때문에 구문 밖 어딘가에 살아야
한다. CUBRID의 결과 객체인 DB_QUERY_RESULT는 컬럼 타입 목록과 커서를
모두 담는다. 타입 태그(T_SELECT / T_CALL / T_OBJFETCH / T_GET /
T_CACHE_HIT)가 모든 커서 함수에서 어느 분기가 일할지를 결정한다.
PostgreSQL의 PGresult와 MySQL의 MYSQL_RES도 같은 패턴을 따른다.
비내장 클라이언트를 위한 와이어 드라이버 파사드
섹션 제목: “비내장 클라이언트를 위한 와이어 드라이버 파사드”위의 DBI 표면은 내장형이다. CUBRID 클라이언트 라이브러리를 직접
실행하는 프로세스에 링크된다. JDBC, ODBC, Python, PHP는 모두 다른
프로세스에서 살며, 브로커 데몬에 말을 건다. 브로커의 CAS 워커는
CUBRID 클라이언트 라이브러리를 내장하고 와이어 호출을 다시 db_*
호출로 변환한다. 브로커는 이 층을 CCI(CUBRID Call Interface)라고 부르며,
서버 측은 src/broker/cas_execute.c에 일련의 ux_* 함수
(ux_database_connect, ux_prepare, ux_execute, ux_fetch,
ux_end_tran)로 산다. 모양은 db_*를 닮았지만 CCI 고유의 관심사가
추가된다. 서버 핸들 ID(T_SRV_HANDLE), CCI 플래그
(CCI_PREPARE_UPDATABLE, CCI_PREPARE_HOLDABLE,
CCI_EXEC_QUERY_INFO), 브로커 로그 스트림, 그리고 같은 호출 안에서
db_commit_transaction을 유발하는 자동 커밋 힌트가 그것이다.
스키마 조작과 값 기본 연산은 절차형으로 유지
섹션 제목: “스키마 조작과 값 기본 연산은 절차형으로 유지”스키마 정의(db_create_class, db_add_attribute, db_add_method)와
값 구성(db_make_int, db_make_string, db_make_object)은 X/Open CLI
명세에 없지만, OO 성향 RDBMS의 모든 C 클라이언트 API에 존재한다.
CUBRID는 OODB 뿌리에서 무거운 스키마 표면을 물려받았다. MOP(메모리
객체 포인터)가 보편적인 클래스 또는 인스턴스 핸들이며, db_create /
db_put / db_get은 SQL을 거치지 않고 인스턴스에 직접 작용한다.
CUBRID의 구현
섹션 제목: “CUBRID의 구현”CUBRID의 클라이언트 API는 계층화된 시스템이며 규칙은 하나다. 위 계층은
항상 아래 계층에게 말하며, 건너뛰는 경로는 없다. 위에서부터 보면:
(1) 언어 바인딩 — JDBC(cubrid-jdbc/), CCI 네이티브 C 드라이버
(cubrid-cci/, 별도 저장소), Python, PHP, Perl DBD, CSQL
(src/executables/csql.c). (2) CCI 와이어 — cas_protocol.h의
CAS_FC_* 플랫 오피코드 공간이 broker/cas.c의 server_fn_table으로
디스패치된다. (3) CAS 측 ux_* — broker/cas_execute.c의
브로커 측 파사드. 각 함수는 db_*를 호출하고 T_NET_BUF로 직렬화
해 돌려보내는 얇은 래퍼다. (4) DBI db_* — src/compat/의 공개 C
클라이언트 API. 모든 함수는 순수하게 클라이언트 측에서 실행되거나
network_cl으로 NRP 요청을 발행한다. (5) network_cl — 대칭
와이어 마샬링 층(cubrid-network-protocol.md 참조). (6) boot_cl —
연결 수명 주기 층. db_restart는 boot_restart_client에 위임한다.
flowchart TD
JDBC[JDBC / Python / ODBC 클라이언트]
CCI_drv[CCI 네이티브 드라이버<br/>cubrid-cci/]
CSQL[CSQL 명령행<br/>src/executables/csql.c]
Broker[브로커 데몬<br/>src/broker/broker.c]
CAS[CAS 워커 프로세스<br/>src/broker/cas.c]
UX[ux_prepare / ux_execute / ux_fetch<br/>src/broker/cas_execute.c]
DBI[db_compile_statement / db_execute_statement<br/>src/compat/db_vdb.c]
Boot[boot_restart_client<br/>src/transaction/boot_cl.c]
NET[network_cl<br/>src/communication/network_cl.c]
Server[cub_server<br/>NET_SERVER_QM_QUERY_PREPARE]
JDBC -->|CCI 와이어| Broker
CCI_drv -->|CCI 와이어| Broker
Broker --> CAS
CAS --> UX
UX --> DBI
CSQL --> DBI
DBI --> Boot
DBI --> NET
Boot --> NET
NET --> Server
이 그림이 문서 전체에서 핵심이다. 모든 클라이언트 경로는 결국
src/compat/의 db_*에 닿는다. 브로커가 존재하는 이유는 CUBRID 서버가
연결당 단일 스레드이고, 무거운 연결 작업 — boot_restart_client가
수행하는 로케일 초기화, 시간대 로드, 파라미터 동기화, 호스트 장애
조치, 스키마 버전 협상 — 을 CAS 프로세스에서 풀링하는 것이 수천 개의
단기 JDBC 연결을 허용 가능한 지연으로 처리하는 유일한 방법이기 때문이다.
연결: db_login, db_restart, db_shutdown
섹션 제목: “연결: db_login, db_restart, db_shutdown”DBI의 연결 수명 주기는 의도적으로 단순하다. 연결 핸들이 없다. 연결은
db_Connect_status와 db_Database_name에 기록되는 프로세스 전역 상태
기계다. 모든 db_* 함수는 CHECK_CONNECT_* 매크로로 이를 확인하고
프로세스가 연결되지 않은 상태라면 일찍 반환한다.
db_login은 au_login에 전적으로 위임하고 자격 증명을 인증 모듈에
기록하는 한 줄짜리 함수다. 네트워크에는 손대지 않는다. 두 단계가
분리되어 있는 이유는, CUBRID 유틸리티가 독립 실행 가능하고 db_login을
먼저 호출한 뒤 db_init(생성) 또는 db_restart(열기)를 호출하기
때문이다.
db_restart는 모듈 스코프 변수(db_Client_type, db_Preferred_hosts,
db_Connect_order, db_Client_ip_addr)로부터 BOOT_CLIENT_CREDENTIAL을
채우고 boot_restart_client를 호출한다. 성공하면
db_Connect_status = DB_CONNECTION_STATUS_CONNECTED로 설정하고,
volume을 db_Database_name에 복사하며, install_static_methods를
실행하고, SIGFPE 핸들러를 설치한다. 자격 증명은 객체가 아닌 평탄한
구조체다. 호출자는 그 핸들을 받지 못한다. 무거운 작업은
boot_restart_client에서 이루어진다(cubrid-boot.md 참조).
db_restart_ex는 au_login, db_set_client_type, db_restart를 하나로
합쳐서 대부분의 현대 코드가 쓰는 형태다.
// db_restart_ex — db_admin.cdb_restart_ex (const char *program, const char *db_name, const char *db_user, const char *db_password, const char *preferred_hosts, int client_type){ retval = au_login (db_user, db_password, false); if (retval != NO_ERROR) return retval; db_set_client_type (client_type); if (preferred_hosts != NULL) db_set_preferred_hosts (preferred_hosts); return db_restart (program, false, db_name);}이 함수는 JDBC 클라이언트가 처음 요청할 때 브로커의 CAS 워커가
ux_database_connect 안에서 호출하는 함수다. client_type은 중요하다.
이 연결이 일반 브로커인지, 읽기 전용 브로커인지, 슬레이브 전용 브로커인지,
독립 실행 CSQL인지, 관리자 유틸리티인지 서버에 알려서, 연결이 어떤 HA
역할, 잠금 모드, 보조에서 읽기 기능을 상속할지 결정하기 때문이다.
db_shutdown은 세션을 끝내고 연결을 끊는다. db_end_session을 호출해
서버 측 SESSION_STATE를 비우고(준비된 구문과 SET 변수 바인딩이 서버에
누출되지 않도록 — cubrid-server-session.md 참조), 그 다음
boot_shutdown_client (true)를 호출하며, 연결 상태 전역 변수를 지우고,
SIGFPE 핸들러를 복원하고, 캐시된 실행 계획을 해제한다.
구문 컴파일: db_open_buffer → db_compile_statement
섹션 제목: “구문 컴파일: db_open_buffer → db_compile_statement”컴파일은 두 API 호출로 나뉜다. db_open_buffer는 파서 컨텍스트를 설정하고
파싱하며, db_compile_statement는 의미 검사, 뷰 변환, XASL 준비를 진행한다.
둘을 이어주는 세션 구조체는 작다.
// db_session struct — db_session.hstruct db_session{ char *stage; /* per-statement FSM state */ char include_oid; /* NO_OIDS, ROW_OIDS */ int dimension; /* number of statements */ int stmt_ndx; /* next statement to compile */ int line_offset; int column_offset; PARSER_CONTEXT *parser; /* parser context */ DB_QUERY_TYPE **type_list; /* nice column headings, per stmt */ PT_NODE **statements; /* parse trees, per stmt */
bool is_subsession_for_prepared; DB_SESSION *next; /* sub-sessions for prepared stmts */};db_open_buffer는 파싱만 한다. 클래스를 잠그지 않고, 서버를 호출하지
않으며, stmt_id도 생성하지 않는다. db_open_local을 호출해 세션과
새 PARSER_CONTEXT를 할당한 뒤, parser_parse_string_with_escapes로
버퍼 위에서 Bison/Flex를 돌린다. 성공하면 initialize_session이
dimension = count(statements)를 기록하고 반환한다. db_open_file과
db_open_file_name 변형은 FILE *을 받는다.
db_compile_statement에서 실제 작업이 이루어진다. cubrid-query-rewrite.md
와 cubrid-semantic-check.md에서 참조되는 함수 이름이 바로 이것이며,
모든 CUBRID 클라이언트 SQL 요청은 서버로 가기 전에 이 함수를 통과한다.
// db_compile_statement — db_vdb.cintdb_compile_statement (DB_SESSION * session){ int statement_id; er_clear (); CHECK_CONNECT_MINUSONE (); statement_id = db_compile_statement_local (session); return statement_id;}db_compile_statement_local은 stmt_ndx 인덱스의 구문 하나를
클라이언트 측 SQL 파이프라인 전체를 실행한다. 그 단계들은 각각의
분석 파일에서 다뤄진 모듈과 일치한다.
sequenceDiagram
participant Caller
participant DB as db_compile_statement_local
participant Parser as PARSER (PT_*)
participant DBlink as pt_rewrite_for_dblink
participant Cls as pt_class_pre_fetch
participant Sem as pt_compile (semantic)
participant View as mq_translate
participant Plan as do_prepare_statement
participant Server as cub_server
Caller->>DB: db_compile_statement(session)
DB->>Parser: pt_reset_error
DB->>DBlink: pt_rewrite_for_dblink(parser, statement)
DB->>Parser: pt_get_titles (SELECT용 qtype)
DB->>Cls: pt_class_pre_fetch (클래스 잠금)
Cls->>Server: locator_fetch_class (NRP)
DB->>Sem: pt_compile(parser, statement)
DB->>View: mq_translate(parser, statement)
DB->>Cls: pt_class_pre_fetch 재실행 (mq 이후)
DB->>Plan: do_prepare_statement(parser, statement)
Plan->>Server: NET_SERVER_QM_QUERY_PREPARE
Server-->>Plan: XASL_ID
Plan-->>DB: NO_ERROR
DB->>DB: stage[ndx] = StatementPreparedStage
DB-->>Caller: stmt_id (1부터 시작)
핵심 규칙은 컴파일이 서버를 두 번 건드린다는 점이다. 한 번은
pt_class_pre_fetch에서(실행 시점의 교착 상태를 피하기 위해 참조된
클래스를 잠금), 한 번은 do_prepare_statement에서(XASL 스트림을 서버의
XASL 캐시로 보내고 XASL_ID를 돌려받음). 나머지 단계는 모두 순수하게
클라이언트 측이다. 이 때문에 db_compile_statement가 적지 않은 비용을
치르며, JDBC PreparedStatement를 사용할 때 CUBRID 브로커가 결과
DB_SESSION을 여러 ux_execute 호출에 걸쳐 살려 두는 이유이기도 하다.
함수는 성공 시 ODBC 관례대로 1부터 시작하는 구문 번호를 반환하고,
세션에 컴파일할 구문이 더 없으면 0을 반환한다. 음수는 오류 코드다.
여러 구문을 담은 입력(SELECT ... ; UPDATE ... ; INSERT ... ;)은
호출마다 구문 하나씩 컴파일하므로, 호출자가 컴파일과 실행을 교차할
수 있다. stage[] 바이트는 호출 끝에서 컴파일됨 → 준비됨으로 전진한다.
db_open_buffer_and_compile_first_statement는 일회성 SQL을 위한 편의
래퍼다. CSQL이 유틸리티 명령에 쓰는 경로가 여기다.
// db_open_buffer_and_compile_first_statement — db_vdb.c, condensedintdb_open_buffer_and_compile_first_statement (const char *CSQL_query, DB_QUERY_ERROR *query_error, int include_oid, DB_SESSION **session, int *stmt_no){ CHECK_CONNECT_ERROR ();
*session = db_open_buffer_local (CSQL_query); if (*session == NULL) return er_errid ();
db_include_oid (*session, include_oid); *stmt_no = db_compile_statement_local (*session);
errs = db_get_errors (*session); if (errs != NULL) { int line, col; (void) db_get_next_error (errs, &line, &col); error = er_errid (); if (query_error) { query_error->err_lineno = line; query_error->err_posno = col; } }
if (*stmt_no < 0 || error < 0) { db_close_session_local (*session); *session = NULL; return er_errid (); } return error;}바인드와 실행: db_push_values, db_execute_statement
섹션 제목: “바인드와 실행: db_push_values, db_execute_statement”호스트 변수는 실행 전에 파서의 host_variables 배열로 밀어 넣는다.
파서는 파싱 중에 ? 마커를 세어 필요한 슬롯 수(parser->host_var_count)
를 이미 알고 있으므로, API 계약은 정확히 이 크기의 배열을 줘다.
db_push_values는 pt_set_host_variables를 호출해 DB_VALUE 배열을
복사하고 파서에 기존 의미 오류가 있으면 pt_report_to_ersys로 보고한다.
pt_set_host_variables는 타입 강제 변환을 하지 않는다는 점이다. 강제
변환은 의미 검사에서 파서가 기대 타입을 추론한 후에
db_execute_and_keep_statement_local 안의
do_cast_host_variables_to_expected_domain이 수행한다.
db_execute_statement는 공개 진입점이며, 호출 후 구문의 파스 트리를
해제한다는 점에서 db_execute_and_keep_statement와 다르다. 따라서 이
형태로는 구문을 재실행할 수 없다. and-keep 형태는 반복 사용을 위해
파스 트리를 보존한다. 두 래퍼 모두 세 가지를 한다. 연결 상태 확인,
db_invalidate_mvcc_snapshot_before_statement 호출, 그리고 대응하는
_local 워커 호출이다. 반환할 때는
db_set_read_fetch_instance_version (LC_FETCH_MVCC_VERSION)을 설정한다.
둘 다 결국 db_execute_and_keep_statement_local을 실행한다. 이 함수가
실행의 핵심이다. 호스트 변수 배열이 채워졌는지(host_var_count > 0일 때
set_host_var == 1), 구문이 최소 준비됨 단계인지(아니면 자동으로
db_compile_statement_local을 다시 실행)를 검증한 뒤 다음 순서를
밟는다.
- 구문이
SYSTIMESTAMP나 로컬 트랜잭션 ID를 참조하면qp_get_server_info로 서버 시각 / 트랜잭션 ID 값을 가져온다. do_execute_subquery_pre로 캐시된 CTE 하위 쿼리를 미리 실행한다.- SQL
PREPARE/EXECUTE/DEALLOCATE PREPARE계열을 위해do_process_prepare_statement,do_recompile_and_execute_prepared_statement,do_process_deallocate_prepare로 분기한다. do_execute_statement(XASL 캐시 빠른 경로)를 호출하거나, 캐시가 비활성화되었거나 구문이 캐시 불가라면 파스 트리를 복사하고pt_bind_values_to_hostvars/pt_resolve_names/pt_semantic_type을 실행한 뒤do_statement를 호출한다.stage[stmt_ndx] = StatementExecutedStage로 설정한다.DB_QUERY_RESULT기술자를 구성한다.CUBRID_STMT_SELECT와CUBRID_STMT_EXECUTE_PREPARE는pt_new_query_result_descriptor를 호출해 서버 측 목록 파일을 커서로 감싼다.CUBRID_STMT_INSERT/CUBRID_STMT_CALL은 AST의pt_node_etc페이로드에서 행 수 또는 생성된 키를 꺼낸다.update_execution_values로 파서의row_count캐시를 갱신한다.
이 루틴에서 놓치기 쉽지만 중요한 세 가지 점이 있다.
MVCC 스냅샷 무효화는 구문 경계가 아닌 API 경계에서 일어난다.
db_execute_statement는 _local에 위임하기 전에
db_invalidate_mvcc_snapshot_before_statement를 호출하고, 돌아올 때
LC_FETCH_MVCC_VERSION을 재설정한다. 이것이 READ COMMITTED 격리 수준을
동작하게 만드는 장치다. 최상위 API 실행 호출마다 새 스냅샷을 얻는다.
그러나 래퍼 없이 브로커 내부에서 db_execute_statement_local을 직접
호출하면, 낡은 스냅샷을 조용히 관찰하게 된다는 점이다.
XASL 캐시는 실행 중에 밑에서 실패할 수 있다. 서버가 캐시된 계획을
퇴출하거나 실행 중 스키마 변경을 감지하면 실행기는
ER_QPROC_INVALID_XASLNODE 또는
ER_QPROC_XASLNODE_RECOMPILE_REQUESTED를 반환한다. DBI는 구문의
xasl_id를 지우고 준비-실행 쌍을 다시 조용히 실행한다. 성공 시에는
호출자에게 보이지 않지만, 스키마 변경이 자동 복구되지 않으면
(DB_CLASS_MODIFIED) 오류가 호출자에게 전달된다.
INSERT 행 수는 REPLACE를 특별 처리가 있다. REPLACE 구문은
개념적으로 DELETE + INSERT다. 실행기가 insert 수를 반환하면,
DBI는 그것을 pt_node_etc(statement)의 행 수(실행기가 AST 노드에
보관하는 값)와 비교하고 더 큰 값을 유지할 수 있다.
커서: db_query_first_tuple, db_query_next_tuple, db_query_get_tuple_value
섹션 제목: “커서: db_query_first_tuple, db_query_next_tuple, db_query_get_tuple_value”DB_QUERY_RESULT는 태그가 달린 공용체다. result->type은 T_SELECT,
T_CALL, T_OBJFETCH, T_GET, T_CACHE_HIT 중 하나다. result->res
공용체는 태그마다 한 팔을 갖는다. T_SELECT(실제 목록 파일 커서를 가진
유일한 타입)는 res.s에 {QUERY_ID query_id; CURSOR_ID cursor_id; CACHE_TIME cache_time; int stmt_id;}를 담는다. T_CALL은 res.c에
{DB_VALUE *val_ptr; CURSOR_POSITION crs_pos;}를 담는다. T_OBJFETCH는
res.o에 {DB_VALUE **valptr_list; CURSOR_POSITION crs_pos;}를 담는다.
커서 함수들은 외부에서 보면 균일하지만 내부에서는 타입 태그에 따라
분기한다. db_query_first_tuple, db_query_next_tuple,
db_query_prev_tuple, db_query_last_tuple, db_query_seek_tuple은
모두 같은 모양을 공유한다. 검증하고, result->type으로 분기해,
T_SELECT는 cursor_*(목록 파일 기반 커서)로, T_CALL / T_OBJFETCH
는 crs_pos ∈ { C_BEFORE, C_ON, C_AFTER }의 작은 상태 기계를 이동한다.
db_query_seek_tuple이 흥미롭다. 처음, 현재, 끝으로부터 각각 세 가지
상대 오프셋을 계산하고 절댓값이 가장 작은 것을 골라
cursor_first_tuple / cursor_last_tuple을 호출하고 스텝한다.
stateDiagram-v2
[*] --> Initial: db_open_buffer
Initial --> Compiled: db_compile_statement_local\n(pt_compile, mq_translate)
Compiled --> Prepared: do_prepare_statement\n(서버 XASL 캐시 미스 => 채움)
Prepared --> Executed: db_execute_and_keep_statement_local\n(do_execute_statement)
Executed --> Prepared: 재실행\n(구문 유지)
Executed --> Initial: db_drop_statement
Prepared --> Initial: db_drop_statement
Compiled --> Initial: db_drop_statement
Initial --> [*]: db_close_session
Executed --> [*]: db_close_session
위치가 결정되면 컬럼 인덱스(db_query_get_tuple_value)나 컬럼 이름
(db_query_get_tuple_value_by_name)으로 값을 추출한다. T_SELECT는
cursor_get_tuple_value를 호출해 목록 파일 버퍼에서 가져온다.
T_OBJFETCH는 valptr_list[index]를 읽는다. T_CALL은 단일
val_ptr을 읽는다. 모든 경우에서 값은 pr_clone_value로 호출자의
DB_VALUE에 복사된다.
pr_clone_value는 값을 호출자의 DB_VALUE에 깊은 복사(deep copy)로
담는다. 호출자는 결과를 소유하므로 문자열/객체/집합 저장소를 해제하기
위해 db_value_clear(또는 힙 할당 값에는 db_value_free)를 호출해야
한다. API 계약의 이 측면은 새 사용자가 자주 놓치는 부분이다. 문자열
값을 지우지 않으면 메모리 누수가 발생하며, API는 경고하지 않지만
valgrind는 잡아낸다.
db_query_get_tuple_value_by_name은 result->query_type을 선형으로
탐색해 컬럼 이름을 찾은 뒤(대소문자 구분 없음) db_query_get_tuple_value
에 위임하는 편의 래퍼다. 컬럼 별칭(name)과 원래 이름(original_name)
을 모두 시도하므로, SELECT ... AS x 형태의 쿼리는 별칭과 원래 표현식
이름 양쪽으로 접근 가능하다.
결과 수명 주기: db_query_end
섹션 제목: “결과 수명 주기: db_query_end”db_query_end는 db_execute_statement가 생성한 결과의 짝 파괴자다.
tran_was_latest_query_ended를 확인해 서버에 알림을 보낼지 결정한 뒤
db_query_end_internal에 위임한다. 이 함수는 (a) status == T_CLOSED를
멱등성을 갖고, (b) T_SELECT 결과를
qmgr_end_query (result->res.s.query_id)를 호출해 서버가 목록 파일을
해제할 수 있게 하는 NRP를 보내며(트랜잭션이 이미 끝난 경우 생략),
(c) cursor_close를 호출해 클라이언트 측 커서 핸들을 해제하고,
(d) db_free_query_result로 DB_QUERY_RESULT 자체를 해제한다.
스키마 조작: db_create_class, db_add_attribute
섹션 제목: “스키마 조작: db_create_class, db_add_attribute”db_class.c의 스키마 표면은 작고 절차형이다. db_create_class (name)은
smt_def_class로 SM_TEMPLATE을 구성하고, locator_reserve_class_name
으로 이름을 예약하며, sm_update_class로 커밋한다. 반환된 MOP가
새 클래스 객체다. SQL DDL이 동작하는 방향과 반대다. SQL 경로는 파서 →
의미 → do_alter/do_create_entity → 스키마 템플릿 관리자로 흐른다.
직접 API는 SQL 파서를 건너뛰고 같은 SM_TEMPLATE을 직접 구성한다.
두 경로 모두 sm_update_class를 공유하므로 스키마 의미에서 어긋날 수
없다.
db_add_attribute (obj, name, domain, default) 속성 추가는
db_add_attribute_internal에 위임한다. 이 함수는 smt_edit_class_mop로
템플릿을 열고, smt_add_attribute_w_dflt로 기본값과 함께 새 컬럼을
기록하며, sm_update_class로 커밋한다. "INTEGER나 VARCHAR(64)"
같은 도메인 문자열은 SQL 파서가 아닌 스키마 템플릿 모듈이 파싱한다.
따라서 프로그래밍 방식으로 컬럼을 추가할 때 전체 Bison 비용을 피할 수
있다. db_add_shared_attribute와 db_add_class_attribute 변형은 CUBRID
OODB 뿌리에서 내려온 공유 속성과 클래스 수준 속성을 위한 것이다.
트랜잭션: db_commit_transaction, db_abort_transaction
섹션 제목: “트랜잭션: db_commit_transaction, db_abort_transaction”트랜잭션 API는 표면에서 가장 작은 부분이다. db_commit_transaction은
tran_commit (false)를 호출한다. false는 API가 노출하지 않는 “잠금
유지” 플래그다. 그 뒤
cubmethod::get_callback_handler ()->free_query_handle_all (true)를
호출해 SP 메서드 콜백 층에 묶인 준비된 구문 핸들을 모두 강제 닫는다.
이는 연결이 보류 가능하지 않을 때 커밋 시 모든 PreparedStatement
객체를 닫는 JDBC 동작의 SP 동치물이다. db_abort_transaction은
tran_abort를 쓰는 것 외에 같은 구조다. 둘 다 db_admin.c에 있다.
오류 보고: db_get_errors, er_*
섹션 제목: “오류 보고: db_get_errors, er_*”CUBRID에는 두 개의 오류 채널이 있고 DBI는 둘 모두에 말을 건다. 세션
스코프 채널 db_get_errors / db_get_next_error는 파서의 오류 목록
(오류당 줄과 컬럼)을 반복하면서 er_set으로 전역 오류 스택에도
메시지를 밀어 넣는다. 그래서 er_errid()만 확인하는 호출자도 문법
오류를 볼 수 있다. 스레드 로컬 전역 오류 채널이 나머지 모든 것의
주된 메커니즘이다. 모든 db_*는 NO_ERROR / -1 / er_errid()를
반환하고, 호출자는 db_error_string / db_error_code를 불러 가장
최근 오류를 꺼낼 것으로 기대된다. db_vdb.c 곳곳에 박힌
assert(er_errid() != NO_ERROR) 단언이 -1을 반환할 때 항상
er_errid()가 설정되도록 강제한다.
CCI 드라이버: 브로커 측 ux_* 파사드
섹션 제목: “CCI 드라이버: 브로커 측 ux_* 파사드”CCI 와이어 프로토콜은 src/broker/cas_protocol.h에 있고 서버 측은
src/broker/cas_function.c와 src/broker/cas_execute.c에 있다.
broker/cas.c의 디스패치 테이블 server_fn_table[]은 (func_code - 1)
로 인덱싱되는 평탄한 배열이다. 항목들은 JDBC 표면을 끝에서 끝까지
덮는다. 트랜잭션 제어(fn_end_tran), 준비/실행(fn_prepare,
fn_execute, fn_prepare_and_execute, fn_execute_batch,
fn_execute_array), 결과 탐색(fn_cursor, fn_fetch,
fn_cursor_update, fn_cursor_close, fn_next_result,
fn_close_req_handle, fn_get_generated_keys, fn_make_out_rs),
스키마 조회(fn_schema_info, fn_get_db_version,
fn_get_class_num_objs, fn_get_attr_type_str, fn_parameter_info,
fn_get_query_info), 객체 접근(fn_oid_get, fn_oid_put, fn_oid,
fn_collection), 세션 상태(fn_get_db_parameter,
fn_set_db_parameter, fn_savepoint, fn_get_row_count,
fn_get_last_insert_id, fn_end_session, fn_set_cas_change_mode,
fn_check_cas), XA 2-phase commit(fn_xa_prepare, fn_xa_recover,
fn_xa_end_tran), LOB 입출력(fn_lob_new, fn_lob_write,
fn_lob_read). 사용되지 않는 인덱스에는 폐기된 GLO 슬롯
(CAS_FC_DEPRECATED1..4)과 CAS_FC_GET_SHARD_INFO 자리가
fn_deprecated / fn_not_supported로 자리를 지킨다.
CAS 프로세스는 소켓에서 함수 코드를 읽어 해당 fn_*로 디스패치한다.
fn_*는 argv[]에서 인자를 꺼내 DBI 형태의 C 타입으로 변환한 뒤
하나 이상의 ux_* 함수를 호출한다. 실제 작업은 ux_* 층에서
이루어지며, fn_* 층은 순수하게 와이어 연결 고리다.
연결 경로에서, ux_database_connect는 브로커의 접근 모드
(shm_appl->access_mode ∈ {READ_ONLY_ACCESS_MODE,
SLAVE_ONLY_ACCESS_MODE, 기본값})를 클라이언트 타입
(DB_CLIENT_TYPE_BROKER, DB_CLIENT_TYPE_READ_ONLY_BROKER,
DB_CLIENT_TYPE_SLAVE_ONLY_BROKER, 그리고 *_REPLICA_ONLY 변형)으로
매핑하고, db_set_preferred_hosts / db_set_connect_order /
db_set_max_num_delayed_hosts_lookup으로 브로커의 선호 호스트/연결
순서 설정을 DBI 전역 변수로 복사한 뒤, db_restart_ex를 호출해 데이터베이스
연결을 연다. 성공하면 데이터베이스 이름과 호스트를 as_info에 복사하고,
last_connect_time을 설정하며, ux_get_default_setting으로 브로커의
잠금 타임아웃, 격리 수준, 시스템 파라미터를 읽는다. 트리거 토글
(db_enable_trigger / db_disable_trigger)은 shm_appl->trigger_action_flag
를 따른다.
CCI 고유의 특성이 두 가지 두드러진다. 첫째, 브로커는 모든 CCI 연결을
상태를 가진 데이터베이스 클라이언트 연결로 다루지만, 그것을 많은 CCI
연결(JDBC 클라이언트로부터)에 걸쳐 풀링한다. JDBC 클라이언트가 연결하면
브로커는 유휴 CAS 프로세스를 넘겨주며, 그 CAS 프로세스는 이미 데이터베이스에
붙어 있다는 점이다. 둘째, 데이터베이스에 대한 CAS 연결은 프로세스
단위이며 많은 JDBC 연결에 걸쳐 유지된다. ux_database_connect는 브로커
프로세스가 올바른 사용자로 이미 올바른 데이터베이스에 붙어 있으면 일찍
반환한다.
준비 경로에서, ux_prepare는 hm_new_srv_handle로 T_SRV_HANDLE을
할당하고, db_open_buffer로 DB_SESSION을 열며, db_compile_statement
를 호출해 클라이언트 측 컴파일 파이프라인 전체를 진행한다. 그 뒤
T_NET_BUF로 새 srv_h_id, 결과 캐시 수명, 구문 타입 바이트,
바인드 마커 수 네 가지를 직렬화해 돌려보낸다. 컬럼 목록(컬럼 이름,
속성 이름, 명세 이름, 타입, 크기, 도메인)은 prepare_column_list_info_set
으로 이어진다. DB_SESSION *은 나중에 ux_execute가 다시 쓸 수
있도록 srv_handle->session 안에 보관된다. CCI_PREPARE_INCLUDE_OID,
CCI_PREPARE_XASL_CACHE_PINNED, CCI_PREPARE_HOLDABLE,
CCI_PREPARE_UPDATABLE 플래그는 컴파일 전에 db_include_oid와
db_session_set_* 호출로 일대일 변환된다. HOLDABLE과 UPDATABLE을
함께 쓰면 CAS_ER_HOLDABLE_NOT_ALLOWED로 일찍 거부된다.
CCI 서버 핸들 T_SRV_HANDLE은 hm_new_srv_handle(cas_handle.c)이
할당하고 정수 인덱스로 JDBC 클라이언트에 돌아간다. 이후 ux_execute /
ux_fetch 호출은 hm_find_srv_handle로 핸들을 찾는다. 이 층 덕분에
JDBC 연결 하나가 여러 열린 PreparedStatement 객체를 동시에 들 수 있다.
비록 아래의 CAS 프로세스가 db_* 연결을 하나만 갖더라도 말이다.
실행 경로에서, 브로커가 DBI 가장 깊은 곳까지 들어간다. ux_execute가
하는 일은 다음과 같다.
hm_qresult_end를 호출해 같은 핸들에 있는 이전 결과를 해제한다.- 구문이 미리 준비되었다면
srv_handle->session에서DB_SESSION *을 복구한다. 그렇지 않으면db_open_buffer (srv_handle->sql_stmt)로 다시 파싱하고 재컴파일한다. make_bind_value로argv[]를DB_VALUE *배열로 변환한 뒤set_host_variables(db_push_values를 감싼)로 파서에 밀어 넣는다.CCI_EXEC_RETURN_GENERATED_KEYS,CCI_EXEC_QUERY_INFO,srv_handle->is_holdable,srv_handle->auto_commit_mode를 해당db_session_set_*세터로 처리한다.db_execute_and_keep_statement (session, stmt_id, &result)를 실행한다. JDBC가 재실행할 수 있어야 하므로db_execute_statement를 쓰지 않는다.db_query_set_copy_tplvalue (result, 0)으로 결과를 들여다보기 모드로 전환해, 행 값 포인터가 목록 파일 버퍼를 별칭으로 참조하게 한다. 컬럼당 malloc을 피하기 위함이다.- JDBC가
max_row > 0을 주면,db_query_seek_tuple (result, max_row, 1)을 호출하고 행 수를 잘라낸다. - 결과 행 수를 직렬화하고,
execute_info_set으로 컬럼 정보를, 그리고 프로토콜 버전에 따라 추가 필드를(PROTOCOL_V2에서 컬럼 수명 정보,PROTOCOL_V5에서 샤드 id) 직렬화한다. do_commit_after_execute가 참이면(비 SELECT 구문에서의 구문별 자동 커밋),req_info->need_auto_commit = TRAN_AUTOCOMMIT을 설정해 와이어 응답이 전송된 뒤 디스패처가 커밋하도록 한다.
db_execute_statement가 아닌 db_execute_and_keep_statement를 쓴다는
점에 주목하라. 브로커는 동일한 PreparedStatement의 재실행에 걸쳐 파스
트리를 살려 둔다. CCI_EXEC_QUERY_INFO 플래그는 JDBC의
CUBRIDPreparedStatement.setQueryInfo 토글이다. 설정되면 CAS가 구문을
재컴파일하고 계획을 임시 파일에 써서 JDBC 클라이언트가 가져갈 수 있게
한다. max_row 상한은 커서를 탐색해 브로커 측(서버 관점에서는 클라이언트
측)에서 적용된다. 서버는 JDBC의 Statement.setMaxRows를 알지 못한다.
가져오기 경로에서, ux_fetch는 핸들을 검증하고, CCI_PREPARE_CALL이
설정되면 저장 프로시저 호출을 fetch_call로 디스패치하며, 그렇지
않으면 srv_handle->schema_type(-1이면 일반 쿼리 → fetch_result,
CCI_SCH_* 상수면 fetch_class, fetch_attribute 같은 스키마 정보
가져오기)으로 소규모 디스패치 테이블 fetch_func[]를 인덱싱한다.
잎 함수 fetch_result는 db_query_seek_tuple로 cursor_pos로 이동한 뒤,
fetch_count번 루프를 돌면서 컬럼마다 db_query_get_tuple_value를
호출하고 dbval_to_net_buf로 각 DB_VALUE를 와이어 버퍼에 직렬화하는
cur_tuple을 호출한다.
sequenceDiagram
participant JDBC as JDBC PreparedStatement.executeQuery
participant CCI as CCI 드라이버 (TCP)
participant Broker as 브로커 데몬
participant CAS as CAS 워커
participant DBI as db_compile_statement / db_execute_and_keep_statement
participant NetCl as network_cl
participant Server as cub_server
JDBC->>CCI: SQL + 파라미터
CCI->>Broker: CAS_FC_PREPARE + sql_stmt
Broker->>CAS: 디스패치
CAS->>DBI: db_open_buffer + db_compile_statement
DBI->>NetCl: pt_class_pre_fetch (NRP 잠금)
NetCl->>Server: NET_SERVER_LC_FETCH_LOCKSET
DBI->>NetCl: do_prepare_statement
NetCl->>Server: NET_SERVER_QM_QUERY_PREPARE
Server-->>NetCl: XASL_ID
DBI-->>CAS: stmt_id
CAS-->>CCI: srv_h_id, num_markers, 컬럼 타입
JDBC->>CCI: setInt(1, 42)
JDBC->>CCI: executeQuery
CCI->>Broker: CAS_FC_EXECUTE + 바인드 값
Broker->>CAS: 디스패치
CAS->>DBI: db_push_values + db_execute_and_keep_statement
DBI->>NetCl: do_execute_statement
NetCl->>Server: NET_SERVER_QM_QUERY_EXECUTE
Server-->>NetCl: 행 수, list_id
DBI-->>CAS: DB_QUERY_RESULT
CAS-->>CCI: 행 수, 컬럼 정보, 첫 번째 배치
JDBC->>CCI: rs.next()
CCI->>Broker: CAS_FC_FETCH
Broker->>CAS: 디스패치
CAS->>DBI: db_query_seek_tuple + db_query_get_tuple_value
DBI-->>CAS: 컬럼당 DB_VALUE
CAS-->>CCI: 직렬화된 행
핵심은 CCI 실행기가 따로 없다는 점이다. 브로커의 CCI 층은 db_* 위의
얇은 파사드이며 클라이언트 측 컴파일, 실행, 가져오기 논리 전체를 재사용
한다. 중요한 분리는 브로커 위의 네트워크 경계다. CSQL csql -u dba demodb
세션은 인프로세스에서 db_*를 직접 호출한다. JDBC 세션은 세 개의 TCP
소켓을 거친다(JDBC ↔ 브로커, 브로커 ↔ CAS, CAS ↔ 서버). 하지만 가장
깊은 두 소켓은 같은 머신 안에 있고, 가장 깊은 하나 — CAS ↔ 서버 — 는
cubrid-network-protocol.md에 문서화된 동일한 NRP 와이어 형식을 쓴다.
Source Walkthrough
섹션 제목: “Source Walkthrough”DBI 표면은 src/compat/의 아홉 파일에 걸쳐 있다. 책임별 핵심 심볼:
연결 수명 주기 (db_admin.c) — db_init, db_login,
db_restart, db_restart_ex, db_shutdown,
db_shutdown_without_request_to_server, db_ping_server,
db_disable_modification, db_enable_modification,
db_end_session, db_set_client_type, db_set_preferred_hosts,
db_get_row_count, db_get_last_insert_id.
트랜잭션 제어 (db_admin.c) — db_commit_transaction,
db_abort_transaction, db_set_isolation, db_set_lock_timeout,
db_set_system_parameters, db_get_system_parameters.
구문 컴파일과 실행 (db_vdb.c) — db_open_buffer,
db_open_buffer_local, db_open_file,
db_compile_statement, db_compile_statement_local,
db_rewind_statement, db_statement_count,
db_open_buffer_and_compile_first_statement,
db_compile_and_execute_queries_internal,
db_execute_statement, db_execute_statement_local,
db_execute_and_keep_statement,
db_execute_and_keep_statement_local,
db_drop_statement, db_drop_all_statements,
db_close_session, db_close_session_local,
db_push_values, db_get_hostvars, db_get_lock_classes,
db_get_errors, db_get_next_error, db_get_warnings,
db_session_set_holdable, db_session_set_xasl_cache_pinned,
db_session_set_return_generated_keys,
db_set_statement_auto_commit, db_get_query_type_list,
db_invalidate_mvcc_snapshot_before_statement,
do_process_prepare_statement, do_get_prepared_statement_info,
do_cast_host_variables_to_expected_domain,
do_recompile_and_execute_prepared_statement,
do_process_deallocate_prepare.
쿼리 결과와 커서 (db_query.c) — db_query_first_tuple,
db_query_next_tuple, db_query_prev_tuple, db_query_last_tuple,
db_query_seek_tuple, db_query_get_tplpos,
db_query_set_tplpos, db_query_free_tplpos,
db_query_get_tuple_value, db_query_get_tuple_value_by_name,
db_query_get_tuple_valuelist, db_query_tuple_count,
db_query_column_count, db_query_end, db_query_end_internal,
db_query_set_copy_tplvalue, db_is_client_cache_reusable,
db_query_prefetch_columns, db_free_query_format,
db_cp_query_type, or_pack_query_format,
or_unpack_query_format.
스키마 정의 (db_class.c) — db_create_class,
db_drop_class, db_drop_class_ex, db_rename_class,
db_add_attribute, db_add_shared_attribute,
db_add_class_attribute, db_drop_attribute, db_add_method,
db_add_class_method, db_add_super, db_drop_super,
db_change_default, db_constrain_non_null,
db_constrain_unique.
객체 API (db_obj.c) — db_create, db_create_by_name,
db_get, db_get_shared, db_get_expression, db_put,
db_get_attribute_descriptor, db_get_method_descriptor,
db_create_trigger, db_get_serial_current_value,
db_get_serial_next_value.
값 기본 연산 (db_macro.c, db_set.c) — db_make_int,
db_make_bigint, db_make_string, db_make_varchar,
db_make_date, db_make_datetime, db_make_timestamp,
db_make_set, db_make_multiset, db_make_sequence,
db_make_object, db_make_oid, db_make_null, db_value_clear,
db_value_clone, db_value_free, db_value_create,
db_value_domain_init.
CCI 브로커 서버 측 (broker/cas_execute.c) —
ux_database_connect, ux_database_shutdown, ux_set_session_id,
ux_prepare, ux_execute, ux_execute_all, ux_execute_call,
ux_execute_batch, ux_execute_array, ux_fetch, ux_oid_get,
ux_cursor, ux_cursor_update, ux_cursor_close, ux_end_tran,
ux_end_session, ux_get_row_count, ux_get_last_insert_id,
fetch_result, fetch_call, prepare_column_list_info_set,
set_host_variables, make_bind_value, netval_to_dbval,
dbval_to_net_buf, do_commit_after_execute.
CCI 브로커 디스패치 (broker/cas.c, broker/cas_function.c) —
server_fn_table, server_func_name, process_request,
fn_end_tran, fn_prepare, fn_execute, fn_get_db_parameter,
fn_close_req_handle, fn_cursor, fn_fetch, fn_schema_info,
fn_oid_get, fn_oid_put, fn_oid, fn_collection,
fn_next_result, fn_execute_batch, fn_execute_array,
fn_cursor_update, fn_xa_prepare, fn_xa_recover,
fn_xa_end_tran, fn_con_close, fn_check_cas,
fn_get_generated_keys, fn_end_session, fn_get_row_count,
fn_get_last_insert_id, fn_prepare_and_execute,
fn_cursor_close.
네트워크 클라이언트 래퍼 (communication/network_cl.c) —
net_client_init, net_client_final, net_client_request,
net_client_request_with_callback,
net_client_request_2recv_copyarea.
부트 (transaction/boot_cl.c, cubrid-boot.md 상호 참조) —
boot_initialize_client, boot_restart_client,
boot_shutdown_client, boot_client_initialize_css.
위치 힌트 (updated: 날짜 기준)
섹션 제목: “위치 힌트 (updated: 날짜 기준)”| 심볼 | 파일 | 줄 |
|---|---|---|
db_open_buffer_local | src/compat/db_vdb.c | 214 |
db_open_buffer | src/compat/db_vdb.c | 246 |
db_compile_statement_local | src/compat/db_vdb.c | 531 |
db_compile_statement | src/compat/db_vdb.c | 851 |
db_get_errors | src/compat/db_vdb.c | 1011 |
db_push_values | src/compat/db_vdb.c | 1612 |
db_execute_and_keep_statement_local | src/compat/db_vdb.c | 1708 |
do_process_prepare_statement | src/compat/db_vdb.c | 2492 |
do_cast_host_variables_to_expected_domain | src/compat/db_vdb.c | 2797 |
do_recompile_and_execute_prepared_statement | src/compat/db_vdb.c | 3064 |
db_execute_and_keep_statement | src/compat/db_vdb.c | 3243 |
db_execute_statement_local | src/compat/db_vdb.c | 3276 |
db_execute_statement | src/compat/db_vdb.c | 3315 |
db_open_buffer_and_compile_first_statement | src/compat/db_vdb.c | 3344 |
db_compile_and_execute_queries_internal | src/compat/db_vdb.c | 3433 |
db_close_session_local | src/compat/db_vdb.c | 3581 |
db_close_session | src/compat/db_vdb.c | 3659 |
db_init | src/compat/db_admin.c | 170 |
db_login | src/compat/db_admin.c | 854 |
db_restart | src/compat/db_admin.c | 918 |
db_restart_ex | src/compat/db_admin.c | 982 |
db_shutdown | src/compat/db_admin.c | 1012 |
db_end_session | src/compat/db_admin.c | 1086 |
db_commit_transaction | src/compat/db_admin.c | 1169 |
db_abort_transaction | src/compat/db_admin.c | 1194 |
db_query_next_tuple | src/compat/db_query.c | 2188 |
db_query_first_tuple | src/compat/db_query.c | 2409 |
db_query_seek_tuple | src/compat/db_query.c | 2555 |
db_query_get_tuple_value | src/compat/db_query.c | 2978 |
db_query_get_tuple_value_by_name | src/compat/db_query.c | 3064 |
db_query_tuple_count | src/compat/db_query.c | 3194 |
db_query_end | src/compat/db_query.c | 3477 |
db_query_end_internal | src/compat/db_query.c | 3588 |
db_create_class | src/compat/db_class.c | 70 |
db_add_attribute_internal | src/compat/db_class.c | 184 |
db_add_attribute | src/compat/db_class.c | 248 |
db_create | src/compat/db_obj.c | 70 |
db_get | src/compat/db_obj.c | 234 |
db_put | src/compat/db_obj.c | 319 |
db_create_trigger | src/compat/db_obj.c | 1302 |
boot_initialize_client | src/transaction/boot_cl.c | 275 |
boot_restart_client | src/transaction/boot_cl.c | 690 |
boot_shutdown_client | src/transaction/boot_cl.c | 1352 |
net_client_init | src/communication/network_cl.c | 3657 |
net_client_request_with_callback | src/communication/network_cl.c | 1153 |
ux_database_connect | src/broker/cas_execute.c | 376 |
ux_database_shutdown | src/broker/cas_execute.c | 589 |
ux_prepare | src/broker/cas_execute.c | 618 |
ux_end_tran | src/broker/cas_execute.c | 874 |
ux_execute | src/broker/cas_execute.c | 992 |
ux_execute_all | src/broker/cas_execute.c | 1307 |
ux_execute_array | src/broker/cas_execute.c | 2086 |
ux_fetch | src/broker/cas_execute.c | 2442 |
ux_cursor | src/broker/cas_execute.c | 2583 |
ux_cursor_close | src/broker/cas_execute.c | 2732 |
fetch_result | src/broker/cas_execute.c | 5006 |
fetch_call | src/broker/cas_execute.c | 9018 |
server_fn_table | src/broker/cas.c | 75 |
fn_prepare | src/broker/cas_function.c | 231 |
fn_execute | src/broker/cas_function.c | 345 |
fn_prepare_and_execute | src/broker/cas_function.c | 661 |
fn_fetch | src/broker/cas_function.c | 959 |
struct db_session | src/compat/db_session.h | 26 |
Cross-check Notes
섹션 제목: “Cross-check Notes”-
세션이라는 단어가 세 군데서 쓰인다. CUBRID에서 세션이라는 이름을 공유하는 세 가지가 있다. 여기서 다룬 파서 세션
DB_SESSION(하나 또는 여러 SQL 구문 범위),db_login/db_restart가 만드는 클라이언트 세션(프로세스 전역 데이터베이스 연결), 그리고src/session/session.c의 서버 세션SESSION_STATE(준비된 구문 바인딩과 SET 변수 바인딩, 마지막 삽입 ID를 담는 클라이언트별 서버 측 컨테이너)가 그것이다.DB_SESSION은 완전히 클라이언트에 살며db_close_session으로 죽는다.SESSION_STATE는 완전히 서버에 살며 클라이언트가 모든 NRP 요청에 담아 보내는 정수SESSION_ID로 키잉된다.cubrid-server-session.md참조. -
db_compile_statement는 1부터 시작하는 구문 번호를 반환한다. 반환값은stmt_ndx + 1이므로, 호출자는 성공한 첫 번째 구문(1 반환)과 구문 없음(0 반환)을 구분할 수 있다.db_execute_statement계열은 내부에서 인덱스를 감소시킨다.db_execute_and_keep_statement_local윗 부분의stmt_ndx--;줄이 정확히 이 변환이다. -
db_execute_statement는 파스 트리를 해제하고,db_execute_and_keep_statement는 해제하지 않는다. 두 형태의 유일한 동작 차이지만, 구문을 재실행할 수 있는지를 결정한다. 브로커는 JDBCPreparedStatement의미가 재실행을 요구하기 때문에 and-keep 형태를 쓴다. -
CCI 클라이언트 라이브러리는 별도 저장소에 있다. 여기서 다룬 서버 측 CCI 로직은
src/broker/cas_*.c에 있으며 CUBRID 서버 소스와 함께 배포된다. JDBC/Python/ODBC가 링크하는 클라이언트 측 CCI 라이브러리(libcascci.so)는 별도의cubrid-ccigit 서브모듈에 산다. 분석 시점에 작업 트리에 없었다. 이 문서의 와이어 세부 내용은 서버 측cas_*.c파일과cas_protocol.h에서 파생했다. -
스키마 조작은 세션 API 수준에서 MVCC를 우회한다.
db_create_class와db_add_attribute는 즉시 스키마 변경을 일으킨다. API 수준에 템플릿 안 단계가 노출되지 않는다. C 코드에서 여러 단계의 변경을 하려면smt_def_class/smt_edit_class_mop/smt_add_attribute로 템플릿을 직접 조합하고sm_update_class로 한 번 커밋해야 한다. -
do_prepare_statement서버 왕복은 조건부다.db_compile_statement_local에서 준비 단계는PRM_ID_XASL_CACHE_MAX_ENTRIES > 0이고statement->flag.cannot_prepare == 0일 때만 실행된다. 그렇지 않으면 컴파일은 컴파일됨 단계에서 멈추고 실행기는 매번 XASL 스트림을 구성하는 오래된do_statement경로를 쓴다. 준비를 건너뛰었더라도stage[stmt_ndx]가StatementPreparedStage로 설정된다는 점에 주의해야 한다. 이 이름은 실행 준비됨을 의미하지 XASL 캐시에 등록됨을 의미하지 않는다. -
T_CALL과T_OBJFETCH에서의 커서 임의 접근은 퇴화형이다. 이 타입들은 정확히 하나의 행을 담으며 커서 함수는crs_pos를C_BEFORE/C_ON/C_AFTER사이에서만 이동시킨다. 완전한 임의 접근 의미론은 실제 목록 파일 커서를 아래에 가진T_SELECT에만 적용된다. -
브로커의
T_SRV_HANDLE과 DBI의DB_SESSION이 항상 1:1은 아니다.is_prepared == TRUE로 준비된 핸들은ux_cursor_close/ux_close_req_handle까지 자신의DB_SESSION을 소유한다.is_prepared == FALSE로 준비된 핸들(준비 시점에 의미 검사가 실패했지만 브로커가 실행 시점에 재시도하려는 경우)은ux_execute안에서 새DB_SESSION을 연다. -
db_query_set_copy_tplvalue (result, 0)이 들여다보기 모드를 활성화한다. 기본적으로 커서는 목록 파일에서 행 값을 복사한다. 브로커는ux_execute이후0인자로 이 함수를 호출해 들여다보기 모드로 전환하고 컬럼당 malloc을 피한다. 브로커가 즉시 와이어 버퍼로 직렬화하기 때문에 안전하다. -
XASL 캐시 무효화 재시도는 성공 경로에서 조용하다.
do_execute_statement가ER_QPROC_INVALID_XASLNODE또는ER_QPROC_XASLNODE_RECOMPILE_REQUESTED를 반환하면, DBI는 구문의xasl_id를 지우고do_prepare_statement를 다시 호출하며 재시도한다. 호출자는 복구가 실패할 때만 오류를 본다. -
db_invalidate_mvcc_snapshot_before_statement는 모든 최상위 실행 호출에서 실행된다. 이것이 CUBRID를 구문 단위 READ COMMITTED 엔진으로 만드는 장치다. 래퍼를 건너뛰고(바로_local로 가면) 이전 스냅샷이 유지된다. 브로커는 래퍼를 호출하도록 신경을 쓴다.
Open Questions
섹션 제목: “Open Questions”-
cubrid-cci서브모듈의 CCI 클라이언트 라이브러리가 이 작업 트리에 없었으므로, 이 문서는 서버 측 CCI 면을 다루지만 JDBC의cubrid-jdbc가 실제로 호출하는 클라이언트 측cci_prepare,cci_execute,cci_fetchAPI는 다루지 않는다. 다음 단계에서 서브모듈의cci_handle.c와cci_query.c를 다루는 절을 추가해야 한다. -
db_session_set_holdable과 서버 측 보류 가능 커서 목록 (SESSION_STATE.holdable_cursors)의 관계가 완전히 추적되지 않았다. 구체적으로, JDBC 클라이언트가 보류 가능 커서를 잊어버렸을 때 — 트랜잭션 커밋, 세션 타임아웃, 명시적db_query_end중 무엇이 — 해제하는지가 불분명하다. -
db_execute_statement_local은 실행 후 인덱스stmt_ndx - 1의 파스 트리를 해제한다. 호출자가 세션 하나에서db_execute_statement와db_execute_and_keep_statement를 혼용하고db_rewind_statement를 호출할 때의 실패 모드가 불분명하다. -
do_prepare_statement와do_execute_statement가 사용하는 정확한 NRP 오피코드는src/communication/network_interface_cl.c에 있지만 여기서 열거하지 않았다.cubrid-network-protocol.md로의 상호 참조는 이름으로만 이루어진다. -
ux_execute_all(다중 구문 실행)은 구조적으로ux_execute와 비슷하지만 자동 커밋이 꺼져 있을 때 부분 실패 시 해당 구문만 롤백하도록 세이브포인트 로직을 삽입한다. 정확한 세이브포인트 이름 형식과ux_execute_batch/ux_execute_array와의 상호작용은 여기서 분석하지 않았다.
Sources
섹션 제목: “Sources”이 문서는 CUBRID 소스를 직접 읽어 정리했다.
src/compat/db_admin.c— 연결과 트랜잭션 제어.src/compat/db_vdb.c— 구문 컴파일/실행, 호스트 변수, 준비된 구문 지원.src/compat/db_query.c— 결과 집합과 커서.src/compat/db_class.c— 스키마 정의.src/compat/db_obj.c— 객체 API.src/compat/db_session.h,db_query.h— 구조체 정의.src/transaction/boot_cl.c—boot_restart_client.src/communication/network_cl.c—net_client_*요청 계열.src/broker/cas.c,cas_function.c— CCI 디스패치 테이블과fn_*와이어 연결 고리.src/broker/cas_execute.c—ux_*파사드.
이 코드 분석 트리 내 상호 참조: cubrid-network-protocol.md(NRP 와이어
형식), cubrid-server-session.md(SESSION_STATE), cubrid-boot.md
(boot_restart_client), cubrid-broker.md(브로커 데몬과 CAS),
cubrid-query-rewrite.md(pt_compile / mq_translate),
cubrid-semantic-check.md(후기 바인드 의미 단계), cubrid-cursor.md
(cursor_* 계열), cubrid-xasl-cache.md(do_prepare_statement 캐시).
이론적 참조: X/Open CLI 명세(1995); Database System Concepts (Silberschatz, Korth, Sudarshan, 5장 §Embedded SQL); Database Internals (Petrov, 5장 §Transactions); 스크롤 가능한 커서 탐색 모델에 대한 ODBC 프로그래머 참조; 모양과 명명 관례에서 가장 가까운 외부 대응물인 Oracle Call Interface 프로그래머 가이드.