Skip to content

Commit

Permalink
spin 3 postgres support
Browse files Browse the repository at this point in the history
Signed-off-by: Michelle Dhanani <michelle@fermyon.com>
Co-authored-by: Brian Hardock <brian.hardock@fermyon.com>
  • Loading branch information
michelleN and fibonacci1729 committed Nov 6, 2024
1 parent 28b6dda commit 72d948f
Show file tree
Hide file tree
Showing 17 changed files with 987 additions and 0 deletions.
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ name = "spin_sdk"
[dependencies]
anyhow = "1"
async-trait = "0.1.74"
chrono = "0.4.38"
form_urlencoded = "1.0"
spin-executor = { version = "3.0.1", path = "crates/executor" }
spin-macro = { version = "3.0.1", path = "crates/macro" }
Expand Down Expand Up @@ -52,6 +53,7 @@ members = [
"examples/key-value",
"examples/mysql",
"examples/postgres",
"examples/postgres-v3",
"examples/redis-outbound",
"examples/mqtt-outbound",
"examples/variables",
Expand Down
2 changes: 2 additions & 0 deletions examples/postgres-v3/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "wasm32-wasi"
16 changes: 16 additions & 0 deletions examples/postgres-v3/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "rust-outbound-pg-v3"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
# Useful crate to handle errors.
anyhow = "1"
# General-purpose crate with common HTTP types.
http = "1.0.0"
# The Spin SDK.
spin-sdk = { path = "../.." }

49 changes: 49 additions & 0 deletions examples/postgres-v3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Spin Outbound PostgreSQL example

This example shows how to access a PostgreSQL database from Spin component.

## Spin up

From example root:

```
createdb spin_dev
psql -d spin_dev -f db/testdata.sql
RUST_LOG=spin=trace spin build --up
```

Curl the read route:

```
$ curl -i localhost:3000/read
HTTP/1.1 200 OK
content-length: 501
date: Sun, 25 Sep 2022 15:45:02 GMT
Found 2 article(s) as follows:
article: Article {
id: 1,
title: "My Life as a Goat",
content: "I went to Nepal to live as a goat, and it was much better than being a butler.",
authorname: "E. Blackadder",
}
article: Article {
id: 2,
title: "Magnificent Octopus",
content: "Once upon a time there was a lovely little sausage.",
authorname: "S. Baldrick",
}
(Column info: id:DbDataType::Int32, title:DbDataType::Str, content:DbDataType::Str, authorname:DbDataType::Str)
```

Curl the write route:

```
$ curl -i localhost:3000/write
HTTP/1.1 200 OK
content-length: 9
date: Sun, 25 Sep 2022 15:46:22 GMT
Count: 3
```
22 changes: 22 additions & 0 deletions examples/postgres-v3/db/testdata.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
CREATE TABLE articletest (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title varchar(40) NOT NULL,
content text NOT NULL,
authorname varchar(40) NOT NULL,
published date NOT NULL,
coauthor text
);

INSERT INTO articletest (title, content, authorname) VALUES
(
'My Life as a Goat',
'I went to Nepal to live as a goat, and it was much better than being a butler.',
'E. Blackadder'
'2024-11-05'
),
(
'Magnificent Octopus',
'Once upon a time there was a lovely little sausage.',
'S. Baldrick'
'2024-11-06'
);
17 changes: 17 additions & 0 deletions examples/postgres-v3/spin.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
spin_manifest_version = 2

[application]
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
name = "rust-outbound-pg-v3-example"
version = "0.1.0"

[[trigger.http]]
route = "/..."
component = "outbound-pg"

[component.outbound-pg]
environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" }
source = "../../target/wasm32-wasi/release/rust_outbound_pg.wasm"
allowed_outbound_hosts = ["postgres://localhost"]
[component.outbound-pg.build]
command = "cargo build --target wasm32-wasi --release"
132 changes: 132 additions & 0 deletions examples/postgres-v3/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#![allow(dead_code)]
use anyhow::Result;
use http::{Request, Response};
use spin_sdk::{
http_component,
pg3::{Date, Decode},
};

// The environment variable set in `spin.toml` that points to the
// address of the Pg server that the component will write to
const DB_URL_ENV: &str = "DB_URL";

#[derive(Debug, Clone)]
struct Article {
id: i32,
title: String,
content: String,
authorname: String,
published: Date,
coauthor: Option<String>,
}

impl TryFrom<&pg::Row> for Article {
type Error = anyhow::Error;

fn try_from(row: &pg::Row) -> Result<Self, Self::Error> {
let id = i32::decode(&row[0])?;
let title = String::decode(&row[1])?;
let content = String::decode(&row[2])?;
let authorname = String::decode(&row[3])?;
let published = Date::decode(&row[4])?;
let coauthor = Option::<String>::decode(&row[5])?;

Ok(Self {
id,
title,
content,
authorname,
published,
coauthor,
})
}
}

#[http_component]
fn process(req: Request<()>) -> Result<Response<String>> {
match req.uri().path() {
"/read" => read(req),
"/write" => write(req),
"/pg_backend_pid" => pg_backend_pid(req),
_ => Ok(http::Response::builder()
.status(404)
.body("Not found".into())?),
}
}

fn read(_req: Request<()>) -> Result<Response<String>> {
let address = std::env::var(DB_URL_ENV)?;
let conn = pg::Connection::open(&address)?;

let sql = "SELECT id, title, content, authorname, coauthor FROM articletest";
let rowset = conn.query(sql, &[])?;

let column_summary = rowset
.columns
.iter()
.map(format_col)
.collect::<Vec<_>>()
.join(", ");

let mut response_lines = vec![];

for row in rowset.rows {
let article = Article::try_from(&row)?;

println!("article: {:#?}", article);
response_lines.push(format!("article: {:#?}", article));
}

// use it in business logic

let response = format!(
"Found {} article(s) as follows:\n{}\n\n(Column info: {})\n",
response_lines.len(),
response_lines.join("\n"),
column_summary,
);

Ok(http::Response::builder().status(200).body(response)?)
}

fn write(_req: Request<()>) -> Result<Response<String>> {
let address = std::env::var(DB_URL_ENV)?;
let conn = pg::Connection::open(&address)?;

let sql =
"INSERT INTO articletest (title, content, authorname, published) VALUES ('aaa', 'bbb', 'ccc', '2024-01-01')";
let nrow_executed = conn.execute(sql, &[])?;

println!("nrow_executed: {}", nrow_executed);

let sql = "SELECT COUNT(id) FROM articletest";
let rowset = conn.query(sql, &[])?;
let row = &rowset.rows[0];
let count = i64::decode(&row[0])?;
let response = format!("Count: {}\n", count);

Ok(http::Response::builder().status(200).body(response)?)
}

fn pg_backend_pid(_req: Request<()>) -> Result<Response<String>> {
let address = std::env::var(DB_URL_ENV)?;
let conn = pg::Connection::open(&address)?;
let sql = "SELECT pg_backend_pid()";

let get_pid = || {
let rowset = conn.query(sql, &[])?;
let row = &rowset.rows[0];

i32::decode(&row[0])
};

assert_eq!(get_pid()?, get_pid()?);

let response = format!("pg_backend_pid: {}\n", get_pid()?);

Ok(http::Response::builder().status(200).body(response)?)
}

fn format_col(column: &pg::Column) -> String {
format!("{}:{:?}", column.name, column.data_type)
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub mod wit {
}
});
pub use fermyon::spin2_0_0 as v2;
pub use spin::postgres::postgres as pg3;
}

/// Needed by the export macro
Expand Down Expand Up @@ -101,6 +102,9 @@ pub mod redis {
/// Implementation of the spin postgres db interface.
pub mod pg;

/// Implementation of the spin postgres v3 db interface.
pub mod pg3;

/// Implementation of the Spin MySQL database interface.
pub mod mysql;

Expand Down
Loading

0 comments on commit 72d948f

Please sign in to comment.