Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ jobs:
# TODO: Switch to Spin 4.0 when it's available
version: "canary"

- name: Run unit tests
run: go test -v -count=1 ./pg

- name: Run integration tests
run: go test -v -count=1 .
30 changes: 30 additions & 0 deletions examples/pg-outbound/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Requirements
- [**go**](https://go.dev/dl/) - v1.25+
- [**spin**](https://github.com/spinframework/spin) - Latest version
- [**docker**](https://docs.docker.com/get-started/get-docker/) - Latest version

# Usage
In a terminal window, use the below command to run PostgreSQL:
```sh
docker compose up -d
```

Then, you'll build and run your Spin app:
```sh
spin up --build
```

In another terminal window, you can interact with the Spin app:
```sh
curl localhost:3000
```

You should see the output:
```json
[{"ID":1,"Name":"Splodge","Prey":null,"IsFinicky":false},{"ID":2,"Name":"Kiki","Prey":"Cicadas","IsFinicky":false},{"ID":3,"Name":"Slats","Prey":"Temptations","IsFinicky":true},{"ID":4,"Name":"Maya","Prey":"bananas","IsFinicky":true}]
```

To stop and clean up the PostgreSQL container, run the following:
```sh
docker compose down -v
```
22 changes: 22 additions & 0 deletions examples/pg-outbound/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
services:
postgres:
image: postgres:17
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: spin_dev
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/pets.sql:/docker-entrypoint-initdb.d/pets.sql
networks:
- postgres_network

volumes:
postgres_data:

networks:
postgres_network:
driver: bridge
17 changes: 13 additions & 4 deletions examples/pg-outbound/db/pets.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
CREATE TABLE pets (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, prey VARCHAR(100), is_finicky BOOL NOT NULL);
INSERT INTO pets VALUES (1, 'Splodge', NULL, false);
INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false);
INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true);
CREATE TABLE pets (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
prey VARCHAR(100),
is_finicky BOOL NOT NULL,
timestamp TIMESTAMP
);
INSERT INTO pets VALUES (1, 'Splodge', NULL, false, '2026-04-20 12:30:00');
INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false, '2026-04-20 12:30:00');
INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true, '2026-04-20 12:30:00');

-- For creating uuids using uuid_generate_v4() in the example.
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
52 changes: 48 additions & 4 deletions examples/pg-outbound/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"net/http"
"os"
"slices"
"time"

spinhttp "github.com/spinframework/spin-go-sdk/v3/http"
"github.com/spinframework/spin-go-sdk/v3/pg"
Expand All @@ -15,19 +17,53 @@ type Pet struct {
Name string
Prey *string // nullable field must be a pointer
IsFinicky bool
Timestamp time.Time
}

func init() {
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {

// addr is the environment variable set in `spin.toml` that points to the
// address of the Mysql server.
// address of the postgres server.
addr := os.Getenv("DB_URL")

db := pg.Open(addr)
defer db.Close()

_, err := db.Query("INSERT INTO pets VALUES ($1, 'Maya', $2, $3);", int32(4), "bananas", true)
var uuid pg.UUID
if err := db.QueryRow(`SELECT uuid_generate_v4()`).Scan(&uuid); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Printf("Generated UUID: %#v\n", uuid)

// Testing Array parsing
var x []int32
if err := db.QueryRow(`SELECT ARRAY[200, 404]`).Scan(&x); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if !slices.Equal(x, []int32{200, 404}) {
http.Error(w, fmt.Sprintf("Slices aren't equal, got: %v", x), http.StatusInternalServerError)
return
}

// Testing Range parsing
var rangeInt32 pg.Int32Range
if err := db.QueryRow(`SELECT int4range(10, 20)`).Scan(&rangeInt32); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if *rangeInt32.Lower != 10 {
http.Error(w, fmt.Sprintf("Error parsing lower range, got: %v", *rangeInt32.Lower), http.StatusInternalServerError)
return
}
if *rangeInt32.Upper != 20 {
http.Error(w, fmt.Sprintf("Error parsing upper range, got: %v", *rangeInt32.Upper), http.StatusInternalServerError)
return
}

_, err := db.Exec("INSERT INTO pets (id, name, prey, is_finicky, timestamp) VALUES ($1, 'Maya', $2, $3, $4);", int32(4), "bananas", true, time.Now())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand All @@ -38,16 +74,24 @@ func init() {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()

var pets []*Pet
for rows.Next() {
var pet Pet
if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky); err != nil {
if err := rows.Scan(&pet.ID, &pet.Name, &pet.Prey, &pet.IsFinicky, &pet.Timestamp); err != nil {
fmt.Println(err)
}
pets = append(pets, &pet)
}
json.NewEncoder(w).Encode(pets)
if err := rows.Err(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(pets); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}

Expand Down
2 changes: 1 addition & 1 deletion examples/pg-outbound/spin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ route = "/..."
component = "pg-outbound"

[component.pg-outbound]
environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" }
environment = { DB_URL = "host=localhost user=postgres password=postgres dbname=spin_dev" }
source = "main.wasm"
allowed_outbound_hosts = ["postgres://localhost"]
[component.pg-outbound.build]
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ module github.com/spinframework/spin-go-sdk/v3
go 1.25.5

require go.bytecodealliance.org/pkg v0.2.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.bytecodealliance.org/pkg v0.2.1 h1:TdRagooIcCW3UmlKqVO4cDR3GNDyfDnbiBzGI6TOvyg=
go.bytecodealliance.org/pkg v0.2.1/go.mod h1:OjA+V8g3uUFixeCKFfamm6sYhTJdg8fvwEdJ2GO0GSk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading
Loading