Skip to content

Commit aa72356

Browse files
authored
Plugins docs (#11)
1 parent 4bdcac0 commit aa72356

File tree

5 files changed

+152
-310
lines changed

5 files changed

+152
-310
lines changed

docs/configuration/pgdog.toml/plugins.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
# Plugin settings
22

3-
!!! warning
4-
Plugins are not currently supported anymore. We will pick them up again if there is more interest
5-
from the community.
6-
7-
[Plugins](../../features/plugins/index.md) are dynamically loaded at pooler startup. These settings control which plugins are loaded. In the future, more
8-
options will be available to configure plugin behavior.
3+
[Plugins](../../features/plugins/index.md) are dynamically loaded at PgDog startup. These settings control which plugins are loaded.
94

105
Plugins are a TOML list, so for each plugin you want to enable, add a `[[plugins]]` entry to `pgdog.toml`. For example:
116

@@ -24,4 +19,12 @@ name = "alice_router"
2419
### **`name`**
2520

2621
Name of the plugin to load. This is used by PgDog to look up the shared library object in [`LD_LIBRARY_PATH`](https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html). For example, if your plugin
27-
name is `router`, PgDog will look for `librouter.so` on Linux, `librouter.dll` on Windows and `librouter.dylib` on Mac OS.
22+
name is `router`, PgDog will look for `librouter.so` on Linux, `librouter.dll` on Windows, and `librouter.dylib` on Mac OS.
23+
24+
Additionally, you can pass the relative or absolute path to the shared library itself:
25+
26+
```toml
27+
name = "/opt/plugins/librouter.so"
28+
```
29+
30+
Make sure the user running PgDog has read & execute permissions on the library.

docs/features/plugins/.pages

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
nav:
2-
- 'index.md'
3-
- 'rust.md'
42
- '...'

docs/features/plugins/c.md

Lines changed: 0 additions & 52 deletions
This file was deleted.

docs/features/plugins/index.md

Lines changed: 142 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,178 @@
11
# Plugins overview
22

3-
!!! warning
4-
Plugins are currently disabled. The C FFI interface is too awkward to pass all the required
5-
context to the query router.
3+
PgDog comes with a powerful plugin system that allows you to customize the query routing behavior. Plugins are written in Rust, compiled into shared libraries, and loaded at runtime.
64

7-
One of features that make PgDog particularly powerful is its plugin system. Users of PgDog can write plugins
8-
in any language and inject them inside the query router to direct query traffic, to rewrite queries, or to block
9-
them entirely and return custom results.
105

11-
## API
6+
## Getting started
127

13-
PgDog plugins are shared libraries loaded at application startup. They can be written in any programming language, as long
14-
as that language can be compiled to a shared library, and can expose a predefined set of C ABI-compatible functions.
8+
PgDog plugins are Rust libraries. To create a plugin, first create a project with Cargo:
159

16-
### Functions
10+
```
11+
cargo init --lib my_plugin
12+
```
1713

18-
#### `pgdog_init`
14+
#### Dependencies
1915

20-
This function is executed once when PgDog loads the plugin, at application startup. It allows to initialize any
21-
kind of internal plugin state. Execution of this function is synchronized, so it's safe to execute any thread-unsafe
16+
Inside your project's `Cargo.toml`, add the following settings and dependencies:
17+
18+
```toml
19+
[lib]
20+
crate-type = ["rlib", "cdylib"]
21+
22+
[dependencies]
23+
pgdog-plugin = "0.1.6"
24+
```
25+
26+
This turns the crate into a shared library, exposing its functions using the C ABI, which PgDog will call at runtime.
27+
28+
!!! note
29+
The `pgdog-plugin` crate is published on [crates.io](https://crates.io/crates/pgdog-plugin) and is fully documented. You can find our [Rust docs here](https://docsrs.pgdog.dev), including all dependencies like [`pg_query`](https://docsrs.pgdog.dev/pg_query/index.html).
30+
31+
### Writing plugins
32+
33+
When writing plugins, it's helpful to import most commonly used macros, functions and types. You can do so with just one line of code:
34+
35+
```rust
36+
use pgdog_plugin::prelude::*;
37+
```
38+
39+
PgDog plugins have a list of required methods they need to expose. They are called by PgDog at plugin startup and validate that it
40+
was correctly written.
41+
42+
You don't need to implement them yourself. Add the following to your plugin's `src/lib.rs` file:
43+
44+
```rust
45+
macros::plugin!();
46+
```
47+
48+
These ensure the following requirements are followed:
49+
50+
1. The plugin is compiled with the same version of the Rust compiler as PgDog itself
51+
2. They are using the same version of `pg_query`
52+
53+
See [Safety](#safety) section for more info.
54+
55+
56+
## Functions
57+
58+
### `init`
59+
60+
This function is executed once at startup, when PgDog loads the plugin. It allows to initialize any
61+
kind of internal plugin state. Execution of this function is synchronized, so it's safe to include any thread-unsafe
2262
functions or initialize synchronization primitives, like mutexes.
2363

2464

2565
This function has the following signature:
2666

27-
=== "Rust"
28-
```rust
29-
pub extern "C" fn pgdog_init() {}
30-
```
31-
=== "C/C++"
32-
```c
33-
void pgdog_init();
34-
```
67+
```rust
68+
#[init]
69+
fn init() {
70+
// Perform any initialization routines here.
71+
}
72+
```
3573

3674

37-
#### `pgdog_route_query`
75+
### `route`
3876

39-
This function is called every time the query router sees a new query and needs to figure out
40-
where this query should be sent. The query text and parameters will be provided and the router
41-
expects the plugin to parse the query and provide a route.
77+
This function is called every time the query router processes a query and needs to figure out
78+
where this query should be sent.
4279

4380
This function has the following signature:
4481

45-
=== "Rust"
46-
```rust
47-
use pgdog_plugin::*;
82+
```rust
83+
#[route]
84+
fn route(context: Context) -> Route {
85+
Route::unknown()
86+
}
87+
```
88+
89+
#### Inputs
90+
91+
The [`Context`](https://docsrs.pgdog.dev/pgdog_plugin/context/struct.Context.html) struct provides the following information:
92+
93+
- Number of shards in the database cluster
94+
- Does the cluster have replicas
95+
- The Abstract Syntax Tree (AST) of the statement, parsed by `pg_query`
4896

49-
pub extern "C" fn pgdog_route_query(Input query) -> Output {
50-
Route::unknown()
51-
}
52-
```
53-
=== "C/C++"
54-
```c
55-
Output pgdog_route_query(Input query);
56-
```
5797

98+
#### Outputs
5899

59-
##### Data structures
100+
The plugin is expected to return a [`Route`](https://docsrs.pgdog.dev/pgdog_plugin/context/struct.Route.html). It can pass the following information back to PgDog:
60101

61-
This function expects an input of type `Input` and must return a struct of type `Output`. The input contains
62-
the query PgDog received and the current database configuration, e.g. number of shards, replicas, and if there
63-
is a primary database that can serve writes.
102+
- Which shard(s) to send the query to
103+
- Is the query a read or a write, sending it to a replica or the primary, respectively
64104

65-
The output structure contains the routing decision (e.g. query should go to a replica) and any additional information that the plugin wants to communicate, which depends on the routing decision. For example,
66-
if the plugin wants PgDog to intercept this query and return a custom result, rows of that result will be
67-
included in the output.
105+
Both of these are optional. If you don't return either one, the plugin doesn't influence the routing decision at all and can be used for logging queries, or some other purpose.
68106

69107

70-
#### `pgdog_fini`
71108

72-
This function is called before the pooler is shut down. This allows plugins to perform any tasks, like saving
109+
### `fini`
110+
111+
This function is called before PgDog is shut down. It allows plugins to perform any cleanup tasks, like saving
73112
some internal state to a durable medium.
74113

75114
This function has the following signature:
76115

77-
=== "Rust"
78-
```rust
79-
pub extern "C" fn pgdog_fini() {}
80-
```
81-
=== "C/C++"
82-
```c
83-
void pgdog_fini();
84-
```
116+
```rust
117+
#[fini]
118+
fn fini() {
119+
// Any cleanup routines go here.
120+
}
121+
```
122+
123+
## Loading plugins
124+
125+
Plugins need to be compiled and placed into a folder on your machine where PgDog can find them. This can be achieved using several approaches:
126+
127+
1. Place the shared library into a standard operating system folder, e.g.: `/usr/lib` or `/lib`
128+
2. Export the plugin's parent directory into the `LD_LIBRARY_PATH` environment variable, provided to PgDog at runtime
129+
3. Pass the absolute (or relative) path to the plugin in [`pgdog.toml`](../../configuration/pgdog.toml/plugins.md)
130+
131+
!!! note
132+
Make sure to compile plugins in release mode for good performance: `cargo build --release`. The plugin's shared library will be in `target/release` folder of your Cargo project, e.g., `target/release/libmy_plugin.so`.
133+
134+
You then need to specify which plugins you'd like PgDog to load at runtime:
135+
136+
```toml
137+
[[plugins]]
138+
name = "my_plugin"
139+
```
140+
141+
This can be the name of the library (without the `lib` prefix or the `.so`/`.dylib` extension) or relative/absolute path to the shared library, for example:
142+
143+
```toml
144+
[[plugins]]
145+
name = "/usr/lib/libmy_plugin.so"
146+
```
85147

86148
## Examples
87149

88-
Example plugins written in Rust and C are
89-
included in [GitHub](https://github.com/levkk/pgdog/tree/main/examples).
150+
Example plugins written in Rust are in [GitHub](https://github.com/pgdogdev/pgdog/tree/main/plugins).
151+
152+
## Safety
153+
154+
Rust plugins can do anything. There is no virtualization layer or checks on their behavior. With great power comes great responsibility, so make sure the plugins you use are trusted (and tested).
155+
156+
This is intentional. We don't want to limit what you can do inside plugins nor are we there to tell you what you shouldn't be doing. It's your data stack, and you're the owner.
157+
158+
An additional benefit of using Rust is: plugins are very fast! If written correctly, they will have minimal to no latency impact of your database.
159+
160+
### Rust/C ABI
161+
162+
Unlike C, the Rust language doesn't have a stable ABI. Therefore, additional care needs to be taken when loading and executing routines from shared libraries. This is enforced automatically by `pgdog-plugin`, but you should still be aware of them.
163+
164+
#### Rust compiler version
165+
166+
Whatever Rust compiler version is used to build PgDog itself needs to be used to build the plugins. This is checked at runtime and plugins that don't follow this requirement are **not loaded**.
167+
168+
#### `pg_query` version
169+
170+
Since we're passing the AST itself down to the plugins, we need to make sure that the versions of the `pg_query` library used by PgDog and the plugin are the same. This is done automatically if you're using the primitives exported by the `pgdog-plugin` crate:
90171

91-
## Learn more
172+
```rust
173+
// Manually use the exported primitives.
174+
use pgdog_plugin::pg_query;
92175

93-
- [Plugins in Rust](rust.md)
94-
- [Plugins in C](c.md)
176+
// Automatically import them.
177+
use pgdog_plugin::prelude::*;
178+
```

0 commit comments

Comments
 (0)