sqlx

package
v0.1.64 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package sqlx lets one store implementation serve both SQL backends.

SQLite and PostgreSQL stores in this repository were historically written twice. Comparing the two halves, the differences were never about business logic — they were the driver handle type, the placeholder syntax, the no-rows sentinel, the Exec result shape, and a handful of DDL type names. This package absorbs exactly those five things and nothing else.

Deliberately NOT abstracted:

  • Value binding and scanning. Both drivers already agree: a Go bool binds to SQLite INTEGER and PostgreSQL BOOLEAN, an INTEGER scans into *bool, and TEXT/JSON/JSONB all scan into *[]byte. Stores bind and scan plain Go types on both backends.
  • Genuinely dialect-specific migrations. Rewriting a table is a different operation in each engine; those stay behind a Dialect() check rather than pretending to be portable.
  • MongoDB. Document semantics are not a SQL dialect and stay hand-written.

Index

Constants

View Source
const (
	// TypeInt64 is a 64-bit integer column: unix timestamps, byte counts.
	TypeInt64 = "{int64}"

	// TypeBool is a boolean column. SQLite has no boolean type and stores
	// 0/1 in an INTEGER; both drivers bind and scan Go bool against it.
	TypeBool = "{bool}"

	// TypeFloat is a floating-point column: costs, scores.
	TypeFloat = "{float}"

	// TypeJSON is a JSON column that SQLite declares as JSON.
	TypeJSON = "{json}"

	// TypeJSONText is a JSON column that SQLite declares as TEXT.
	TypeJSONText = "{json_text}"

	// TypeTimestamp is an absolute-time column. Note that the two engines do
	// not agree on how a value binds to it: see TimestampArg.
	TypeTimestamp = "{timestamp}"

	// TypeSerialPK is an auto-assigned integer primary key.
	TypeSerialPK = "{serial_pk}"
)

Portable DDL type tokens.

The two backends declare the same logical column with different type names. A store writes the token; the dialect expands it. Types that are already spelled the same on both backends (TEXT, INTEGER for genuinely 32-bit counters) are written literally and need no token.

The expansions reproduce the column types the hand-written stores already used, so `CREATE TABLE IF NOT EXISTS` stays a no-op against existing databases and a fresh database gets a byte-identical schema. Both JSON tokens exist for that reason: some SQLite tables declared JSON columns as `JSON` and others as `TEXT`, and preserving each avoids changing the column affinity of tables already in the field.

Variables

View Source
var ErrNoRows = errors.New("sqlx: no rows in result set")

ErrNoRows is returned by Row.Scan when a query selected no rows. It replaces the driver-specific sql.ErrNoRows and pgx.ErrNoRows so store code has one sentinel to match with errors.Is.

Functions

func AddColumns

func AddColumns(ctx context.Context, db DB, statements ...string) error

AddColumns applies ALTER TABLE ... ADD COLUMN migrations, expanding type tokens and ignoring the error each engine returns when the column is already present.

Tolerating the error is the portable form: SQLite has no ADD COLUMN IF NOT EXISTS, so the PostgreSQL store used the IF NOT EXISTS spelling while the SQLite store pattern-matched its error text. Both engines reject a duplicate add with a recognisable message, so one list now serves both.

func IsDuplicateColumnError

func IsDuplicateColumnError(err error) bool

IsDuplicateColumnError reports whether err is an engine's complaint that a column being added already exists: SQLite says "duplicate column name", and PostgreSQL says `column "x" of relation "y" already exists`.

The "already exists" arm requires the word "column" as well. The two stores that pattern-matched this before disagreed on that point, and the loose form would let an unrelated already-exists failure pass for an applied migration.

Types

type DB

