database/sql Overview
Purpose and Scope
Package database/sql is the standard Go abstraction for SQL and SQL-like databases. Its job is to give application code a stable way to open a database handle, run commands, query rows, scan results, prepare statements, and coordinate transactions without embedding a specific database protocol in the application. The package is deliberately paired with separately imported drivers. Most programs call the public database/sql API, while driver authors implement the interfaces in database/sql/driver. That split keeps application code portable and lets each driver translate generic operations into the wire protocol, authentication rules, placeholder syntax, and result formats of a particular database system. Sources: src/database/sql/driver/driver.go
The most important concept for readers is that the application-level database handle is not a single physical connection. Official package documentation describes the handle as a pool-capable object: opening a handle records a driver name and data source name, but connecting to the database typically happens later, when the handle is pinged or used. The driver source reinforces the same architecture by documenting that Driver.Open returns one driver connection, that the returned connection is used by one goroutine at a time, and that the sql package maintains the idle connection pool. In practice, applications share the handle, and the package manages the lifecycle of underlying sessions. Sources: src/database/sql/driver/driver.go
This overview is written as an architectural map before the task-focused database pages. It explains how a shared handle, pooled driver connections, prepared statements, result rows, scan conversion, context cancellation, and close synchronization fit together. It also identifies where driver-specific extension points appear. When a driver implements newer context-aware interfaces, deadlines and cancellation can travel to the database operation itself. When it only implements older interfaces, the package can check context state around the call, but it cannot force a legacy in-flight driver operation to stop. Sources: src/database/sql/ctxutil.go, src/database/sql/driver/driver.go
Relevant Source Files
src/database/sql/closemu.go- ImplementsclosingMutex, a specialized read/write synchronization primitive used to coordinate ordinary operations with close operations while allowing reentrant reads and giving read locks priority over close.src/database/sql/convert.go- Contains argument and scan conversion support, including named-value validation,driver.Valuerhandling, statement column converters, default argument conversion, and error reporting for unsupported driver values.src/database/sql/ctxutil.go- Provides adapter helpers for context-aware and legacy driver operations, including prepare, exec, query, statement exec, statement query, transaction begin, and named-value fallback conversion.src/database/sql/driver/driver.go- Defines the core driver interfaces and driver-author guidance, includingDriver,NamedValue, connection pooling expectations, context extension points, validation, session reset, row metadata, and multiple result set contracts.src/database/sql/driver/types.go- Defines driver value conversion contracts such asValueConverter,Valuer, and standard converters includingBoolandInt32.src/database/sql/internal/sql.go- Provides the internalScanContextbridge shared betweendatabase/sqlanddatabase/sql/driverwithout exposing that representation directly to ordinary users.
Core Architecture
The database stack has a narrow waist. Application code works with high-level operations such as opening a handle, beginning a transaction, preparing a statement, executing a command, querying for rows, and scanning values into destinations. Beneath that surface, the package talks to driver interfaces. The driver package defines a minimal Driver entry point with Open(name string) (Conn, error), and its comments explicitly say drivers do not need to implement their own idle pooling because the standard package maintains a pool. This division of responsibility is why a driver connection can be simpler than an application handle: the connection represents one underlying session, while the handle manages concurrent use and reuse. Sources: src/database/sql/driver/driver.go
A driver connection is intentionally not the public concurrency boundary. The driver documentation states that the returned connection is used by one goroutine at a time. That frees drivers from making every connection method independently concurrent, while allowing the public handle to be the object applications safely share across goroutines. The same source also recommends optional connection lifecycle interfaces: Pinger for checking reachability, Validator for deciding whether a connection should return to the pool, and SessionResetter for cleaning session state before reuse. These hooks let the pool avoid stale sessions and reduce application-level boilerplate without requiring every database to behave identically. Sources: src/database/sql/driver/driver.go
Prepared statements and result rows sit directly on the boundary between generic SQL calls and driver-provided behavior. A connection may support context-aware preparation through ConnPrepareContext, or only the older Prepare method. Query and exec may be implemented by connection-level interfaces, statement-level interfaces, or legacy positional-argument interfaces. Rows returned from a driver produce driver values, not arbitrary Go values, and higher layers convert those values when the application calls scan. This arrangement keeps each responsibility separable: connection selection belongs to the pool, SQL execution belongs to the driver, and data conversion belongs to a shared conversion layer. Sources: src/database/sql/ctxutil.go, src/database/sql/convert.go, src/database/sql/driver/driver.go
System-to-Code Mapping
| Concept | Source-level contract | Practical meaning |
|---|---|---|
| Shared database handle and pool | Driver.Open comments say sql maintains the idle pool | Applications share a handle; drivers provide individual connections |
| Driver connection | Conn values returned by Driver.Open are used by one goroutine at a time | Pooling and concurrency are managed above the driver session |
| Query arguments | driver.NamedValue, NamedValueChecker, ColumnConverter, and Valuer | Arguments are normalized, checked, converted, or removed before driver calls |
| Context-aware operations | ConnPrepareContext, ExecerContext, QueryerContext, StmtExecContext, StmtQueryContext, and ConnBeginTx | Deadlines and cancellation reach drivers that implement the newer interfaces |
| Legacy adapters | namedValueToValue and the helpers in ctxutil.go | Older drivers still work, but named parameters and some cancellation behavior are limited |
| Scan and value conversion | Value, ValueConverter, Valuer, and internal ScanContext | Data crosses the driver boundary through a constrained value model |
| Close coordination | closingMutex | Close operations wait for active operations while ordinary read-side use remains cheap |
The conversion layer matters because application code is allowed to pass ordinary Go values, named arguments, and custom types, but drivers operate on a smaller common representation. The driver package defines a driver value as nil, a value handled by a driver-specific named value checker, or one of a defined set such as integer, floating point, boolean, byte slice, string, or time value. In the package implementation, caller arguments are rewritten into named values with one-based ordinals, optional names are validated, and driver or statement checkers get a chance to transform the values before the actual driver call. Sources: src/database/sql/convert.go, src/database/sql/driver/driver.go
Custom values flow through several layers rather than through one universal conversion function. If an argument implements driver.Valuer, the package calls its value method so the type can represent itself as a driver value. A statement or connection implementing NamedValueChecker can accept special database types, translate named parameters, or remove per-query options from the argument list. A statement implementing ColumnConverter can apply column-specific checks, such as rejecting a value that would overflow a smaller database column or refusing a nil value before it is sent over the network. If none of those hooks handles the value, the default converter produces a standard driver representation or returns an error. Sources: src/database/sql/convert.go, src/database/sql/driver/types.go
Execution Flow
A typical operation begins when application code calls a method on a shared database handle. The handle obtains an underlying driver connection from the pool or opens one through the registered driver path. From there, the package chooses the most capable interface available for the operation. For preparation, the helper first checks whether the driver connection implements the context-aware preparation interface. If it does, the context is passed directly to the driver. If not, the helper calls the legacy prepare method and then checks whether the context was canceled after the statement was created; if cancellation won the race, the statement is closed before the context error is returned. Sources: src/database/sql/ctxutil.go
Exec and query follow the same compatibility pattern. The adapter helpers prefer context-aware execution and query interfaces. If only legacy interfaces are available, they first convert named values into positional driver values. That fallback is intentionally lossy: if any argument has a name, the conversion fails because the old interface has no way to represent named parameters. The helpers also check the context before making a legacy call. This design preserves older driver compatibility while making unsupported features visible. A program that relies on deadlines, cancellation, or named parameters should therefore choose a driver that implements the newer context and named-value contracts. Sources: src/database/sql/ctxutil.go, src/database/sql/driver/driver.go
Starting a transaction has an extra correctness requirement because transaction options change database semantics. The transaction adapter uses the driver’s context-aware begin interface when available and maps public transaction options into driver transaction options. If the driver only supports the legacy begin method, the adapter rejects non-default isolation levels and read-only transactions, because the legacy method cannot communicate those requests. When a legacy transaction begins successfully but the context is already canceled, the helper rolls the transaction back and returns the context error. This prevents the package from silently ignoring caller intent or leaking a transaction that should not proceed. Sources: src/database/sql/ctxutil.go
Closing resources is another runtime flow rather than a simple final call. A handle, statement, row set, or related object may be closed while other goroutines are still trying to use it. The implementation includes closingMutex, described as an RWMutex for synchronizing close, but with read locks taking priority over write-side close. Its state tracks the number of active readers and whether a writer is waiting, and its condition variables are created lazily only when contention occurs. That design favors the common case of ordinary operations while still giving close a safe way to wait for active users. Sources: src/database/sql/closemu.go
API Components and Driver Contracts
The driver package is the reference point for implementers and for application developers evaluating driver quality. The minimum driver contract is small, but the comments encourage modern drivers to implement Connector and DriverContext so a data source name can be parsed once for a pool of connections instead of repeatedly per connection. The same documentation says Connector.Connect and Driver.Open should never return ErrBadConn; that error is reserved for later phases such as validation, session reset, or query paths when an existing connection is already invalid. That distinction helps the pool make consistent decisions about retrying, discarding, or reusing connections. Sources: src/database/sql/driver/driver.go
Optional interfaces describe capabilities without forcing every driver to implement one large surface. For normal pool health, the driver comments recommend Pinger, SessionResetter, and Validator on connections. For context and named-parameter support, they recommend ExecerContext, QueryerContext, ConnPrepareContext, and ConnBeginTx. For custom data types, NamedValueChecker lets the driver accept driver-specific values or remove per-query options by returning the designated remove-argument error. For richer result metadata, row implementations can report scan types, database type names, lengths, nullability, precision, scale, and multiple result set progression. Sources: src/database/sql/driver/driver.go
| Name | Kind | Contract visible in the supplied sources |
|---|---|---|
type Value any | Driver value model | Represents nil, driver-handled custom values, or standard driver-supported types |
type NamedValue struct | Argument representation | Holds Name, one-based Ordinal, and Value |
type Driver interface | Driver entry point | Provides Open(name string) (Conn, error) |
type ValueConverter interface | Conversion hook | Provides ConvertValue(v any) (Value, error) |
type Valuer interface | Custom value hook | Provides Value() (Value, error) and must not panic |
func namedValueToValue | Legacy adapter | Converts unnamed arguments to positional values and rejects named parameters |
func ctxDriverBegin | Transaction adapter | Uses context-aware begin when available and validates legacy fallback options |
type ScanContext | Internal bridge | Carries opaque scan-related data between the two packages |
Security, Cancellation, and Correctness Signals
The official SQL injection guidance for Go emphasizes passing parameter values as separate arguments rather than formatting them into SQL text. That advice aligns with the architecture in the conversion code. Arguments are represented separately as named values, validated, converted, and then passed through driver interfaces. A query that uses placeholders and separate values lets the database or driver bind the values as parameters. By contrast, formatting user input into the SQL string constructs a different statement before the driver sees it, bypassing the package boundary that keeps statement text and values distinct. The exact placeholder syntax still depends on the database and driver. Sources: src/database/sql/convert.go, src/database/sql/driver/driver.go
Cancellation is also a boundary between application intent and driver capability. The public documentation warns that drivers without context cancellation support will not return until a query completes. The adapter code explains why that limitation exists. When a context-aware method is implemented, the package passes the context into the driver call. When only a legacy method exists, the package can check whether the context is already done before the call, or clean up immediately after certain operations, but it cannot interrupt an arbitrary blocking driver operation. Applications that enforce request deadlines should prefer drivers that implement the context interfaces named in the driver package. Sources: src/database/sql/ctxutil.go, src/database/sql/driver/driver.go
Scan conversion and parameter conversion are correctness-sensitive because databases often store values with narrower or different types than Go variables. The driver Bool converter accepts booleans, parseable strings or byte slices, and integer zero or one, but rejects other integers. The Int32 converter converts integer-like inputs to a driver integer while checking range. These examples show the intended behavior of converters: they should normalize values while preventing silent truncation or ambiguous interpretation. The internal ScanContext bridge gives the two database packages a shared opaque carrier for scan-related state without making that implementation detail part of the ordinary user API. Sources: src/database/sql/driver/types.go, src/database/sql/internal/sql.go
Next Steps
Use this page as the orientation layer before reading task-specific database guidance. If you are writing application code, focus next on obtaining a database handle, verifying connectivity with a context-aware ping, setting pool limits intentionally, running queries with separate parameter values, scanning rows, closing rows promptly, and choosing transaction options that your driver actually supports. The driver boundary explains many practical surprises: opening a handle is not necessarily connecting, the shared handle is not one physical session, and cancellation only reaches the database when the driver implements the context-aware interfaces. Sources: src/database/sql/ctxutil.go, src/database/sql/driver/driver.go
If you are writing or reviewing a driver, start with the driver package contracts. Implement the minimum entry point, then add the capability interfaces that make the driver integrate well with the standard package: connector creation, ping, session reset, validation, context-aware prepare, query, exec, begin, named-value checking, custom conversion, result metadata, and multiple result sets where appropriate. If you are debugging data conversion, read the conversion helpers and standard converters next. For broader application workflows, continue to the pages on opening handles and managing connections, querying and changing data, prepared statements and transactions, and cancellation and SQL injection.