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}