type DB interface {
	Querier

	// Dialect reports the backend, for the rare statement that cannot be
	// written portably (schema migrations, mostly).
	Dialect() Dialect

	// Schema executes DDL statements in order, expanding portable type tokens
	// (see Dialect.ExpandTypes). It is what store constructors call to create
	// their tables and indexes.
	Schema(ctx context.Context, statements ...string) error

	// InTx runs fn inside a transaction, committing when fn returns nil and
	// rolling back otherwise.
	//
	// Atomicity is guaranteed on both engines; serializability is not. SQLite
	// takes the write lock up front (BEGIN IMMEDIATE), so concurrent
	// transactions queue. PostgreSQL runs at its default READ COMMITTED, so
	// two transactions can read the same value and the second fails on a
	// constraint instead of waiting. Code that allocates a key from a MAX must
	// therefore be prepared for a conflict error rather than assuming it was
	// serialized.
	InTx(ctx context.Context, fn func(Querier) error) error
}

DB is a database handle shared by every SQL store.

func NewPostgreSQL

func NewPostgreSQL(pool *pgxpool.Pool) (DB, error)

NewPostgreSQL wraps a pgx pool. The caller retains ownership of the pool.

func NewSQLite

func NewSQLite(db *sql.DB) (DB, error)

NewSQLite wraps a SQLite handle. The caller retains ownership: closing the returned DB is not this package's concern, matching how stores have always left connection lifecycle to the storage layer.

type Dialect

type Dialect string

Dialect identifies a SQL backend.

const (
	SQLite     Dialect = "sqlite"
	PostgreSQL Dialect = "postgresql"
)

func (Dialect) ExpandTypes

func (d Dialect) ExpandTypes(statement string) string

ExpandTypes replaces portable type tokens with this dialect's column types. Statements containing no tokens are returned unchanged.

func (Dialect) NullableTimestampArg

func (d Dialect) NullableTimestampArg(t time.Time) any

NullableTimestampArg is TimestampArg, mapping the zero time to SQL NULL.

func (Dialect) TimestampArg

func (d Dialect) TimestampArg(t time.Time) any

TimestampArg converts a time into the form this dialect's TypeTimestamp column expects.

PostgreSQL binds a time.Time to TIMESTAMPTZ directly. SQLite has no real date type: these columns hold RFC3339 text, which is what the readers parse back, so the store must keep writing text rather than letting the driver choose a representation.

type Querier

type Querier interface {
	// Exec runs a statement and reports how many rows it affected.
	Exec(ctx context.Context, query string, args ...any) (int64, error)

	// Query runs a query returning multiple rows.
	Query(ctx context.Context, query string, args ...any) (Rows, error)

	// QueryRow runs a query returning at most one row. Errors surface from
	// the returned Row's Scan.
	QueryRow(ctx context.Context, query string, args ...any) Row
}

Querier runs statements against a database or an open transaction.

Queries are written with `?` placeholders regardless of backend; the PostgreSQL adapter rewrites them to $1, $2, ... in order. A `?` inside a string literal, quoted identifier, or comment is left alone.

type Row

type Row interface {
	// Scan copies the row's columns into dest. It returns ErrNoRows when the
	// query selected nothing.
	Scan(dest ...any) error
}

Row is a single-row query result.

type Rows

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Err() error
	Close()
}

Rows is a multi-row query result. Callers must Close it, and must check Err after the Next loop ends.

type Timestamp

type Timestamp struct {
	Time  time.Time
	Valid bool
	Raw   string
}

Timestamp scans a TypeTimestamp column from either engine. It is the read side of TimestampArg: PostgreSQL hands back a time.Time, SQLite the RFC3339 text that was written.

Text it cannot parse leaves Time zero and Valid false rather than failing the scan. A reader returning a page of rows should not fail the whole page because one row holds an unreadable timestamp; callers that care report Raw.

func (*Timestamp) Scan

func (t *Timestamp) Scan(src any) error

Scan implements sql.Scanner, which both drivers honour.

Directories

Path Synopsis
Package sqlxtest runs a store's test suite against every SQL dialect.
Package sqlxtest runs a store's test suite against every SQL dialect.

Jump to

Keyboard shortcuts

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