[CBRD-26877] DEFAULT EXPR (4/6) - VOLATILE evaluation - #7896
Open
childyouth wants to merge 15 commits into
Open
Conversation
…d REGU form pt_to_default_expr_stream records the STABLE/VOLATILE classification in two spare regu flag bits so the server can pick the evaluation cadence without a parser. The PT_VOLATILITY values become explicit now that they are persisted.
The stored REGU form is deserialized once per INSERT statement and the cached func_pred is re-fetched per row for a VOLATILE residual, while a STABLE one keeps its single per-statement value. The UUIDv7 sequence state is shared with fetch.c so a residual embedding UUID(7) stays monotonic across rows.
do_evaluate_default_expr_by_smclass picks the evaluation cadence from the rehydrated tree's volatility and reuses a per-statement cache of rehydrated trees instead of rehydrating per row. UUID(7) requests the statement clock sync it needs on the local path.
… ADD UUID() and SYS_GUID() are classified VOLATILE (UUID_FORMAT IMMUTABLE), pt_check_data_default admits a VOLATILE residual alongside STABLE ones, and DDL compiles its stored forms. Adding a VOLATILE DEFAULT column to a populated table rewrites the rows through SET col = DEFAULT so each row gets a distinct value.
A rehydrated CONCAT/CASE-family residual lost its unparse hint and printed in a different form than the original expression; the stream now carries continued_case.
…TABLE RANDOM and DRANDOM draw a new value on every evaluation, so a DEFAULT embedding them is evaluated once per row. NULLIF, LEAST and GREATEST only select among their operands, so a constant DEFAULT built from them folds to a literal. RAND and DRAND stay unclassified until their no-argument and seeded overloads can be classified apart.
The server's NOT NULL predicate is built from the explicitly listed columns only, so a residual DEFAULT yielding NULL for an omitted column was stored as NULL. The client path reported such a row as a missing value, which hides that the column does have a DEFAULT.
The per-statement pass no longer builds a throwaway cache of its own: the row-loop owner's cache evaluates STABLE residuals once when it is built and VOLATILE ones per row, walking the cache itself instead of the attribute list. The attribute walk now serves only the legacy pseudo-column enum path.
🧪 TC Test Environment ReadyCircleCI Testing:
TC Repositories & Branches:
Next Steps:
|
Contributor
Author
|
/run all |
childyouth
marked this pull request as ready for review
September 9, 2026 04:06
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
|
이전 리뷰 이후 수정 사항에서 새로 발생한 merge 차단 결함은 확인되지 않아 현재 상태는 병합해도 안전해 보입니다. Reviews (3) · Last reviewed commit: "Keep a parser-owned copy of the CDT stre..." |
beyondykk9
reviewed
Sep 10, 2026
beyondykk9
reviewed
Sep 10, 2026
beyondykk9
approved these changes
Sep 10, 2026
… Tree RESIDUAL_DEFAULT_CACHE was neither a cache in the eviction sense nor a list of every residual: it is the set of Compact DEFAULT Trees one INSERT evaluates on the Local Evaluation path, with their volatility. A DEFAULT has a CDT exactly when it is residual (an Expression-Derived Literal is frozen to a value, a legacy pseudo-column DEFAULT has no stream), so CDT_EVAL_SET names both what the set holds and why literals and legacy defaults are absent. No behavior change.
The Local Evaluation row loop evaluated every VOLATILE residual of the class, including columns the INSERT assigns explicitly, whose DEFAULT populate_defaults never reads. A runtime error in such an unused DEFAULT failed the whole INSERT on the trigger path while the server path, which evaluates only the omitted columns, succeeded. The set is now built from the object template: a column that already holds a real assignment is skipped, so neither its STABLE nor its VOLATILE tree is rehydrated or evaluated. One statement assigns the same columns on every row, so the first row's template decides for the statement; a virtual-class template indexes assignments by the base class and keeps the old behavior.
Three client-side readers decoded the same stream on their own: every Default Reference (a 200-row VALUES with one DEFAULT keyword per row decoded it 200 times), the statement-clock probe of an INSERT, and the Local Evaluation set. A parser-wide CDT registry now decodes each attribute's stream once, keyed on (class, attribute id) and validated against the stream bytes so a class re-fetched after an ALTER gets a fresh tree. Entries and trees are parser memory and go away with the parser. Default References copy the shared tree, the probe only walks it, and the eval set evaluates it in place.
Contributor
Author
|
@greptileai 리뷰해줘 |
The Compact DEFAULT Tree reader doubles as a probe and never sets an error, so its callers decided what to report: the Local Evaluation set said "unknown error" and a Default Reference raised an internal error. Both now report ER_SM_INVALID_DEFAULT_EXPR_STREAM naming the attribute whose stored expression this build cannot restore (a version mismatch or a corrupted stream).
A registry entry validated its tree against the attribute's own stream buffer, which belongs to the workspace: once the class is released and re-fetched while the parser lives on (a prepared statement), the pointer dangles and the compare reads freed memory, or a reused address passes for the old content. The entry now keeps a parser_alloc'd copy of the bytes and compares only against that; the copy goes away with the parser like the tree.
Contributor
Author
|
@greptileai 코드 리뷰해줘 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
http://jira.cubrid.org/browse/CBRD-26877
Purpose
effective volatility가 VOLATILE인 잔여식 DEFAULT(예:
DEFAULT (CONCAT(SYS_GUID(), '')),UUID(7)을 품은 식)를 서버 경로와 클라이언트(LOCAL) 경로 양쪽에서 행당 1회 평가로 동작시킨다.STABLE(문장당 1회) vsVOLATILE(행당 1회) 분기는 레거시 enum이 아니라 volatility 분류를 기준으로 한다.NULL로 평가될 수 있으므로, NOT NULL 컬럼에 대한 런타임 가드를 함께 넣는다.용어
CDT : Compact DEFAULT Tree — 잔여식 DEFAULT의 저장형(EDL은 값으로 동결, 레거시 enum은 스트림 없음). 코드에서 CDT는 그 재수화 트리도 가리킨다.
Default Reference : 값 자리의
DEFAULT키워드 /DEFAULT(col)(PT_DEFAULTF)동작 변화
CREATE TABLE t (c VARCHAR(64) DEFAULT (CONCAT(SYS_GUID(), '')))허용 (이전:STABLE이하만)ALTER ADD(기존 행 있음)VOLATILE이면 eager 재기록(UPDATE ALL [t] SET [c] = DEFAULT)으로 행마다 distinct.STABLE이하는 기존대로 instant(original_value단일 스냅샷)NULL로 평가된 행NULL저장, 클라이언트는 "값 누락"으로 오인 보고VALUES (DEFAULT),SELECT DEFAULT(col),INSERT ... SELECT DEFAULT(col), prepared 반복 실행 모두 VOLATILE 행별·STABLE 문장당. 재수화만 참조 수 → 속성 수로 감소RANDOM/DRANDOMVOLATILE분류 → DEFAULT 허용, 행당 평가UUID_FORMAT/NULLIF/LEAST/GREATESTIMMUTABLE분류 → 상수 DEFAULT는 리터럴로 폴딩Implementation
1. Volatility 분류 추가 (
pt_get_expression_definition)VOLATILEUUID(전 overload),SYS_GUID,RANDOM,DRANDOMIMMUTABLEUUID_FORMAT,NULLIF,LEAST,GREATESTUNSET유지RAND,DRANDcaselabel을 공유하던 연산자는 독립 블록으로 분리했다.pt_get_op_volatility가overloads[0]만 읽는 현 구조에서 whole-op로 정확한 것만 분류한다.pt_volatility_max)로 판정하며,pt_check_data_default가IMMUTABLE/잔여식(STABLE|VOLATILE)을 허용한다.2. Volatility 전달 — 분류 테이블이 단일 진실원, 소비는 경로별로
pt_get_expr_tree_volatility)regu->flags에 얹으므로 스트림 포맷 변경이 없다. 이 2비트는 카탈로그에 영속되므로 런타임 플래그로 재사용하면 안 된다(주석으로 명시).pt_to_default_expr_stream(xasl_generation.c).pt_check_data_default가 검증한 트리이므로UNSET은 assert로 고정한다.3. SERVER 평가 — fill 루프 + per-row 패스 (
query_executor.c)qexec_prepare_default_expr_streamFUNC_PRED와 stamped volatility를 얻음qexec_eval_default_expr_func_predfunc_pred를 평가(fetch_copy_dbval+tp_value_cast), NOT NULL 가드qexec_free_default_expr_cachesqexec_clear_func_pred로func_regu의 결과·피연산자DB_VALUE를 클리어 (없으면 CS 경로resource_tracker누수로 서버 abort)STABLE잔여식만 평가하고VOLATILE은 volatility만 캡처한다. per-row 패스(qexec_evaluate_row_default_exprs)가 유일한 평가자다.func_pred가 행마다 다시 계산되는 근거는VOLATILEleaf의NOT_CONST전파이며 디버그 빌드가 "클라 VOLATILE ⟺ 서버 NOT_CONST" 를 assert한다.thread_p->uuidv7_last_ms/uuidv7_seq를 직접 가리킨다. 레거시 row-determined 경로와fetch.c경로가 하나의 단조 증가 소스를 공유해야 한다.4. LOCAL 평가 — CDT registry(파서 범위) + CDT_EVAL_SET(문장 범위) (
parser_support.c,name_resolution.c,execute_statement.c)클라이언트에서 CDT 스트림을 읽는 곳은 셋이다: Default Reference 해석, INSERT의 SI(문장 시계) 판정 워커, LOCAL 행 루프.
실행, prepared면 preparedeallocate)att->default_value.value갱신,populate_defaults가 소비parser_copy_tree;pt_fold_const_expr의 DEFAULTF 치환과 노드 해제가 소유권을 가정), CDT_EVAL_SET은 포인터 참조(같은 트리 재평가는pt_evaluate_tree에 노드 메모이제이션이 없어 안전).is_template_assigned_attr): 오브젝트 템플릿이 이미 실제 값을 할당한 컬럼(컬럼 목록 명시, SELECT 공급, Default Reference)은populate_defaults가 DEFAULT를 읽지 않으므로 eval set에서 뺀다. 한 구문은 모든 행에 같은 컬럼 집합을 할당하므로 첫 행 템플릿의 필터가 구문 전체에 유효하다. vclass 템플릿은 assignments가 base 클래스 순서로 색인되어 판정을 생략한다.do_evaluate_default_expr_by_smclass의 평가 모드 분기(DEFAULT_EXPR_EVAL_BY_STATEMENT_ONLYvsBY_ROW_ONLY)는 유지. 속성 목록 순회는 레거시 pseudo-column enum 경로만 담당한다.UUID(7)은 시간 정렬이라 LOCAL 평가가 동기화된 문장 시계를 읽어야 한다. SI 워커(pt_residual_needs_si_datetime_walk)에PT_UUID케이스를 추가했다(버전 인자 7 또는 미확정 →si_datetime).5. ALTER ADD — VOLATILE만 eager 재기록 (
execute_schema.c)PT_IS_VOLATILE_RESIDUAL_DEFAULT→UPDATE ALL [t] SET [c] = DEFAULT,STABLE이하 → 기존대로 instant. slice 3의 Default Reference +do_not_fold경로를 타므로 각 행이 서로 다른 값을 갖는다.get_att_default_from_def가 잔여식도 DDL 시점 1회 평가한 뒤 컬럼 타입으로 coerce한다. 비호환 결과 타입은 DDL에서 거부된다.PT_IS_STABLE_RESIDUAL_DEFAULT→PT_IS_RESIDUAL_DEFAULT(STABLE|VOLATILE) +PT_IS_VOLATILE_RESIDUAL_DEFAULT.6. NOT NULL 런타임 가드
qexec_eval_default_expr_func_pred— 도메인 캐스트 직후attr->is_notnull && DB_IS_NULLER_NULL_CONSTRAINT_VIOLATIONpopulate_defaults(object_template.c) — 소비 지점ER_OBJ_ATTRIBUTE_CANT_BE_NULLcons_pred(명시 컬럼 한정)가 커버하지 못하므로 평가 지점에서 강제한다. fill 루프와 per-row 패스가 같은 헬퍼를 지나 가드 1곳으로 양쪽을 덮는다.obt_check_missing_assignments는 "DEFAULT가 있는가"만 보므로 실제로 값이 쓰이는populate_defaults에서 강제한다.7. 부수 수정
continued_case직렬화:CONCAT/CONCAT_WS/FIELD및CASE/DECODE/COALESCE/LEAST/GREATEST체인의 unparse 힌트가 CDT에 실리지 않아 재수화된 잔여식이 다른 형태로 출력되던 버그. OP/FUNC 노드 헤더에continued_case를 추가했다.Remarks
RAND/DRAND는 overload별 volatility가 달라 whole-op로 분류할 수 없다. matched-overload stamping이 들어가는 CBRD-26879에서 분류하며, 그때까지 DEFAULT에서 거부된다.default_expr_si_requirement로 대체·삭제된다. 그때까지 워커가 명시 컬럼의 CDT도 registry에 올릴 수 있다. volatility도 같은 방식으로 영속화되면 registry의pt_get_expr_tree_volatility순회가 사라진다.INSERT INTO <view> SELECT ...는pt_resolve_vclass_args의 서브쿼리 분기가pt_sm_attribute_default_value_to_node(저장값)를 써 생략 컬럼의 잔여식 DEFAULT가 DDL 시점 값으로 동결된다. CBRD-26878의 CREATE VIEW 동결 회귀와 함께 처리한다.