(KO) CUBRID XASL Generator — 최적화된 plan 트리를 서버 측 실행 트리로 컴파일하기
목차
학술적 배경
섹션 제목: “학술적 배경”옵티마이저가 plan 을 골랐다고 해서 그것이 곧 데이터베이스 엔진이
실행하는 대상이 되는 것은 아니다. Database Internals(Petrov,
12장 Query Processing) 가 그 간극을 명시적으로 짚는다. 옵티마이저는
plan 을 만들고, 실행기는 실행 가능한 형태(executable form) 를
소비한다. 둘 사이를 누군가는 걸어서 메워 줘야 한다는 점이다. plan은
선언적 산출물이다. “테이블 A에는 인덱스 X 를 쓰고, 그다음 nested-loop
조인으로 B에 들어가고, 마지막에 C로 정렬한다” — 비용 추정과 접근
경로 선택으로 만들어진다. 실행 가능한 형태는 절차적 산출물이다.
operator 트리이며 각 operator는 자신을 open / fetch / close 하는 방법을
알고 있다. 구체적인 버퍼, 술어 평가기, tuple 레이아웃이 모두 결선되어
있다는 뜻이다. Codd 의 관계 대수와 Selinger 의 System R 비용 모델
모두 plan 경계에서 멈춘다. 즉 σ(p)(R) 이 어떻게 CPU가 호출할 수
있는 함수로 변하는지에 대해서는 아무 말도 하지 않는다.
이 간극에는 교과서적 문제 셋이 살고 있다. 그리고 그 셋이 모든 관계형 엔진의 plan-to-IR 레이어 모양을 결정한다.
- 실행 모델의 선택. Database Internals 12장은 iterator(Volcano)
모델을 설명한다. 이 모델에서 각 operator는
open() / next() / close()를 노출하고, 부모는 자식으로부터 tuple 을 끌어 온다. 더 새로운 엔진들은 push 기반 실행(벡터화, MonetDB 풍)이나 push-down 컴파일(HyPer, LLVM-IR JIT) 을 위에 얹기도 한다. 어느 쪽을 고르는가가 IR이 무엇을 담느냐를 바꾼다. 순수 iterator IR은 operator 당 하나의 open/next/close 트리플을 부호화한다. 벡터화 IR은 batch 단위 루프를 부호화한다. 컴파일된 IR은 생성된 코드 조각을 부호화한다. - plan 안의 데이터 이름 붙이기. plan에 들어 있는 술어와 projection은
여전히 parser 레벨의 객체(컬럼 이름, 표현식 트리, host variable)를
참조한다. IR은 이것들을 실행기가 런타임에 읽을 수 있는 메모리
위치로 역참조해야만 한다. “클래스 oid X 의 attribute id 3을 이
DB_VALUE 슬롯으로 fetch하라” 라고 말해 주는 디스크립터가 필요하다는
점이다. 런타임 변수 전용 하위 IR은 보편적이다. PostgreSQL의
Var/Const/Param/Aggref노드, MySQL의Item트리, Oracle의 row source expression, CUBRID의REGU_VARIABLE이 모두 같은 모양이다. - 클라이언트-서버 실행을 위한 IR 직렬화. 많은 엔진이 최적화는
클라이언트에서 돌리고 실행은 서버에서 돌린다. CUBRID도 그중
하나다.
xasl_generation.c는 클라이언트에서 컴파일되고,query_executor.c는 서버에서 실행된다. 따라서 IR은 바이트 스트림으로 round-trip 해야만 한다. 포인터를 그대로 와이어로 보낼 수는 없으니 직렬화기는 순환적이고 공유가 많은 IR을 자기 기술적인 버퍼로 평탄화해야 한다. offset 기반 참조와 이미 본 주소 테이블 을 동원해서다. Designing Data-Intensive Applications(Kleppmann) 4장은 이를 positional encoding 이라 부르고, 인코더가 공유 서브트리를 한 번만 방문하고 이후 참조는 offset으로 발행해야 한다는 요구 사항을 짚는다. 그렇지 않으면 와이어 크기가 지수적으로 부풀어 오르기 때문이다.
이렇게 셋이 명명되고 나면, 본 문서의 모든 CUBRID 고유 구조는 셋 중 하나를 구현하거나 결과 자료구조를 와이어 위에서 효율적으로 만드는 일을 하고 있다는 점이 보인다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”SQL을 절차적 plan 트리로 떨어뜨리는 모든 관계형 엔진은 대수 위에 같은 한 줌의 패턴을 채택한다. Selinger 의 원래 System R 논문에는 들어 있지 않다. 옵티마이저가 고른 plan과 그것을 실행하는 런타임 사이에 사는 공학적 어휘다.
물리 operator 의 struct 트리
섹션 제목: “물리 operator 의 struct 트리”PostgreSQL의 nodes/plannodes.h 는 Plan 을 추상 부모로 정의하고,
그 아래 SeqScan, IndexScan, BitmapHeapScan, NestLoop,
MergeJoin, HashJoin, Agg, Sort, Limit, Append, SetOp,
ModifyTable 같은 물리 operator 마다 한 개의 C struct를 둔다. 모두
lefttree 와 righttree 포인터로 연결되어 일반적인 binary plan
트리가 된다. 실행기는 시작 시점에 이 트리를 top-down으로 걷고
(ExecInitNode), 런타임에는 bottom-up으로 tuple을 끌어 올린다
(ExecProcNode). MySQL 8.x 의 iterator 실행기도 RowIterator 와
AccessPath(sql/access_path.h) 클래스로 같은 모양이다. SQL Server 의
showplan XML 은 같은 구조의 텍스트 표현이다. 패턴은 보편적이다. 물리 operator 당 한 개의 트리 노드, 조인 다리나 입력 소스마다 자식
링크, 그리고 이 노드가 어떤 종류인지를 적어 놓는 공통 헤더.
CUBRID는 더 압축된 형태를 고른다. XASL_NODE 라는 단일 struct가
모든 operator 종류를 담는다. PROC_TYPE type 이라는 디스크리미네이터
와 그 변종을 덮는 태그드 union proc 이 그 역할을 한다. PostgreSQL
이 Plan 아래 30개 이상의 struct 타입을 두는 자리에 CUBRID는 17개의
PROC_TYPE 값을 가진 단일 struct를 둔다. 각 proc은 union arm 안에
자기 특화 필드를 들고 있다. 공통 필드(술어, 출력 리스트, sort spec,
limit, sub-pointer)는 xasl_node 위에 직접 살면서 proc 사이에서
재사용된다. 타입 안전성을 메모리 지역성과 맞바꾼 셈이다. 그리고
직렬화기 코드를 단순하게 만든다. 디스크리미네이터 당 process 함수
하나로 끝나기 때문이다.
operator 아래의 access-path 객체
섹션 제목: “operator 아래의 access-path 객체”IR 의 모든 leaf operator는 물리 access path 를 참조해야 한다. heap
scan, index scan, list-file scan, 또는 구성된 값들의 집합이다. 이름은
다양하다. PostgreSQL의 Path, MySQL의 AccessPath, Spark의
SparkPlan — 그러나 목적은 동일하다. tuple stream 이 어디서 오는가
를 그것으로 무엇을 할 것인가 와 분리해서 부호화한다는 점이다.
CUBRID는 이를 xasl->spec_list 에 매달린 ACCESS_SPEC_TYPE 사슬로
빼낸다. 그리고 HYBRID_NODE union 으로 종류(class scan, list scan,
set scan, JSON-table scan, showstmt, dblink 등) 를 고른다. index scan
의 경우 access spec은 INDX_INFO 를 가리키고, 거기에 range 종류
(R_KEY, R_RANGE, R_KEYLIST, …), btree id, key range가 들어
있다. heap scan 의 경우 클래스 oid 와 attribute 디스크립터를 들고
다닌다.
런타임 표현식을 위한 하위 IR
섹션 제목: “런타임 표현식을 위한 하위 IR”옵티마이저를 통과해도 술어와 projection은 parser 레벨 이름을 참조하는
표현식 트리 그대로 살아남는다. 실행기는 parser 이름 위에서 t.col1 = 7 을 평가할 수 없다. 표현식 IR이 필요하다는 뜻이다. 이 IR의
leaf는 tuple 버퍼 안의 슬롯이고, 상수는 미리 바인딩된 DB_VALUE 이며,
노드는 자기 연산 종류를 string lookup 없이도 안다. PostgreSQL의
ExprState(런타임 평가기) 는 parse-tree 표현식으로부터 ExecInitExpr
이 생성한다. MySQL은 파싱된 Item 트리를 그대로 두되 평가마다
해소된 필드 위치를 캐시한다. CUBRID의 REGU_VARIABLE 이 등가물이다.
상수 DB_VALUE, offset 을 가진 attribute id, 산술 노드의 결과,
함수 호출, tuple 리스트 안의 위치 라는 디스크리미네이션 union
이다. XASL generator 의 pt_to_regu_* 가족이 번역기이고,
실행기의 fetch_peek_dbval 이 소비자다.
서브쿼리 배치 — 중첩 plan 은 어디에 사는가
섹션 제목: “서브쿼리 배치 — 중첩 plan 은 어디에 사는가”SQL 의 서브쿼리는 그 자체로 하나의 plan 트리가 된다. 그리고 부모 plan 은 이걸 언제 실행할지 알아야 한다. 정통적인 전략은 셋이다.
- outer plan 안으로 끌어올려 join 으로 만든다. 서브쿼리가 query
rewrite 단계에서 join 으로 바뀌고 IR 에는 서브쿼리 노드가 아예
남지 않는다 (PostgreSQL의
PLANNER는 대부분의 비상관IN서브쿼리를convert_ANY_sublink로 이렇게 처리한다). - 한 번 미리 계산해서 입력으로 넣는다. 서브쿼리는 outer scan
이전에 실행되고 그 결과 list-file 이 입력으로 바인딩된다
(PostgreSQL의
InitPlan). 비상관 서브쿼리에만 가능하다는 점이다. IR 은 부모 위에 init-plan 리스트를 들고 다닌다. - outer 행마다 다시 평가한다. 상관 서브쿼리는 outer 컬럼을
참조하는 술어를 갖기 때문에 outer 행마다 다시 돌아야 한다. IR
은 이것을 outer 에 붙은 sub-plan 으로 들고 다닌다 (PostgreSQL의
SubPlan).
CUBRID는 이 셋 모두를 XASL_NODE 한 개에 부호화한다. aptr_list
(ahead pointer) 는 부모보다 먼저 한 번 실행되는 비상관 서브쿼리를
담는다. dptr_list(driving pointer) 는 outer tuple 마다 다시 도는
상관 서브쿼리를 담는다. scan_ptr 은 현재 scan 에 사슬로 묶이는
조인의 inner 를 담는다. 두 종류의 서브쿼리와 다중 join 을 모두 가진
SELECT 하나는 단일 XASL 로 컴파일되며, 그 자식 슬롯이 세 역할을
한 struct 안에서 모두 덮는다는 점이다.
클라이언트-서버 실행을 위한 직렬화
섹션 제목: “클라이언트-서버 실행을 위한 직렬화”최적화는 클라이언트에서 돌고 실행은 서버에서 돈다면, IR 은 바이트로
실려 가야 한다. 이렇게 하는 시스템은 모두 같은 세 문제를 마주한다.
공유 서브트리는 한 번만 발행되어야 한다 (그렇지 않으면 인코딩이
부풀어 오른다). 포인터는 offset 으로 다시 쓰여야 한다. 수신 측은
unpack 전에 연속된 arena 를 할당해야 한다. PostgreSQL의 outfuncs.c /
readfuncs.c 는 공유 트리를 (@1) 같은 back-reference 를 가진
텍스트 인코딩을 쓴다. CUBRID 의 xts_map_xasl_to_stream /
stx_map_stream_to_xasl 은 방문 포인터 해시 테이블(XTS_VISITED_PTR)
과 단일 xts_Stream_buffer 안의 offset 을 쓰는 바이너리 스트림을
쓴다. 거기에 더해, 본문이 시작되기 전에 클래스 oid 와 lock 요구를
적어 놓는 작은 헤더가 따로 붙는다. 그래야 서버가 XASL 을 할당하기
전에 lock 을 먼저 잡을 수 있다는 점이다.
이론 ↔ CUBRID 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론적 개념 (Theoretical concept) | CUBRID 명칭 (CUBRID name) |
|---|---|
| Plan 트리 노드 | XASL_NODE (src/query/xasl.h) |
| 물리 operator 디스크리미네이터 | PROC_TYPE { UNION_PROC, DIFFERENCE_PROC, INTERSECTION_PROC, OBJFETCH_PROC, BUILDLIST_PROC, BUILDVALUE_PROC, SCAN_PROC, MERGELIST_PROC, HASHJOIN_PROC, UPDATE_PROC, DELETE_PROC, INSERT_PROC, CONNECTBY_PROC, DO_PROC, MERGE_PROC, BUILD_SCHEMA_PROC, CTE_PROC } (xasl.h) |
| Operator 별 페이로드 | xasl_node::proc 태그드 union (buildlist, buildvalue, union_, mergelist, hashjoin, update, insert, delete_, connect_by, merge, cte, fetch) |
| Access path | xasl->spec_list 의 ACCESS_SPEC_TYPE 사슬, HYBRID_NODE union이 class / list / set / showstmt / json-table / dblink / method / regu-value 중 하나로 분기 |
| 런타임 표현식 IR | REGU_VARIABLE (regu_var.hpp), TYPE_CONSTANT, TYPE_ATTR_ID, TYPE_POSITION, TYPE_INARITH, TYPE_FUNC 등 |
| 술어 IR | PRED_EXPR (xasl/xasl_predicate.hpp), T_PRED / T_EVAL_TERM / T_NOT_TERM 디스크리미네이터 |
| 출력 projection | xasl->outptr_list 에 매달린 OUTPTR_LIST / REGU_VARIABLE_LIST 사슬 |
| spec 별 컬럼 버퍼 | QPROC_DB_VALUE_LIST 노드의 VAL_LIST. 같은 컬럼을 이름하는 형제 regu 들 사이에서 재사용 |
| 미리 계산되는 (비상관) 서브쿼리 | xasl->aptr_list |
| 상관 서브쿼리 | xasl->dptr_list |
| 현재 scan 에 사슬로 묶이는 조인의 inner | xasl->scan_ptr |
| Path-fetch (OBJFETCH) sub-plan | xasl->bptr_list, xasl->fptr_list |
| 인덱스 access 메타데이터 | ACCESS_SPEC_TYPE::indexptr 가 가리키는 INDX_INFO (R_KEY, R_RANGE, R_KEYLIST, R_RANGE_LIST, …) |
| Scan 안의 술어 자리 (수직 / 수평 / 힙) | ACCESS_SPEC_TYPE 위의 where_range, where_key, where_pred |
| PT_NODE → XASL_NODE 워크의 최상위 진입점 | parser_generate_xasl (xasl_generation.c) |
| SELECT 별 plan 컴파일러 | pt_plan_query (xasl_generation.c) |
| 다행 vs 단행 select 디스패치 | pt_to_buildlist_proc / pt_to_buildvalue_proc (xasl_generation.c) |
| spec/scan/서브쿼리 슬롯을 채우는 plan walker | qo_to_xasl → gen_outer / gen_inner (optimizer/plan_generation.c) |
| Parser 트리 → 표현식 IR | pt_to_regu_variable, pt_to_pred_expr, pt_to_outlist, pt_to_val_list, pt_to_index_info, pt_to_spec_list (xasl_generation.c) |
| Aptr / dptr 주입기 | pt_set_aptr, pt_set_dptr (xasl_generation.c) |
| XASL 할당기 | regu_xasl_node_alloc (xasl_regu_alloc.cpp) |
| 직렬화 드라이버 | xts_map_xasl_to_stream (query/xasl_to_stream.c) |
| XASL 별 pack 함수 | xts_save_xasl_node, xts_process_xasl_node (query/xasl_to_stream.c) |
| 방문 포인터 offset 테이블 | xts_get_offset_visited_ptr, xts_mark_ptr_visited (query/xasl_to_stream.c) |
| 서버 측 unpacker | stx_init_xasl_unpack_info, stream_to_xasl (query/stream_to_xasl.c, xasl/xasl_stream.cpp) |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”XASL generator 에는 네 개의 이동 부품이 있다. 진입 워크 가 query
형태의 PT_NODE 를 XASL proc 트리로 바꾼다. plan 주도의 내부 워크
(gen_outer / gen_inner) 가 spec list, scan pointer, 서브쿼리 슬롯
을 채운다. 하위 IR 빌더(pt_to_regu_variable, pt_to_pred_expr,
pt_to_outlist, pt_to_val_list, pt_to_index_info,
pt_to_spec_list) 가 표현식, 술어, projection, access spec 을 컴파일
한다. 직렬화기(xts_map_xasl_to_stream 가족) 가 만들어진 포인터
트리를 서버가 unpack 할 수 있는 평탄한 바이트 스트림으로 바꾼다.
이 순서대로 본다.
전체 구조
섹션 제목: “전체 구조”flowchart LR
subgraph CLIENT["클라이언트 (xasl_generation.c, plan_generation.c)"]
PT["PT_NODE 트리<br/>(이름 해소,<br/>타입 검사,<br/>view 재작성 완료)"]
QO["QO_PLAN 트리<br/>(옵티마이저 출력:<br/>scan / join / sort / follow)"]
PGX["parser_generate_xasl<br/>· parser_generate_xasl_post<br/>(parser_walk_tree)"]
PPQ["pt_plan_query<br/>PT_SELECT 마다"]
BLP["pt_to_buildlist_proc<br/>(다행)"]
BVP["pt_to_buildvalue_proc<br/>(단행)"]
POL["pt_to_outlist<br/>pt_to_val_list"]
PSA["pt_set_aptr"]
PGOP["pt_gen_optimized_plan"]
QTX["qo_to_xasl<br/>gen_outer / gen_inner"]
AAS["add_access_spec<br/>add_scan_proc<br/>add_subqueries"]
PSD["pt_set_dptr"]
XASL["XASL_NODE 트리<br/>(클라이언트, 인메모리)"]
XTS["xts_map_xasl_to_stream<br/>xts_save_xasl_node<br/>xts_process_xasl_node"]
STREAM["XASL stream<br/>(packed bytes)"]
end
subgraph SERVER["서버 (stream_to_xasl.c, query_executor.c)"]
STX["stx_init_xasl_unpack_info<br/>stream_to_xasl"]
XASLS["XASL_NODE 트리<br/>(서버 측, 재구성)"]
EXEC["query_executor.c<br/>qexec_execute_mainblock"]
end
PT --> PGX
PGX --> PPQ
PPQ --> BLP
PPQ --> BVP
BLP --> POL
BLP --> PSA
BLP --> PGOP
PGOP --> QTX
QTX --> AAS
AAS --> XASL
BLP --> PSD
PSD --> XASL
QO --> PGOP
XASL --> XTS
XTS --> STREAM
STREAM --> STX
STX --> XASLS
XASLS --> EXEC
이 그림은 세 경계를 부호화한다. (parser ↔ plan) PT_NODE 측은
src/parser/ 의 xasl_generation.c 가 소유한다. QO_PLAN 측은
src/optimizer/ 의 plan_generation.c 가 소유한다. 둘은
pt_gen_optimized_plan 안에서 만난다. 부모 XASL 을 할당하고 그
outptr_list 와 aptr_list 를 시드한 다음 qo_to_xasl 을 호출하는
지점이다. (client ↔ server) XASL generator 자체는 모두
클라이언트 측에 산다. 그 출력이 서버에 도달하는 유일한 경로가
직렬화다. (buildlist ↔ buildvalue) 다행 select 는 BUILDLIST_PROC
으로 컴파일된다. 이는 임시 list-file 로 materialise 한다는 뜻이다.
단행이 보장된 select 는 BUILDVALUE_PROC 으로 컴파일된다. 이는
임시 파일 없이 단일 tuple 슬롯에 결과를 쓴다는 뜻이다.
XASL_NODE — 한 struct, 17개 proc
섹션 제목: “XASL_NODE — 한 struct, 17개 proc”이 노드는 넓다. 공통 필드가 모든 proc 종류를 덮고, proc 별 부가
필드는 proc union 안에 숨는다.
// xasl_node — src/query/xasl.hstruct xasl_node{ XASL_NODE_HEADER header; /* xasl_flag + id, packed first on the wire */ XASL_NODE *next; /* sibling XASL block (UNION_PROC chain etc.) */ PROC_TYPE type; /* discriminator: BUILDLIST_PROC, SCAN_PROC, … */ int flag; QFILE_LIST_ID *list_id; /* materialised result handle */ // ... condensed: sort, limit, instnum/ordbynum bookkeeping ...
OUTPTR_LIST *outptr_list; /* projection (select list) */ ACCESS_SPEC_TYPE *spec_list; /* leaf access specs (class / list / set / …) */ VAL_LIST *val_list; /* per-tuple column buffer for spec_list */
XASL_NODE *aptr_list; /* CTEs + uncorrelated subqueries (run once, ahead) */ XASL_NODE *bptr_list; /* OBJFETCH_PROC list (path expressions) */ XASL_NODE *dptr_list; /* correlated subqueries (run per outer tuple) */ XASL_NODE *fptr_list; /* OBJFETCH after dptr */ XASL_NODE *scan_ptr; /* inner side of join chained off current scan */ XASL_NODE *connect_by_ptr; /* CONNECT BY xasl */
PRED_EXPR *during_join_pred; /* predicate evaluated during outer join match */ PRED_EXPR *after_join_pred; /* predicate evaluated after the join */ PRED_EXPR *if_pred; /* WHERE clause residual */ PRED_EXPR *instnum_pred; /* INST_NUM() < N */
union { UNION_PROC_NODE union_; /* UNION/DIFFERENCE/INTERSECTION */ FETCH_PROC_NODE fetch; /* OBJFETCH_PROC */ BUILDLIST_PROC_NODE buildlist; /* multi-row result */ BUILDVALUE_PROC_NODE buildvalue; /* single-row aggregate result */ MERGELIST_PROC_NODE mergelist; HASHJOIN_PROC_NODE hashjoin; UPDATE_PROC_NODE update; INSERT_PROC_NODE insert; DELETE_PROC_NODE delete_; CONNECTBY_PROC_NODE connect_by; MERGE_PROC_NODE merge; CTE_PROC_NODE cte; } proc;
/* XASL cache + serialization metadata */ OID creator_oid; int n_oid_list; OID *class_oid_list; int *class_locks; int *tcard_list; // ...};네 개의 포인터 슬롯(aptr_list, bptr_list, dptr_list, scan_ptr)
에 next 까지 더하면, 단일 XASL_NODE 구조체가 SELECT가 만들어
낼 수 있는 네 가지 중첩 plan 을 모두 부호화하게 된다. union 사슬은
next 로, outer-then-inner 조인 사슬은 scan_ptr 로, 부모 이전에
materialise 되는 비상관 서브쿼리는 aptr_list 로, outer tuple 마다
다시 도는 상관 서브쿼리는 dptr_list 로 들어간다는 점이다. 분석
자료의 표어 XASL(aptr, dptr, scan_ptr) 가 곧 실행기의 멘탈
모델이다. outer 행마다 dptr 들을 돌리고, 그다음 scan_ptr 사슬을
내려간다.
진입점 — parser_generate_xasl
섹션 제목: “진입점 — parser_generate_xasl”컴파일러는 재작성 후의 PT_NODE 위를 도는 순서가 있는 워크 다.
한 번에 끝내는 번역이 아니다. 새 symbol-info 프레임을
밀어 넣는 pre-order 훅(parser_generate_xasl_pre) 과 XASL 을
bottom-up 으로 만드는 post-order 훅(parser_generate_xasl_post) 을
가진 parser_walk_tree 로 동작한다. 그래서 안쪽 서브쿼리가, 그것을
참조하는 바깥 query 보다 먼저 XASL 이 된다.
// parser_generate_xasl — src/parser/xasl_generation.c (condensed)XASL_NODE *parser_generate_xasl (PARSER_CONTEXT * parser, PT_NODE * node){ XASL_NODE *xasl = NULL; // ... condensed: check abort, save next, walk for is_system_generated_stmt ...
switch (node->node_type) { case PT_SELECT: case PT_UNION: case PT_DIFFERENCE: case PT_INTERSECTION: node->info.query.is_subquery = (PT_MISC_TYPE) 0;
if (node) node = meth_translate (parser, node); /* method calls */
if (node) { xasl_Supp_info.query_list = parser_new_node (parser, PT_SELECT); xasl_Supp_info.query_list->info.query.xasl = NULL; pt_init_xasl_supp_info ();
/* the actual XASL build, bottom-up over subqueries */ node = parser_walk_tree (parser, node, parser_generate_xasl_pre, NULL, parser_generate_xasl_post, &xasl_Supp_info); }
if (node && !pt_has_error (parser)) { xasl = (XASL_NODE *) node->info.query.xasl; } break; }
/* fill in XASL cache info: creator oid, class_oid_list, locks, tcard */ if (xasl) { // ... condensed: oid + locks + tcard arrays from xasl_Supp_info ... } return xasl;}pre-order 훅 parser_generate_xasl_pre 는 묵은 info.query.xasl 을
지운다(parse 트리는 prepared statement 재실행 시 재사용된다는 점에서
중요하다). 그리고 pt_push_symbol_info 로 이 query 레벨에 새 symbol
스코프를 연다. 심볼은 pt_to_val_list 가 spec 별 컬럼 버퍼를 만들
때 소비하는 table_info 를 들고 다닌다.
post-order 훅 parser_generate_xasl_post 가 실제 작업이 일어나는
자리다. 자식들로부터 제어가 돌아왔을 때 모든 서브쿼리는 이미 자기
XASL 을 info.query.xasl 에 매달아 두었다. 그래서 부모의
parser_generate_xasl_proc 호출은 그것들을 결선하기만 하면 된다.
// parser_generate_xasl_post — src/parser/xasl_generation.c (condensed)static PT_NODE *parser_generate_xasl_post (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk){ XASL_NODE *xasl; XASL_SUPP_INFO *info = (XASL_SUPP_INFO *) arg;
switch (node->node_type) { case PT_SELECT: case PT_UNION: case PT_DIFFERENCE: case PT_INTERSECTION: assert (node->info.query.xasl == NULL); xasl = parser_generate_xasl_proc (parser, node, info->query_list); pt_pop_symbol_info (parser); /* matched with the pre-order push */ // ... condensed: walk select.from for class oid list ... break;
case PT_CTE: xasl = parser_generate_xasl_proc (parser, node, info->query_list); break; } return node;}parser_generate_xasl_proc 는 query 노드 타입별로 디스패치한다
(PT_SELECT → pt_plan_query, PT_UNION 등 → set-op 빌더). 결과는
node->info.query.xasl 에 다시 들어가서 부모의 post-hook 이 찾을
수 있게 된다.
SELECT 단위 — pt_plan_query
섹션 제목: “SELECT 단위 — pt_plan_query”각 SELECT 의 컴파일은 두 단계다. 옵티마이저에 plan 을 요청한 다음,
parse 트리와 plan 을 함께 XASL 로 번역한다. BUILDLIST_PROC 을 쓸지
BUILDVALUE_PROC 을 쓸지는 실행기가 한 행을 만들지 여러 행을 만들지에
달려 있다.
// pt_plan_query — src/parser/xasl_generation.c (condensed)static XASL_NODE *pt_plan_query (PARSER_CONTEXT * parser, PT_NODE * select_node){ XASL_NODE *xasl; QO_PLAN *plan = NULL;
if (select_node->node_type != PT_SELECT) return NULL;
/* 1) cost-based optimization */ plan = qo_optimize_query (parser, select_node);
/* if hint-driven optimization fails, retry without hints */ if (!plan && select_node->info.query.q.select.hint != PT_HINT_NONE) { // ... condensed: clear all hint sublists, retry qo_optimize_query ... plan = qo_optimize_query (parser, select_node); }
/* 2) translate plan + select_node into XASL */ if (pt_is_single_tuple (parser, select_node)) { xasl = pt_to_buildvalue_proc (parser, select_node, plan); /* aggregate w/o GROUP BY */ } else { xasl = pt_to_buildlist_proc (parser, select_node, plan); /* normal SELECT */ }
// ... condensed: dump plan + cache plan text on xasl ... return xasl;}pt_is_single_tuple 은 select list 가 단일 aggregate 이고
(SELECT SUM(x) FROM t) GROUP BY 가 없을 때 true 다. 이는 분석
자료가 BUILDVALUE_PROC 의 자리라고 짚는 정확히 그 경우다. 그
밖의 모든 경우는 BUILDLIST_PROC 이고 list-file 을 만든다.
Buildlist 구성 — pt_to_buildlist_proc
섹션 제목: “Buildlist 구성 — pt_to_buildlist_proc”buildlist 컴파일러는 파일에서 가장 긴 함수다 (aggregate, group-by,
having, order-by, limit, window 함수, connect-by 가 모두 들어 있어
수천 줄에 이른다). 그러나 골격이 본질이다. 할당하고, 부모 컨텍스트
를 세팅하고, outptr 을 만들고, aptr 을 세팅하고, 최적화된 plan 을
생성하고(여기서 spec_list/scan_ptr 이 채워진다), dptr 을 세팅한다.
순서가 중요하다. outptr 이 aptr 보다 먼저인 이유는 select list 에서
비상관 서브쿼리를 참조할 수 있기 때문이다. aptr 이 optimized-plan
보다 먼저인 이유는 plan 안의 spec 별 서브쿼리가 이미 끌어올려진 게
무엇인지 알아야 하기 때문이다. dptr 이 마지막인 이유는 상관 서브쿼리
가 leaf scan 에 매달려야 하는데, 그 leaf 는 gen_outer 가 끝난 뒤에야
존재하기 때문이다.
// pt_to_buildlist_proc — src/parser/xasl_generation.c (condensed)static XASL_NODE *pt_to_buildlist_proc (PARSER_CONTEXT * parser, PT_NODE * select_node, QO_PLAN * qo_plan){ XASL_NODE *xasl, *save_parent_proc_xasl; SYMBOL_INFO *symbols = parser->symbols; PT_NODE *from = select_node->info.query.q.select.from;
if (symbols == NULL || from == NULL || select_node->node_type != PT_SELECT) return NULL;
/* 1) allocate the BUILDLIST_PROC shell */ xasl = regu_xasl_node_alloc (BUILDLIST_PROC); if (xasl == NULL) return NULL;
/* 2) tell child compiles who their parent is — pt_set_aptr/pt_set_dptr will read this */ save_parent_proc_xasl = parser->parent_proc_xasl; parser->parent_proc_xasl = xasl;
/* 3) limit / inst_num / ordby_num bookkeeping */ // ... condensed ...
/* 4) build select list as outptr */ if (pt_has_aggregate (parser, select_node)) { /* group key + aggregate exprs become outptr; aggregates collected separately */ xasl->outptr_list = pt_to_outlist (parser, group_out_list, NULL, UNBOX_AS_VALUE); // ... condensed: aggregate list, having, group-by sort spec ... } else { xasl->outptr_list = pt_to_outlist (parser, select_node->info.query.q.select.list, &xasl->selected_upd_list, UNBOX_AS_VALUE); }
/* 5) hoist uncorrelated subqueries into aptr_list before plan generation */ pt_set_aptr (parser, select_node, xasl);
/* 6) walk QO_PLAN to fill spec_list / scan_ptr / per-leaf subqueries */ xasl = pt_gen_optimized_plan (parser, select_node, qo_plan, xasl);
/* 7) attach correlated subqueries that reference outer columns */ /* pt_gen_optimized_plan already calls pt_set_dptr on the deepest leaf via qo_to_xasl */
parser->parent_proc_xasl = save_parent_proc_xasl; return xasl;}두 헬퍼 pt_set_aptr 와 pt_set_dptr 은 parse 트리를 돌면서 post-order
워크가 info.query.xasl 을 채워 둔 PT_NODE 들을 찾는다. 비상관이면
xasl->aptr_list 에 prepend, 상관이면 xasl->dptr_list 에 prepend
한다는 점이다.
Plan 워크 — pt_gen_optimized_plan 과 qo_to_xasl
섹션 제목: “Plan 워크 — pt_gen_optimized_plan 과 qo_to_xasl”pt_gen_optimized_plan 은 얇은 래퍼다. qo_to_xasl 을 호출한 다음,
query hint(USE_IDX_DESC, NO_IDX_DESC, NLJ_KEEP_HEAP_PAGE_PINNED)
가 결정하는 몇 개의 인덱스 방향 플래그를 패치한다. 재귀적인 plan
워크 자체는 qo_to_xasl 과 그 상호 헬퍼 gen_outer, gen_inner 에
산다.
// qo_to_xasl — src/optimizer/plan_generation.c (condensed)xasl_node *qo_to_xasl (QO_PLAN * plan, xasl_node * xasl){ QO_ENV *env; XASL_NODE *lastxasl;
if (plan && xasl && (env = plan->info->env)) { xasl = gen_outer (env, plan, &EMPTY_SET, NULL, NULL, xasl);
/* find the deepest scan node — that's where correlated subqueries attach */ lastxasl = xasl; while (lastxasl) { if (lastxasl->scan_ptr) lastxasl = lastxasl->scan_ptr; else if (lastxasl->fptr_list) lastxasl = lastxasl->fptr_list; else break; } pt_set_dptr (env->parser, env->pt_tree->info.query.q.select.list, lastxasl, MATCH_ALL);
xasl = preserve_info (env, plan, xasl); } return xasl;}pt_set_dptr 가 lastxasl 로 호출되는 데에는 이유가 있다. dptr
서브쿼리는 outer 행마다 발화되므로 가장 안쪽 scan(루트에서 도달
가능한 가장 깊은 scan_ptr)에 매달려야 한다는 점이다. 거기가 매
행이 materialise 되는 자리이기 때문이다. 만약 루트에 매달면 전체
query 의 결과 행마다 한 번씩 실행된다. 잘못이다. 두 번째 위에 매달면
그 위의 조인이 단 한 번도 평가되지 않은 채 materialise 된다. 역시
잘못이다.
gen_outer 는 plan 을 타입별로 번역하는 재귀다.
// gen_outer — src/optimizer/plan_generation.c (condensed)static XASL_NODE *gen_outer (QO_ENV * env, QO_PLAN * plan, BITSET * subqueries, XASL_NODE * inner_scans, XASL_NODE * fetches, XASL_NODE * xasl){ // ... condensed: bitset bookkeeping for predicates and subqueries ...
switch (plan->plan_type) { case QO_PLANTYPE_SCAN: /* leaf — attach the access spec, then accumulated inner scans + fetches + correlated subqs */ xasl = add_access_spec (env, xasl, plan); xasl = add_scan_proc (env, xasl, inner_scans); xasl = add_fetch_proc (env, xasl, fetches); xasl = add_subqueries (env, xasl, &new_subqueries); break;
case QO_PLANTYPE_SORT: /* if we're inside a join leg, materialise the sub-plan into a list-file and rescan it; otherwise just recurse and add a sort spec */ if (inner_scans != NULL || plan->plan_un.sort.sort_type == SORT_LIMIT) { listfile = make_buildlist_proc (env, namelist); listfile = gen_outer (env, plan->plan_un.sort.subplan, &EMPTY_SET, NULL, NULL, listfile); listfile = add_sort_spec (env, listfile, plan, xasl->ordbynum_val, false); xasl = add_uncorrelated (env, xasl, listfile); xasl = init_list_scan_proc (env, xasl, listfile, namelist, &(plan->sarged_terms), NULL); // ... condensed ... } else { xasl = gen_outer (env, plan->plan_un.sort.subplan, &new_subqueries, inner_scans, fetches, xasl); xasl = add_sort_spec (env, xasl, plan, NULL, true /* add instnum pred */); } break;
case QO_PLANTYPE_JOIN: outer = plan->plan_un.join.outer; inner = plan->plan_un.join.inner;
switch (plan->plan_un.join.join_method) { case QO_JOINMETHOD_NL_JOIN: case QO_JOINMETHOD_IDX_JOIN: /* build the inner side as a SCAN_PROC, then recurse on the outer passing the inner as inner_scans */ inner_scans = gen_inner (env, inner, &predset, &new_subqueries, inner_scans); xasl = gen_outer (env, outer, &EMPTY_SET, inner_scans, fetches, xasl); break;
case QO_JOINMETHOD_MERGE_JOIN: // ... condensed: build both sides as listfiles, attach via mergelist proc ... break; } break; // ... condensed: FOLLOW (path expr), WORST cases ... } // ... condensed: free bitsets ... return xasl;}이 재귀 구조가 분석 자료의 표어 “조인 순서: A → B → C 는 BUILDLIST(A)
→ SCAN(B) → SCAN(C) 로 컴파일된다” 의 출처다. outer 자체가 다시
조인인 QO_PLANTYPE_JOIN 은 outer 쪽을 따라 재귀하다가 QO_PLANTYPE_SCAN
에 도달하면 거기가 access spec 을 가진 BUILDLIST_PROC 이 된다.
거꾸로 올라오는 길에서 각 층의 inner 는 SCAN_PROC 으로 감싸져
(gen_inner 와 make_buildlist_proc 으로) outer 의 scan_ptr
에 사슬로 묶인다. 결과는 루트가 list-file 로 materialise 되고 그
scan_ptr 후손들은 순수 파이프라인인 left-deep XASL 사슬이다.
add_access_spec 은 leaf 컴파일러다. parse 트리의 class spec 을
ACCESS_SPEC_TYPE 으로 변환하고, 술어를 key/access 종류로 쪼개고,
spec 별 val_list 를 바인딩한다.
// add_access_spec — src/optimizer/plan_generation.c (condensed)static XASL_NODE *add_access_spec (QO_ENV * env, XASL_NODE * xasl, QO_PLAN * plan){ PARSER_CONTEXT *parser = QO_ENV_PARSER (env); PT_NODE *class_spec = QO_NODE_ENTITY_SPEC (plan->plan_un.scan.node); PT_NODE *key_pred = NULL; PT_NODE *access_pred = NULL; PT_NODE *if_pred = NULL; PT_NODE *instnum_pred = NULL; QO_XASL_INDEX_INFO *info = qo_get_xasl_index_info (env, plan);
/* 1) split predicates: key (index range), access (heap-side residual), if/instnum */ make_pred_from_plan (env, plan, &key_pred, &access_pred, info, NULL);
/* 2) build the access spec — class oid, index info, range/key/pred regu lists */ xasl->spec_list = pt_to_spec_list (parser, class_spec, key_pred, access_pred, plan, info, NULL, NULL);
/* 3) per-spec column buffer */ xasl->val_list = pt_to_val_list (parser, class_spec->info.spec.id);
/* 4) attach if-predicate and inst_num predicate to the parent xasl */ if_pred = make_if_pred_from_plan (env, plan); instnum_pred = make_instnum_pred_from_plan (env, plan); xasl = add_if_predicate (env, xasl, if_pred); xasl = pt_to_instnum_pred (parser, xasl, instnum_pred);
// ... condensed: free pointer lists, free index info ... return xasl;}where_range / where_key / where_pred 로의 분할(분석 자료가
수직 / 수평 / 힙 축이라 부르는 것) 은 pt_to_index_info 와
pt_to_spec_list 안에서 일어난다. 인덱스의 leading prefix 와 매칭
되는 술어가 where_range 를 끌고 간다. 나머지 인덱스 컬럼 위의
술어가 where_key 가 된다(인덱스 워크 도중 leaf 레벨 필터링). 인덱스
가 닿지 않는 컬럼을 참조하는 술어는 where_pred 가 된다(힙 행을
fetch 한 뒤 데이터 페이지에서 평가).
하위 IR — REGU_VARIABLE, OUTPTR_LIST, VAL_LIST
섹션 제목: “하위 IR — REGU_VARIABLE, OUTPTR_LIST, VAL_LIST”SELECT 의 projection, 술어, 런타임 상수는 모두 REGU_VARIABLE 트리
로 떨어진다. pt_to_regu_variable 이 모든 PT_NODE 모양을 regu 로
사상하는 큰 switch 다. 그리고 실행기는 그것을 fetch_peek_dbval 로
다시 읽는다.
// pt_to_regu_variable — src/parser/xasl_generation.c (condensed signature + skeleton)REGU_VARIABLE *pt_to_regu_variable (PARSER_CONTEXT * parser, PT_NODE * node, UNBOX unbox){ REGU_VARIABLE *regu = NULL; DB_VALUE *val = NULL;
if (node == NULL) { /* default: empty varchar constant — used as a placeholder */ regu_alloc (val); db_value_domain_init (val, DB_TYPE_VARCHAR, DB_DEFAULT_PRECISION, DB_DEFAULT_SCALE); regu = pt_make_regu_constant (parser, val, DB_TYPE_VARCHAR, NULL); } else if (PT_IS_POINTER_REF_NODE (node)) { /* TYPE_CONSTANT pointing into another node's etc */ // ... condensed: domain resolution, regu->type = TYPE_CONSTANT ... } else { switch (node->node_type) { case PT_DOT_: /* path expr — resolves to TYPE_ATTR_ID via pt_attribute_to_regu */ case PT_NAME: /* column reference — TYPE_ATTR_ID or TYPE_POSITION */ case PT_VALUE: /* literal — TYPE_CONSTANT with a pre-bound DB_VALUE */ case PT_HOST_VAR: /* parameter — TYPE_CONSTANT with the host_var slot */ case PT_EXPR: /* arith/func — TYPE_INARITH with an ARITH_TYPE child */ case PT_FUNCTION: /* built-in or aggregate — TYPE_FUNC */ case PT_METHOD_CALL: /* method — special-cased */ // ... condensed: each shape allocates a regu and fills the union ... } } return regu;}WHERE col1 IN (1,2) 술어에 대한 분석 자료의 예시는 따라가 볼 만하다.
IN 리스트는 두 개의 REGU_VARIABLE 로 떨어진다. 타입은 TYPE_POS_VALUE
이고 val_pos = 0, val_pos = 1 로 상수 1, 2 를 들고 있는 공유
VAL_DESCR 배열을 가리킨다. IN 자체는 T_PRED 타입의 PRED_EXPR
이 되고, 그 좌우는 T_EVAL_TERM 비교다. 이 레이아웃의 장점은 인덱스
range scan 이 키마다 position 값을 다시 바인딩할 수 있고, 힙 측
잔여 술어가 다시 파싱 없이 같은 PRED_EXPR 을 재사용할 수 있다는
점이다.
pt_to_outlist 는 select list 위를 걸으며 OUTPTR_LIST(개수와
REGU_VARIABLE_LIST 노드 사슬) 를 만든다. pt_to_val_list 는
spec id 에 대한 SYMBOL_INFO 의 table_info 위를 걸으며 VAL_LIST 를
만들고, 그 엔트리는 outptr 의 regu 들과 같은 컬럼을 참조할 때 공유
된다는 점이다. 이 공유가 “한 번 컬럼을 fetch 해서 여러 자리에
projection” 을 런타임에 싸게 만든다.
서브쿼리 배치 — aptr / dptr / scan_ptr 한 그림으로
섹션 제목: “서브쿼리 배치 — aptr / dptr / scan_ptr 한 그림으로”flowchart TB Q["SELECT a.col1<br/>FROM tab a, tab b<br/>WHERE a.col1 = b.col1<br/> AND a.col1 = (SELECT col1 FROM tab d)<br/> /* 비상관 */<br/> AND EXISTS<br/> (SELECT 1 FROM tab c WHERE c.k = a.k)<br/> /* 상관 */"] R["XASL_NODE 루트<br/>type=BUILDLIST_PROC<br/>(driver = tab a)"] AP["XASL_NODE<br/>type=BUILDLIST_PROC<br/>(비상관 서브쿼리: tab d)"] DP["XASL_NODE<br/>type=BUILDLIST_PROC<br/>(상관 서브쿼리: tab c, a.k 참조)"] SP["XASL_NODE<br/>type=SCAN_PROC<br/>(inner: tab b)"] Q --> R R -. aptr_list .-> AP R -. dptr_list .-> DP R -. scan_ptr .-> SP AP -. R 이전 1회 .-> R DP -. outer 행마다 .-> R SP -. outer 행마다 중첩 .-> R
실행기의 루프는 정확히 이 순서로 슬롯을 읽는다.
run aptr_list once (materialises into a list-file, bound by LIST_SPEC_NODE)for each outer tuple of R: run dptr_list (correlated subq evaluated against the new outer) descend scan_ptr chain (joined inner scan) project R's outptr into list-file (BUILDLIST) or single value (BUILDVALUE)aptr_list 는 CTE 가 들어가는 자리이기도 하다(분석 자료가 CTE_PROC
을 언급하고, xasl.h 의 코멘트는 “CTEs are guaranteed always before
the subqueries” 라고 적혀 있다는 점이다). 이것이 CTE 가 실행기의
관점에서 비상관 서브쿼리와 똑같이 읽히는 이유다. 재귀 플래그를
빼면 사실상 같다.
술어 배치 — RANGE / KEY / PRED
섹션 제목: “술어 배치 — RANGE / KEY / PRED”INDEX(col1, col2, col3) 와 query
WHERE col1 = 1 AND col3 = 2 AND col4 = 2 가 있으면, 옵티마이저는
각 conjunct 를 인덱스의 leading-key prefix 와의 위치 관계로 분류한다.
flowchart LR WHERE["WHERE 절"] RANGE["where_range<br/>col1 = 1<br/>(B-tree 수직 하강)"] KEY["where_key<br/>col3 = 2<br/>(B-tree leaf 수평 스캔,<br/> 비-leading 인덱스 컬럼)"] PRED["where_pred<br/>col4 = 2<br/>(힙 페이지 평가,<br/> 비-인덱스 컬럼)"] IDX["INDEX(col1, col2, col3)"] WHERE --> RANGE WHERE --> KEY WHERE --> PRED IDX -.- RANGE IDX -.- KEY
pt_to_index_info 는 where_range 가 소비할 INDX_INFO(range 종류,
btree id, key info 사슬) 를 만든다. 잔여는 pt_to_pred_expr 가 만든
PRED_EXPR 트리 형태로 where_key / where_pred 에 들어간다는
점이다. 분석 자료가 짚듯 PRED_EXPR 은 “PT_NODE 보다 가벼운
struct” 다. 이유는 parse 트리가 소스 위치, 타입 도출 이력, 재작성
상태를 들고 다니지만 실행기에는 이게 필요 없기 때문이다. 술어 IR
은 연산 종류, 좌우 regu 포인터, short-circuit 플래그만 보유한다.
직렬화 — xts_*
섹션 제목: “직렬화 — xts_*”XASL_NODE 트리가 만들어지고 나면, 그것을 서버로 실어 보내야 한다.
송신자가 xts_map_xasl_to_stream 이다.
// xts_map_xasl_to_stream — src/query/xasl_to_stream.c (condensed)intxts_map_xasl_to_stream (const XASL_NODE * xasl_tree, XASL_STREAM * stream){ int offset, header_size, body_size; char *p;
if (!xasl_tree || !stream) return ER_QPROC_INVALID_XASLNODE;
/* Header: dbval_cnt + creator_oid + n_oid_list + class_oid_list + locks + tcard */ header_size = sizeof (int) + sizeof (OID) + sizeof (int) + sizeof (OID) * xasl_tree->n_oid_list + sizeof (int) * xasl_tree->n_oid_list + sizeof (int) * xasl_tree->n_oid_list;
/* layout: [size of header][header data][size of body][body data...] */ offset = sizeof (int) + header_size + sizeof (int); offset = xasl_stream_make_align (offset); xts_reserve_location_in_stream (offset);
xts_id_serial = 0;
/* recursive walk: each xts_save_* checks the visited table first, emits the body once, returns the offset for subsequent references */ if (xts_save_xasl_node (xasl_tree) == ER_FAILED) goto end;
/* now backfill the header */ p = or_pack_int (xts_Stream_buffer, header_size); p = or_pack_int (p, xasl_tree->dbval_cnt); p = or_pack_oid (p, (OID *) (&xasl_tree->creator_oid)); p = or_pack_int (p, xasl_tree->n_oid_list); for (i = 0; i < xasl_tree->n_oid_list; i++) p = or_pack_oid (p, &xasl_tree->class_oid_list[i]); for (i = 0; i < xasl_tree->n_oid_list; i++) p = or_pack_int (p, xasl_tree->class_locks[i]); for (i = 0; i < xasl_tree->n_oid_list; i++) p = or_pack_int (p, xasl_tree->tcard_list[i]);
body_size = xts_Free_offset_in_stream - offset; p = or_pack_int (p, body_size);
stream->buffer = xts_Stream_buffer; stream->buffer_size = xts_Free_offset_in_stream;end: xts_free_visited_ptrs (); return xts_Xasl_errcode;}헤더는 클래스 oid 리스트와 oid 별 lock 모드 리스트를 들고 다닌다.
서버는 본문을 unpack 하기 전에 이것을 읽고 필요한 lock 을 먼저
잡는다는 점이다. lock 획득에 실패하면 서버는 XASL 트리를 한 번도
materialise 하지 않은 채 query 를 거절할 수 있다. tcard_list 는
옵티마이저가 사용한 클래스별 table-card 힌트다. 서버가 plan 의
신선도를 검증할 수 있게 해 준다. 클래스의 카디널리티가 plan 시점
대비 의미 있게 변했으면, 캐시된 XASL 은 무효화된다.
xts_save_xasl_node 는 공유 서브트리 처리의 핫 패스다.
// xts_save_xasl_node — src/query/xasl_to_stream.c (condensed)static intxts_save_xasl_node (const XASL_NODE * xasl){ int offset, size;
if (xasl == NULL) return NO_ERROR;
/* deduplication: if we've packed this node before, return the prior offset */ offset = xts_get_offset_visited_ptr (xasl); if (offset != ER_FAILED) return offset;
size = xts_sizeof_xasl_node (xasl); offset = xts_reserve_location_in_stream (size); xts_mark_ptr_visited (xasl, offset);
/* pack the body into a temp buffer, then memcpy into the stream */ buf = xts_process_xasl_node (buf_p, xasl); // ... condensed: copy into xts_Stream_buffer at offset ... return offset;}방문 포인터 검사가 인코딩을 유일 노드 수에 비례하게 만든다(경로
수가 아니라). 이게 없으면 UNION 의 두 가지에서 참조되는 CTE 가 두
번 packed 된다. xts_process_xasl_node 는 노드 별 필드-by-필드 pack
이다.
// xts_process_xasl_node — src/query/xasl_to_stream.c (condensed)static char *xts_process_xasl_node (char *ptr, const XASL_NODE * xasl){ int offset, cnt; ACCESS_SPEC_TYPE *access_spec;
/* 1) header: assigned a fresh id_serial so the executor can identify nodes */ ((XASL_NODE *) xasl)->header.id = xts_id_serial++; ptr = xts_process_xasl_header (ptr, xasl->header); ptr = or_pack_int (ptr, xasl->type); ptr = or_pack_int (ptr, xasl->flag);
/* 2) every pointer becomes an offset (or 0 for NULL) */ offset = xts_save_list_id (xasl->list_id); ptr = or_pack_int (ptr, offset); offset = xts_save_sort_list (xasl->after_iscan_list); ptr = or_pack_int (ptr, offset); offset = xts_save_sort_list (xasl->orderby_list); ptr = or_pack_int (ptr, offset); offset = xts_save_pred_expr (xasl->ordbynum_pred); ptr = or_pack_int (ptr, offset); offset = xts_save_db_value (xasl->ordbynum_val); ptr = or_pack_int (ptr, offset); offset = xts_save_regu_variable (xasl->orderby_limit); ptr = or_pack_int (ptr, offset); // ... condensed: limit_offset, limit_row_count, single_tuple, outptr_list, // selupd_list, val_list, merge_val_list ...
/* 3) access spec list: pack count then each spec */ for (cnt = 0, access_spec = xasl->spec_list; access_spec; access_spec = access_spec->next, cnt++) ; ptr = or_pack_int (ptr, cnt); for (access_spec = xasl->spec_list; access_spec; access_spec = access_spec->next) ptr = xts_process_access_spec_type (ptr, access_spec);
/* 4) recurse into the four pointer slots */ offset = xts_save_xasl_node (xasl->aptr_list); ptr = or_pack_int (ptr, offset); offset = xts_save_xasl_node (xasl->bptr_list); ptr = or_pack_int (ptr, offset); offset = xts_save_xasl_node (xasl->dptr_list); ptr = or_pack_int (ptr, offset); // ... condensed: fptr_list, scan_ptr, connect_by_ptr, predicates, instnum/orderby_num, // proc-union (per type), creator_oid, class oid list, ... ...
return ptr;}두 가지 인코딩 규칙을 짚어 둔다. (1) Pointers-as-offsets. C 에서
포인터인 모든 필드는 xts_save_* 헬퍼가 반환한 offset 의
or_pack_int 가 된다. offset 0 은 NULL 을 의미한다. stream_to_xasl.c
의 수신자는 각 pack 을 or_unpack_int 와 xts_get_addr_unpack_info
스타일 lookup 으로 짝지어 포인터를 재수화한다. (2) Pack-by-discriminator.
proc-union 은 type 으로 디스패치되어 활성 arm 만 packed 된다는 점이다.
수신자도 동일하게 동작한다. 그래서 BUILDVALUE_PROC 은 가장 큰
변종이 아니라 BUILDVALUE_PROC_NODE 의 바이트만큼만 비용이 든다.
unpack 측은 대칭이다. stx_init_xasl_unpack_info 가 서버 측 arena
를 할당한다 (unpack_info->packed_size * UNPACK_SCALE. 여기서
UNPACK_SCALE = 3 은 align 과 포인터 재수화 때문에 deserialised
트리가 와이어 크기의 약 3배가 되는 것을 감안한 값이다). 그다음
stream_to_xasl 이 재귀적으로 각 offset 을 읽고, 이미 unpack 된 표
를 lookup 해서 캐시된 포인터를 반환하거나 본문을 unpack 해서 삽입
한다.
한데 모아 보기 — 한 SELECT 컴파일의 처음부터 끝까지
섹션 제목: “한데 모아 보기 — 한 SELECT 컴파일의 처음부터 끝까지”sequenceDiagram participant CT as 클라이언트 스레드 (PT walker) participant QO as qo_optimize_query participant PB as pt_to_buildlist_proc participant QX as qo_to_xasl / gen_outer participant XTS as xts_map_xasl_to_stream participant SVR as 서버 CT->>CT: parser_walk_tree pre-order:<br/>query 마다 pt_push_symbol_info CT->>CT: 안쪽 서브쿼리부터 post-order<br/>(parser_generate_xasl_proc) CT->>QO: qo_optimize_query(parser, select_node) QO-->>CT: QO_PLAN 트리 (scan/join/sort/follow) CT->>PB: pt_to_buildlist_proc(parser, select_node, plan) PB->>PB: regu_xasl_node_alloc(BUILDLIST_PROC) PB->>PB: pt_to_outlist → xasl->outptr_list PB->>PB: pt_set_aptr → xasl->aptr_list PB->>QX: pt_gen_optimized_plan QX->>QX: gen_outer 재귀<br/>scan / join / sort / follow QX->>QX: add_access_spec → spec_list, val_list,<br/> where_range / where_key / where_pred QX->>QX: add_scan_proc → scan_ptr 사슬 QX->>QX: add_subqueries → leaf 별 aptr/dptr QX-->>PB: 채워진 xasl PB->>PB: lastxasl(가장 깊은 scan)에 pt_set_dptr PB-->>CT: xasl CT->>CT: parser_generate_xasl 마무리:<br/>class_oid_list, locks, tcard, creator_oid 채움 CT->>XTS: xts_map_xasl_to_stream(xasl, stream) XTS->>XTS: 헤더 pack (dbval_cnt, oids, locks, tcard) XTS->>XTS: xts_save_xasl_node 재귀<br/>방문 표가 공유 서브트리 dedup XTS-->>CT: stream->buffer + buffer_size CT->>SVR: prepare/execute RPC 가 stream 운반 SVR->>SVR: stx_init_xasl_unpack_info (arena = size * UNPACK_SCALE) SVR->>SVR: stream_to_xasl 가 트리를 재수화 SVR->>SVR: qexec_execute_mainblock 가 실행
분석 자료가 짚는 sub-plan 처리 순서
섹션 제목: “분석 자료가 짚는 sub-plan 처리 순서”분석 자료의 XASL generator 진행 절차 슬라이드는 buildlist 케이스 의 명시적 순서를 준다. 이는 소스와 정확히 일치한다.
pt_to_buildlist_proc (PT_SELECT, QO_PLAN) pt_to_outlist (select.list) — outlist (projection) pt_set_aptr (select) — aptr (uncorrelated subqueries) pt_gen_optimized_plan (select, plan, xasl) qo_to_xasl (plan, xasl) gen_outer (env, plan, ...) case QO_PLANTYPE_SCAN: add_access_spec — spec_list (range/key/pred, indexptr) add_scan_proc — scan_ptr add_subqueries — aptr + dptr per leaf case QO_PLANTYPE_JOIN: inner_scan = gen_inner (inner) gen_outer (outer, ..., inner_scan) pt_set_dptr (select.list, lastxasl) — dptr on deepest leaf이 호출들 가운데 둘이 미묘하다. pt_set_aptr 는 pt_gen_optimized_plan
전에 돌아야 한다. 왜냐하면 add_subqueries 가 각 서브쿼리의
PT_NODE 위에 이미 붙은 XASL 을 lookup 하기 때문이다. 사전 부착이
바로 pt_set_aptr 의 비상관 PT_SELECT 워크에서 일어난다. pt_set_dptr
는 재귀 워크 후에 돌아야 한다. 왜냐하면 상관 서브쿼리는 가장
깊은 scan 의 val_list 슬롯으로만 존재하는 outer 컬럼을 참조하기
때문이다. 더 일찍 바인딩하면 잘못된 tuple 버퍼를 가리키게 된다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”라인 번호가 아니라 심볼 이름을 닻으로 삼는다. CUBRID 소스는 움직인다. 문서 끝의 위치 표는 본 문서의
updated:날짜에만 유효한 힌트다.
최상위 진입과 query 별 디스패치
섹션 제목: “최상위 진입과 query 별 디스패치”parser_generate_xasl(xasl_generation.c) — prepare/execute 경로 에서 호출되는 가장 바깥 진입점. 두-패스 워크를 돌리고 캐시 메타데이터를 채운다.parser_generate_xasl_pre/parser_generate_xasl_post(xasl_generation.c) — pre-order 가 symbol info 를 푸시, post-order 가 XASL 을 bottom-up 으로 구성.parser_generate_xasl_proc(xasl_generation.c) — query 별 디스패처. SELECT →pt_plan_query, set op →pt_to_union_proc가족.pt_plan_query(xasl_generation.c) —qo_optimize_query호출 후pt_to_buildlist_proc/pt_to_buildvalue_proc호출.
Buildlist / buildvalue / set-op 컴파일러
섹션 제목: “Buildlist / buildvalue / set-op 컴파일러”pt_to_buildlist_proc(xasl_generation.c) — 다행 메인 컴파일러. aggregate, group-by, having, order-by, limit, window, connect-by 모두.pt_to_buildvalue_proc(xasl_generation.c) — 단행 aggregate 컴파일 러 (GROUP BY 없음).pt_to_union_proc(xasl_generation.c) —UNION_PROC,DIFFERENCE_PROC,INTERSECTION_PROC빌더.pt_to_cte_proc(xasl_generation.c) —CTE_PROC빌더.pt_make_aptr_parent_node(xasl_generation.c) — aptr 가 자식 query 를 들고 있는 부모 XASL 을 할당하는 공통 헬퍼 (INSERT/UPDATE/DELETE/MERGE 가 사용).pt_to_xasl_for_dblink(xasl_generation.c) — dblink 형태 XASL.
Plan 주도 워크
섹션 제목: “Plan 주도 워크”pt_gen_optimized_plan(xasl_generation.c) —qo_to_xasl을 호출 하고 인덱스 힌트를 패치하는 래퍼.pt_gen_simple_plan(xasl_generation.c) — QO_PLAN 이 없을 때의 폴백 (예: 최적화 없이 시스템이 생성한 stmt).qo_to_xasl(optimizer/plan_generation.c) — 재귀 워크의 진입점.gen_outer(optimizer/plan_generation.c) — SCAN / JOIN / SORT / FOLLOW 의 outer-side 재귀.gen_inner(optimizer/plan_generation.c) — inner-side 빌더. outer 의 scan_ptr 에 사슬로 묶이는 SCAN_PROC 을 만든다.make_buildlist_proc(optimizer/plan_generation.c) — sub-plan 의 list-file materialisation.make_sort_limit_proc(optimizer/plan_generation.c) — Top-N sort 변종.add_access_spec(optimizer/plan_generation.c) — leaf scan 컴파일러.add_scan_proc/add_fetch_proc/add_uncorrelated/add_subqueries(optimizer/plan_generation.c) — XASL_NODE 의 네 포인터 슬롯에 사슬 삽입.add_sort_spec(optimizer/plan_generation.c) — orderby / groupby sort spec 구성.make_pred_from_plan(optimizer/plan_generation.c) — 잔여 술어를 key / access 종류로 분할.qo_get_xasl_index_info(optimizer/plan_generation.c) — 선택된 인덱스에 대한 QO_XASL_INDEX_INFO 추출.
하위 IR 빌더 (parse 트리 → XASL 조각)
섹션 제목: “하위 IR 빌더 (parse 트리 → XASL 조각)”pt_to_outlist(xasl_generation.c) — select list 를OUTPTR_LIST로.pt_to_val_list(xasl_generation.c) — table_info 를VAL_LIST로.pt_to_regu_variable(xasl_generation.c) — 일반 표현식을REGU_VARIABLE로 (큰 switch).pt_to_pred_expr(xasl_generation.c) — 술어 트리를PRED_EXPR로.pt_to_index_info(xasl_generation.c) — 인덱스 access 를INDX_INFO로 (range 종류, key info, key1/key2 regu).pt_to_class_spec_list/pt_to_spec_list(xasl_generation.c) class spec 을ACCESS_SPEC_TYPE으로. key/pred/range regu 리스트 포함.pt_set_aptr/pt_set_dptr(xasl_generation.c) — 비상관 / 상관 서브쿼리 부착.pt_set_numbering_node_etc(xasl_generation.c) — INST_NUM / ORDERBY_NUM DB_VALUE 를 parse-tree 참조에 결선.pt_to_pos_descr(xasl_generation.c) — 이름을 outptr 안의 position 디스크립터로.pt_attribute_to_regu(xasl_generation.c) — 컬럼 참조를 TYPE_ATTR_ID regu 로.pt_make_regu_constant(xasl_generation.c) — 리터럴 / host_var 를 TYPE_CONSTANT regu 로.
regu_xasl_node_alloc(xasl_regu_alloc.cpp) — XASL_NODE 할당기 (per-query parser arena,regu_alloc가족).regu_alloc(xasl_regu_alloc.hpp) — 일반 typed 할당기.pt_init_xasl_supp_info(xasl_generation.c) — query 사이에 부가 정보 누적기를 비운다.
직렬화 (xts_*)
섹션 제목: “직렬화 (xts_*)”xts_map_xasl_to_stream(query/xasl_to_stream.c) — 최상위 pack 진입점.XASL_STREAM { buffer, buffer_size }출력.xts_save_xasl_node(query/xasl_to_stream.c) — 방문 검사 + 본문 pack + 스트림으로 memcpy.xts_process_xasl_node(query/xasl_to_stream.c) — 노드 별 필드 pack.xts_sizeof_xasl_node(query/xasl_to_stream.c) — 사전 사이즈 추정 (버퍼 예약용).xts_save_outptr_list/xts_save_regu_variable/xts_save_pred_expr/xts_save_arith_type/xts_save_indx_info/xts_save_db_value/xts_save_val_list/xts_save_sort_list/xts_save_aggregate_type/xts_save_function_type/xts_save_analytic_type(query/xasl_to_stream.c) — 하위 IR 별 pack 함수. 모두 방문 표를 사용.xts_get_offset_visited_ptr/xts_mark_ptr_visited/xts_free_visited_ptrs(query/xasl_to_stream.c) — 방문 포인터 표.xts_reserve_location_in_stream(query/xasl_to_stream.c) — 버퍼 성장.xts_map_filter_pred_to_stream/xts_map_func_pred_to_stream(query/xasl_to_stream.c) — 필터/함수 술어용 보조 진입점 (트리거와 함수 인덱스가 사용).
역직렬화 (stx_*)
섹션 제목: “역직렬화 (stx_*)”stx_init_xasl_unpack_info(xasl/xasl_stream.cpp) — 서버 arena 할당기 (packed_size * UNPACK_SCALE).stream_to_xasl(query/stream_to_xasl.c) — 최상위 unpack 진입점.stx_get_xasl_node/stx_get_regu_variable/ 등 (query/stream_to_xasl.c) —xts_save_*가족과 대칭.xasl_stream_get_ptr_block/xasl_stream_make_align(xasl/xasl_stream.cpp) — 송신과 수신이 공유하는 align / block 관리.xasl_stream_compare(xasl/xasl_stream.cpp) — packed XASL 조각의 동등성 (XASL 캐시 lookup 이 사용).
소스 검증 (2026-04-30 기준)
섹션 제목: “소스 검증 (2026-04-30 기준)”| 심볼 | 파일 | Line |
|---|---|---|
xasl_node struct | src/query/xasl.h | 1075 |
PROC_TYPE enum | src/query/xasl.h | 188 |
XASL_NODE_HEADER struct | src/query/xasl.h | 73 |
parser_generate_xasl | src/parser/xasl_generation.c | 22460 |
parser_generate_xasl_pre | src/parser/xasl_generation.c | 22322 |
parser_generate_xasl_post | src/parser/xasl_generation.c | 22373 |
pt_plan_query | src/parser/xasl_generation.c | 17680 |
pt_to_buildlist_proc | src/parser/xasl_generation.c | 16148 |
pt_to_buildvalue_proc | src/parser/xasl_generation.c | 17204 |
pt_gen_optimized_plan | src/parser/xasl_generation.c | 14714 |
pt_gen_simple_plan | src/parser/xasl_generation.c | 14812 |
pt_make_aptr_parent_node | src/parser/xasl_generation.c | 18519 |
pt_to_xasl_for_dblink | src/parser/xasl_generation.c | 18807 |
pt_to_outlist | src/parser/xasl_generation.c | 13830 |
pt_to_val_list | src/parser/xasl_generation.c | 13237 |
pt_set_aptr | src/parser/xasl_generation.c | 13388 |
pt_set_dptr | src/parser/xasl_generation.c | 13370 |
pt_to_regu_variable | src/parser/xasl_generation.c | 7577 |
pt_to_pred_expr | src/parser/xasl_generation.c | 2194 |
pt_to_index_info | src/parser/xasl_generation.c | 11852 |
pt_to_class_spec_list | src/parser/xasl_generation.c | 12290 |
pt_to_spec_list | src/parser/xasl_generation.c | 13167 |
pt_to_pos_descr | src/parser/xasl_generation.c | 5377 |
pt_to_pos_descr_groupby | src/parser/xasl_generation.c | 24103 |
qo_to_xasl | src/optimizer/plan_generation.c | 2915 |
gen_outer | src/optimizer/plan_generation.c | 1955 |
gen_inner | src/optimizer/plan_generation.c | 2759 |
add_access_spec | src/optimizer/plan_generation.c | 895 |
add_scan_proc | src/optimizer/plan_generation.c | 978 |
add_subqueries | src/optimizer/plan_generation.c | 1066 |
make_buildlist_proc | src/optimizer/plan_generation.c | 728 |
regu_xasl_node_alloc | src/parser/xasl_regu_alloc.cpp | 40 |
xts_map_xasl_to_stream | src/query/xasl_to_stream.c | 276 |
xts_save_xasl_node | src/query/xasl_to_stream.c | 1682 |
xts_process_xasl_node | src/query/xasl_to_stream.c | 2810 |
xts_sizeof_xasl_node | src/query/xasl_to_stream.c | 5941 |
xts_save_aggregate_type | src/query/xasl_to_stream.c | 498 |
xts_save_function_type | src/query/xasl_to_stream.c | 634 |
xts_save_analytic_type | src/query/xasl_to_stream.c | 702 |
xts_save_arith_type | src/query/xasl_to_stream.c | 970 |
xts_save_indx_info | src/query/xasl_to_stream.c | 1038 |
xts_save_outptr_list | src/query/xasl_to_stream.c | 1106 |
xts_save_pred_expr | src/query/xasl_to_stream.c | 1242 |
xts_save_regu_variable | src/query/xasl_to_stream.c | 1310 |
xts_save_sort_list | src/query/xasl_to_stream.c | 1394 |
xts_save_val_list | src/query/xasl_to_stream.c | 1462 |
xts_save_db_value | src/query/xasl_to_stream.c | 1614 |
xts_save_filter_pred_node | src/query/xasl_to_stream.c | 1767 |
xts_save_func_pred | src/query/xasl_to_stream.c | 1835 |
xts_save_update_info | src/query/xasl_to_stream.c | 2109 |
stx_init_xasl_unpack_info | src/xasl/xasl_stream.cpp | 74 |
xasl_stream_make_align | src/xasl/xasl_stream.cpp | 231 |
분석 자료와 소스 사이의 어긋남
섹션 제목: “분석 자료와 소스 사이의 어긋남”-
분석 자료의
BUILDLIST_PROC정의는 맞지만 불완전하다. 자료 는 BUILDLIST 를 ROW 가 여러 건이 될 수 있는 SQL 이라 한다. 소스 도 동의한다.pt_is_single_tuple(BUILDVALUE 를 고르는 함수) 은 GROUP BY 없는 단일 aggregate 를 요구한다. 다만 자료가 짚지 않는 케이스가 하나 더 있다.pt_make_aptr_parent_node로 UPDATE/ DELETE/INSERT 캐리어 안으로 끌어 올려진SELECT는 카디널리티와 무관하게 항상 BUILDLIST 다. 소비자는 사용자가 아니라 outer modify proc 이기 때문이다. -
SCAN_PROC에 임시 파일이 없다. 확인됨. 자료의 약식 표현 SCAN_PROC: TEMP 영역을 사용하지 않음 은 의미와 부합한다.scan_ptr에 매달리는 SCAN_PROC 노드는 부모 루프에 파이프라인되며 디스크에 아무것도 쓰지 않는다.BUILDLIST_PROC노드는QFILE_LIST_ID를 할당하고 materialise 한다. 만약 그것이scan_ptr아래에 나타나면 (드물고, join-leg materialisation 을 위해make_buildlist_proc으로 만들어진 경우에 한정) materialisation 은 위치와 무관하게 일어난다. -
aptr_list는 자료에서 잘못 명명되어 있다.xasl->aptr_list의 소스 코멘트는 “CTEs and uncorrelated subquery. CTEs are guaranteed always before the subqueries 라고 적혀 있다. 자료는 aptr 를 비상관 서브쿼리” 만으로 다룬다. CTE 도 같은 슬롯을 공유하며, 실행기는 순서대로 평가한다. CTE 가 먼저다. -
자료의 PRED_EXPR 는 PT_NODE 보다 가볍다 는 구조적인 이유로 참이다. PT_NODE 는 소스 추적, 재작성 이력, 타입 도출 상태, 그리고 70여 arm 의
infounion 을 들고 다닌다. PRED_EXPR 의 union 은 세 arm 이다 (pred,eval_term,not_term). 컴파일 타임에PT_NODE는 수백 바이트,PRED_EXPR은 ~32 바이트다. 5×–10× 의 축소가 행 단위 술어 평가를 싸게 만든다. -
다중 테이블 예시에서 자료의 SCAN_PROC 셀 모양은 단순화다. 자료는 조인의 inner 쪽을
SCAN_PROC (tab b)라고만 그린다. 소스는 실제로는 outer 의scan_ptr에 사슬로 묶이는 SCAN_PROC 을 만든다. 거기에 자기spec_list(tab b 의 access spec 포함) 와 자기val_list를 들고 있다. SCAN_PROC 은 부모의 outptr 를 projection forwarding 으로 상속한다. inner 를 완전한 XASL_NODE 로 시각화하면 재귀 구조가 명시적으로 보인다. -
pt_set_dptr는 buildlist 루트가 아니라 가장 깊은 scan 에서 돈다. 자료의 의사코드는qo_to_xasl반환 후pt_set_dptr (select.list)를 보여 준다. 소스는 그 호출을qo_to_xasl자체 안에 두고 leaf 에 닿을 때까지scan_ptr와fptr_list를 내려간다. 이 배치가 옳다.outer.col을 참조하는 상관 서브쿼리는outer.col이 val_list 에 들어간 후에 실행되어야 하고, 그것은 leaf scan 에 서만 일어난다는 점이다. -
방문 포인터 표는 pack 단위지 글로벌이 아니다. 자료는 공유 서브트리를 다루지 않지만, 소스의
xts_map_xasl_to_stream끝에 있는xts_free_visited_ptrs가 query 마다 표를 리셋한다는 점을 보여 준다. query 간 공유는 없다. 그러나 단일 query 안에서 outptr 와 where_pred 가 공유하는REGU_VARIABLE은 정확히 한 번만 발행 된다.
열린 질문
섹션 제목: “열린 질문”-
UNPACK_SCALE = 3의 근거. 서버 측 unpack arena 는 `packed_size- 3
으로 사이즈가 정해진다. 이 3 은 어디서 왔는가? 보통의 XASL 를 packed-vs-rehydrated 비를 측정해 보면 3 이 보수적인 값인지, 경험적으로 도출된 값인지, 임의의 값인지 알 수 있을 것이다. 조사 경로:stx_init_xasl_unpack_info와db_private_alloc` 을 계측해서 샘플 워크로드의 실제 사용량을 찍어 본다.
- 3
-
해시 GROUP BY 아래의 aggregate 배치. 자료는 “BUILDLIST 가 임시 파일을 만든다” 에서 멈춘다. 소스의
pt_to_buildlist_proc에는g_hash_eligible분기가 있다 (PT_HINT_NO_HASH_AGGREGATE와pt_is_hash_agg_eligible워크). 옵티마이저는 언제 해시 aggregation 을 결정하며, 해시 테이블은 XASL_NODE proc-union 안에서 어떻게 자리하는가? 조사 경로: 실행기에서의BUILDLIST_PROC_NODE::g_hash_eligible과BUILDLIST_PROC_NODE::g_output_first_tuple의미. -
Connect-by 의
connect_by_ptr와 proc-unionconnect_by.xasl_node는connect_by_ptr(XASL_NODE) 와proc.connect_by(CONNECTBY_PROC_NODE) 를 모두 들고 다닌다. 왜 둘인가? 아마도 connect-by sub-plan 자체는 XASL_NODE (재귀 scan) 이고, proc-union 은 prior/level 관리 정보를 담기 때문일 것이다. 조사 경로:pt_to_connect_by_proc(그 이름으로 존재한다면) 과qexec_execute_connect_by를 따라 둘이 어떻게 상호작용하는지 본다. -
xasl_generation 으로부터의
HASHJOIN_PROC도달 가능성.PROC_TYPEenum 은 HASHJOIN_PROC 을 나열하지만, 자료는 언급하지 않는다. 어디서 인스턴스화되는가?gen_outer의QO_JOINMETHOD_HASH_JOINarm 이 가장 그럴듯한 후보다. 다만 현재 소스에서 그 분기 본문이 다른 proc 타입이나add_hash_pred확장으로 옮겨졌을 가능성도 있다. 조사 경로:git grep으로regu_xasl_node_alloc (HASHJOIN_PROC)과make_hashjoin_proc을 찾는다. -
tcard_list를 통한 plan 무효화. 헤더가 클래스별 table-card 힌트를 운반한다. 서버 측에서 그것을 현재 카디널리티와 비교해 캐시된 XASL 의 무효화를 결정하는 경로는 자료에서 다루지 않는다. 조사 경로:xasl_cache.c와xcache_find_xasl_id안에서tcard_list를 소비하는 lookup. -
vfetch_to를 통한 REGU_VARIABLE 공유. 자료는REGU_VARIABLE이 DB_VALUE_LIST 엔트리를 가리키는vfetch_to포인터를 들고 있음을 보여 주고, “동일 컬럼의 regu_var일 경우 db_value의 값을 공유함 이라고 적는다. 컴파일러가 같은 컬럼” 을 감지할 때 쓰는 정확한 동등 술어가 무엇인지 —attr_descr.id에 클래스 oid 를 더한 것인가, 아니면 PT_NODE 포인터 동일성인가? 조사 경로:PT_NAME에 대한pt_to_regu_variable와 (있다면)mq_regu_var_lookup캐시. -
메소드 호출 regu 의 병렬성과의 상호작용.
xasl_node::px_executor와executed_parallelism필드가 XASL 을 병렬 인지하게 만든다. 메소드 호출 (JSP/SP) 은meth_translate에서 병렬-안전하지 않다고 표시된다. generator 는 query 가 병렬 적격인지 아닌지를 어떻게 결정하며, 그 게이트는 어디인가? 조사 경로:parser_generate_xasl끝의scan_check_parallel_heap_scan_possible와check_parallel_subquery_possible호출.
Raw 분석 (raw/code-analysis/cubrid/query-processing/)
섹션 제목: “Raw 분석 (raw/code-analysis/cubrid/query-processing/)”analysis_QP_XASL_generator_1.0.pptx— 본 문서가 닻으로 삼은 분석 자료._converted/analysisqpxaslgenerator1.0.pptx.md— markitdown 추출본.
형제 문서
섹션 제목: “형제 문서”- (계획 중)
cubrid-query-optimizer.md— 여기서 소비되는QO_PLAN을 만든다. - (계획 중)
cubrid-query-executor.md— 본 generator 가 만드는 XASL 트리를 소비한다. - (계획 중)
cubrid-xasl-cache.md— SQL 과 클래스 oid 집합을 키로 packed XASL 스트림을 캐시한다.xts_map_xasl_to_stream의 헤더가 캐시 키 페이로드다.
교과서 챕터 / 논문
섹션 제목: “교과서 챕터 / 논문”- Database Internals (Petrov), 12장 Query Processing — plan vs executable form 의 구분, iterator(Volcano) 모델.
- Designing Data-Intensive Applications (Kleppmann), 4장 “Encoding and Evolution” — back-reference 를 가진 positional encoding, 직렬화의 공유 서브트리 문제.
- Selinger et al., Access Path Selection in a Relational Database Management System, SIGMOD 1979 — plan 선택과 plan 실행의 경계를 그은 System R 논문.
- Graefe, Volcano—An Extensible and Parallel Query Evaluation System, IEEE TKDE 1994 — XASL 의 open/next/close 후예가 구현하는 iterator 모델.
- Graefe, Query Evaluation Techniques for Large Databases, ACM Computing Surveys 1993 — 물리 operator 와 plan IR 의 부호화에 대한 정본 격 서베이.
CUBRID 소스 (/data/hgryoo/references/cubrid/)
섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/)”src/parser/xasl_generation.{c,h}— parser 측 컴파일러의 본체. C 770 KB 가량.src/parser/xasl_regu_alloc.{cpp,hpp}— parser arena 에 묶인 XASL_NODE / regu / 술어 할당기.src/optimizer/plan_generation.c— plan 주도 워크 (gen_outer,gen_inner,add_access_spec, 헬퍼들).src/optimizer/query_planner.h— 여기서 소비되는QO_PLAN모양.src/query/xasl.h—xasl_node,PROC_TYPE, 모든 proc-union 노드 정의, ACCESS_SPEC / VAL_LIST / OUTPTR_LIST 헤더.src/query/xasl_to_stream.{c,h}— 송신자. 방문 포인터 직렬화기.src/query/stream_to_xasl.{c,h}— 수신자.src/xasl/xasl_stream.{cpp,hpp}— 송신과 수신이 공유하는 align / block 헬퍼와 unpack-info arena.src/xasl/xasl_predicate.hpp—PRED_EXPR정의.src/xasl/access_spec.hpp—ACCESS_SPEC_TYPE/ hybrid 노드.src/query/regu_var.hpp—REGU_VARIABLE정의.src/query/query_executor.c— 소비자. 진입점qexec_execute_mainblock.