# BeforeConnect Callback in go-sql-driver/mysql: Use Cases and Implementation

> Explore BeforeConnect callback use cases in go-sql-driver/mysql. Dynamically select databases, inject credentials, and configure session variables before establishing a MySQL connection.

- Repository: [Go SQL Drivers/mysql](https://github.com/go-sql-driver/mysql)
- Tags: use-cases
- Published: 2026-03-02

---

**The `BeforeConnect` callback allows you to modify MySQL connection configuration dynamically right before the network handshake, enabling per-connection database selection, runtime credential injection, and session variable configuration.**

The `go-sql-driver/mysql` package provides this hook to address scenarios where static DSN strings are insufficient. By intercepting the connection establishment process, applications can adjust parameters based on request context, external secret stores, or routing logic without rebuilding the entire driver configuration.

## How BeforeConnect Works in the MySQL Driver

The callback operates as a functional option that the driver invokes during the final stage of physical connection establishment. It receives a cloned copy of the configuration, ensuring that modifications apply only to the current connection attempt while preserving the immutability of the base `Config`.

### Configuration Storage and Registration

The option is defined in **[`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go)**, where the `Config` struct stores the callback in a private `beforeConnect` field. The `BeforeConnect` function returns an `Option` that assigns your callback to this field during configuration construction.

### Execution During Connection Establishment

When the `connector.Connect` method in **[`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go)** creates a new physical connection, it checks for the presence of the callback. If configured, the driver clones the current configuration and invokes the callback with the clone before proceeding with the TCP handshake and MySQL protocol authentication.

```go
// From connector.go - connector.Connect method
if c.cfg.beforeConnect != nil {
    cfg = c.cfg.Clone()  // Work on a copy to avoid mutating shared state
    err = c.cfg.beforeConnect(ctx, cfg)
    if err != nil {
        return nil, err
    }
}
// Proceed with handshake using potentially modified cfg

```

## Practical Use Cases for BeforeConnect

The ability to mutate configuration after initialization but before the network handshake enables several critical operational patterns:

- **Dynamic database selection**: Override `cfg.DBName` to route connections to different logical databases based on request context or sharding logic, eliminating the need to parse and rebuild DSN strings for every connection.
- **Runtime credential injection**: Fetch passwords from secret management systems (HashiCorp Vault, AWS Secrets Manager) by assigning `cfg.Passwd` within the callback, keeping sensitive data out of static configuration.
- **Session variable pre-configuration**: Inject specific `SET SESSION` parameters by modifying `cfg.Params`, ensuring variables like `sql_mode` or `time_zone` are established immediately after authentication.
- **Connection attributes for auditing**: Populate `cfg.ConnectionAttributes` with application metadata (version, environment, request ID) that appears in MySQL 5.7+ connection attribute tables for monitoring and compliance.
- **Context-aware TLS and timeouts**: Adjust `cfg.TLS` configurations or `cfg.Timeout` values based on the context passed to `QueryContext` or `ExecContext`, enabling different security postures for internal vs. external requests.

## Implementation Examples

### Dynamic Database Selection

This example demonstrates routing to a specific database based on context, mirroring the test case in [`driver_test.go`](https://github.com/go-sql-driver/mysql/blob/main/driver_test.go):

```go
package main

import (
    "context"
    "database/sql"
    "fmt"
    "github.com/go-sql-driver/mysql"
)

func main() {
    // Base DSN with placeholder database name
    cfg, _ := mysql.ParseDSN("user:pass@tcp(127.0.0.1:3306)/_?parseTime=true")
    
    // Configure BeforeConnect to set the actual database name
    cfg.Apply(mysql.BeforeConnect(func(ctx context.Context, c *mysql.Config) error {
        c.DBName = "sales_data"
        return nil
    }))
    
    connector, _ := mysql.NewConnector(cfg)
    db := sql.OpenDB(connector)
    defer db.Close()
    
    // Verify connection targets the correct database
    var currentDB string
    db.QueryRow("SELECT DATABASE()").Scan(&currentDB)
    fmt.Println("Connected to:", currentDB) // Output: sales_data
}

```

### Injecting Session Variables

Use the callback to ensure specific system variables are set for every new connection:

```go
cfg.Apply(mysql.BeforeConnect(func(ctx context.Context, c *mysql.Config) error {
    // Ensure strict SQL mode for this connection
    if c.Params == nil {
        c.Params = make(map[string]string)
    }
    c.Params["sql_mode"] = "'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'"
    
    // Set connection attributes for monitoring
    c.ConnectionAttributes = "app:payment_service,version:2.1.0"
    return nil
}))

```

### Context-Aware Credential Retrieval

Fetch passwords from a secret manager using the request context:

```go
cfg.Apply(mysql.BeforeConnect(func(ctx context.Context, c *mysql.Config) error {
    // Extract tenant from context
    tenant, ok := ctx.Value("tenant_id").(string)
    if !ok {
        return fmt.Errorf("missing tenant context")
    }
    
    // Fetch credentials for specific tenant
    secret, err := vaultClient.GetMySQLCredentials(ctx, tenant)
    if err != nil {
        return err
    }
    
    c.User = secret.Username
    c.Passwd = secret.Password
    c.DBName = tenant + "_db"
    return nil
}))

```

## Summary

- The **`BeforeConnect`** callback in `go-sql-driver/mysql` enables dynamic modification of connection parameters immediately before the network handshake.
- Defined in **[`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go)** and invoked in **[`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go)**, the callback receives a cloned `Config` pointer, ensuring thread-safe mutations without affecting the base configuration.
- Primary use cases include dynamic database routing, runtime credential injection from secret managers, pre-configuring session variables, setting connection attributes for auditing, and context-aware TLS adjustments.
- The callback runs **once per physical connection**, making it ideal for initialization logic that must execute before MySQL protocol authentication begins.

## Frequently Asked Questions

### What is the difference between BeforeConnect and other connection hooks?

**`BeforeConnect`** executes specifically after the driver decides to create a new physical connection but before any network packets are exchanged with the MySQL server. This differs from `sql.DB` connection pooling logic, which operates at the standard library level, and from post-connection initialization that would require executing SQL statements after the handshake completes.

### Can BeforeConnect modify the DSN string directly?

No, the callback receives a `*Config` struct pointer, not the raw DSN string. You must modify the exported fields of the `Config` struct (such as `DBName`, `User`, `Passwd`, or `Params`). The driver converts this modified struct into the wire protocol format internally; the original DSN string is not reconstructed.

### Is BeforeConnect called for every query or just once per connection?

**`BeforeConnect`** runs exactly **once per physical connection**, not per query. When using `sql.DB` connection pooling, the standard library may reuse existing connections for multiple queries. The callback only fires when the pool needs to establish a new TCP connection to the MySQL server, making it ideal for per-connection initialization rather than per-request logic.

### How does BeforeConnect handle connection pooling in database/sql?

The `database/sql` package manages connection pooling independently of the driver. When `sql.DB` needs a new connection and calls the driver's `Connector.Connect` method, the MySQL driver invokes `BeforeConnect` at that moment. Because the driver clones the `Config` before calling your callback, concurrent connection attempts from the pool receive independent configuration copies, preventing race conditions while allowing each new physical connection to have customized parameters.