Help us shape the future of Go! Take the 2026 Go Developer Survey

postgres

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package postgres connects Vukem to PostgreSQL through pgx. It is a separate module so apps that only use SQLite never download pgx.

vukem.New(vukem.WithDatabaseDrivers(postgres.Driver()))
// DATABASE_URL=postgres://user:pass@localhost:5432/app?sslmode=disable

Once Driver is registered, everything built on the database package (ORM, schema builder, migrations, database-backed stores) speaks the PostgreSQL dialect.

NewBroker is the second half of the module: an sse.Broker on LISTEN/NOTIFY, so server-sent events cross processes (web service to queue:work service) on the database the app already has.

broker, err := postgres.NewBroker(db)
hub := sse.NewHub(broker)

See https://github.com/AlexGalhardo/vukem/blob/main/docs/database.md.

Example

An app registers the driver once; DATABASE_URL then selects it by scheme.

package main

import (
	"fmt"
	"log"

	"github.com/AlexGalhardo/vukem"
	"github.com/AlexGalhardo/vukem/drivers/postgres"
)

func main() {
	// DATABASE_URL=postgres://user:pass@localhost:5432/app?sslmode=disable
	app, err := vukem.New(vukem.WithDatabaseDrivers(postgres.Driver()))
	if err != nil {
		log.Fatal(err)
	}

	db, err := app.DB()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(db.Dialect().Placeholder(1))
}
Example (Open)

Outside an app, pass the driver to database.Open.

package main

import (
	"context"
	"log"
	"os"

	"github.com/AlexGalhardo/vukem/database"
	"github.com/AlexGalhardo/vukem/drivers/postgres"
)

func main() {
	ctx := context.Background()

	db, err := database.Open(ctx, os.Getenv("DATABASE_URL"), database.WithDriver(postgres.Driver()))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	if err := db.Ping(ctx); err != nil {
		log.Print(err)
	}
}

Index

Examples

Constants

View Source
const MaxNotifyPayload = 7999

MaxNotifyPayload is the largest payload, in bytes, that Publish accepts.

PostgreSQL rejects a notification payload of 8000 bytes or more, so this is the last size it takes. The server derives that bound from its block size, so a server built with a smaller BLCKSZ accepts less and answers with its own error; the default build accepts exactly this much. Events on an SSE topic are tens of bytes, so in practice the limit only catches a mistake such as streaming a whole document instead of a progress update.

Variables

View Source
var ErrNotPostgres = errors.New("postgres: the broker needs a PostgreSQL connection through pgx")

ErrNotPostgres is returned by NewBroker when the database is not PostgreSQL, and by Broker.Subscribe when it is not reached through pgx. Both are configuration mistakes, and failing loudly beats a broker that accepts every publish and delivers nothing.

View Source
var ErrPayloadTooLarge = errors.New("postgres: payload over the NOTIFY limit")

ErrPayloadTooLarge is returned by Broker.Publish for a payload over MaxNotifyPayload. An event that large does not belong on a notification channel, so it is refused rather than truncated: a truncated event would reach the browser as valid-looking, unparseable data.

Functions

func Driver

func Driver() database.Driver

Driver returns the PostgreSQL driver for database.Open and vukem.WithDatabaseDrivers. It accepts postgres:// and postgresql:// URLs.

Example
package main

import (
	"fmt"

	"github.com/AlexGalhardo/vukem/drivers/postgres"
)

func main() {
	d := postgres.Driver()
	fmt.Println(d.SQLName, d.Schemes, d.Dialect.Placeholder(2))
}
Output:
pgx [postgres postgresql] $2

Types

type Broker added in v0.5.0

type Broker struct {
	// contains filtered or unexported fields
}

Broker carries server-sent events between processes over PostgreSQL LISTEN/NOTIFY. It implements sse.Broker, so an app whose web service and queue:work service are separate processes streams progress from the worker to the browser on the database it already has, without adding Redis purely as a message bus.

broker, err := postgres.NewBroker(db)
hub := sse.NewHub(broker)

What it costs:

  • A payload must stay at or under MaxNotifyPayload bytes (PostgreSQL refuses a NOTIFY payload of 8000 bytes or more); Publish refuses a larger one with ErrPayloadTooLarge rather than truncate it.
  • LISTEN occupies a connection for the life of the subscription, so each one is taken out of the pool (sse.Hub holds one per topic that has local subscribers and releases it when the last one leaves, so the count follows watched topics, not watching browsers). Size database.Config.MaxOpen (10 by default) for the topics an instance watches at once, plus the connections its requests need.
  • A notification is not persisted and is not replayed: a subscriber that is not connected when the event is published never sees it. The stream is the fast path; durable state belongs in a table.

func NewBroker added in v0.5.0

func NewBroker(db *database.DB, opts ...BrokerOption) (*Broker, error)

NewBroker returns a broker on db, which must be PostgreSQL. It fails rather than degrade on any other database: a broker that accepts every publish and delivers nothing looks like working code and breaks only in production, under load, in the one path nobody watches. Whether the connection really goes through pgx is only visible on a connection, so that half of ErrNotPostgres comes from Broker.Subscribe.

Example

The broker carries events between processes, so the queue:work service can stream progress to a browser connected to the web service. Both processes build the hub the same way, on the same database.

package main

import (
	"context"
	"log"
	"os"

	"github.com/AlexGalhardo/vukem/database"
	"github.com/AlexGalhardo/vukem/drivers/postgres"
	"github.com/AlexGalhardo/vukem/sse"
)

func main() {
	ctx := context.Background()

	db, err := database.Open(ctx, os.Getenv("DATABASE_URL"), database.WithDriver(postgres.Driver()))
	if err != nil {
		log.Fatal(err)
	}
	// Not PostgreSQL: the broker would accept every publish and deliver
	// nothing, so an app fails to boot instead.
	broker, err := postgres.NewBroker(db)
	if err != nil {
		log.Fatal(err)
	}

	defer db.Close()

	hub := sse.NewHub(broker)
	defer hub.Close()

	// In the worker: progress for one conversion, at most postgres.MaxNotifyPayload bytes.
	if err := hub.Publish(ctx, "conversions:42", sse.Event{Event: "progress", Data: `{"percent":10}`}); err != nil {
		log.Print(err)
	}
}

func (*Broker) Publish added in v0.5.0

func (b *Broker) Publish(ctx context.Context, channel string, payload []byte) error

Publish sends payload to every listener of channel. A payload over MaxNotifyPayload bytes is refused with ErrPayloadTooLarge.

func (*Broker) Subscribe added in v0.5.0

func (b *Broker) Subscribe(ctx context.Context, channels ...string) (<-chan []byte, error)

Subscribe delivers the payloads published on channels until ctx is done, then closes the returned channel. It returns once every LISTEN is in place, so an event published after it returns is delivered.

The subscription holds one connection of the pool until ctx is done. A notification that the returned channel has no room for is dropped rather than allowed to block that connection, which serves every other subscriber of the same channels: sse.Hub already treats a gap as a reason for the client to reconnect, and a blocked listener would also stop the server from truncating its notification queue.

type BrokerOption added in v0.5.0

type BrokerOption func(*Broker)

BrokerOption configures a Broker.

func WithBrokerLogger added in v0.5.0

func WithBrokerLogger(log *slog.Logger) BrokerOption

WithBrokerLogger sets the logger a Broker reports a subscription that ended unexpectedly on; the default is slog.Default. A nil logger is ignored.

Directories

Path Synopsis
Package pgtest gives each test its own throwaway PostgreSQL database.
Package pgtest gives each test its own throwaway PostgreSQL database.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL