pgdog_plugin/
plugin.rs

1//! PgDog's plugin interface.
2//!
3//! This loads the shared library using [`libloading`] and exposes
4//! a safe interface to the plugin's methods.
5//!
6
7use std::path::Path;
8
9use libloading::{library_filename, Library, Symbol};
10
11use crate::{PdRoute, PdRouterContext, PdStr};
12
13/// Plugin interface.
14///
15/// Methods are loaded using `libloading`. If required methods aren't found,
16/// the plugin isn't loaded. All optional methods are checked first, before being
17/// executed.
18///
19/// Using this interface is reasonably safe.
20///
21#[derive(Debug)]
22pub struct Plugin<'a> {
23    /// Plugin name.
24    name: String,
25    /// Initialization routine.
26    init: Option<Symbol<'a, unsafe extern "C" fn()>>,
27    /// Shutdown routine.
28    fini: Option<Symbol<'a, unsafe extern "C" fn()>>,
29    /// Route query.
30    route: Option<Symbol<'a, unsafe extern "C" fn(PdRouterContext, *mut PdRoute)>>,
31    /// Compiler version.
32    rustc_version: Option<Symbol<'a, unsafe extern "C" fn(*mut PdStr)>>,
33    /// Plugin version.
34    plugin_version: Option<Symbol<'a, unsafe extern "C" fn(*mut PdStr)>>,
35}
36
37impl<'a> Plugin<'a> {
38    /// Load plugin's shared library using a cross-platform naming convention.
39    ///
40    /// Plugin has to be in `LD_LIBRARY_PATH`, in a standard location
41    /// for the operating system, or be provided as an absolute or relative path,
42    /// including the platform-specific extension.
43    ///
44    /// ### Example
45    ///
46    /// ```no_run
47    /// use pgdog_plugin::Plugin;
48    ///
49    /// let plugin_lib = Plugin::library("/home/pgdog/plugin.so").unwrap();
50    /// let plugin_lib = Plugin::library("plugin.so").unwrap();
51    /// ```
52    ///
53    pub fn library<P: AsRef<Path>>(name: P) -> Result<Library, libloading::Error> {
54        if name.as_ref().extension().is_some() {
55            let name = name.as_ref().display().to_string();
56            unsafe { Library::new(&name) }
57        } else {
58            let name = library_filename(name.as_ref());
59            unsafe { Library::new(name) }
60        }
61    }
62
63    /// Load standard plugin methods from the plugin library.
64    ///
65    /// ### Arguments
66    ///
67    /// * `name`: Plugin name. Can be any name you want, it's only used for logging.
68    /// * `library`: `libloading::Library` reference. Must have the same, ideally static, lifetime as the plugin.
69    ///
70    pub fn load(name: &str, library: &'a Library) -> Self {
71        let init = unsafe { library.get(b"pgdog_init\0") }.ok();
72        let fini = unsafe { library.get(b"pgdog_fini\0") }.ok();
73        let route = unsafe { library.get(b"pgdog_route\0") }.ok();
74        let rustc_version = unsafe { library.get(b"pgdog_rustc_version\0") }.ok();
75        let plugin_version = unsafe { library.get(b"pgdog_plugin_version\0") }.ok();
76
77        Self {
78            name: name.to_owned(),
79            init,
80            fini,
81            route,
82            rustc_version,
83            plugin_version,
84        }
85    }
86
87    /// Execute plugin's initialization routine.
88    /// Returns true if the route exists and was executed, false otherwise.
89    pub fn init(&self) -> bool {
90        if let Some(init) = &self.init {
91            unsafe {
92                init();
93            }
94            true
95        } else {
96            false
97        }
98    }
99
100    /// Execute plugin's shutdown routine.
101    pub fn fini(&self) {
102        if let Some(ref fini) = &self.fini {
103            unsafe { fini() }
104        }
105    }
106
107    /// Execute plugin's route routine. Determines where a statement should be sent.
108    /// Returns a route if the routine is defined, or `None` if not.
109    ///
110    /// ### Arguments
111    ///
112    /// * `context`: Statement context created by PgDog's query router.
113    ///
114    pub fn route(&self, context: PdRouterContext) -> Option<PdRoute> {
115        if let Some(ref route) = &self.route {
116            let mut output = PdRoute::default();
117            unsafe {
118                route(context, &mut output as *mut PdRoute);
119            }
120            Some(output)
121        } else {
122            None
123        }
124    }
125
126    /// Returns plugin's name. This  is the same name as what
127    /// is passed to [`Plugin::load`] function.
128    pub fn name(&self) -> &str {
129        &self.name
130    }
131
132    /// Returns the Rust compiler version used to build the plugin.
133    /// This version must match the compiler version used to build
134    /// PgDog, or the plugin won't be loaded.
135    pub fn rustc_version(&self) -> Option<PdStr> {
136        let mut output = PdStr::default();
137        self.rustc_version.as_ref().map(|rustc_version| unsafe {
138            rustc_version(&mut output);
139            output
140        })
141    }
142
143    /// Get plugin version. It's set in plugin's
144    /// `Cargo.toml`.
145    pub fn version(&self) -> Option<PdStr> {
146        let mut output = PdStr::default();
147        self.plugin_version.as_ref().map(|func| unsafe {
148            func(&mut output as *mut PdStr);
149            output
150        })
151    }
152}