sqldriver

package
v0.0.0-...-d6d8d39 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package sqldriver implements a database/sql driver for the FoundationDB Record Layer relational (SQL) layer.

Register the driver by blank-importing this package, then open a connection:

import (
    "database/sql"
    _ "fdb.dev/pkg/relational/sqldriver"
)

db, err := sql.Open("fdbsql", "fdbsql:///mydb?cluster_file=/etc/foundationdb/fdb.cluster")

DSN shape mirrors Java's JDBC URI (minus the jdbc: prefix):

fdbsql:///PATH                          — embedded, default cluster file
fdbsql:///PATH?cluster_file=/path       — embedded, explicit cluster file
fdbsql://HOST:PORT/PATH                 — remote (gRPC) — NOT YET IMPLEMENTED

This is the public entry point. Internally it wraps pkg/relational/core which implements the SQL engine over FDB.

The port follows Java's fdb-relational-* modules 1:1 wherever reasonable; database/sql compatibility is the single intentional deviation — the Go-idiomatic driver surface is at the edge, the Java surface (pkg/relational/api.Connection etc.) is preserved underneath.

Index

Constants

View Source
const DriverName = "fdbsql"

DriverName is the database/sql driver name.

View Source
const PlannerStatisticsParam = "planner_statistics"

PlannerStatisticsParam is the DSN query parameter that sets api.OptPlannerStatistics (RFC-236) on every connection the DSN opens. Spelled as the lower-cased option name, like its sibling above, so the DSN parameter and the option stay obviously the same knob.

It belongs here — decided before the first statement — because it is part of the plan-cache key: a connection that changes its mind mid-session would otherwise share cache entries with one that did not.

View Source
const RestrictDDLToSessionDatabaseParam = "restrict_ddl_to_session_database"

RestrictDDLToSessionDatabaseParam is the DSN query parameter that sets api.OptRestrictDDLToSessionDatabase on every connection the DSN opens. It is spelled as the lower-cased option name so the DSN parameter and the option stay obviously the same knob.

View Source
const Scheme = "fdbsql"

Scheme is the DSN scheme accepted by this driver. It matches Java's JDBC scheme (minus the "jdbc:" prefix, which Go's database/sql does not use).

View Source
const TransactionTagsParam = "transaction_tags"

TransactionTagsParam is the DSN query parameter that sets api.OptTransactionTags on every connection the DSN opens. Multiple tags are comma-separated: `?transaction_tags=tenant-a,bulk`.

Variables

This section is empty.

Functions

func EnableStoreTimer

func EnableStoreTimer(clusterFile string) *recordlayer.StoreTimer

EnableStoreTimer arms record-layer instrumentation for the given cluster_file key and returns the StoreTimer that collects it. Idempotent: repeat calls for the same key return the same timer, so an operator never has to reason about who called first.

This is the inversion the SQL path needs. The driver opens the record-layer database itself and caches it privately (fdbDBCache), so a tenant service that speaks only database/sql has no *recordlayer.FDBDatabase to call SetTimer on — and record-layer counters were, in consequence, unobservable from SQL. Arming by key instead of by handle works before the lazy Connect that opens the database:

timer := sqldriver.EnableStoreTimer("/etc/foundationdb/fdb.cluster")
http.Handle("/metrics/recordlayer", rlmetrics.Handler(timer))
db, _ := sql.Open("fdbsql", "fdbsql:///t/1?cluster_file=/etc/foundationdb/fdb.cluster")

SCOPE — and this is the part that decides what the numbers mean. One timer per cluster-file key means one timer per process, aggregating EVERY tenant, connection and transaction that runs against that cluster. It is deliberately not per-tenant: tenant count in a SaaS deployment is unbounded and operator-driven, so a tenant label would make the cardinality of every record-layer metric grow with the customer list — the classic way to take down a Prometheus. Per-tenant attribution is already available at a layer that can afford it, because it is sampled and log-shaped rather than a live time series: PlanGenerationLogger and ExecutionStatsLogger are installed per connection and close over the tenant ID (see docs/mt-saas.md §4).

A caller who genuinely wants finer scope has it without a label: build the *recordlayer.FDBDatabase, call SetTimer on it, and RegisterBackend it under a key of its own. That keeps the cardinality decision explicit and in the caller's hands.

func RegisterBackend

func RegisterBackend(key string, db *recordlayer.FDBDatabase) (unregister func())

RegisterBackend associates an already-built FDBDatabase with a cluster_file key, so a DSN of the form "fdbsql:///db?cluster_file=<key>" drives the full SQL engine (parser → Cascades → executor → record layer) over that backend instead of opening a real cluster. It returns a func that unregisters the key.

This is the seam for deterministic-simulation harnesses that back the SQL stack with SimFDB (RFC-199 DST): register a SimFDB-backed FDBDatabase, open a `fdbsql` DSN against its key, and the whole relational layer runs in-process with no Docker. It only exposes the cache population that connect() already performs internally — production behavior is unchanged.

Types

type Connector

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

