Skip to content

Adapters

gerpo never talks to a specific driver directly — it communicates through the executor.Adapter interface. Three implementations ship in the box — all three wrap PostgreSQL drivers.

PostgreSQL-only today

The SQL gerpo emits (CAST(? AS text) inside LIKE, INSERT … RETURNING, COUNT(*) OVER (), etc.) is PostgreSQL-shaped. The bundled adapters are wrappers around PG drivers. PG-compatible databases (CockroachDB, MariaDB ≥10.5, SQLite ≥3.35) are likely to work, not formally tested. MySQL / MS SQL Server / older SQLite are not supported — see TODO for the multi-dialect backlog.

Bundled adapters

pgx v5

import (
    "github.com/insei/gerpo/executor/adapters/pgx5"
    "github.com/jackc/pgx/v5/pgxpool"
)

pool, _ := pgxpool.New(ctx, dsn)
adapter := pgx5.NewPoolAdapter(pool)

Placeholders: $1, $2, ….

pgx v4

import (
    "github.com/insei/gerpo/executor/adapters/pgx4"
    "github.com/jackc/pgx/v4/pgxpool"
)

pool, _ := pgxpool.Connect(ctx, dsn)
adapter := pgx4.NewPoolAdapter(pool)

Identical API, just a different pgx major.

database/sql

A universal adapter for any *sql.DB. Use it with a PostgreSQL driver (pq, pgx/stdlib). Defaults to ? placeholders; for PG switch to $1:

import (
    "database/sql"
    _ "github.com/jackc/pgx/v5/stdlib"

    "github.com/insei/gerpo/executor/adapters/databasesql"
    "github.com/insei/gerpo/executor/adapters/placeholder"
)

db, _ := sql.Open("pgx", dsn)
adapter := databasesql.NewAdapter(db, databasesql.WithPlaceholder(placeholder.Dollar))

The Adapter interface

To write a custom adapter — implement three methods:

type Adapter interface {
    ExecContext(ctx context.Context, query string, args ...any) (Result, error)
    QueryContext(ctx context.Context, query string, args ...any) (Rows, error)
    BeginTx(ctx context.Context) (Tx, error)
}

Result, Rows, Tx live in executor/types:

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

type Result interface {
    RowsAffected() (int64, error)
}

type Tx interface {
    ExecQuery
    Commit() error
    Rollback() error
    RollbackUnlessCommitted() error
}

Why write a custom adapter

  • Tracing — wrap an existing adapter and add spans/logs around ExecContext/QueryContext.
  • A different PostgreSQL driver — gerpo ships pgx v4, pgx v5 and database/sql; if your stack uses a different PG driver, wrap it in ~50 lines.
  • Mocks — this is exactly how the mock benchmarks and some unit tests are wired (see tests/mockdb_test.go).

A non-PG dialect (MySQL, MSSQL, ClickHouse) needs more than a new adapter — gerpo's SQL generation is PostgreSQL-shaped. See TODO.

A small tracing wrapper:

type tracingAdapter struct {
    inner executor.Adapter
    tr    trace.Tracer
}

func (a *tracingAdapter) QueryContext(ctx context.Context, q string, args ...any) (types.Rows, error) {
    ctx, span := a.tr.Start(ctx, "db.query")
    span.SetAttributes(attribute.String("db.statement", q))
    rows, err := a.inner.QueryContext(ctx, q, args...)
    if err != nil {
        span.RecordError(err)
    }
    span.End()
    return rows, err
}
// same idea for ExecContext and BeginTx

Placeholder rewriting

Internally gerpo emits ? placeholders. Each adapter decides whether to rewrite them:

  • pgx5 / pgx4 — rewrite to $1, $2, … (placeholder.Dollar).
  • databasesql — configurable via WithPlaceholder(placeholder.Question | placeholder.Dollar).

If your driver accepts ?, no rewriting is needed.