-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy pathlib.rs
More file actions
172 lines (151 loc) · 8.71 KB
/
Copy pathlib.rs
File metadata and controls
172 lines (151 loc) · 8.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! Low-level `cxx` bindings to the Sirius C++ API.
//!
//! This crate is intentionally thin: it exposes the C++ types and free functions
//! declared in the `#[cxx::bridge]` module below and nothing else. Safe, idiomatic
//! wrappers live in the [`sirius`](https://docs.rs/sirius) crate.
//!
//! The bridge binds Sirius's **public C++ surface** (`src/include/sirius_ffi.hpp`):
//! an RAII [`Context`] held via [`cxx::UniquePtr`], plus the [`Fragment`] it
//! creates — one plan fragment of a multi-fragment query. Constructing a context
//! brings up an initialized engine; dropping the `UniquePtr` tears it down. The
//! header is lightweight, so the bridge compiles without any of Sirius's internal headers
//! (cudf/rmm/duckdb). It is the seed of the public API `libsirius` will expose;
//! the bindings link whichever Sirius artifact provides these symbols (the DuckDB
//! extension today, a dedicated `libsirius` later — see `build.rs`).
//!
//! The `make_context*` functions are bound as fallible (`Result`): bringing up
//! the engine (or parsing a config file) can throw, and cxx turns a C++ exception
//! into `Err(cxx::Exception)` instead of aborting, so consumers can fail fast.
// The `# Safety` docs on the unsafe bridge fns live on the declarations below;
// cxx's macro expansion hides them from clippy's `missing_safety_doc`, so allow
// it for the generated module.
#[allow(clippy::missing_safety_doc)]
#[cxx::bridge(namespace = "sirius::ffi")]
mod ffi {
unsafe extern "C++" {
include!("sirius_ffi.hpp");
/// RAII handle to an initialized Sirius engine context.
type Context;
/// Construct an initialized [`Context`] from built-in defaults, owned by
/// the returned `UniquePtr`.
fn make_context() -> Result<UniquePtr<Context>>;
/// Construct an initialized [`Context`] from the YAML config file at
/// `config_path`, owned by the returned `UniquePtr`. `config_path` binds
/// to the C++ `const std::string&` parameter.
fn make_context_from_config(config_path: &CxxString) -> Result<UniquePtr<Context>>;
/// Execute a serialized Substrait plan on the GPU, writing the results
/// into the Arrow C Data Interface stream at `out_stream_addr` — the
/// address (as `usize`) of a caller-owned `ArrowArrayStream` the caller
/// releases per the Arrow ABI. `plan` binds to the C++ `const
/// std::string&` and carries the protobuf-encoded `substrait::Plan`
/// bytes. Bound as fallible: translation or execution failure surfaces as
/// `Err(cxx::Exception)`.
///
/// # Safety
/// `out_stream_addr` must be the address of a valid, writable
/// `ArrowArrayStream` that outlives this call; C++ writes the result
/// stream through it. The safe [`sirius`](https://docs.rs/sirius) wrapper
/// upholds this.
unsafe fn execute_substrait(
self: Pin<&mut Context>,
plan: &CxxString,
out_stream_addr: usize,
) -> Result<()>;
/// One plan fragment of a multi-fragment query. Either declares output
/// streams (an intermediate fragment, whose results park as native GPU
/// batches that outlive its own query) or none (a result fragment, which
/// produces Arrow).
///
/// Usage order: `declare_*` → `build` → `relay_from` every sender → `run`
/// → drain via `relay_from` or `result_to_arrow`. Exactly one fragment may
/// sit between its own `build` and `run`; the engine serializes queries.
type Fragment;
/// Create a [`Fragment`] on `context`, which must outlive it.
fn make_fragment(context: Pin<&mut Context>) -> Result<UniquePtr<Fragment>>;
/// Name of the view a plan reads to consume input stream `stream_id` — the
/// single definition of the convention, so a front end emitting the read
/// and the engine creating the view cannot drift apart.
fn stream_view_name(stream_id: u64) -> UniquePtr<CxxString>;
/// Declare one column of an input stream, in plan order. `ty` is a DuckDB
/// type name; a stream has no file to probe, so the schema is given, never
/// inferred.
fn declare_input_column(
self: Pin<&mut Fragment>,
stream_id: u64,
name: &CxxString,
ty: &CxxString,
) -> Result<()>;
/// Declare a sender that must close this input stream before it ends.
fn declare_input_sender(
self: Pin<&mut Fragment>,
stream_id: u64,
sender_id: u32,
) -> Result<()>;
/// Declare an output stream. A fragment with none is a result fragment.
fn declare_output(self: Pin<&mut Fragment>, stream_id: u64) -> Result<()>;
/// Replicate every batch to all declared outputs. Mutually exclusive with
/// [`declare_output_hash_key`](Fragment::declare_output_hash_key), and must
/// precede `build`; both are the errors raised here. Whether enough
/// destinations exist is not known until `build`.
fn declare_output_broadcast(self: Pin<&mut Fragment>) -> Result<()>;
/// Hash-partition across the declared outputs on `column_index` (call once
/// per key column, in key order). Same exclusivity and ordering rules as
/// broadcast.
fn declare_output_hash_key(self: Pin<&mut Fragment>, column_index: u32) -> Result<()>;
/// Plan `substrait_plan` against the declared streams and open the
/// fragment's query lifecycle. Where the declaration-time rules cannot be
/// checked, they are checked here:
///
/// * a routing mode declared on a fragment with fewer than two outputs is
/// rejected — every row would reach destination 0 regardless, so a
/// silently-dropped spec would look like it worked;
/// * a plan that reads the same declared input stream id more than once is
/// rejected — the second read would overwrite the first's registration
/// and strand its pipeline waiting for a push that never comes.
fn build(self: Pin<&mut Fragment>, substrait_plan: &CxxString) -> Result<()>;
/// Move every batch parked on `source`'s output stream into this fragment's
/// input stream as native handles — no Arrow, no file, no copy — then close
/// `sender_id`. Returns the number of batches moved.
///
/// `source` must have finished [`run`](Fragment::run): before that, an empty
/// stream and a finished one are indistinguishable, and the input would be
/// closed after zero batches. `input_stream_id` must be declared on this
/// fragment, and `source` must have output streams (a result fragment is
/// rejected).
fn relay_from(
self: Pin<&mut Fragment>,
source: Pin<&mut Fragment>,
source_stream_id: u64,
input_stream_id: u64,
sender_id: u32,
) -> Result<usize>;
/// Close `sender_id` on input stream `stream_id`. The end-of-stream mirror
/// for senders that are not local fragments — `relay_from` closes its own.
/// Idempotent per sender; the stream ends once every expected sender has
/// closed.
fn close_input(self: Pin<&mut Fragment>, stream_id: u64, sender_id: u32) -> Result<()>;
/// Execute the fragment and close its query lifecycle. Blocks until its
/// pipelines finish.
fn run(self: Pin<&mut Fragment>) -> Result<()>;
/// Write a result fragment's rows into the caller-owned `ArrowArrayStream`
/// at `out_stream_addr`.
///
/// # Safety
/// `out_stream_addr` must be the address of a valid, writable
/// `ArrowArrayStream` that outlives this call. The safe
/// [`sirius`](https://docs.rs/sirius) wrapper upholds this.
unsafe fn result_to_arrow(self: Pin<&mut Fragment>, out_stream_addr: usize) -> Result<()>;
/// Batches currently parked on an output stream — the evidence that a
/// fragment boundary carried native batches rather than nothing.
fn output_batch_count(self: &Fragment, stream_id: u64) -> Result<usize>;
/// DuckDB type names of a built fragment's output (sink) columns — the
/// types every batch leaving the fragment actually carries, exactly
/// what `relay_from`'s schema guard compares against a receiver's
/// declared input columns. Fallible: unbuilt and result fragments both
/// surface as `Err`.
fn output_types(self: &Fragment) -> Result<UniquePtr<CxxVector<CxxString>>>;
}
}
pub use ffi::{
Context, Fragment, make_context, make_context_from_config, make_fragment, stream_view_name,
};