Skip to main content

dre_protocol/
plugin.rs

1//! The plugin SDK: implement one of [`Source`], [`Format`] or [`Destination`] and call the
2//! matching `serve_*` function from `main`. The SDK owns stdin/stdout, the handshake, framing,
3//! request ids, error replies and panics.
4//!
5//! Formats and destinations declare their options (`options()`) and add any rule a declaration
6//! can't express (`validate()`). The SDK checks a config block against both on `validate` and
7//! before every `write` and `deliver`, so plugin code only sees options that passed.
8//!
9//! - **Logging:** use the `log` crate (`log::info!`, `log::warn!`), or [`log()`] for a message with
10//!   fields; the SDK sends them to core as `log` messages. The plugin's own records keep their
11//!   level; its dependencies' are shown only with `-v`. Long requests may report [`progress`].
12//! - **Errors:** return a [`PluginError`] to give core the problem's kind and code; any other
13//!   error is sent as a plain message.
14//! - **Cancellation:** stdin is read on a background thread while a request runs. On `cancel`
15//!   (or when core goes away) [`cancelled`] turns true and the hook set with [`on_cancel`] runs,
16//!   e.g. to cancel a query on the server.
17
18use std::io::{BufReader, BufWriter};
19use std::path::{Path, PathBuf};
20use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
21use std::sync::mpsc::{Receiver, Sender};
22use std::sync::{Arc, Mutex, OnceLock};
23
24use arrow::array::RecordBatch;
25use arrow::datatypes::SchemaRef;
26use serde_json::{Map, Value};
27
28use crate::frame::{self, Frame, FrameError};
29use crate::msg::{ConnectionField, Envelope, LogLevel, Request, Response, ResultSetMeta};
30use crate::options::{self, OptionField};
31use crate::{CAP_VALIDATE, Kind, MAX_VERSION, MIN_VERSION, PluginId};
32
33pub type Error = Box<dyn std::error::Error + Send + Sync>;
34pub type Result<T> = std::result::Result<T, Error>;
35
36/// What kind of problem a [`PluginError`] is (the kinds of DRE's error-code registry).
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum ErrorKind {
39    /// The settings are wrong.
40    Config,
41    /// The plugin itself misbehaved.
42    Plugin,
43    /// Refused something unsafe.
44    Refused,
45    /// A connection couldn't be made; trying again may work.
46    Connection,
47    /// Credentials were refused.
48    Auth,
49    /// A query failed on the database.
50    Query,
51    /// A file or message couldn't be delivered.
52    Delivery,
53    /// Something unexpected: a bug.
54    Internal,
55    Cancelled,
56    TimedOut,
57}
58
59impl ErrorKind {
60    pub fn as_str(self) -> &'static str {
61        match self {
62            ErrorKind::Config => "config",
63            ErrorKind::Plugin => "plugin",
64            ErrorKind::Refused => "refused",
65            ErrorKind::Connection => "connection",
66            ErrorKind::Auth => "auth",
67            ErrorKind::Query => "query",
68            ErrorKind::Delivery => "delivery",
69            ErrorKind::Internal => "internal",
70            ErrorKind::Cancelled => "cancelled",
71            ErrorKind::TimedOut => "timed_out",
72        }
73    }
74}
75
76/// An error with its kind and, optionally, a code. The SDK namespaces the code by the plugin
77/// (`host-key-mismatch` is sent as `sftp/host-key-mismatch`).
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct PluginError {
80    pub kind: ErrorKind,
81    pub code: Option<String>,
82    pub message: String,
83}
84
85impl PluginError {
86    pub fn new(kind: ErrorKind, message: impl Into<String>) -> PluginError {
87        PluginError {
88            kind,
89            code: None,
90            message: message.into(),
91        }
92    }
93
94    #[must_use]
95    pub fn code(mut self, code: impl Into<String>) -> PluginError {
96        self.code = Some(code.into());
97        self
98    }
99}
100
101impl std::fmt::Display for PluginError {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.write_str(&self.message)
104    }
105}
106
107impl std::error::Error for PluginError {}
108
109/// The SDK's process-wide state, shared by the request loop, the stdin reader, and logging
110/// from any thread.
111struct Shared {
112    out: Mutex<BufWriter<Box<dyn std::io::Write + Send>>>,
113    /// The protocol version settled on (0 until the handshake).
114    version: AtomicU32,
115    /// The request running now (0: none).
116    current: AtomicU64,
117    /// The last request core cancelled (0: none).
118    cancelled: AtomicU64,
119    hook: Mutex<Option<CancelHook>>,
120    /// The plugin being served, for namespacing its error codes.
121    name: OnceLock<String>,
122}
123
124type CancelHook = Arc<dyn Fn() + Send + Sync>;
125
126fn shared() -> &'static Shared {
127    static SHARED: OnceLock<Shared> = OnceLock::new();
128    SHARED.get_or_init(|| Shared {
129        out: Mutex::new(BufWriter::new(protocol_out())),
130        version: AtomicU32::new(0),
131        current: AtomicU64::new(0),
132        cancelled: AtomicU64::new(0),
133        hook: Mutex::new(None),
134        name: OnceLock::new(),
135    })
136}
137
138/// Where frames go: the process's original stdout, kept for the protocol alone. Stdout itself is
139/// pointed at stderr, so a dependency that prints (`println!`) can't corrupt the frame stream;
140/// core keeps stderr for crash reports.
141fn protocol_out() -> Box<dyn std::io::Write + Send> {
142    #[cfg(unix)]
143    {
144        use std::os::fd::FromRawFd;
145        // SAFETY: plain descriptor calls on fds 1 and 2, made once before any frame is written.
146        unsafe {
147            let fd = libc::dup(1);
148            if fd >= 0 && libc::dup2(2, 1) >= 0 {
149                return Box::new(std::fs::File::from_raw_fd(fd));
150            }
151        }
152    }
153    #[cfg(windows)]
154    {
155        use std::os::windows::io::FromRawHandle;
156        use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
157        use windows_sys::Win32::System::Console::{
158            GetStdHandle, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE, SetStdHandle,
159        };
160        // SAFETY: the process's own standard handles, swapped once before any frame is written;
161        // Rust's stdout looks its handle up on every write, so `println!` then reaches stderr.
162        unsafe {
163            let out = GetStdHandle(STD_OUTPUT_HANDLE);
164            let err = GetStdHandle(STD_ERROR_HANDLE);
165            if !out.is_null() && out != INVALID_HANDLE_VALUE && SetStdHandle(STD_OUTPUT_HANDLE, err) != 0 {
166                return Box::new(std::fs::File::from_raw_handle(out as _));
167            }
168        }
169    }
170    Box::new(std::io::stdout())
171}
172
173/// Whether core has cancelled the request running now. Long loops check it between steps.
174pub fn cancelled() -> bool {
175    let s = shared();
176    let current = s.current.load(Ordering::SeqCst);
177    current != 0 && s.cancelled.load(Ordering::SeqCst) == current
178}
179
180/// Run `hook` when core cancels a request, on the stdin reader's thread, while the request is
181/// still running on the main one (e.g. ask the server to cancel the query). Replaces any hook
182/// set before. The request then fails, and the SDK replies with kind `cancelled`.
183///
184/// # Panics
185///
186/// Panics if the hook's lock is poisoned (a hook set or run before panicked).
187pub fn on_cancel(hook: impl Fn() + Send + Sync + 'static) {
188    *shared().hook.lock().unwrap() = Some(Arc::new(hook));
189}
190
191/// Cancel request `id`: remembered, so a request cancelled before it starts never runs, and
192/// the hook runs if it's running now.
193fn cancel(id: u64) {
194    let s = shared();
195    s.cancelled.store(id, Ordering::SeqCst);
196    if id != 0 && s.current.load(Ordering::SeqCst) == id {
197        let hook = s.hook.lock().unwrap().clone();
198        if let Some(hook) = hook {
199            hook();
200        }
201    }
202}
203
204/// Send a log message to core, with structured `fields` (never secrets).
205pub fn log_fields(level: LogLevel, message: &str, fields: Map<String, Value>) {
206    let s = shared();
207    if s.version.load(Ordering::SeqCst) == 0 {
208        // Protocol 0 has no `log` message: stderr, with the prefixes core 0.3 shows.
209        let prefix = match level {
210            LogLevel::Error | LogLevel::Warn => "warning: ",
211            LogLevel::Info => "info: ",
212            LogLevel::Debug | LogLevel::Trace => "",
213        };
214        eprintln!("{prefix}{message}");
215        return;
216    }
217    send(&Response::Log {
218        level,
219        message: message.to_string(),
220        fields,
221    });
222}
223
224/// Send a log message to core.
225pub fn log(level: LogLevel, message: &str) {
226    log_fields(level, message, Map::new())
227}
228
229/// Tell core how far a long request has got; it shows the latest.
230pub fn progress(message: Option<&str>, done: Option<u64>, total: Option<u64>) {
231    if shared().version.load(Ordering::SeqCst) == 0 {
232        if let Some(m) = message {
233            log(LogLevel::Info, m);
234        }
235        return;
236    }
237    send(&Response::Progress {
238        message: message.map(str::to_string),
239        done,
240        total,
241    });
242}
243
244/// Bridges the `log` crate to `log` messages.
245struct FrameLogger;
246
247impl ::log::Log for FrameLogger {
248    /// The plugin's own records down to debug; a dependency's (a driver, an HTTP client, a
249    /// layout engine) down to info only: their debug output can run to millions of lines.
250    fn enabled(&self, m: &::log::Metadata) -> bool {
251        m.level() <= ::log::Level::Info || (m.level() <= ::log::Level::Debug && m.target().starts_with("dre"))
252    }
253
254    fn log(&self, r: &::log::Record) {
255        if !self.enabled(r.metadata()) {
256            return;
257        }
258        let level = match r.level() {
259            ::log::Level::Error => LogLevel::Error,
260            ::log::Level::Warn => LogLevel::Warn,
261            ::log::Level::Info => LogLevel::Info,
262            ::log::Level::Debug => LogLevel::Debug,
263            ::log::Level::Trace => LogLevel::Trace,
264        };
265        // A dependency's records (a driver, an HTTP client) are for `-v` only.
266        let level = if r.target().starts_with("dre") {
267            level
268        } else {
269            level.max(LogLevel::Debug)
270        };
271        log(level, &r.args().to_string());
272    }
273
274    fn flush(&self) {}
275}
276
277/// Write one reply or streamed message, with the running request's id from protocol 1.
278fn send(r: &Response) {
279    let s = shared();
280    let id = match s.version.load(Ordering::SeqCst) {
281        0 => None,
282        _ => Some(s.current.load(Ordering::SeqCst)).filter(|id| *id != 0),
283    };
284    let mut out = s.out.lock().unwrap();
285    if frame::write_json(&mut *out, &Envelope::new(id, r)).is_err() {
286        // Core has gone; nothing left to talk to.
287        std::process::exit(1);
288    }
289}
290
291/// The `error` reply for a failed request: kind `cancelled` if core cancelled it, else the
292/// kind and code of a [`PluginError`], else the message alone.
293fn error_reply(e: &Error) -> Response {
294    if cancelled() {
295        return Response::Error {
296            message: e.to_string(),
297            kind: Some("cancelled".into()),
298            code: None,
299        };
300    }
301    match e.downcast_ref::<PluginError>() {
302        Some(pe) => Response::Error {
303            message: pe.message.clone(),
304            kind: Some(pe.kind.as_str().into()),
305            code: pe.code.as_ref().map(|c| match shared().name.get() {
306                Some(name) if !c.contains('/') => format!("{name}/{c}"),
307                _ => c.clone(),
308            }),
309        },
310        None => Response::error(e.to_string()),
311    }
312}
313
314/// Identity reported in the handshake.
315#[derive(Debug, Clone)]
316pub struct About {
317    pub name: &'static str,
318    pub version: &'static str,
319    pub capabilities: &'static [&'static str],
320}
321
322impl About {
323    /// `version` "0.0.0" (the crate has no version yet) is reported as "unreleased".
324    pub fn new(name: &'static str, version: &'static str) -> About {
325        let version = if version == "0.0.0" { "unreleased" } else { version };
326        About {
327            name,
328            version,
329            capabilities: &[],
330        }
331    }
332
333    #[must_use]
334    pub fn capabilities(mut self, caps: &'static [&'static str]) -> About {
335        self.capabilities = caps;
336        self
337    }
338}
339
340/// Where a source pushes the outcome of `execute`: either `no_result`, or `begin` followed by
341/// any number of `batch` calls.
342pub trait ResultSink {
343    fn no_result(&mut self, rows_affected: Option<u64>) -> Result<()>;
344    fn begin(&mut self, schema: SchemaRef) -> Result<()>;
345    /// Returns `false` once the row limit is reached: stop producing batches.
346    fn batch(&mut self, batch: RecordBatch) -> Result<bool>;
347}
348
349pub trait Source {
350    fn connection_fields(&self) -> Vec<ConnectionField> {
351        Vec::new()
352    }
353    /// The character the database quotes identifiers with, reported in `describe`: `"` for
354    /// most SQL databases, a backtick for Databricks and MySQL. Core doubles it inside a name.
355    fn identifier_quote(&self) -> Option<&'static str> {
356        None
357    }
358    /// How core should spell a string holding `'` or `\` (an inlined lookup), reported in
359    /// `describe`: `None` for `chr(39)`/`chr(92)`, `Some("backslash")` for a database without
360    /// `chr()`.
361    fn string_literal(&self) -> Option<&'static str> {
362        None
363    }
364    /// A connection field naming a file only one process may write at a time, reported in
365    /// `describe` (DuckDB's `path`, unless `:memory:`): core then runs one Binding at a time on
366    /// a connection that sets it.
367    fn single_writer(&self) -> Option<crate::msg::SingleWriter> {
368        None
369    }
370    /// Rules the declared [`Source::connection_fields`] can't express (two keys that can't go
371    /// together, a value's form), each a sentence naming the field, never its value. Static:
372    /// no network. Run by `validate_connection` and before `open`.
373    /// A value set through an unset `env_var()` arrives as `null`.
374    fn validate_connection(&self, _connection: &Map<String, Value>) -> Vec<String> {
375        Vec::new()
376    }
377    fn open(&mut self, connection: &Map<String, Value>, read_only: bool) -> Result<()>;
378    /// Open a read-only session for core's `dre query`: also refuse files, programs and the
379    /// network outside the database where the engine can. Default: a read-only [`Source::open`].
380    fn open_sandboxed(&mut self, connection: &Map<String, Value>) -> Result<()> {
381        self.open(connection, true)
382    }
383    /// Run one statement. `row_limit` is a hint; the SDK enforces it either way.
384    fn execute(&mut self, sql: &str, row_limit: Option<u64>, out: &mut dyn ResultSink) -> Result<()>;
385    /// Verify without executing. Only called when the plugin advertises `check`.
386    fn check(&mut self, _sql: &str) -> Result<()> {
387        Err("this source can't check statements".into())
388    }
389    /// Load `data` into a temporary table on the session, named after `name`. Only called when
390    /// the plugin advertises `load`.
391    fn load(&mut self, _name: &str, _data: &mut ResultSet<'_>) -> Result<Loaded> {
392        Err("this source can't load rows".into())
393    }
394    /// Discover accessible relations through native metadata. Only called when the plugin
395    /// advertises `catalog`; both lists use the existing `dre catalog` request.
396    fn catalog(&mut self, _tables: &[String], _schemas: &[String]) -> Result<Value> {
397        Err("this source can't discover catalogue metadata".into())
398    }
399    /// End the session cleanly (called on `close` and at end of input, before exiting).
400    fn close(&mut self) {}
401}
402
403/// The outcome of `Source::load`.
404pub struct Loaded {
405    /// How SQL refers to the loaded rows, e.g. the temp table's name.
406    pub relation: String,
407    pub rows: u64,
408    /// Shown to the user, e.g. when the database has no bulk path.
409    pub warning: Option<String>,
410}
411
412/// One incoming result set, streamed from core.
413pub struct ResultSet<'a> {
414    pub meta: ResultSetMeta,
415    pub schema: SchemaRef,
416    first: Option<Vec<RecordBatch>>,
417    input: &'a mut Input,
418    done: bool,
419}
420
421impl ResultSet<'_> {
422    /// The next batch, or `None` at the end of this result set.
423    pub fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
424        loop {
425            if let Some(first) = self.first.as_mut() {
426                if !first.is_empty() {
427                    return Ok(Some(first.remove(0)));
428                }
429                self.first = None;
430            }
431            if self.done {
432                return Ok(None);
433            }
434            match self.input.read()? {
435                Frame::Arrow(ipc) => {
436                    let (_, batches) = frame::decode_batches(&ipc)?;
437                    self.first = Some(batches);
438                }
439                Frame::Json(v) => match serde_json::from_value::<Request>(v)? {
440                    Request::ResultSetEnd {} => {
441                        self.done = true;
442                        return Ok(None);
443                    }
444                    other => return Err(format!("expected result data, got {other:?}").into()),
445                },
446            }
447        }
448    }
449
450    fn drain(&mut self) -> Result<()> {
451        while self.next_batch()?.is_some() {}
452        Ok(())
453    }
454}
455
456/// A format `write` request.
457pub struct WriteRequest {
458    pub path: String,
459    pub format: String,
460    pub options: Map<String, Value>,
461    pub result_sets: Vec<ResultSetMeta>,
462    pub template: Option<Value>,
463    pub font_requests: bool,
464}
465
466/// Hands a format plugin its result sets one at a time, in order.
467pub struct ResultSets<'a> {
468    metas: std::vec::IntoIter<ResultSetMeta>,
469    input: &'a mut Input,
470    warnings: Vec<String>,
471    finished: bool,
472}
473
474impl ResultSets<'_> {
475    /// Tell the person something about the output, shown as a warning by core.
476    pub fn warn(&mut self, message: impl Into<String>) {
477        self.warnings.push(message.into());
478    }
479
480    /// Ask core for an immutable registry font after consuming all result sets.
481    /// Only call when `WriteRequest::font_requests` is true. The outer error is a protocol
482    /// failure; the inner one is core's reason it couldn't supply the font.
483    pub fn request_fonts(
484        &mut self,
485        font: crate::msg::FontRequest,
486    ) -> Result<std::result::Result<Vec<String>, crate::msg::FontError>> {
487        if self.metas.len() != 0 {
488            return Err("read all result sets before requesting fonts".into());
489        }
490        if !self.finished {
491            match self.input.read()? {
492                Frame::Json(v) if v.get("type").and_then(Value::as_str) == Some("finish") => {}
493                other => return Err(format!("expected finish before font request, got {other:?}").into()),
494            }
495            self.finished = true;
496        }
497        send(&Response::NeedFonts { font });
498        match self.input.read()? {
499            Frame::Json(v) => match serde_json::from_value::<crate::msg::Envelope<Request>>(v)?.body {
500                Request::Fonts {
501                    paths, error: None, ..
502                } => Ok(Ok(paths)),
503                Request::Fonts {
504                    error: Some(message),
505                    repeated,
506                    ..
507                } => Ok(Err(crate::msg::FontError { message, repeated })),
508                other => Err(format!("expected font paths, got {other:?}").into()),
509            },
510            other => Err(format!("expected font paths, got {other:?}").into()),
511        }
512    }
513
514    /// The next result set, or `None` after the last. Each must be read (or is drained) before
515    /// the next is requested.
516    pub fn next_set(&mut self) -> Result<Option<ResultSet<'_>>> {
517        let Some(meta) = self.metas.next() else {
518            return Ok(None);
519        };
520        let ipc = match self.input.read()? {
521            Frame::Arrow(ipc) => ipc,
522            Frame::Json(v) => {
523                return Err(format!(
524                    "expected an Arrow frame starting result set `{}`, got {v}",
525                    meta.name
526                )
527                .into());
528            }
529        };
530        let (schema, batches) = frame::decode_batches(&ipc)?;
531        Ok(Some(ResultSet {
532            meta,
533            schema,
534            first: Some(batches),
535            input: &mut *self.input,
536            done: false,
537        }))
538    }
539}
540
541pub trait Format {
542    /// The options this format takes: every key of a report's `output:` block except `format`,
543    /// `destination`, `template` and `extension`, which core owns.
544    fn options(&self) -> Vec<OptionField> {
545        Vec::new()
546    }
547    /// Problems the declared [`Format::options`] can't catch, each a sentence naming the key.
548    /// Only called on options whose declared types already passed.
549    fn validate(&self, _options: &Map<String, Value>) -> Vec<String> {
550        Vec::new()
551    }
552    /// Problems with the output's template (`validate`'s `template`, sent for the formats core
553    /// gives a template context): errors, then warnings. Only called when the options passed.
554    fn validate_template(
555        &self,
556        _options: &Map<String, Value>,
557        _template: &Value,
558    ) -> (Vec<String>, Vec<String>) {
559        (Vec::new(), Vec::new())
560    }
561    /// A hint added to the "unknown option" error for `key`, for an option this format doesn't
562    /// take but a sibling format does (`password` on `html`).
563    fn unknown_option_hint(&self, _key: &str) -> Option<String> {
564        None
565    }
566    /// Write every result set to `req.path` (and siblings, if the format needs several files);
567    /// return the files written.
568    fn write(&mut self, req: &WriteRequest, sets: &mut ResultSets<'_>) -> Result<Vec<String>>;
569}
570
571/// A destination `deliver` request.
572pub struct Delivery {
573    /// One file, or every file of an output when the plugin advertises `multi_file`. With a
574    /// `message`: the files to attach to it (often none).
575    pub files: Vec<DeliveryFile>,
576    pub connection: Map<String, Value>,
577    /// The destination entry's plugin options, rendered by core (empty when none).
578    pub options: Map<String, Value>,
579    /// A message to post; only sent to a plugin advertising `message`.
580    pub message: Option<crate::msg::Message>,
581}
582
583/// One file to deliver: the local copy in `target/run/` and its rendered remote path, if any.
584pub struct DeliveryFile {
585    pub local: PathBuf,
586    pub remote: Option<String>,
587}
588
589pub trait Destination {
590    fn connection_fields(&self) -> Vec<ConnectionField> {
591        Vec::new()
592    }
593    /// The options a report's destination entry takes: every key but `profile` and `path`.
594    /// String values may still hold Jinja when checked (see [`options::is_template`]); core
595    /// renders it before `deliver`.
596    fn options(&self) -> Vec<OptionField> {
597        Vec::new()
598    }
599    /// Problems the declared [`Destination::options`] can't catch, each a sentence naming the key.
600    /// Only called on options whose declared types already passed.
601    fn validate(&self, _options: &Map<String, Value>) -> Vec<String> {
602        Vec::new()
603    }
604    /// Rules the declared [`Destination::connection_fields`] can't express, each a sentence
605    /// naming the field, never its value. Static: no network. Run by `validate_connection` and
606    /// before every delivery.
607    fn validate_connection(&self, _connection: &Map<String, Value>) -> Vec<String> {
608        Vec::new()
609    }
610    /// Deliver `local` to `remote` (rendered by core); return where it landed. Enough for a
611    /// destination that takes no options; others implement [`Destination::deliver_files`].
612    fn deliver(
613        &mut self,
614        _local: &Path,
615        _remote: Option<&str>,
616        _connection: &Map<String, Value>,
617    ) -> Result<String> {
618        Err("this destination doesn't implement `deliver`".into())
619    }
620    /// The most characters a message may have in the service (after translation), reported in
621    /// `describe` by a plugin advertising `message`.
622    fn message_limit(&self) -> Option<u64> {
623        None
624    }
625    /// Post `d.message` (with `d.files` attached). Called only for a plugin advertising
626    /// `message`, which implements it.
627    fn deliver_message(&mut self, _d: &Delivery, _m: &crate::msg::Message) -> Result<String> {
628        Err("this destination doesn't take messages".into())
629    }
630    /// The whole request, options included (already checked). The default hands a single file
631    /// to [`Destination::deliver`]; several files arrive only with `multi_file` advertised.
632    fn deliver_files(&mut self, d: &Delivery) -> Result<String> {
633        match d.files.as_slice() {
634            [f] => self.deliver(&f.local, f.remote.as_deref(), &d.connection),
635            _ => Err("this destination takes one file per delivery".into()),
636        }
637    }
638}
639
640/// The frames core sends, read on a background thread (see `read_stdin`).
641pub struct Input {
642    rx: Receiver<std::result::Result<Frame, FrameError>>,
643}
644
645impl Input {
646    fn next(&mut self) -> std::result::Result<Frame, FrameError> {
647        self.rx.recv().unwrap_or(Err(FrameError::Eof))
648    }
649
650    fn read(&mut self) -> Result<Frame> {
651        self.next().map_err(|e| -> Error {
652            match e {
653                FrameError::Eof => "core closed the connection".into(),
654                e => e.to_string().into(),
655            }
656        })
657    }
658}
659
660/// Read stdin into `tx`, acting on `cancel` at once rather than queueing it behind the request
661/// it cancels. End of input cancels the running request too: core has gone.
662fn read_stdin(tx: Sender<std::result::Result<Frame, FrameError>>) {
663    let mut r = BufReader::new(std::io::stdin());
664    loop {
665        let f = frame::read_frame(&mut r);
666        match &f {
667            Ok(Frame::Json(v)) if v.get("type").and_then(Value::as_str) == Some("cancel") => {
668                cancel(v.get("id").and_then(Value::as_u64).unwrap_or(0));
669                continue;
670            }
671            Err(_) => cancel(shared().current.load(Ordering::SeqCst)),
672            Ok(_) => {}
673        }
674        let stop = f.is_err();
675        if tx.send(f).is_err() || stop {
676            break;
677        }
678    }
679}
680
681/// Writes replies and result data (see [`send`]).
682struct Output;
683
684impl Output {
685    fn send(&mut self, r: &Response) {
686        send(r)
687    }
688
689    fn batch(&mut self, b: &RecordBatch) -> Result<()> {
690        let ipc = frame::encode_batch(b)?;
691        let mut out = shared().out.lock().unwrap();
692        frame::write_arrow(&mut *out, &ipc).map_err(|_| -> Error { "core closed the connection".into() })
693    }
694}
695
696enum Handler<'a> {
697    Source(&'a mut dyn Source),
698    Format(&'a mut dyn Format),
699    Destination(&'a mut dyn Destination),
700}
701
702impl Handler<'_> {
703    fn kind(&self) -> Kind {
704        match self {
705            Handler::Source(_) => Kind::Source,
706            Handler::Format(_) => Kind::Format,
707            Handler::Destination(_) => Kind::Destination,
708        }
709    }
710
711    fn option_fields(&self) -> Vec<OptionField> {
712        match self {
713            Handler::Source(_) => Vec::new(),
714            Handler::Format(f) => f.options(),
715            Handler::Destination(d) => d.options(),
716        }
717    }
718
719    /// Every problem with a config block: the declared checks, then the plugin's own once the
720    /// declared ones pass.
721    fn check(&self, name: &str, o: &Map<String, Value>) -> Vec<String> {
722        let mut errs = options::check(self.kind(), name, &self.option_fields(), o);
723        if let Handler::Format(f) = self {
724            for e in &mut errs {
725                let hint = o
726                    .keys()
727                    .find(|k| e.starts_with(&format!("unknown option `{k}`")))
728                    .and_then(|k| f.unknown_option_hint(k));
729                if let Some(h) = hint {
730                    e.push_str("; ");
731                    e.push_str(&h);
732                }
733            }
734        }
735        if !errs.is_empty() {
736            return errs;
737        }
738        match self {
739            Handler::Source(_) => Vec::new(),
740            Handler::Format(f) => f.validate(o),
741            Handler::Destination(d) => d.validate(o),
742        }
743    }
744
745    /// The config block's problems as one error, for `write` and `deliver`.
746    fn checked(&self, name: &str, o: &Map<String, Value>) -> Result<()> {
747        let errs = self.check(name, o);
748        if errs.is_empty() {
749            Ok(())
750        } else {
751            // Wrong options are the project's settings: kind `config`.
752            Err(PluginError::new(ErrorKind::Config, errs.join("; ")).into())
753        }
754    }
755}
756
757pub fn serve_source(about: About, s: impl Source + 'static) -> ! {
758    serve_package(vec![Plugin::Source(about, Box::new(s))])
759}
760
761pub fn serve_format(about: About, f: impl Format + 'static) -> ! {
762    serve_package(vec![Plugin::Format(about, Box::new(f))])
763}
764
765pub fn serve_destination(about: About, d: impl Destination + 'static) -> ! {
766    serve_package(vec![Plugin::Destination(about, Box::new(d))])
767}
768
769/// One of the plugins a package's executable serves (see [`serve_package`]).
770pub enum Plugin {
771    Source(About, Box<dyn Source>),
772    Format(About, Box<dyn Format>),
773    Destination(About, Box<dyn Destination>),
774}
775
776impl Plugin {
777    fn id(&self) -> PluginId {
778        let (kind, about) = match self {
779            Plugin::Source(a, _) => (Kind::Source, a),
780            Plugin::Format(a, _) => (Kind::Format, a),
781            Plugin::Destination(a, _) => (Kind::Destination, a),
782        };
783        PluginId::new(kind, about.name)
784    }
785
786    fn handler(&mut self) -> (&About, Handler<'_>) {
787        match self {
788            Plugin::Source(a, s) => (a, Handler::Source(s.as_mut())),
789            Plugin::Format(a, f) => (a, Handler::Format(f.as_mut())),
790            Plugin::Destination(a, d) => (a, Handler::Destination(d.as_mut())),
791        }
792    }
793}
794
795/// Serve every plugin of a package from one executable. Core's `hello` names the plugin it
796/// wants; without one, the first is served.
797///
798/// # Panics
799///
800/// Panics if `plugins` is empty.
801pub fn serve_package(mut plugins: Vec<Plugin>) -> ! {
802    assert!(!plugins.is_empty(), "a package serves at least one plugin");
803    // Take stdout for frames before anything else can print to it.
804    shared();
805    let (tx, rx) = std::sync::mpsc::channel();
806    std::thread::spawn(move || read_stdin(tx));
807    let mut input = Input { rx };
808    let mut out = Output;
809    if ::log::set_logger(&FrameLogger).is_ok() {
810        ::log::set_max_level(::log::LevelFilter::Debug);
811    }
812    let provides: Vec<PluginId> = plugins.iter().map(Plugin::id).collect();
813    let chosen = loop {
814        let Some(req) = next_request(&mut input, &mut out) else {
815            continue;
816        };
817        let Request::Hello {
818            min_version,
819            max_version,
820            plugin,
821            ..
822        } = req
823        else {
824            out.send(&Response::error("the first request must be `hello`"));
825            continue;
826        };
827        let Some(chosen) = negotiate((min_version, max_version), (MIN_VERSION, MAX_VERSION)) else {
828            out.send(&Response::VersionMismatch {
829                min_version: MIN_VERSION,
830                max_version: MAX_VERSION,
831            });
832            std::process::exit(1);
833        };
834        let i = match &plugin {
835            None => 0,
836            Some(want) => match provides.iter().position(|p| p == want) {
837                Some(i) => i,
838                None => {
839                    let list: Vec<String> = provides.iter().map(|p| p.to_string()).collect();
840                    out.send(&Response::error(format!(
841                        "this executable provides {}, not {want}",
842                        list.join(", ")
843                    )));
844                    std::process::exit(1);
845                }
846            },
847        };
848        // `i` indexes `provides`, which lists `plugins` (asserted non-empty) in order.
849        let (about, h) = plugins[i].handler();
850        let _ = shared().name.set(about.name.to_string());
851        out.send(&Response::Hello {
852            protocol_version: chosen,
853            kind: h.kind(),
854            name: about.name.to_string(),
855            version: about.version.to_string(),
856            capabilities: about
857                .capabilities
858                .iter()
859                .chain(std::iter::once(&CAP_VALIDATE))
860                .map(|c| c.to_string())
861                .collect(),
862            provides: if provides.len() > 1 { provides } else { Vec::new() },
863        });
864        shared().version.store(chosen, Ordering::SeqCst);
865        break i;
866    };
867    let (about, h) = plugins[chosen].handler();
868    serve(about.name, h, input, out)
869}
870
871/// The next request, or `None` after answering one that can't be read. Exits at end of input.
872fn next_request(input: &mut Input, out: &mut Output) -> Option<Request> {
873    let frame = match input.next() {
874        Ok(f) => f,
875        Err(FrameError::Eof) => std::process::exit(0),
876        Err(e) => {
877            eprintln!("{e}");
878            out.send(&Response::error(e.to_string()));
879            std::process::exit(2);
880        }
881    };
882    match frame {
883        Frame::Json(v) => match serde_json::from_value::<Request>(v.clone()) {
884            Ok(r) => Some(r),
885            Err(_) => {
886                let t = v.get("type").and_then(Value::as_str).unwrap_or("?").to_string();
887                out.send(&Response::error(format!("unsupported request `{t}`")));
888                None
889            }
890        },
891        Frame::Arrow(_) => {
892            out.send(&Response::error("unexpected Arrow frame"));
893            None
894        }
895    }
896}
897
898/// Serve requests after the handshake.
899fn serve(name: &str, mut h: Handler<'_>, mut input: Input, mut out: Output) -> ! {
900    loop {
901        let frame = match input.next() {
902            Ok(f) => f,
903            Err(FrameError::Eof) => {
904                if let Handler::Source(src) = &mut h {
905                    src.close();
906                }
907                std::process::exit(0)
908            }
909            Err(e) => {
910                eprintln!("{e}");
911                out.send(&Response::error(e.to_string()));
912                std::process::exit(2);
913            }
914        };
915        let s = shared();
916        let id = match &frame {
917            Frame::Json(v) => v.get("id").and_then(Value::as_u64).unwrap_or(0),
918            Frame::Arrow(_) => 0,
919        };
920        s.current.store(id, Ordering::SeqCst);
921        let req = match frame {
922            Frame::Json(v) => match serde_json::from_value::<Request>(v.clone()) {
923                Ok(r) => r,
924                Err(_) => {
925                    let t = v.get("type").and_then(Value::as_str).unwrap_or("?").to_string();
926                    out.send(&Response::error(format!("unsupported request `{t}`")));
927                    s.current.store(0, Ordering::SeqCst);
928                    continue;
929                }
930            },
931            Frame::Arrow(_) => {
932                out.send(&Response::error("unexpected Arrow frame"));
933                continue;
934            }
935        };
936        if let Request::Hello { .. } = req {
937            out.send(&Response::error("`hello` was already answered"));
938            s.current.store(0, Ordering::SeqCst);
939            continue;
940        }
941        if let Request::Close {} = req {
942            if let Handler::Source(src) = &mut h {
943                src.close();
944            }
945            out.send(&Response::Ok {});
946            std::process::exit(0);
947        }
948        // Cancelled before it started: it never runs.
949        let result = if cancelled() {
950            Ok(Err("cancelled before it started".into()))
951        } else {
952            // Defense in depth, not error handling: a panic is a plugin bug, and this only turns it
953            // into an error reply so the host reports it instead of a closed pipe.
954            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
955                handle(&mut h, name, req, &mut input, &mut out)
956            }))
957        };
958        match result {
959            Ok(Ok(())) => {}
960            Ok(Err(e)) => out.send(&error_reply(&e)),
961            Err(p) => {
962                let msg = p
963                    .downcast_ref::<String>()
964                    .cloned()
965                    .or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string()))
966                    .unwrap_or_else(|| "unknown panic".into());
967                out.send(&Response::Error {
968                    message: format!("plugin panicked: {msg}"),
969                    kind: Some("internal".into()),
970                    code: None,
971                });
972                std::process::exit(101);
973            }
974        }
975        s.current.store(0, Ordering::SeqCst);
976    }
977}
978
979/// The declared fields' generic checks, then the plugin's own rules (when the generic ones
980/// passed), with secret values redacted.
981fn check_connection(
982    h: &Handler<'_>,
983    name: &str,
984    connection: &Map<String, Value>,
985    unresolved: &[String],
986) -> Result<crate::connection::Checked> {
987    let (kind, fields) = match h {
988        Handler::Source(s) => (Kind::Source, s.connection_fields()),
989        Handler::Destination(d) => (Kind::Destination, d.connection_fields()),
990        Handler::Format(_) => return Err("a format has no connection settings".into()),
991    };
992    let mut c = crate::connection::check(kind, name, &fields, connection, unresolved);
993    if c.errors.is_empty() {
994        // An unresolved value arrives as `null`: set, but not known (a rule about two keys
995        // going together checks `contains_key`).
996        c.errors = match h {
997            Handler::Source(s) => s.validate_connection(connection),
998            Handler::Destination(d) => d.validate_connection(connection),
999            Handler::Format(_) => Vec::new(),
1000        };
1001    }
1002    c.errors = crate::connection::redact(c.errors, &fields, connection);
1003    c.warnings = crate::connection::redact(c.warnings, &fields, connection);
1004    Ok(c)
1005}
1006
1007fn handle(h: &mut Handler<'_>, name: &str, req: Request, input: &mut Input, out: &mut Output) -> Result<()> {
1008    if let Request::Describe {} = req {
1009        let connection_fields = match h {
1010            Handler::Source(s) => s.connection_fields(),
1011            Handler::Destination(d) => d.connection_fields(),
1012            Handler::Format(_) => Vec::new(),
1013        };
1014        let (identifier_quote, string_literal, single_writer) = match h {
1015            Handler::Source(s) => (
1016                s.identifier_quote().map(str::to_string),
1017                s.string_literal().map(str::to_string),
1018                s.single_writer(),
1019            ),
1020            _ => (None, None, None),
1021        };
1022        let message_limit = match h {
1023            Handler::Destination(d) => d.message_limit(),
1024            _ => None,
1025        };
1026        out.send(&Response::Describe {
1027            connection_fields,
1028            option_fields: h.option_fields(),
1029            identifier_quote,
1030            string_literal,
1031            message_limit,
1032            single_writer,
1033        });
1034        return Ok(());
1035    }
1036    if let Request::Validate { options, template } = req {
1037        let mut errors = h.check(name, &options);
1038        let mut warnings = Vec::new();
1039        if let (Handler::Format(f), Some(t), true) = (&*h, &template, errors.is_empty()) {
1040            let (e, w) = f.validate_template(&options, t);
1041            errors.extend(e);
1042            warnings.extend(w);
1043        }
1044        out.send(&Response::Validated { errors, warnings });
1045        return Ok(());
1046    }
1047    if let Request::ValidateConnection {
1048        connection,
1049        unresolved,
1050    } = req
1051    {
1052        let c = check_connection(h, name, &connection, &unresolved)?;
1053        out.send(&Response::Validated {
1054            errors: c.errors,
1055            warnings: c.warnings,
1056        });
1057        return Ok(());
1058    }
1059    // The same checks before connecting: errors stop it, warnings are logged.
1060    if let Request::Open { connection, .. } | Request::Deliver { connection, .. } = &req {
1061        let c = check_connection(h, name, connection, &[])?;
1062        for w in &c.warnings {
1063            crate::log::warn!("{w}");
1064        }
1065        if !c.errors.is_empty() {
1066            return Err(PluginError::new(ErrorKind::Config, c.errors.join("; ")).into());
1067        }
1068    }
1069    let checked = match &req {
1070        Request::Write { options, .. } | Request::Deliver { options, .. } => h.checked(name, options),
1071        _ => Ok(()),
1072    };
1073    match (h, req) {
1074        (
1075            Handler::Source(s),
1076            Request::Open {
1077                connection,
1078                read_only,
1079                sandbox,
1080            },
1081        ) => {
1082            if sandbox {
1083                s.open_sandboxed(&connection)?;
1084            } else {
1085                s.open(&connection, read_only)?;
1086            }
1087            out.send(&Response::Ok {});
1088        }
1089        (Handler::Source(s), Request::Load { name }) => {
1090            let ipc = match input.read()? {
1091                Frame::Arrow(ipc) => ipc,
1092                Frame::Json(v) => return Err(format!("expected the rows to load, got {v}").into()),
1093            };
1094            let (schema, batches) = frame::decode_batches(&ipc)?;
1095            let mut data = ResultSet {
1096                meta: ResultSetMeta {
1097                    name: name.clone(),
1098                    query: name.clone(),
1099                    result_index: 1,
1100                    anchor: None,
1101                    header: None,
1102                    columns: Default::default(),
1103                    autofit: None,
1104                    style: None,
1105                },
1106                schema,
1107                first: Some(batches),
1108                input,
1109                done: false,
1110            };
1111            let r = s.load(&name, &mut data);
1112            data.drain()?;
1113            let loaded = r?;
1114            out.send(&Response::Loaded {
1115                relation: loaded.relation,
1116                rows: loaded.rows,
1117                warning: loaded.warning,
1118            });
1119        }
1120        (Handler::Source(s), Request::Check { sql }) => {
1121            s.check(&sql)?;
1122            out.send(&Response::Ok {});
1123        }
1124        (Handler::Source(s), Request::Catalog { tables, schemas }) => {
1125            let catalog = s.catalog(&tables, &schemas)?;
1126            out.send(&Response::Catalog { catalog })
1127        }
1128        (Handler::Source(s), Request::Execute { sql, row_limit }) => {
1129            let mut sink = FrameSink {
1130                out,
1131                row_limit,
1132                rows: 0,
1133                state: SinkState::Idle,
1134                schema: None,
1135                sent_any: false,
1136            };
1137            let r = s.execute(&sql, row_limit, &mut sink);
1138            match (r, sink.state) {
1139                (Err(e), _) => return Err(e),
1140                (Ok(()), SinkState::Idle) => {
1141                    return Err("the source produced neither a result nor `no_result`".into());
1142                }
1143                (Ok(()), SinkState::NoResult) => {}
1144                (Ok(()), SinkState::Result) => {
1145                    if !sink.sent_any {
1146                        let schema = sink.schema.clone().expect("a result sets the schema");
1147                        sink.out.batch(&RecordBatch::new_empty(schema))?;
1148                    }
1149                    let rows = sink.rows;
1150                    sink.out.send(&Response::ResultEnd { rows });
1151                }
1152            }
1153        }
1154        (
1155            Handler::Format(f),
1156            Request::Write {
1157                path,
1158                format,
1159                options,
1160                result_sets,
1161                template,
1162                font_requests,
1163            },
1164        ) => {
1165            let req = WriteRequest {
1166                path,
1167                format,
1168                options,
1169                result_sets: result_sets.clone(),
1170                template,
1171                font_requests,
1172            };
1173            let mut sets = ResultSets {
1174                metas: result_sets.into_iter(),
1175                input,
1176                warnings: Vec::new(),
1177                finished: false,
1178            };
1179            let written = checked.and_then(|()| f.write(&req, &mut sets));
1180            // A failed write is reported at once, so core can stop streaming; this is the
1181            // request's one reply.
1182            if let Err(e) = &written {
1183                out.send(&error_reply(e));
1184            }
1185            // Consume whatever the format didn't read (after an error, possibly the rest of a
1186            // result set), so the stream stays in sync.
1187            while !sets.finished {
1188                match sets.input.read()? {
1189                    Frame::Json(v) if v.get("type").and_then(Value::as_str) == Some("finish") => break,
1190                    Frame::Json(v) if v.get("type").and_then(Value::as_str) == Some("result_set_end") => {}
1191                    Frame::Arrow(_) => {}
1192                    other => return Err(format!("expected `finish`, got {other:?}").into()),
1193                }
1194            }
1195            if let Ok(files) = written {
1196                out.send(&Response::Written {
1197                    files,
1198                    warnings: sets.warnings,
1199                });
1200            }
1201        }
1202        (
1203            Handler::Destination(d),
1204            Request::Deliver {
1205                local_path,
1206                remote_path,
1207                files,
1208                connection,
1209                options,
1210                message,
1211            },
1212        ) => {
1213            checked?;
1214            let files = match (local_path, files.is_empty(), &message) {
1215                (Some(local), true, None) => vec![DeliveryFile {
1216                    local: PathBuf::from(local),
1217                    remote: remote_path,
1218                }],
1219                (None, false, _) | (None, true, Some(_)) => files
1220                    .into_iter()
1221                    .map(|f| DeliveryFile {
1222                        local: PathBuf::from(f.local_path),
1223                        remote: f.remote_path,
1224                    })
1225                    .collect(),
1226                _ => return Err("`deliver` needs exactly one of `local_path` or `files`".into()),
1227            };
1228            let delivery = Delivery {
1229                files,
1230                connection,
1231                options,
1232                message,
1233            };
1234            crate::delivery::take_attempts();
1235            let location = match &delivery.message {
1236                Some(m) => d.deliver_message(&delivery, m)?,
1237                None => d.deliver_files(&delivery)?,
1238            };
1239            let attempts = Some(crate::delivery::take_attempts()).filter(|n| *n > 1);
1240            out.send(&Response::Delivered { location, attempts });
1241        }
1242        (h, req) => {
1243            let t = serde_json::to_value(&req)
1244                .ok()
1245                .and_then(|v| v.get("type").cloned())
1246                .unwrap_or_default();
1247            return Err(format!("a {} plugin doesn't handle {t} requests", h.kind()).into());
1248        }
1249    }
1250    Ok(())
1251}
1252
1253#[derive(PartialEq)]
1254enum SinkState {
1255    Idle,
1256    NoResult,
1257    Result,
1258}
1259
1260struct FrameSink<'a> {
1261    out: &'a mut Output,
1262    row_limit: Option<u64>,
1263    rows: u64,
1264    state: SinkState,
1265    schema: Option<SchemaRef>,
1266    sent_any: bool,
1267}
1268
1269impl ResultSink for FrameSink<'_> {
1270    fn no_result(&mut self, rows_affected: Option<u64>) -> Result<()> {
1271        if self.state != SinkState::Idle {
1272            return Err("`no_result` after the result started".into());
1273        }
1274        self.state = SinkState::NoResult;
1275        self.out.send(&Response::NoResult { rows_affected });
1276        Ok(())
1277    }
1278
1279    fn begin(&mut self, schema: SchemaRef) -> Result<()> {
1280        if self.state != SinkState::Idle {
1281            return Err("a statement can only produce one result".into());
1282        }
1283        self.state = SinkState::Result;
1284        self.out.send(&Response::Result {
1285            columns: schema.fields().iter().map(|f| f.name().clone()).collect(),
1286        });
1287        self.schema = Some(schema);
1288        Ok(())
1289    }
1290
1291    fn batch(&mut self, b: RecordBatch) -> Result<bool> {
1292        if self.state != SinkState::Result {
1293            return Err("`batch` before `begin`".into());
1294        }
1295        if self.row_limit.is_some_and(|l| self.rows >= l) {
1296            return Ok(false);
1297        }
1298        let b = match self.row_limit {
1299            Some(l) if self.rows + b.num_rows() as u64 > l => b.slice(0, (l - self.rows) as usize),
1300            _ => b,
1301        };
1302        if b.num_rows() > 0 || !self.sent_any {
1303            self.rows += b.num_rows() as u64;
1304            self.out.batch(&b)?;
1305            self.sent_any = true;
1306        }
1307        Ok(self.row_limit.is_none_or(|l| self.rows < l))
1308    }
1309}
1310
1311/// The highest version in both ranges.
1312fn negotiate(core: (u32, u32), ours: (u32, u32)) -> Option<u32> {
1313    let (lo, hi) = (core.0.max(ours.0), core.1.min(ours.1));
1314    (lo <= hi).then_some(hi)
1315}
1316
1317/// Read a string field from a connection map.
1318pub fn conn_str<'a>(c: &'a Map<String, Value>, key: &str) -> Option<&'a str> {
1319    c.get(key).and_then(Value::as_str).filter(|s| !s.is_empty())
1320}
1321
1322/// Read a required string field from a connection map.
1323pub fn conn_required<'a>(c: &'a Map<String, Value>, key: &str) -> Result<&'a str> {
1324    conn_str(c, key).ok_or_else(|| format!("the profile output needs a `{key}` field").into())
1325}
1326
1327/// Read a flag that may be written as a boolean or a string.
1328pub fn conn_bool(c: &Map<String, Value>, key: &str) -> Option<bool> {
1329    match c.get(key)? {
1330        Value::Bool(b) => Some(*b),
1331        Value::String(s) => Some(matches!(s.as_str(), "true" | "yes" | "1")),
1332        _ => None,
1333    }
1334}