diff --git a/bench/meson.build b/bench/meson.build index b607ac65..1df36c15 100644 --- a/bench/meson.build +++ b/bench/meson.build @@ -115,7 +115,7 @@ bench_col_layout = executable( bench_thread_src, bench_workqueue_src, include_directories: [wirelog_inc, wirelog_src_inc], - dependencies: [nanoarrow_dep, threads_dep, xxhash_dep, mbedtls_dep], + dependencies: [nanoarrow_dep, threads_dep, xxhash_dep, mbedtls_dep, math_dep], install: false, ) diff --git a/docs/ARCHITECTURE.ko.md b/docs/ARCHITECTURE.ko.md index ca507b8f..c50a48ae 100644 --- a/docs/ARCHITECTURE.ko.md +++ b/docs/ARCHITECTURE.ko.md @@ -110,7 +110,7 @@ Wirelog는 **순수 C11**로 구현된 **Timely-Differential 개념** 기반의 - 워크큐 기반 병렬 실행에 자연스러운 적합성 **코드 위치**: -- 계획 수준: `wirelog/exec_plan.h:259` (WL_PLAN_OP_K_FUSION 연산자 타입) +- 계획 수준: `wirelog/exec_plan.h` (WL_PLAN_OP_K_FUSION 연산자 타입) - 계획 생성: `wirelog/exec_plan_gen.c:1574-1746` (expand_multiway_k_fusion) - 실행: `wirelog/columnar/internal.h:1257` (col_op_k_fusion) - 워커 격리: `wirelog/columnar/diff_arrangement.h:88-98` (col_diff_arrangement_deep_copy) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6abc8648..ebf79a04 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -111,7 +111,7 @@ This document describes the **invariant architectural principles** that must be - Natural fit for workqueue-based parallel execution **Code locations**: -- Plan-level: `wirelog/exec_plan.h:259` (WL_PLAN_OP_K_FUSION operator type) +- Plan-level: `wirelog/exec_plan.h` (WL_PLAN_OP_K_FUSION operator type) - Plan generation: `wirelog/exec_plan_gen.c:1574-1746` (expand_multiway_k_fusion) - Execution: `wirelog/columnar/internal.h:1257` (col_op_k_fusion) - Worker isolation: `wirelog/columnar/diff_arrangement.h:88-98` (col_diff_arrangement_deep_copy) diff --git a/tests/test_plan_exchange.c b/tests/test_plan_exchange.c index cc322403..6a5675f5 100644 --- a/tests/test_plan_exchange.c +++ b/tests/test_plan_exchange.c @@ -393,6 +393,33 @@ test_plan_exchange_cleanup(void) PASS(); } +static void +test_plan_op_abi_values(void) +{ + TEST("exec_plan op enum ABI values (Issue #495)"); + + /* Universal operators (0-8) */ + ASSERT(WL_PLAN_OP_VARIABLE == 0, "VARIABLE should be 0"); + ASSERT(WL_PLAN_OP_MAP == 1, "MAP should be 1"); + ASSERT(WL_PLAN_OP_FILTER == 2, "FILTER should be 2"); + ASSERT(WL_PLAN_OP_JOIN == 3, "JOIN should be 3"); + ASSERT(WL_PLAN_OP_ANTIJOIN == 4, "ANTIJOIN should be 4"); + ASSERT(WL_PLAN_OP_REDUCE == 5, "REDUCE should be 5"); + ASSERT(WL_PLAN_OP_CONCAT == 6, "CONCAT should be 6"); + ASSERT(WL_PLAN_OP_CONSOLIDATE == 7, "CONSOLIDATE should be 7"); + ASSERT(WL_PLAN_OP_SEMIJOIN == 8, "SEMIJOIN should be 8"); + + /* Backend-specific boundary */ + ASSERT(WL_PLAN_OP__BACKEND_START == 9, "__BACKEND_START should be 9"); + + /* Columnar backend operators (9-11) */ + ASSERT(WL_PLAN_OP_K_FUSION == 9, "K_FUSION should be 9"); + ASSERT(WL_PLAN_OP_LFTJ == 10, "LFTJ should be 10"); + ASSERT(WL_PLAN_OP_EXCHANGE == 11, "EXCHANGE should be 11"); + + PASS(); +} + /* ---------------------------------------------------------------- * Main * ---------------------------------------------------------------- */ @@ -402,6 +429,7 @@ main(void) { printf("=== Plan Exchange Insertion Tests (#319) ===\n"); + test_plan_op_abi_values(); test_plan_tc_exchange_conservative(); test_plan_no_exchange_nonrecursive(); test_plan_exchange_key_metadata(); diff --git a/wirelog/columnar/eval.c b/wirelog/columnar/eval.c index c238faac..6ad7456e 100644 --- a/wirelog/columnar/eval.c +++ b/wirelog/columnar/eval.c @@ -127,6 +127,17 @@ dedup_set_init_from_rel(col_rel_t *r) /* Stratum Evaluator */ /* ======================================================================== */ +/* Compile-time check: WL_PLAN_OP__BACKEND_START must match the first + * backend-specific operator (Issue #495). */ +#if defined(_MSC_VER) +/* MSVC C mode: use typedef array trick for compile-time check */ +typedef char static_check_backend_start_ + [(WL_PLAN_OP_K_FUSION == WL_PLAN_OP__BACKEND_START) ? 1 : -1]; +#else +_Static_assert(WL_PLAN_OP_K_FUSION == WL_PLAN_OP__BACKEND_START, + "WL_PLAN_OP__BACKEND_START must equal WL_PLAN_OP_K_FUSION"); +#endif + /* * col_eval_relation_plan: * Evaluate all operators for one relation plan using the eval stack. @@ -253,6 +264,7 @@ col_eval_relation_plan(const wl_plan_relation_t *rplan, eval_stack_t *stack, rc = col_op_exchange(op, stack, sess); break; default: + assert(0 && "unknown plan op type in columnar eval dispatch"); break; } if (rc != 0) diff --git a/wirelog/exec_plan.h b/wirelog/exec_plan.h index cd0357de..312169c6 100644 --- a/wirelog/exec_plan.h +++ b/wirelog/exec_plan.h @@ -230,6 +230,15 @@ typedef enum { * Operator types in a backend execution plan. * Explicit integer values for stable ABI across backends. * + * Range partitioning (Issue #495): + * Universal operators (0-8): Backend-agnostic relational algebra ops. + * Use flat fields in wl_plan_op_t only. + * Backend-specific ops (9+): Columnar backend optimizations. + * Use opaque_data for backend-defined metadata. + * See WL_PLAN_OP__BACKEND_START below. + * + * --- Universal operators (0-8) --- + * * WL_PLAN_OP_VARIABLE: Reference to an input collection (EDB or IDB). * WL_PLAN_OP_MAP: Column projection / rename. * WL_PLAN_OP_FILTER: Predicate filter (expr in serialized buffer). @@ -239,14 +248,20 @@ typedef enum { * WL_PLAN_OP_CONCAT: Union of multiple collections. * WL_PLAN_OP_CONSOLIDATE: Deduplication / consolidation. * WL_PLAN_OP_SEMIJOIN: Semijoin (SIP pre-filter). + * + * --- Columnar backend operators (9-11) --- + * + * WL_PLAN_OP_K_FUSION: Parallel semi-naive delta expansion (Issue #370). + * opaque_data -> wl_plan_op_k_fusion_t. * WL_PLAN_OP_LFTJ: Multi-way leapfrog triejoin on a single shared key * column across k >= 3 EDB relations (Issue #195). + * opaque_data -> wl_plan_op_lftj_t. * WL_PLAN_OP_EXCHANGE: Redistribute tuples by hash(key_columns) % W across * workers for partition-correct parallel evaluation - * (Issue #316). opaque_data points to - * wl_plan_op_exchange_t. + * (Issue #316). opaque_data -> wl_plan_op_exchange_t. */ typedef enum { + /* Universal operators (0-8): backend-agnostic */ WL_PLAN_OP_VARIABLE = 0, WL_PLAN_OP_MAP = 1, WL_PLAN_OP_FILTER = 2, @@ -256,11 +271,33 @@ typedef enum { WL_PLAN_OP_CONCAT = 6, WL_PLAN_OP_CONSOLIDATE = 7, WL_PLAN_OP_SEMIJOIN = 8, + + /* Columnar backend operators (9+): use opaque_data */ WL_PLAN_OP_K_FUSION = 9, WL_PLAN_OP_LFTJ = 10, - WL_PLAN_OP_EXCHANGE = 11, /* Redistribute tuples by hash(key) % W */ + WL_PLAN_OP_EXCHANGE = 11, } wl_plan_op_type_t; +/* First backend-specific operator value. All ops with numeric value + * >= WL_PLAN_OP__BACKEND_START carry their metadata in opaque_data + * (defined by the active backend, e.g. columnar/columnar_nanoarrow.h). + * Universal ops (< WL_PLAN_OP__BACKEND_START) use flat wl_plan_op_t fields. */ +#define WL_PLAN_OP__BACKEND_START 9 + +/** Returns true if @op is a backend-specific operator (>= BACKEND_START). */ +static inline bool +wl_plan_op_is_backend_specific(wl_plan_op_type_t op) +{ + return (int)op >= WL_PLAN_OP__BACKEND_START; +} + +/** Returns true if @op is a universal (backend-agnostic) operator. */ +static inline bool +wl_plan_op_is_universal(wl_plan_op_type_t op) +{ + return (int)op < WL_PLAN_OP__BACKEND_START; +} + /* ======================================================================== */ /* Operator Node */ /* ======================================================================== */ @@ -274,6 +311,7 @@ typedef enum { * * Field usage by operator type: * + * Universal operators (use flat fields only, opaque_data is NULL): * VARIABLE: relation_name * MAP: project_indices, project_count (and/or map_exprs) * FILTER: filter_expr @@ -286,9 +324,11 @@ typedef enum { * CONSOLIDATE: (no fields used) * SEMIJOIN: right_relation, right_filter_expr, left_keys, right_keys, * key_count, project_indices, project_count - * K_FUSION: opaque_data (points to wl_plan_op_k_fusion_t in columnar backend) - * LFTJ: opaque_data (points to wl_plan_op_lftj_t in columnar backend) - * EXCHANGE: opaque_data (points to wl_plan_op_exchange_t in columnar backend) + * + * Backend-specific operators (>= WL_PLAN_OP__BACKEND_START, use opaque_data): + * K_FUSION: opaque_data -> wl_plan_op_k_fusion_t (columnar) + * LFTJ: opaque_data -> wl_plan_op_lftj_t (columnar) + * EXCHANGE: opaque_data -> wl_plan_op_exchange_t (columnar) */ typedef struct { wl_plan_op_type_t op; @@ -315,11 +355,10 @@ typedef struct { wl_delta_mode_t delta_mode; /* semi-naive delta/full selection control */ bool materialized; /* hint: cache this intermediate result for CSE reuse */ - /* Backend-specific metadata. NULL for all ops except K_FUSION. - * For K_FUSION: points to a wl_plan_op_k_fusion_t (defined in - * backend/columnar_nanoarrow.h) containing K operator sequences - * for parallel semi-naive evaluation. Owned by the plan; freed - * via wl_plan_free() -> free_op() path. */ + /* Backend-specific metadata. NULL for all universal ops (< BACKEND_START). + * For backend-specific ops (K_FUSION, LFTJ, EXCHANGE): points to a + * backend-defined struct in columnar/columnar_nanoarrow.h. + * Owned by the plan; freed via wl_plan_free() -> free_op() path. */ void *opaque_data; /* Filter predicate applied to right-child tuples before join probe.