When would you use sql.OpenDB with a driver.Connector rather than sql.Open with a DSN?
answer
- one takes a string, one takes an object
- no registry lookup, no DSN parsing
- notice the missing error result
- called again for every new connection
- credentials that expire every few minutes
basics
~20 ssql.OpenDB takes a driver.Connector, an object that dials on demand, so it needs no registered driver name and no DSN string. Reach for it when connection setup must be typed, wrapped, or must mint a fresh credential per connection.
solid answer
~50 s`sql.Open` is the string-based door: it looks a driver name up in `database/sql`'s global registry and hands the driver an opaque DSN to parse. `sql.OpenDB(connector)` is the typed door: it takes a `driver.Connector` — an interface with `Connect(context.Context) (driver.Conn, error)` and `Driver() driver.Driver` — and returns a `*sql.DB` with no error at all, because there is nothing to look up and nothing to parse. Three reasons to prefer it. First, configuration becomes typed values instead of a stringly-typed DSN you assemble and escape. Second, no global registration is required, so you avoid the blank import and any name collision. Third and most useful, the pool calls `Connect` for every new connection it opens, so a connector can fetch a short-lived credential each time rather than embedding a static password — and you can wrap a driver's own connector to add logging or instrumentation around connection creation.
code
go · 8 lines// driver.Connector, the interface sql.OpenDB accepts:
// Connect(context.Context) (driver.Conn, error)
// Driver() driver.Driver
// Connect is called for each new pooled connection, so a connector can
// mint a fresh short-lived credential instead of embedding a password.
db := sql.OpenDB(connector)
defer db.Close()go deeper
Know that there are two ways to build a *sql.DB: a driver name plus a DSN string, or a connector object. Being able to say sql.OpenDB exists and takes a connector is enough at this level.
Explain the interface: Connect plus Driver, no registry lookup, no parsing, and therefore no error result. Say when Connect is called and why that timing is the interesting part.
Make the operational case: rotating short-lived credentials, keeping secrets out of long-lived strings, wrapping connection creation for instrumentation, and why most services are still fine with a DSN.
Own the standard: whether services in your estate authenticate to databases with static DSNs or with connectors that fetch credentials per connection, and what that costs in driver portability and shared tooling.
## Two doors into the same pool `database/sql` gives you two constructors for a `*sql.DB`: - `sql.Open(driverName, dataSourceName string) (*sql.DB, error)` - `sql.OpenDB(c driver.Connector) *sql.DB` They produce the same kind of pool. The difference is entirely in how the pool learns to make a connection. `sql.Open` is *name plus string*. The name is a key into a package-level registry that drivers populate at init time by calling `sql.Register(name, driver)` — which is what the blank import of a driver package exists to trigger. The string is opaque to `database/sql`; only the driver understands it. That is why `sql.Open` returns an error: the name may not be registered, and the driver may reject the string. `sql.OpenDB` is *object*. You hand it something that already knows how to dial: ```go type Connector interface { Connect(context.Context) (driver.Conn, error) Driver() driver.Driver } ``` There is no registry lookup and no parsing, so there is no error to return — the signature has one result. Under the hood the two paths converge: when a driver implements `driver.DriverContext`, `sql.Open` asks it for a connector via `OpenConnector(dsn)` and then builds the pool from it, exactly as `OpenDB` would. ## Why the connector form is worth reaching for **Credentials that rotate.** This is the strongest reason. `Connect` is called by the pool every time it opens a *new* connection — at startup, when demand grows, and after old connections are retired. A DSN, by contrast, is fixed at open time. If your database authenticates with a short-lived token that expires every few minutes, a static DSN gives you a pool that works until the token dies and then fails on every new connection. A connector can obtain a fresh token inside `Connect`, so every new connection is authenticated with a currently valid credential and the rotation is invisible to callers. It also keeps the secret out of a long-lived string that tends to end up in logs and crash dumps. **Typed configuration.** A DSN is a mini-language: host, port, timeouts, TLS mode and pool hints all crammed into a string with its own escaping rules, validated only at runtime by the driver. A connector is usually built from a driver-specific config struct, so mistakes are compile-time or at least clearly located, and composing configuration from environment variables stops being string concatenation. **No global registration.** `sql.Register` panics if the same name is registered twice, and the registry is process-global — a real annoyance if you want two builds of the same driver, or a wrapped version, side by side. `OpenDB` sidesteps the registry entirely; nothing needs a name. **Wrapping.** Because `Connector` is a small interface, you can implement it yourself around a driver's connector to add behaviour at connection-creation time: emit a metric, apply your own dial deadline, log which replica was chosen, or run a fixed setup statement on each new connection before handing it over. ## When the DSN form is still right Most of the time. `sql.Open` is one line, every driver supports it, the DSN travels naturally in configuration and secret stores, and the connector interface is only exposed by drivers that choose to export a connector type or a constructor for one. If your credentials are static and your configuration is a URL from the environment, the string form is the simpler, more portable choice — and the pool you get is identical. ## The parts that do not change Whichever door you use, the result is a `*sql.DB`: a pool handle, safe for concurrent use, that dials lazily. `OpenDB` does not connect either — like `sql.Open`, it returns before any network work happens, so you still prove reachability with a bounded `PingContext`. And you still call `Close` on the handle only when the program is finished with that database.
- What does the blank import of a driver package have to do with sql.Open, and does sql.OpenDB need it?The blank import runs the driver package's init, which calls sql.Register to put its name into database/sql's global registry. sql.Open resolves its first argument there, and without the import you get an unknown-driver error. sql.OpenDB is handed the connector directly, so no registration and no blank import is required.
- Why does sql.OpenDB return no error when sql.Open does?sql.Open can fail in two ways that OpenDB cannot: the driver name may not be registered, and the driver may reject the DSN it is asked to parse. OpenDB receives an already-constructed connector, so there is nothing left to resolve or validate. Failures move to Connect, which the pool calls when it actually dials.
- Does sql.OpenDB connect any earlier than sql.Open does?No. Both return a pool handle that has opened nothing. The connector's Connect is called the first time the pool needs a connection, not when OpenDB returns, so you still need a bounded PingContext or a real query to prove the database is reachable.
saying these in an interview costs you the question
- Thinks sql.OpenDB opens a connection immediately
- Expects sql.OpenDB to return an error like sql.Open
- Believes a connector is consulted only once
- Bakes an expiring auth token into a static DSN
- Thinks OpenDB still requires the driver to be registered