Connector holds a parsed DSN and produces connections on demand. The FDB database, keyspace, and factory are initialised lazily on the first Connect call. The catalog Bootstrap (Initialize) is deferred further — it runs inside the first DDL transaction, not at Connect time.

func (*Connector) Connect

func (c *Connector) Connect(ctx context.Context) (driver.Conn, error)

Connect opens a connection. Honors ctx.Done() for cancellation. On first call, initialises the FDB database and catalog (idempotent).

func (*Connector) DSN

func (c *Connector) DSN() *DSN

DSN returns a COPY of the parsed DSN. Exposed for diagnostics.

The copy is the point: the Connector's DSN is an immutable snapshot taken at OpenConnector, and handing out the internal pointer would make it mutable again through this accessor — the only route by which anything outside this package can reach it. Mutating the returned value affects nothing; to change a connection's configuration, open a new Connector with a new DSN string.

func (*Connector) Driver

func (c *Connector) Driver() driver.Driver

Driver returns the driver that created this Connector.

type DSN

type DSN struct {
	// Mode selects embedded vs. remote.
	Mode Mode
	// Path is the database path (corresponds to Java's
	// RelationalConnection.getPath()). Always starts with "/".
	Path string
	// Schema is the initial schema name set on the connection.
	// Corresponds to the ?schema= query option.
	Schema string
	// Host is the gRPC host:port for remote mode. Empty for embedded.
	Host string
	// Options are raw query-string options. Empty values are kept as "".
	Options map[string]string
}

DSN is a parsed connection string.

Accepted forms:

fdbsql:///PATH                             — embedded, default cluster file
fdbsql:///PATH?cluster_file=/path          — embedded, explicit cluster file
fdbsql://HOST:PORT/PATH                    — remote (gRPC), NOT YET IMPLEMENTED

The path component is the database path (Java's RelationalConnection.getPath()) and is mandatory.

func ParseDSN

func ParseDSN(s string) (*DSN, error)

ParseDSN parses a DSN string into a DSN.

Returns a relational Error with code InvalidPath if the DSN is malformed or uses an unsupported scheme. Matches Java's behavior (JDBCRelationalDriver.acceptsURL + connect).

func (*DSN) Clone

func (d *DSN) Clone() *DSN

Clone returns a deep copy of d, including its Options map. Callers may mutate the result without affecting the original.

This is what makes a Connector's DSN a snapshot rather than a shared handle. Every field a Connector reads after construction — Path, Schema, Mode, Host and the Options map that carries cluster_file — must come from the same frozen value as the connection options decoded at OpenConnector time. Anything else is a split brain: the security option validated and frozen up front while the fields around it stay live and mutable, so a mutation between OpenConnector and the first Connect would be honoured by some reads and ignored by others.

func (*DSN) ConnectionOptions

func (d *DSN) ConnectionOptions() (*api.Options, error)

ConnectionOptions converts the DSN's recognised query parameters into the api.Options installed on each connection.

Only options that must be decided before the first statement belong here. Unrecognised parameters stay in the raw Options map and are ignored, matching how cluster_file and schema are handled.

func (*DSN) String

func (d *DSN) String() string

String renders the DSN back to its canonical URI form. Always includes the scheme and a valid path. Query options are sorted for deterministic output.

type Driver

type Driver struct{}

Driver is the database/sql/driver.Driver for fdbsql.

Implements driver.Driver and driver.DriverContext.

func (*Driver) Open

func (d *Driver) Open(name string) (driver.Conn, error)

Open satisfies driver.Driver. Prefer OpenConnector (via driver.DriverContext) for lazy connection pooling.

func (*Driver) OpenConnector

func (d *Driver) OpenConnector(name string) (driver.Connector, error)

OpenConnector parses the DSN and returns a lazy Connector. Parsing errors are reported here so misconfigured DSNs surface at sql.Open time, not at first query.

Connection options are decoded here too, and the decoded value is kept on the Connector. Deferring the decode to Connect would break the contract this doc-comment states: Connect opens FDB before it would ever look at the options, so a misspelled option value would surface as a cluster-connection failure instead of the DSN error it is — misleading exactly when the operator needs a clear message, and a security-relevant option that never took effect.

The DSN is FROZEN here: the Connector keeps a private deep clone, and every later read — Path, Schema, Mode, cluster_file, and the decoded options — comes from that one snapshot. Freezing only the decoded options while the rest of the struct stayed live would be a split brain, and the security-relevant direction is the bad one: a caller holding the *DSN could flip restrict_ddl_to_session_database after OpenConnector, have Connect honour the stale decode (restriction silently not what the DSN now says), and slip a newly-malformed value past validation entirely.

type Mode

type Mode int

Mode selects embedded (in-process FDB client) or remote (gRPC) execution.

const (
	// ModeEmbedded is in-process: the driver talks directly to FDB.
	ModeEmbedded Mode = iota
	// ModeRemote connects to a fdb-relational-server via gRPC.
	// Not yet implemented.
	ModeRemote
)

Jump to

Keyboard shortcuts

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