Skip to content

Commit 28fd554

Browse files
spin 3 postgres support
Signed-off-by: Michelle Dhanani <[email protected]> Co-authored-by: Brian Hardock <[email protected]>
1 parent 28b6dda commit 28fd554

File tree

17 files changed

+987
-0
lines changed

17 files changed

+987
-0
lines changed

Cargo.lock

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ name = "spin_sdk"
1919
[dependencies]
2020
anyhow = "1"
2121
async-trait = "0.1.74"
22+
chrono = "0.4.38"
2223
form_urlencoded = "1.0"
2324
spin-executor = { version = "3.0.1", path = "crates/executor" }
2425
spin-macro = { version = "3.0.1", path = "crates/macro" }
@@ -52,6 +53,7 @@ members = [
5253
"examples/key-value",
5354
"examples/mysql",
5455
"examples/postgres",
56+
"examples/postgres-v3",
5557
"examples/redis-outbound",
5658
"examples/mqtt-outbound",
5759
"examples/variables",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "wasm32-wasi"

examples/postgres-v3/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "rust-outbound-pg-v3"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
# Useful crate to handle errors.
11+
anyhow = "1"
12+
# General-purpose crate with common HTTP types.
13+
http = "1.0.0"
14+
# The Spin SDK.
15+
spin-sdk = { path = "../.." }
16+

examples/postgres-v3/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Spin Outbound PostgreSQL example
2+
3+
This example shows how to access a PostgreSQL database from Spin component.
4+
5+
## Spin up
6+
7+
From example root:
8+
9+
```
10+
createdb spin_dev
11+
psql -d spin_dev -f db/testdata.sql
12+
RUST_LOG=spin=trace spin build --up
13+
```
14+
15+
Curl the read route:
16+
17+
```
18+
$ curl -i localhost:3000/read
19+
HTTP/1.1 200 OK
20+
content-length: 501
21+
date: Sun, 25 Sep 2022 15:45:02 GMT
22+
23+
Found 2 article(s) as follows:
24+
article: Article {
25+
id: 1,
26+
title: "My Life as a Goat",
27+
content: "I went to Nepal to live as a goat, and it was much better than being a butler.",
28+
authorname: "E. Blackadder",
29+
}
30+
article: Article {
31+
id: 2,
32+
title: "Magnificent Octopus",
33+
content: "Once upon a time there was a lovely little sausage.",
34+
authorname: "S. Baldrick",
35+
}
36+
37+
(Column info: id:DbDataType::Int32, title:DbDataType::Str, content:DbDataType::Str, authorname:DbDataType::Str)
38+
```
39+
40+
Curl the write route:
41+
42+
```
43+
$ curl -i localhost:3000/write
44+
HTTP/1.1 200 OK
45+
content-length: 9
46+
date: Sun, 25 Sep 2022 15:46:22 GMT
47+
48+
Count: 3
49+
```
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
CREATE TABLE articletest (
2+
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
3+
title varchar(40) NOT NULL,
4+
content text NOT NULL,
5+
authorname varchar(40) NOT NULL ,
6+
published date NOT NULL,
7+
coauthor text
8+
);
9+
10+
INSERT INTO articletest (title, content, authorname, published) VALUES
11+
(
12+
'My Life as a Goat',
13+
'I went to Nepal to live as a goat, and it was much better than being a butler.',
14+
'E. Blackadder',
15+
'2024-11-05'
16+
),
17+
(
18+
'Magnificent Octopus',
19+
'Once upon a time there was a lovely little sausage.',
20+
'S. Baldrick',
21+
'2024-11-06'
22+
);

examples/postgres-v3/spin.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
spin_manifest_version = 2
2+
3+
[application]
4+
authors = ["Fermyon Engineering <[email protected]>"]
5+
name = "rust-outbound-pg-v3-example"
6+
version = "0.1.0"
7+
8+
[[trigger.http]]
9+
route = "/..."
10+
component = "outbound-pg"
11+
12+
[component.outbound-pg]
13+
environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" }
14+
source = "../../target/wasm32-wasi/release/rust_outbound_pg_v3.wasm"
15+
allowed_outbound_hosts = ["postgres://localhost"]
16+
[component.outbound-pg.build]
17+
command = "cargo build --target wasm32-wasi --release"

examples/postgres-v3/src/lib.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#![allow(dead_code)]
2+
use anyhow::Result;
3+
use http::{Request, Response};
4+
use spin_sdk::{
5+
http_component, pg3,
6+
pg3::{Date, Decode},
7+
};
8+
9+
// The environment variable set in `spin.toml` that points to the
10+
// address of the Pg server that the component will write to
11+
const DB_URL_ENV: &str = "DB_URL";
12+
13+
#[derive(Debug, Clone)]
14+
struct Article {
15+
id: i32,
16+
title: String,
17+
content: String,
18+
authorname: String,
19+
published: Date,
20+
coauthor: Option<String>,
21+
}
22+
23+
impl TryFrom<&pg3::Row> for Article {
24+
type Error = anyhow::Error;
25+
26+
fn try_from(row: &pg3::Row) -> Result<Self, Self::Error> {
27+
let id = i32::decode(&row[0])?;
28+
let title = String::decode(&row[1])?;
29+
let content = String::decode(&row[2])?;
30+
let authorname = String::decode(&row[3])?;
31+
let published = Date::decode(&row[4])?;
32+
let coauthor = Option::<String>::decode(&row[5])?;
33+
34+
Ok(Self {
35+
id,
36+
title,
37+
content,
38+
authorname,
39+
published,
40+
coauthor,
41+
})
42+
}
43+
}
44+
45+
#[http_component]
46+
fn process(req: Request<()>) -> Result<Response<String>> {
47+
match req.uri().path() {
48+
"/read" => read(req),
49+
"/write" => write(req),
50+
"/pg_backend_pid" => pg_backend_pid(req),
51+
_ => Ok(http::Response::builder()
52+
.status(404)
53+
.body("Not found".into())?),
54+
}
55+
}
56+
57+
fn read(_req: Request<()>) -> Result<Response<String>> {
58+
let address = std::env::var(DB_URL_ENV)?;
59+
let conn = pg3::Connection::open(&address)?;
60+
61+
let sql = "SELECT id, title, content, authorname, published, coauthor FROM articletest";
62+
let rowset = conn.query(sql, &[])?;
63+
64+
let column_summary = rowset
65+
.columns
66+
.iter()
67+
.map(format_col)
68+
.collect::<Vec<_>>()
69+
.join(", ");
70+
71+
let mut response_lines = vec![];
72+
73+
for row in rowset.rows {
74+
let article = Article::try_from(&row)?;
75+
76+
println!("article: {:#?}", article);
77+
response_lines.push(format!("article: {:#?}", article));
78+
}
79+
80+
// use it in business logic
81+
82+
let response = format!(
83+
"Found {} article(s) as follows:\n{}\n\n(Column info: {})\n",
84+
response_lines.len(),
85+
response_lines.join("\n"),
86+
column_summary,
87+
);
88+
89+
Ok(http::Response::builder().status(200).body(response)?)
90+
}
91+
92+
fn write(_req: Request<()>) -> Result<Response<String>> {
93+
let address = std::env::var(DB_URL_ENV)?;
94+
let conn = pg3::Connection::open(&address)?;
95+
96+
let sql =
97+
"INSERT INTO articletest (title, content, authorname, published) VALUES ('aaa', 'bbb', 'ccc', '2024-01-01')";
98+
let nrow_executed = conn.execute(sql, &[])?;
99+
100+
println!("nrow_executed: {}", nrow_executed);
101+
102+
let sql = "SELECT COUNT(id) FROM articletest";
103+
let rowset = conn.query(sql, &[])?;
104+
let row = &rowset.rows[0];
105+
let count = i64::decode(&row[0])?;
106+
let response = format!("Count: {}\n", count);
107+
108+
Ok(http::Response::builder().status(200).body(response)?)
109+
}
110+
111+
fn pg_backend_pid(_req: Request<()>) -> Result<Response<String>> {
112+
let address = std::env::var(DB_URL_ENV)?;
113+
let conn = pg3::Connection::open(&address)?;
114+
let sql = "SELECT pg_backend_pid()";
115+
116+
let get_pid = || {
117+
let rowset = conn.query(sql, &[])?;
118+
let row = &rowset.rows[0];
119+
120+
i32::decode(&row[0])
121+
};
122+
123+
assert_eq!(get_pid()?, get_pid()?);
124+
125+
let response = format!("pg_backend_pid: {}\n", get_pid()?);
126+
127+
Ok(http::Response::builder().status(200).body(response)?)
128+
}
129+
130+
fn format_col(column: &pg3::Column) -> String {
131+
format!("{}:{:?}", column.name, column.data_type)
132+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub mod wit {
3434
}
3535
});
3636
pub use fermyon::spin2_0_0 as v2;
37+
pub use spin::postgres::postgres as pg3;
3738
}
3839

3940
/// Needed by the export macro
@@ -101,6 +102,9 @@ pub mod redis {
101102
/// Implementation of the spin postgres db interface.
102103
pub mod pg;
103104

105+
/// Implementation of the spin postgres v3 db interface.
106+
pub mod pg3;
107+
104108
/// Implementation of the Spin MySQL database interface.
105109
pub mod mysql;
106110

0 commit comments

Comments
 (0)