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