Skip to main content

dre_protocol/
host.rs

1//! Core's side of the protocol: spawn a plugin, negotiate a version, send requests.
2//!
3//! Frames are read on a background thread so core can time out a silent plugin during the
4//! handshake and never blocks forever on a plugin that has died. stderr is drained on another
5//! thread, forwarded to a log sink and kept (last lines) for error messages. A plugin's `log` and
6//! `progress` messages (protocol 1) go to the same sink.
7
8use std::collections::VecDeque;
9use std::io::{BufRead, BufReader, BufWriter, Read, Write};
10use std::path::{Path, PathBuf};
11use std::process::{Child, ChildStdin, Command, ExitStatus, Stdio};
12use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
13use std::sync::mpsc::{Receiver, RecvTimeoutError, channel};
14use std::sync::{Arc, Mutex, OnceLock};
15use std::time::Duration;
16
17use arrow::array::RecordBatch;
18use arrow::datatypes::SchemaRef;
19use serde_json::{Map, Value};
20
21use crate::frame::{self, Frame, FrameError};
22use crate::msg::{ConnectionField, DeliveryFile, Envelope, LogLevel, Request, Response, ResultSetMeta};
23use crate::options::OptionField;
24use crate::{
25    CORE_MIN_VERSION, Kind, MAX_VERSION, PluginId, parse_executable_name, parse_package_executable_name,
26};
27
28/// Receives each line a plugin logs: `(plugin, line)`. A `log` message arrives prefixed by its
29/// level (`info: `, `warning: ` for warn and error, `debug: ` for debug and trace), as does
30/// `progress` (`info: `); a stderr line arrives as written.
31pub type LogSink = Arc<dyn Fn(&str, &str) + Send + Sync>;
32
33/// A log sink that prefixes each plugin line with the plugin's file name, on core's stderr.
34pub fn stderr_log() -> LogSink {
35    Arc::new(|plugin, line| eprintln!("[{plugin}] {line}"))
36}
37
38/// Waits for a plugin's stderr to be logged. Lines reach the [`LogSink`] from a background thread,
39/// so a line the plugin wrote before answering a request can be logged after core has the answer.
40/// A host that logs a request's lines differently (hiding them while the request carries a secret)
41/// syncs before it changes how it logs.
42#[derive(Clone)]
43pub struct StderrSync {
44    progress: Arc<StderrProgress>,
45    pipe: RawPipe,
46}
47
48impl StderrSync {
49    /// Wait up to `timeout` until every complete line the plugin has written to stderr so far has
50    /// been logged. False when the plugin kept writing (or the pipe can't be inspected) until then.
51    pub fn wait(&self, timeout: Duration) -> bool {
52        let deadline = std::time::Instant::now() + timeout;
53        loop {
54            let p = &self.progress;
55            if p.done.load(Ordering::SeqCst) {
56                return true;
57            }
58            // Blocked on an empty pipe, and still blocked in the same read after looking: every
59            // line read before that read has been logged, and nothing more is waiting.
60            let reads = p.reads.load(Ordering::SeqCst);
61            if p.reading.load(Ordering::SeqCst)
62                && pending(self.pipe) == Some(0)
63                && p.reading.load(Ordering::SeqCst)
64                && p.reads.load(Ordering::SeqCst) == reads
65            {
66                return true;
67            }
68            if std::time::Instant::now() >= deadline {
69                return false;
70            }
71            std::thread::sleep(Duration::from_millis(1));
72        }
73    }
74}
75
76/// What the stderr forwarder is doing, for [`StderrSync`].
77#[derive(Default)]
78struct StderrProgress {
79    /// In a read of the pipe: every complete line read before it has been logged.
80    reading: AtomicBool,
81    /// Reads that returned.
82    reads: AtomicU64,
83    /// At end of stream: everything has been logged.
84    done: AtomicBool,
85}
86
87/// The pipe, read through [`StderrProgress`] so [`StderrSync`] knows where the forwarder is.
88struct TrackedPipe<R> {
89    pipe: R,
90    progress: Arc<StderrProgress>,
91}
92
93impl<R: Read> Read for TrackedPipe<R> {
94    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
95        self.progress.reading.store(true, Ordering::SeqCst);
96        let n = self.pipe.read(buf);
97        self.progress.reading.store(false, Ordering::SeqCst);
98        self.progress.reads.fetch_add(1, Ordering::SeqCst);
99        n
100    }
101}
102
103#[cfg(unix)]
104type RawPipe = std::os::fd::RawFd;
105#[cfg(windows)]
106type RawPipe = usize;
107#[cfg(not(any(unix, windows)))]
108type RawPipe = ();
109
110#[cfg(unix)]
111fn raw_pipe(pipe: &std::process::ChildStderr) -> RawPipe {
112    std::os::fd::AsRawFd::as_raw_fd(pipe)
113}
114#[cfg(windows)]
115fn raw_pipe(pipe: &std::process::ChildStderr) -> RawPipe {
116    std::os::windows::io::AsRawHandle::as_raw_handle(pipe) as usize
117}
118#[cfg(not(any(unix, windows)))]
119fn raw_pipe(_: &std::process::ChildStderr) -> RawPipe {}
120
121/// Bytes waiting in the pipe, unread.
122#[cfg(unix)]
123fn pending(fd: RawPipe) -> Option<usize> {
124    let mut n: libc::c_int = 0;
125    // SAFETY: FIONREAD writes one c_int; the forwarder keeps the descriptor open until it is done.
126    let r = unsafe { libc::ioctl(fd, libc::FIONREAD, &mut n) };
127    (r == 0).then_some(n.max(0) as usize)
128}
129#[cfg(windows)]
130fn pending(handle: RawPipe) -> Option<usize> {
131    let mut n: u32 = 0;
132    // SAFETY: peeks without reading; the forwarder keeps the handle open until it is done.
133    let ok = unsafe {
134        windows_sys::Win32::System::Pipes::PeekNamedPipe(
135            handle as windows_sys::Win32::Foundation::HANDLE,
136            std::ptr::null_mut(),
137            0,
138            std::ptr::null_mut(),
139            &mut n,
140            std::ptr::null_mut(),
141        )
142    };
143    (ok != 0).then_some(n as usize)
144}
145#[cfg(not(any(unix, windows)))]
146fn pending(_: RawPipe) -> Option<usize> {
147    None
148}
149
150pub const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30);
151
152/// Set to `1` in a plugin's environment when a person is at core's terminal, else `0`.
153pub const INTERACTIVE_ENV: &str = "DRE_INTERACTIVE";
154const STDERR_TAIL: usize = 20;
155
156#[derive(Debug, Clone, PartialEq)]
157pub struct PluginInfo {
158    pub protocol_version: u32,
159    pub kind: Kind,
160    pub name: String,
161    pub version: String,
162    pub capabilities: Vec<String>,
163    /// Every plugin the executable serves (at least the one it's serving now).
164    pub provides: Vec<PluginId>,
165}
166
167/// A plugin's `describe` reply.
168#[derive(Debug, Clone, PartialEq, Default)]
169pub struct Description {
170    pub connection_fields: Vec<ConnectionField>,
171    pub option_fields: Vec<OptionField>,
172    /// A source's identifier quote character.
173    pub identifier_quote: Option<String>,
174    /// How a source wants string literals spelled (`chr` when absent; `backslash`).
175    pub string_literal: Option<String>,
176    /// A message destination's length limit, in characters.
177    pub message_limit: Option<u64>,
178}
179
180#[derive(Debug)]
181pub enum HostError {
182    Spawn {
183        plugin: String,
184        error: std::io::Error,
185    },
186    Crashed {
187        plugin: String,
188        status: Option<ExitStatus>,
189        stderr: Vec<String>,
190    },
191    Malformed {
192        plugin: String,
193        message: String,
194    },
195    Incompatible {
196        plugin: String,
197        core: (u32, u32),
198        plugin_range: (u32, u32),
199    },
200    Timeout {
201        plugin: String,
202        waiting_for: &'static str,
203    },
204    Unexpected {
205        plugin: String,
206        expected: &'static str,
207        got: String,
208    },
209    /// The plugin reported an error for a request, with its kind and code when it gave them.
210    Plugin {
211        plugin: String,
212        message: String,
213        kind: Option<String>,
214        code: Option<String>,
215    },
216    Arrow {
217        plugin: String,
218        message: String,
219    },
220}
221
222impl std::fmt::Display for HostError {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        match self {
225            HostError::Spawn { plugin, error } => write!(f, "can't start plugin `{plugin}`: {error}"),
226            HostError::Crashed {
227                plugin,
228                status,
229                stderr,
230            } => {
231                match status {
232                    Some(s) => write!(f, "plugin `{plugin}` exited unexpectedly ({s})")?,
233                    None => write!(f, "plugin `{plugin}` closed its output unexpectedly")?,
234                }
235                if !stderr.is_empty() {
236                    write!(f, "; its last log lines:\n  {}", stderr.join("\n  "))?;
237                }
238                Ok(())
239            }
240            HostError::Malformed { plugin, message } => {
241                write!(f, "plugin `{plugin}` broke the protocol: {message}")
242            }
243            HostError::Incompatible {
244                plugin,
245                core,
246                plugin_range,
247            } => {
248                write!(
249                    f,
250                    "plugin `{plugin}` speaks protocol versions {}..={}, but this DRE core speaks {}..={}; ",
251                    plugin_range.0, plugin_range.1, core.0, core.1,
252                )?;
253                if plugin_range.1 >= core.0 {
254                    return f.write_str("update DRE");
255                }
256                let package = parse_package_executable_name(plugin)
257                    .or_else(|| parse_executable_name(plugin).map(|(_, n)| n));
258                match package {
259                    Some(p) => write!(f, "update the plugin with `dre plugin update {p}`"),
260                    None => f.write_str("update the plugin"),
261                }
262            }
263            HostError::Timeout { plugin, waiting_for } => {
264                write!(
265                    f,
266                    "plugin `{plugin}` didn't answer in time (waiting for {waiting_for})"
267                )
268            }
269            HostError::Unexpected {
270                plugin,
271                expected,
272                got,
273            } => {
274                write!(f, "plugin `{plugin}` sent {got} where {expected} was expected")
275            }
276            HostError::Plugin { message, .. } => f.write_str(message),
277            HostError::Arrow { plugin, message } => {
278                write!(f, "plugin `{plugin}` sent invalid Arrow data: {message}")
279            }
280        }
281    }
282}
283
284impl std::error::Error for HostError {}
285
286impl HostError {
287    fn plugin(plugin: &str, message: impl Into<String>) -> HostError {
288        HostError::Plugin {
289            plugin: plugin.to_string(),
290            message: message.into(),
291            kind: None,
292            code: None,
293        }
294    }
295}
296
297pub type Result<T> = std::result::Result<T, HostError>;
298
299/// Asks a running request to stop, from any thread (see [`PluginProcess::canceller`]), and
300/// can kill the plugin.
301#[derive(Clone)]
302pub struct Canceller {
303    stdin: Arc<Mutex<Option<BufWriter<ChildStdin>>>>,
304    current: Arc<AtomicU64>,
305    child: Arc<Mutex<Child>>,
306}
307
308impl Canceller {
309    /// Send `cancel` for the request running now, if any. Returns whether one was sent.
310    pub fn cancel(&self) -> bool {
311        let id = self.current.load(Ordering::SeqCst);
312        if id == 0 {
313            return false;
314        }
315        let mut stdin = self.stdin.lock().unwrap();
316        let Some(w) = stdin.as_mut() else {
317            return false;
318        };
319        frame::write_json(w, &Envelope::new(Some(id), Request::Cancel {})).is_ok()
320    }
321
322    /// Kill the plugin process.
323    pub fn kill(&self) {
324        if let Ok(mut c) = self.child.lock() {
325            let _ = c.kill();
326        }
327    }
328}
329
330/// Every plugin process this program has running, by a number of its own, so a cancelled run
331/// can reach all of them wherever they were started.
332static RUNNING: Mutex<Vec<(u64, Canceller)>> = Mutex::new(Vec::new());
333static NEXT_PROCESS: AtomicU64 = AtomicU64::new(1);
334
335/// Send `cancel` to every running plugin's current request (protocol 1). Returns how many had one.
336pub fn cancel_all() -> usize {
337    let all: Vec<Canceller> = RUNNING.lock().unwrap().iter().map(|(_, c)| c.clone()).collect();
338    all.iter().filter(|c| c.cancel()).count()
339}
340
341/// Kill every running plugin process.
342pub fn kill_all() {
343    let all: Vec<Canceller> = RUNNING.lock().unwrap().iter().map(|(_, c)| c.clone()).collect();
344    for c in all {
345        c.kill();
346    }
347}
348
349/// How many plugin processes are running.
350pub fn running() -> usize {
351    RUNNING.lock().unwrap().len()
352}
353/// A message from the plugin.
354pub enum Incoming {
355    Json(Response),
356    Arrow(Vec<u8>),
357}
358
359impl std::fmt::Debug for Incoming {
360    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361        match self {
362            Incoming::Json(r) => write!(f, "Json({r:?})"),
363            Incoming::Arrow(b) => write!(f, "Arrow frame ({} bytes)", b.len()),
364        }
365    }
366}
367
368/// The outcome of a source `execute`.
369#[derive(Debug)]
370pub enum Execution {
371    NoResult { rows_affected: Option<u64> },
372    Result { schema: SchemaRef, rows: u64 },
373}
374
375pub struct PluginProcess {
376    label: String,
377    path: PathBuf,
378    /// Shared with [`Canceller`]s, which can kill it.
379    child: Arc<Mutex<Child>>,
380    /// This process's entry in [`RUNNING`].
381    registered: u64,
382    /// Shared with [`Canceller`]s; `None` once closed.
383    stdin: Arc<Mutex<Option<BufWriter<ChildStdin>>>>,
384    rx: Receiver<std::result::Result<Frame, FrameError>>,
385    log: LogSink,
386    /// The last request id given out.
387    last_id: u64,
388    /// The request running now (0: none).
389    current: Arc<AtomicU64>,
390    stderr: Arc<Mutex<VecDeque<String>>>,
391    stderr_sync: StderrSync,
392    /// Forwards stderr; joined on close/drop so no log line is lost when the plugin exits.
393    stderr_thread: Option<std::thread::JoinHandle<()>>,
394    info: Option<PluginInfo>,
395    /// A reply that arrived while core was still sending (a format failing mid-stream).
396    early: Option<std::result::Result<Frame, FrameError>>,
397    /// How many tries the last delivery took (1 without a retry).
398    last_attempts: u32,
399    /// The plugin asked for in the handshake. By default the one a `dre-<kind>-<name>` file name
400    /// names; none for a package executable, which then serves its first plugin.
401    serve: Option<PluginId>,
402}
403
404impl PluginProcess {
405    /// Spawn the plugin at `path` and complete the handshake.
406    pub fn start(path: &Path, log: LogSink) -> Result<PluginProcess> {
407        Self::start_in(path, log, None)
408    }
409
410    /// Like `start`, running the plugin in `cwd` (core uses the project directory).
411    pub fn start_in(path: &Path, log: LogSink, cwd: Option<&Path>) -> Result<PluginProcess> {
412        Self::start_for(path, None, log, cwd)
413    }
414
415    /// Like `start_in`, asking the executable for `plugin` (one of a package's plugins).
416    pub fn start_for(
417        path: &Path,
418        plugin: Option<&PluginId>,
419        log: LogSink,
420        cwd: Option<&Path>,
421    ) -> Result<PluginProcess> {
422        let timeout = std::env::var("DRE_PLUGIN_HANDSHAKE_TIMEOUT_MS")
423            .ok()
424            .and_then(|v| v.parse().ok())
425            .map(Duration::from_millis)
426            .unwrap_or(DEFAULT_HANDSHAKE_TIMEOUT);
427        let mut p = Self::spawn_in(path, log, &[], cwd)?;
428        if let Some(id) = plugin {
429            p.serve = Some(id.clone());
430        }
431        p.handshake((CORE_MIN_VERSION, MAX_VERSION), timeout)?;
432        Ok(p)
433    }
434
435    /// Spawn and handshake offering an explicit version range (the conformance suite uses this).
436    pub fn start_with(
437        path: &Path,
438        log: LogSink,
439        versions: (u32, u32),
440        timeout: Duration,
441    ) -> Result<PluginProcess> {
442        let mut p = Self::spawn(path, log)?;
443        p.handshake(versions, timeout)?;
444        Ok(p)
445    }
446
447    /// Spawn without a handshake (for tests that probe raw protocol behaviour).
448    pub fn spawn(path: &Path, log: LogSink) -> Result<PluginProcess> {
449        Self::spawn_env(path, log, &[])
450    }
451
452    /// Spawn with extra environment variables, without a handshake.
453    pub fn spawn_env(path: &Path, log: LogSink, env: &[(&str, &str)]) -> Result<PluginProcess> {
454        Self::spawn_in(path, log, env, None)
455    }
456
457    /// Spawn with extra environment variables and a working directory, without a handshake.
458    pub fn spawn_in(
459        path: &Path,
460        log: LogSink,
461        env: &[(&str, &str)],
462        cwd: Option<&Path>,
463    ) -> Result<PluginProcess> {
464        let label = path
465            .file_name()
466            .map(|f| f.to_string_lossy().to_string())
467            .unwrap_or_default();
468        let mut cmd = Command::new(path);
469        if let Some(dir) = cwd {
470            cmd.current_dir(dir);
471        }
472        // Plugins can't see the terminal (their stdio is piped), so tell them whether a person is
473        // there, e.g. to finish a browser sign-in. An existing DRE_INTERACTIVE is left alone.
474        if std::env::var_os(INTERACTIVE_ENV).is_none() {
475            use std::io::IsTerminal;
476            let there = std::io::stderr().is_terminal() && std::io::stdin().is_terminal();
477            cmd.env(INTERACTIVE_ENV, if there { "1" } else { "0" });
478        }
479        // Its own process group: Ctrl-C at the terminal reaches core, which asks the plugin to
480        // stop with `cancel`, instead of killing the plugin mid-request.
481        #[cfg(unix)]
482        {
483            use std::os::unix::process::CommandExt;
484            cmd.process_group(0);
485        }
486        #[cfg(windows)]
487        {
488            use std::os::windows::process::CommandExt;
489            const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
490            cmd.creation_flags(CREATE_NEW_PROCESS_GROUP);
491        }
492        let mut child = cmd
493            .envs(env.iter().copied())
494            .stdin(Stdio::piped())
495            .stdout(Stdio::piped())
496            .stderr(Stdio::piped())
497            .spawn()
498            .map_err(|error| HostError::Spawn {
499                plugin: label.clone(),
500                error,
501            })?;
502        let stdout = child.stdout.take().unwrap();
503        let stderr_pipe = child.stderr.take().unwrap();
504        let stdin = Arc::new(Mutex::new(child.stdin.take().map(BufWriter::new)));
505
506        let (tx, rx) = channel();
507        std::thread::spawn(move || {
508            let mut r = BufReader::new(stdout);
509            loop {
510                let f = frame::read_frame(&mut r);
511                let stop = f.is_err();
512                if tx.send(f).is_err() || stop {
513                    break;
514                }
515            }
516        });
517        let stderr = Arc::new(Mutex::new(VecDeque::new()));
518        let tail = stderr.clone();
519        let who = label.clone();
520        let sink = log.clone();
521        let stderr_sync = StderrSync {
522            progress: Arc::default(),
523            pipe: raw_pipe(&stderr_pipe),
524        };
525        let progress = stderr_sync.progress.clone();
526        let stderr_thread = std::thread::spawn(move || {
527            let mut reader = BufReader::new(TrackedPipe {
528                pipe: stderr_pipe,
529                progress: progress.clone(),
530            });
531            for line in reader.by_ref().lines() {
532                let Ok(line) = line else { break };
533                sink(&who, &line);
534                let mut t = tail.lock().unwrap();
535                if t.len() == STDERR_TAIL {
536                    t.pop_front();
537                }
538                t.push_back(line);
539            }
540            // Before the pipe closes, so a sync never inspects a closed descriptor.
541            progress.done.store(true, Ordering::SeqCst);
542            drop(reader);
543        });
544        let serve = parse_executable_name(&label).map(|(k, n)| PluginId::new(k, n));
545        let child = Arc::new(Mutex::new(child));
546        let current = Arc::new(AtomicU64::new(0));
547        let registered = NEXT_PROCESS.fetch_add(1, Ordering::SeqCst);
548        RUNNING.lock().unwrap().push((
549            registered,
550            Canceller {
551                stdin: stdin.clone(),
552                current: current.clone(),
553                child: child.clone(),
554            },
555        ));
556        Ok(PluginProcess {
557            label,
558            path: path.to_path_buf(),
559            child,
560            registered,
561            stdin,
562            rx,
563            log,
564            last_id: 0,
565            current,
566            stderr,
567            stderr_sync,
568            stderr_thread: Some(stderr_thread),
569            info: None,
570            early: None,
571            last_attempts: 1,
572            serve,
573        })
574    }
575
576    /// Name the plugin to ask for in the handshake (`None`: a package's first).
577    pub fn ask_for(&mut self, plugin: Option<PluginId>) {
578        self.serve = plugin;
579    }
580
581    /// Complete the handshake on a process from `spawn`/`spawn_env`.
582    pub fn handshake(&mut self, (min, max): (u32, u32), timeout: Duration) -> Result<()> {
583        self.send(&Request::Hello {
584            min_version: min,
585            max_version: max,
586            core_version: core_version(),
587            plugin: self.serve.clone(),
588        })?;
589        match self.recv(Some(timeout), "the hello reply")? {
590            Incoming::Json(Response::Hello {
591                protocol_version,
592                kind,
593                name,
594                version,
595                capabilities,
596                mut provides,
597            }) => {
598                if protocol_version < min || protocol_version > max {
599                    return Err(HostError::Incompatible {
600                        plugin: self.label.clone(),
601                        core: (min, max),
602                        plugin_range: (protocol_version, protocol_version),
603                    });
604                }
605                if let Some(want) = &self.serve
606                    && (want.kind != kind || want.name != name)
607                {
608                    return Err(HostError::plugin(
609                        &self.label,
610                        format!(
611                            "`{}` was asked for the {want} plugin but serves {kind}/{name}",
612                            self.label
613                        ),
614                    ));
615                }
616                if provides.is_empty() {
617                    provides.push(PluginId::new(kind, name.clone()));
618                }
619                self.info = Some(PluginInfo {
620                    protocol_version,
621                    kind,
622                    name,
623                    version,
624                    capabilities,
625                    provides,
626                });
627                Ok(())
628            }
629            Incoming::Json(Response::VersionMismatch {
630                min_version,
631                max_version,
632            }) => Err(HostError::Incompatible {
633                plugin: self.label.clone(),
634                core: (min, max),
635                plugin_range: (min_version, max_version),
636            }),
637            Incoming::Json(Response::Error { message, kind, code }) => Err(HostError::Plugin {
638                plugin: self.label.clone(),
639                message,
640                kind,
641                code,
642            }),
643            other => Err(self.unexpected("a hello reply", &other)),
644        }
645    }
646
647    pub fn label(&self) -> &str {
648        &self.label
649    }
650
651    pub fn path(&self) -> &Path {
652        &self.path
653    }
654
655    pub fn info(&self) -> &PluginInfo {
656        self.info.as_ref().expect("handshake completed")
657    }
658
659    /// The process's exit status if it has exited (the lock is held only for the call).
660    fn try_wait(&self) -> std::io::Result<Option<ExitStatus>> {
661        self.child.lock().unwrap().try_wait()
662    }
663
664    /// A handle that can cancel the running request from another thread. Only a plugin on
665    /// protocol 1 or later understands `cancel`.
666    pub fn canceller(&self) -> Canceller {
667        Canceller {
668            stdin: self.stdin.clone(),
669            current: self.current.clone(),
670            child: self.child.clone(),
671        }
672    }
673
674    /// A handle that waits for the plugin's stderr to be logged; see [`StderrSync`].
675    pub fn stderr_sync(&self) -> StderrSync {
676        self.stderr_sync.clone()
677    }
678
679    /// The protocol version settled on in the handshake (0 before it).
680    fn version(&self) -> u32 {
681        self.info.as_ref().map_or(0, |i| i.protocol_version)
682    }
683
684    pub fn has(&self, capability: &str) -> bool {
685        self.info
686            .as_ref()
687            .is_some_and(|i| i.capabilities.iter().any(|c| c == capability))
688    }
689
690    fn unexpected(&self, expected: &'static str, got: &Incoming) -> HostError {
691        let got = match got {
692            Incoming::Json(r) => format!("{:?}", r)
693                .split([' ', '{'])
694                .next()
695                .unwrap_or("?")
696                .to_string(),
697            Incoming::Arrow(_) => "Arrow data".into(),
698        };
699        HostError::Unexpected {
700            plugin: self.label.clone(),
701            expected,
702            got: format!("`{got}`"),
703        }
704    }
705
706    fn crashed(&mut self) -> HostError {
707        // Give the process a moment to finish exiting so its status and last stderr are known.
708        let mut status = None;
709        for _ in 0..50 {
710            if let Ok(Some(s)) = self.try_wait() {
711                status = Some(s);
712                break;
713            }
714            std::thread::sleep(Duration::from_millis(20));
715        }
716        std::thread::sleep(Duration::from_millis(50));
717        let stderr = self.stderr.lock().unwrap().iter().cloned().collect();
718        HostError::Crashed {
719            plugin: self.label.clone(),
720            status,
721            stderr,
722        }
723    }
724
725    /// Send a request. From protocol 1 it carries an id: a new one, or for the messages that
726    /// continue a request (`result_set_end`, `finish`, `cancel`) the running request's.
727    pub fn send(&mut self, req: &Request) -> Result<()> {
728        let id = match req {
729            _ if self.version() == 0 => None,
730            Request::Hello { .. } => None,
731            Request::ResultSetEnd {} | Request::Finish {} | Request::Cancel {} => {
732                Some(self.current.load(Ordering::SeqCst)).filter(|id| *id != 0)
733            }
734            _ => {
735                self.last_id += 1;
736                self.current.store(self.last_id, Ordering::SeqCst);
737                Some(self.last_id)
738            }
739        };
740        let ok = match self.stdin.lock().unwrap().as_mut() {
741            Some(w) => frame::write_json(w, &Envelope::new(id, req)).is_ok(),
742            None => false,
743        };
744        if ok { Ok(()) } else { Err(self.write_failed()) }
745    }
746
747    pub fn send_batch(&mut self, batch: &RecordBatch) -> Result<()> {
748        // A plugin that fails part-way replies before reading the rest: stop sending and
749        // surface its error rather than streaming everything into a plugin that gave up.
750        if self.early.is_none()
751            && let Ok(f) = self.rx.try_recv()
752        {
753            self.early = Some(f);
754        }
755        if let Some(Ok(Frame::Json(v))) = &self.early
756            && let Ok(Response::Error { message, kind, code }) = serde_json::from_value::<Response>(v.clone())
757        {
758            return Err(HostError::Plugin {
759                plugin: self.label.clone(),
760                message,
761                kind,
762                code,
763            });
764        }
765        let ipc = frame::encode_batch(batch).map_err(|e| HostError::Arrow {
766            plugin: self.label.clone(),
767            message: e.to_string(),
768        })?;
769        let ok = match self.stdin.lock().unwrap().as_mut() {
770            Some(w) => frame::write_arrow(w, &ipc).is_ok(),
771            None => false,
772        };
773        if ok { Ok(()) } else { Err(self.write_failed()) }
774    }
775
776    /// Writing failed: the plugin has gone away. Prefer its own error message if it sent one.
777    fn write_failed(&mut self) -> HostError {
778        match self.rx.recv_timeout(Duration::from_secs(2)) {
779            Ok(Ok(Frame::Json(v))) => match serde_json::from_value::<Response>(v) {
780                Ok(Response::Error { message, kind, code }) => HostError::Plugin {
781                    plugin: self.label.clone(),
782                    message,
783                    kind,
784                    code,
785                },
786                _ => self.crashed(),
787            },
788            _ => self.crashed(),
789        }
790    }
791
792    /// Receive the next message. `timeout` of `None` waits as long as the plugin is alive. `log`
793    /// and `progress` messages go to the log sink on the way.
794    pub fn recv(&mut self, timeout: Option<Duration>, waiting_for: &'static str) -> Result<Incoming> {
795        loop {
796            match self.recv_any(timeout, waiting_for)? {
797                Incoming::Json(Response::Log {
798                    level,
799                    message,
800                    fields,
801                }) => {
802                    (self.log)(&self.label, &log_line(level, &message, &fields));
803                }
804                Incoming::Json(Response::Progress { message, done, total }) => {
805                    (self.log)(&self.label, &progress_line(message.as_deref(), done, total));
806                }
807                other => return Ok(other),
808            }
809        }
810    }
811
812    /// The next frame's JSON as sent, with nothing skipped or checked (conformance tests).
813    pub fn recv_raw(&mut self, timeout: Duration) -> Result<Value> {
814        match self.rx.recv_timeout(timeout) {
815            Ok(Ok(Frame::Json(v))) => Ok(v),
816            Ok(Ok(Frame::Arrow(_))) => Err(HostError::Malformed {
817                plugin: self.label.clone(),
818                message: "Arrow data where a JSON message was expected".into(),
819            }),
820            Ok(Err(FrameError::Malformed(m))) => Err(HostError::Malformed {
821                plugin: self.label.clone(),
822                message: m,
823            }),
824            Err(RecvTimeoutError::Timeout) => Err(HostError::Timeout {
825                plugin: self.label.clone(),
826                waiting_for: "a message",
827            }),
828            _ => Err(self.crashed()),
829        }
830    }
831
832    fn recv_any(&mut self, timeout: Option<Duration>, waiting_for: &'static str) -> Result<Incoming> {
833        let got = match timeout {
834            _ if self.early.is_some() => self.early.take(),
835            Some(t) => match self.rx.recv_timeout(t) {
836                Ok(f) => Some(f),
837                Err(RecvTimeoutError::Timeout) => {
838                    return Err(HostError::Timeout {
839                        plugin: self.label.clone(),
840                        waiting_for,
841                    });
842                }
843                Err(RecvTimeoutError::Disconnected) => None,
844            },
845            None => self.rx.recv().ok(),
846        };
847        match got {
848            Some(Ok(Frame::Json(v))) => match serde_json::from_value::<Envelope<Response>>(v.clone()) {
849                Ok(Envelope { id: Some(id), body })
850                    if !matches!(body, Response::Log { .. } | Response::Progress { .. }) =>
851                {
852                    let current = self.current.load(Ordering::SeqCst);
853                    if id != current {
854                        return Err(HostError::Malformed {
855                            plugin: self.label.clone(),
856                            message: format!(
857                                "a reply to request {id} arrived while request {current} was running"
858                            ),
859                        });
860                    }
861                    Ok(Incoming::Json(body))
862                }
863                Ok(e) => Ok(Incoming::Json(e.body)),
864                Err(e) => Err(HostError::Malformed {
865                    plugin: self.label.clone(),
866                    message: format!("unknown message {v}: {e}"),
867                }),
868            },
869            Some(Ok(Frame::Arrow(b))) => Ok(Incoming::Arrow(b)),
870            Some(Err(FrameError::Malformed(m))) => Err(HostError::Malformed {
871                plugin: self.label.clone(),
872                message: m,
873            }),
874            Some(Err(_)) | None => Err(self.crashed()),
875        }
876    }
877
878    /// Receive a JSON response, turning `error` into `HostError::Plugin`.
879    pub fn recv_json(&mut self, waiting_for: &'static str) -> Result<Response> {
880        match self.recv(None, waiting_for)? {
881            Incoming::Json(Response::Error { message, kind, code }) => Err(HostError::Plugin {
882                plugin: self.label.clone(),
883                message,
884                kind,
885                code,
886            }),
887            Incoming::Json(r) => Ok(r),
888            other => Err(self.unexpected(waiting_for, &other)),
889        }
890    }
891
892    /// Send a request and expect `ok`.
893    fn call_ok(&mut self, req: &Request, what: &'static str) -> Result<()> {
894        self.send(req)?;
895        match self.recv_json(what)? {
896            Response::Ok {} => Ok(()),
897            other => Err(self.unexpected(what, &Incoming::Json(other))),
898        }
899    }
900
901    pub fn describe(&mut self) -> Result<Vec<ConnectionField>> {
902        Ok(self.describe_all()?.0)
903    }
904
905    /// The connection fields and the options the plugin declares.
906    pub fn describe_all(&mut self) -> Result<(Vec<ConnectionField>, Vec<OptionField>)> {
907        let d = self.description()?;
908        Ok((d.connection_fields, d.option_fields))
909    }
910
911    /// The whole `describe` reply.
912    pub fn description(&mut self) -> Result<Description> {
913        self.send(&Request::Describe {})?;
914        match self.recv_json("a describe reply")? {
915            Response::Describe {
916                connection_fields,
917                option_fields,
918                identifier_quote,
919                string_literal,
920                message_limit,
921            } => Ok(Description {
922                connection_fields,
923                option_fields,
924                identifier_quote,
925                string_literal,
926                message_limit,
927            }),
928            other => Err(self.unexpected("a describe reply", &Incoming::Json(other))),
929        }
930    }
931
932    /// Check a profile entry's connection settings without connecting (protocol 1): the errors
933    /// and the warnings. `unresolved` keys (an unset `env_var()`) are sent as `null`.
934    pub fn validate_connection(
935        &mut self,
936        mut connection: Map<String, Value>,
937        unresolved: Vec<String>,
938    ) -> Result<(Vec<String>, Vec<String>)> {
939        for k in &unresolved {
940            connection.insert(k.clone(), Value::Null);
941        }
942        self.send(&Request::ValidateConnection {
943            connection,
944            unresolved,
945        })?;
946        match self.recv_json("a validated reply")? {
947            Response::Validated { errors, warnings } => Ok((errors, warnings)),
948            other => Err(self.unexpected("a validated reply", &Incoming::Json(other))),
949        }
950    }
951
952    /// Check a config block of options (needs `validate`); returns every problem found.
953    pub fn validate(&mut self, options: Map<String, Value>) -> Result<Vec<String>> {
954        Ok(self.validate_with(options, None)?.0)
955    }
956
957    /// Check a format's config block together with its `template` (as `write` would send it);
958    /// returns the errors, then the warnings.
959    pub fn validate_with(
960        &mut self,
961        options: Map<String, Value>,
962        template: Option<Value>,
963    ) -> Result<(Vec<String>, Vec<String>)> {
964        self.send(&Request::Validate { options, template })?;
965        match self.recv_json("a validate reply")? {
966            Response::Validated { errors, warnings } => Ok((errors, warnings)),
967            other => Err(self.unexpected("a validate reply", &Incoming::Json(other))),
968        }
969    }
970
971    pub fn open(&mut self, connection: Map<String, Value>, read_only: bool) -> Result<()> {
972        self.call_ok(
973            &Request::Open {
974                connection,
975                read_only,
976                sandbox: false,
977            },
978            "an open reply",
979        )
980    }
981
982    /// Open a read-only session that may not reach files, programs or the network beyond the
983    /// database, where the plugin supports it (`dre query`).
984    pub fn open_sandboxed(&mut self, connection: Map<String, Value>) -> Result<()> {
985        self.call_ok(
986            &Request::Open {
987                connection,
988                read_only: true,
989                sandbox: true,
990            },
991            "an open reply",
992        )
993    }
994
995    pub fn check(&mut self, sql: &str) -> Result<()> {
996        self.call_ok(&Request::Check { sql: sql.to_string() }, "a check reply")
997    }
998
999    /// Run one statement, handing every batch to `on_batch` as it arrives.
1000    pub fn execute(
1001        &mut self,
1002        sql: &str,
1003        row_limit: Option<u64>,
1004        mut on_batch: impl FnMut(&SchemaRef, RecordBatch) -> std::result::Result<(), String>,
1005    ) -> Result<Execution> {
1006        self.send(&Request::Execute {
1007            sql: sql.to_string(),
1008            row_limit,
1009        })?;
1010        match self.recv_json("an execute reply")? {
1011            Response::NoResult { rows_affected } => return Ok(Execution::NoResult { rows_affected }),
1012            Response::Result { .. } => {}
1013            other => return Err(self.unexpected("an execute reply", &Incoming::Json(other))),
1014        }
1015        let mut schema: Option<SchemaRef> = None;
1016        let mut rows = 0u64;
1017        let mut sink_error = None;
1018        loop {
1019            match self.recv(None, "result data")? {
1020                Incoming::Arrow(ipc) => {
1021                    let (s, batches) = frame::decode_batches(&ipc).map_err(|e| HostError::Arrow {
1022                        plugin: self.label.clone(),
1023                        message: e.to_string(),
1024                    })?;
1025                    let schema = schema.get_or_insert(s);
1026                    for b in batches {
1027                        rows += b.num_rows() as u64;
1028                        if sink_error.is_none()
1029                            && let Err(e) = on_batch(schema, b)
1030                        {
1031                            // Keep draining so the session stays usable; report after.
1032                            sink_error = Some(e);
1033                        }
1034                    }
1035                }
1036                Incoming::Json(Response::ResultEnd { .. }) => break,
1037                Incoming::Json(Response::Error { message, kind, code }) => {
1038                    return Err(HostError::Plugin {
1039                        plugin: self.label.clone(),
1040                        message,
1041                        kind,
1042                        code,
1043                    });
1044                }
1045                other => return Err(self.unexpected("result data", &other)),
1046            }
1047        }
1048        if let Some(e) = sink_error {
1049            return Err(HostError::plugin(&self.label, e));
1050        }
1051        let schema = schema.ok_or_else(|| HostError::Malformed {
1052            plugin: self.label.clone(),
1053            message: "a result ended without any Arrow frame carrying its schema".into(),
1054        })?;
1055        Ok(Execution::Result { schema, rows })
1056    }
1057
1058    /// Start a format `write`; follow with `write_result_set` per result set, then `write_finish`.
1059    pub fn write_begin(
1060        &mut self,
1061        path: &str,
1062        format: &str,
1063        options: Map<String, Value>,
1064        result_sets: Vec<ResultSetMeta>,
1065        template: Option<Value>,
1066    ) -> Result<()> {
1067        self.send(&Request::Write {
1068            path: path.to_string(),
1069            format: format.to_string(),
1070            options,
1071            result_sets,
1072            template,
1073        })
1074    }
1075
1076    /// Stream one result set: at least one batch (carrying the schema), then the end marker.
1077    pub fn write_result_set(
1078        &mut self,
1079        schema: &SchemaRef,
1080        batches: impl IntoIterator<Item = RecordBatch>,
1081    ) -> Result<()> {
1082        let mut any = false;
1083        for b in batches {
1084            any = true;
1085            self.send_batch(&b)?;
1086        }
1087        if !any {
1088            self.send_batch(&RecordBatch::new_empty(schema.clone()))?;
1089        }
1090        self.send(&Request::ResultSetEnd {})
1091    }
1092
1093    /// Load rows into a temporary table on the source's session. Returns the relation to use in
1094    /// SQL, and the plugin's warning, if any. Only for plugins advertising `load`.
1095    pub fn load(
1096        &mut self,
1097        name: &str,
1098        schema: &SchemaRef,
1099        batches: impl IntoIterator<Item = RecordBatch>,
1100    ) -> Result<(String, Option<String>)> {
1101        self.send(&Request::Load {
1102            name: name.to_string(),
1103        })?;
1104        self.write_result_set(schema, batches)?;
1105        match self.recv_json("a loaded reply")? {
1106            Response::Loaded {
1107                relation, warning, ..
1108            } => Ok((relation, warning)),
1109            other => Err(self.unexpected("a loaded reply", &Incoming::Json(other))),
1110        }
1111    }
1112
1113    pub fn write_finish(&mut self) -> Result<Vec<String>> {
1114        Ok(self.write_finish_with_warnings()?.0)
1115    }
1116
1117    /// `write_finish`, also returning the format's warnings.
1118    pub fn write_finish_with_warnings(&mut self) -> Result<(Vec<String>, Vec<String>)> {
1119        self.send(&Request::Finish {})?;
1120        match self.recv_json("a written reply")? {
1121            Response::Written { files, warnings } => Ok((files, warnings)),
1122            other => Err(self.unexpected("a written reply", &Incoming::Json(other))),
1123        }
1124    }
1125
1126    /// Deliver one file with no plugin options.
1127    pub fn deliver(
1128        &mut self,
1129        local_path: &str,
1130        remote_path: Option<&str>,
1131        connection: Map<String, Value>,
1132    ) -> Result<String> {
1133        let file = DeliveryFile {
1134            local_path: local_path.to_string(),
1135            remote_path: remote_path.map(str::to_string),
1136        };
1137        self.deliver_files(&[file], connection, Map::new())
1138    }
1139
1140    /// Deliver `files` in one request with the destination entry's `options`. More than one file
1141    /// needs a plugin advertising `multi_file`; call once per file otherwise.
1142    pub fn deliver_files(
1143        &mut self,
1144        files: &[DeliveryFile],
1145        connection: Map<String, Value>,
1146        options: Map<String, Value>,
1147    ) -> Result<String> {
1148        let req = match files {
1149            [] => {
1150                return Err(HostError::Malformed {
1151                    plugin: self.label.clone(),
1152                    message: "nothing to deliver".into(),
1153                });
1154            }
1155            [one] => Request::Deliver {
1156                local_path: Some(one.local_path.clone()),
1157                remote_path: one.remote_path.clone(),
1158                files: Vec::new(),
1159                connection,
1160                options,
1161                message: None,
1162            },
1163            many => {
1164                if !self.has(crate::CAP_MULTI_FILE) {
1165                    return Err(HostError::plugin(
1166                        &self.label,
1167                        format!(
1168                            "{} files in one delivery, but the plugin doesn't advertise `multi_file`",
1169                            many.len()
1170                        ),
1171                    ));
1172                }
1173                Request::Deliver {
1174                    local_path: None,
1175                    remote_path: None,
1176                    files: many.to_vec(),
1177                    connection,
1178                    options,
1179                    message: None,
1180                }
1181            }
1182        };
1183        self.send(&req)?;
1184        match self.recv_json("a delivered reply")? {
1185            Response::Delivered { location, attempts } => {
1186                self.last_attempts = attempts.unwrap_or(1);
1187                Ok(location)
1188            }
1189            other => Err(self.unexpected("a delivered reply", &Incoming::Json(other))),
1190        }
1191    }
1192
1193    /// Deliver a message, with `attach` files, to a plugin advertising `message`.
1194    pub fn deliver_message(
1195        &mut self,
1196        message: &crate::msg::Message,
1197        attach: &[DeliveryFile],
1198        connection: Map<String, Value>,
1199        options: Map<String, Value>,
1200    ) -> Result<String> {
1201        if !self.has(crate::CAP_MESSAGE) {
1202            return Err(HostError::plugin(
1203                &self.label,
1204                "a message, but the plugin doesn't advertise `message`",
1205            ));
1206        }
1207        self.send(&Request::Deliver {
1208            local_path: None,
1209            remote_path: None,
1210            files: attach.to_vec(),
1211            connection,
1212            options,
1213            message: Some(message.clone()),
1214        })?;
1215        match self.recv_json("a delivered reply")? {
1216            Response::Delivered { location, attempts } => {
1217                self.last_attempts = attempts.unwrap_or(1);
1218                Ok(location)
1219            }
1220            other => Err(self.unexpected("a delivered reply", &Incoming::Json(other))),
1221        }
1222    }
1223
1224    /// How many tries the last delivery took: 1, or more when the plugin retried a temporary
1225    /// error.
1226    pub fn last_attempts(&self) -> u32 {
1227        self.last_attempts
1228    }
1229
1230    /// Ask the plugin to exit, and wait for it.
1231    pub fn close(mut self) -> Result<()> {
1232        let _ = self.send(&Request::Close {});
1233        let _ = self.recv(Some(Duration::from_secs(5)), "the close reply");
1234        self.stdin.lock().unwrap().take();
1235        for _ in 0..250 {
1236            if let Ok(Some(_)) = self.try_wait() {
1237                return Ok(());
1238            }
1239            std::thread::sleep(Duration::from_millis(20));
1240        }
1241        let _ = self.child.lock().unwrap().kill();
1242        Ok(())
1243    }
1244
1245    /// Raw access for protocol tests: write arbitrary bytes to the plugin.
1246    pub fn write_raw(&mut self, bytes: &[u8]) -> Result<()> {
1247        let ok = match self.stdin.lock().unwrap().as_mut() {
1248            Some(w) => w.write_all(bytes).and_then(|_| w.flush()).is_ok(),
1249            None => false,
1250        };
1251        if ok { Ok(()) } else { Err(self.write_failed()) }
1252    }
1253
1254    /// Wait up to `timeout` for the process to exit.
1255    pub fn wait_exit(&mut self, timeout: Duration) -> Option<ExitStatus> {
1256        self.stdin.lock().unwrap().take();
1257        let deadline = std::time::Instant::now() + timeout;
1258        while std::time::Instant::now() < deadline {
1259            if let Ok(Some(s)) = self.try_wait() {
1260                return Some(s);
1261            }
1262            std::thread::sleep(Duration::from_millis(20));
1263        }
1264        None
1265    }
1266}
1267
1268impl PluginProcess {
1269    /// Wait (briefly) for the stderr forwarder to reach end of stream.
1270    fn drain_stderr(&mut self) {
1271        if let Some(t) = self.stderr_thread.take() {
1272            let deadline = std::time::Instant::now() + Duration::from_secs(2);
1273            while !t.is_finished() && std::time::Instant::now() < deadline {
1274                std::thread::sleep(Duration::from_millis(5));
1275            }
1276            if t.is_finished() {
1277                let _ = t.join();
1278            }
1279        }
1280    }
1281}
1282
1283impl Drop for PluginProcess {
1284    fn drop(&mut self) {
1285        if let Ok(mut r) = RUNNING.lock() {
1286            r.retain(|(id, _)| *id != self.registered);
1287        }
1288        if let Ok(mut stdin) = self.stdin.lock() {
1289            stdin.take();
1290        }
1291        if let Ok(None) = self.try_wait() {
1292            // Give it a moment to exit on its own (stdin is closed), then insist.
1293            for _ in 0..25 {
1294                if let Ok(Some(_)) = self.try_wait() {
1295                    break;
1296                }
1297                std::thread::sleep(Duration::from_millis(10));
1298            }
1299            let _ = self.child.lock().unwrap().kill();
1300            let _ = self.child.lock().unwrap().wait();
1301        }
1302        self.drain_stderr();
1303    }
1304}
1305
1306/// A `log` message as a log sink line: prefixed by its level, then its fields as `key=value`.
1307fn log_line(level: LogLevel, message: &str, fields: &Map<String, Value>) -> String {
1308    let prefix = match level {
1309        LogLevel::Error | LogLevel::Warn => "warning: ",
1310        LogLevel::Info => "info: ",
1311        LogLevel::Debug | LogLevel::Trace => "debug: ",
1312    };
1313    let mut line = format!("{prefix}{message}");
1314    for (k, v) in fields {
1315        match v {
1316            Value::String(s) => line.push_str(&format!(" {k}={s}")),
1317            v => line.push_str(&format!(" {k}={v}")),
1318        }
1319    }
1320    line
1321}
1322
1323/// A `progress` message as a log sink line.
1324fn progress_line(message: Option<&str>, done: Option<u64>, total: Option<u64>) -> String {
1325    let count = match (done, total) {
1326        (Some(d), Some(t)) => format!("{d}/{t}"),
1327        (Some(d), None) => d.to_string(),
1328        _ => String::new(),
1329    };
1330    match (message, count.is_empty()) {
1331        (Some(m), true) => format!("info: {m}"),
1332        (Some(m), false) => format!("info: {m} ({count})"),
1333        (None, _) => format!("info: {count}"),
1334    }
1335}
1336
1337static CORE_VERSION: OnceLock<String> = OnceLock::new();
1338
1339/// Set the DRE version sent to plugins in the handshake (`core_version`). This crate is versioned
1340/// on its own, so the host names the version of DRE it's part of; unset, it's "unreleased".
1341pub fn set_core_version(version: &str) {
1342    let _ = CORE_VERSION.set(version.to_string());
1343}
1344
1345fn core_version() -> String {
1346    CORE_VERSION.get().cloned().unwrap_or_else(|| "unreleased".into())
1347}