Skip to content
Merged
2 changes: 1 addition & 1 deletion bench/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 28 additions & 0 deletions tests/test_plan_exchange.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
* ---------------------------------------------------------------- */
Expand All @@ -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();
Expand Down
12 changes: 12 additions & 0 deletions wirelog/columnar/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
61 changes: 50 additions & 11 deletions wirelog/exec_plan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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,
Expand All @@ -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 */
/* ======================================================================== */
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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.
Expand Down
Loading