whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Examples
|
||||
|
||||
* chat is a command line chat program using listen/notify.
|
||||
* todo is a command line todo list that demonstrates basic CRUD actions.
|
||||
* url_shortener contains a simple example of using pgx in a web context.
|
||||
* [Tern](https://github.com/jackc/tern) is a migration tool that uses pgx.
|
||||
* [The Pithy Reader](https://github.com/jackc/tpr) is a RSS aggregator that uses pgx.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Description
|
||||
|
||||
This is a sample chat program implemented using PostgreSQL's listen/notify
|
||||
functionality with pgx.
|
||||
|
||||
Start multiple instances of this program connected to the same database to chat
|
||||
between them.
|
||||
|
||||
## Connection configuration
|
||||
|
||||
The database connection is configured via DATABASE_URL and standard PostgreSQL environment variables (PGHOST, PGUSER, etc.)
|
||||
|
||||
You can either export them then run chat:
|
||||
|
||||
export PGHOST=/private/tmp
|
||||
./chat
|
||||
|
||||
Or you can prefix the chat execution with the environment variables:
|
||||
|
||||
PGHOST=/private/tmp ./chat
|
||||
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
var pool *pgxpool.Pool
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
pool, err = pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Unable to connect to database:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go listen()
|
||||
|
||||
fmt.Println(`Type a message and press enter.
|
||||
|
||||
This message should appear in any other chat instances connected to the same
|
||||
database.
|
||||
|
||||
Type "exit" to quit.`)
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
msg := scanner.Text()
|
||||
if msg == "exit" {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
_, err = pool.Exec(context.Background(), "select pg_notify('chat', $1)", msg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error sending notification:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error scanning from stdin:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func listen() {
|
||||
conn, err := pool.Acquire(context.Background())
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error acquiring connection:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Release()
|
||||
|
||||
_, err = conn.Exec(context.Background(), "listen chat")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error listening to chat channel:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
notification, err := conn.Conn().WaitForNotification(context.Background())
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error waiting for notification:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("PID:", notification.PID, "Channel:", notification.Channel, "Payload:", notification.Payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# Description
|
||||
|
||||
This is a sample todo list implemented using pgx as the connector to a
|
||||
PostgreSQL data store.
|
||||
|
||||
# Usage
|
||||
|
||||
Create a PostgreSQL database and run structure.sql into it to create the
|
||||
necessary data schema.
|
||||
|
||||
Example:
|
||||
|
||||
createdb todo
|
||||
psql todo < structure.sql
|
||||
|
||||
Build todo:
|
||||
|
||||
go build
|
||||
|
||||
## Connection configuration
|
||||
|
||||
The database connection is configured via DATABASE_URL and standard PostgreSQL environment variables (PGHOST, PGUSER, etc.)
|
||||
|
||||
You can either export them then run todo:
|
||||
|
||||
export PGDATABASE=todo
|
||||
./todo list
|
||||
|
||||
Or you can prefix the todo execution with the environment variables:
|
||||
|
||||
PGDATABASE=todo ./todo list
|
||||
|
||||
## Add a todo item
|
||||
|
||||
./todo add 'Learn go'
|
||||
|
||||
## List tasks
|
||||
|
||||
./todo list
|
||||
|
||||
## Update a task
|
||||
|
||||
./todo update 1 'Learn more go'
|
||||
|
||||
## Delete a task
|
||||
|
||||
./todo remove 1
|
||||
|
||||
# Example Setup and Execution
|
||||
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ createdb todo
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ psql todo < structure.sql
|
||||
Expanded display is used automatically.
|
||||
Timing is on.
|
||||
CREATE TABLE
|
||||
Time: 6.363 ms
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ go build
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ export PGDATABASE=todo
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo add 'Learn Go'
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list
|
||||
1. Learn Go
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo update 1 'Learn more Go'
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list
|
||||
1. Learn more Go
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo remove 1
|
||||
jack@hk-47~/dev/go/src/github.com/jackc/pgx/examples/todo$ ./todo list
|
||||
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
var conn *pgx.Conn
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
conn, err = pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to connection to database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(os.Args) == 1 {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "list":
|
||||
err = listTasks()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to list tasks: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "add":
|
||||
err = addTask(os.Args[2])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to add task: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "update":
|
||||
n, err := strconv.ParseInt(os.Args[2], 10, 32)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable convert task_num into int32: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = updateTask(int32(n), os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to update task: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "remove":
|
||||
n, err := strconv.ParseInt(os.Args[2], 10, 32)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable convert task_num into int32: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = removeTask(int32(n))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to remove task: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "Invalid command")
|
||||
printHelp()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func listTasks() error {
|
||||
rows, _ := conn.Query(context.Background(), "select * from tasks")
|
||||
|
||||
for rows.Next() {
|
||||
var id int32
|
||||
var description string
|
||||
err := rows.Scan(&id, &description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d. %s\n", id, description)
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func addTask(description string) error {
|
||||
_, err := conn.Exec(context.Background(), "insert into tasks(description) values($1)", description)
|
||||
return err
|
||||
}
|
||||
|
||||
func updateTask(itemNum int32, description string) error {
|
||||
_, err := conn.Exec(context.Background(), "update tasks set description=$1 where id=$2", description, itemNum)
|
||||
return err
|
||||
}
|
||||
|
||||
func removeTask(itemNum int32) error {
|
||||
_, err := conn.Exec(context.Background(), "delete from tasks where id=$1", itemNum)
|
||||
return err
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Print(`Todo pgx demo
|
||||
|
||||
Usage:
|
||||
|
||||
todo list
|
||||
todo add task
|
||||
todo update task_num item
|
||||
todo remove task_num
|
||||
|
||||
Example:
|
||||
|
||||
todo add 'Learn Go'
|
||||
todo list
|
||||
`)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
create table tasks (
|
||||
id serial primary key,
|
||||
description text not null
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
# Description
|
||||
|
||||
This is a sample REST URL shortener service implemented using pgx as the connector to a PostgreSQL data store.
|
||||
|
||||
# Usage
|
||||
|
||||
Create a PostgreSQL database and run structure.sql into it to create the necessary data schema.
|
||||
|
||||
Configure the database connection with `DATABASE_URL` or standard PostgreSQL (`PG*`) environment variables or
|
||||
|
||||
Run main.go:
|
||||
|
||||
```
|
||||
go run main.go
|
||||
```
|
||||
|
||||
## Create or Update a Shortened URL
|
||||
|
||||
```
|
||||
curl -X PUT -d 'http://www.google.com' http://localhost:8080/google
|
||||
```
|
||||
|
||||
## Get a Shortened URL
|
||||
|
||||
```
|
||||
curl http://localhost:8080/google
|
||||
```
|
||||
|
||||
## Delete a Shortened URL
|
||||
|
||||
```
|
||||
curl -X DELETE http://localhost:8080/google
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/log/log15adapter"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
log "gopkg.in/inconshreveable/log15.v2"
|
||||
)
|
||||
|
||||
var db *pgxpool.Pool
|
||||
|
||||
func getUrlHandler(w http.ResponseWriter, req *http.Request) {
|
||||
var url string
|
||||
err := db.QueryRow(context.Background(), "select url from shortened_urls where id=$1", req.URL.Path).Scan(&url)
|
||||
switch err {
|
||||
case nil:
|
||||
http.Redirect(w, req, url, http.StatusSeeOther)
|
||||
case pgx.ErrNoRows:
|
||||
http.NotFound(w, req)
|
||||
default:
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func putUrlHandler(w http.ResponseWriter, req *http.Request) {
|
||||
id := req.URL.Path
|
||||
var url string
|
||||
if body, err := ioutil.ReadAll(req.Body); err == nil {
|
||||
url = string(body)
|
||||
} else {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := db.Exec(context.Background(), `insert into shortened_urls(id, url) values ($1, $2)
|
||||
on conflict (id) do update set url=excluded.url`, id, url); err == nil {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteUrlHandler(w http.ResponseWriter, req *http.Request) {
|
||||
if _, err := db.Exec(context.Background(), "delete from shortened_urls where id=$1", req.URL.Path); err == nil {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func urlHandler(w http.ResponseWriter, req *http.Request) {
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
getUrlHandler(w, req)
|
||||
|
||||
case "PUT":
|
||||
putUrlHandler(w, req)
|
||||
|
||||
case "DELETE":
|
||||
deleteUrlHandler(w, req)
|
||||
|
||||
default:
|
||||
w.Header().Add("Allow", "GET, PUT, DELETE")
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger := log15adapter.NewLogger(log.New("module", "pgx"))
|
||||
|
||||
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
|
||||
if err != nil {
|
||||
log.Crit("Unable to parse DATABASE_URL", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
poolConfig.ConnConfig.Logger = logger
|
||||
|
||||
db, err = pgxpool.ConnectConfig(context.Background(), poolConfig)
|
||||
if err != nil {
|
||||
log.Crit("Unable to create connection pool", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", urlHandler)
|
||||
|
||||
log.Info("Starting URL shortener on localhost:8080")
|
||||
err = http.ListenAndServe("localhost:8080", nil)
|
||||
if err != nil {
|
||||
log.Crit("Unable to start web server", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
create table shortened_urls (
|
||||
id text primary key,
|
||||
url text not null
|
||||
);
|
||||
Reference in New Issue
Block a user