A production-grade Go database/sql driver for Cloudflare D1, enabling seamless integration with Go's standard database interfaces.
- âś… Full
database/sqlinterface implementation - âś… Official Cloudflare Go SDK (v6) integration
- âś… Connection pooling support
- âś… Prepared statements with parameter binding
- âś… Transaction support
- âś… Proper type conversion (string, int64, float64, bool, []byte, time.Time, nil)
- âś… Context-aware operations
- âś… Metadata commands and functions (LIST DATABASES, current_database, current_user, version, connection_id)
- âś… Comprehensive error handling
- âś… Production-ready with best practices
- Go 1.22 or later
go get github.com/synehq/d1_go_sqlpackage main
import (
"database/sql"
"log"
_ "github.com/synehq/d1_go_sql"
)
func main() {
// Open database connection
// Format: d1://accountID:apiToken@databaseID
db, err := sql.Open("d1", "d1://your-account-id:your-api-token@your-database-id")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Execute queries
rows, err := db.Query("SELECT * FROM users WHERE age > ?", 18)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
var age int
if err := rows.Scan(&id, &name, &age); err != nil {
log.Fatal(err)
}
log.Printf("User: %d, %s, %d\n", id, name, age)
}
}The driver accepts connection strings in the following format:
d1://accountID:apiToken@databaseID
Or using DSN parameters:
d1://accountID:apiToken@databaseID?timeout=30s
accountID: Your Cloudflare account IDapiToken: Your Cloudflare API tokendatabaseID: Your D1 database IDtimeout: (Optional) Request timeout duration (default: 30s)
// Query single row
var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
// Query multiple rows
rows, err := db.Query("SELECT id, name FROM users")
defer rows.Close()
for rows.Next() {
var id int
var name string
rows.Scan(&id, &name)
}stmt, err := db.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
result, err := stmt.Exec("Alice", 30)
if err != nil {
log.Fatal(err)
}
lastID, _ := result.LastInsertId()
affected, _ := result.RowsAffected()tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
_, err = tx.Exec("INSERT INTO users (name) VALUES (?)", "Bob")
if err != nil {
tx.Rollback()
log.Fatal(err)
}
err = tx.Commit()
if err != nil {
log.Fatal(err)
}Note: Transactions are implemented using D1's batch API. All statements within a transaction are buffered and sent as a batch when Commit() is called. This means:
- No SQL
BEGIN TRANSACTIONorCOMMITstatements are sent to D1 - All statements execute atomically as a batch
Rollback()simply discards buffered statements without sending anything to D1
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM users")The driver supports special metadata functions and commands that return connection information:
// List all databases in the account
rows, err := db.Query("LIST DATABASES")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name, uuid, version string
rows.Scan(&name, &uuid, &version)
fmt.Printf("Database: %s (UUID: %s, Version: %s)\n", name, uuid, version)
}
// Get current database name
var dbName string
db.QueryRow("SELECT current_database()").Scan(&dbName)
// Returns: database-name (fetched from API)
// Get current account ID (user)
var accountID string
db.QueryRow("SELECT current_user()").Scan(&accountID)
// Returns: your-account-id
// Get driver version
var version string
db.QueryRow("SELECT version()").Scan(&version)
// Returns: D1 Go SQL Driver v0.1.0 (Cloudflare D1 - SQLite compatible)
// Get connection ID
var connID string
db.QueryRow("SELECT connection_id()").Scan(&connID)
// Returns: account-id:database-idSupported Functions:
LIST DATABASES- Lists all D1 databases in the account (returns: name, uuid, version)current_database()/database()- Returns the current D1 database namecurrent_user()/user()- Returns the Cloudflare account IDversion()- Returns driver and database version informationconnection_id()- Returns a unique connection identifier
Notes:
LIST DATABASESmakes an API call to fetch real database informationcurrent_database()makes an API call to fetch the actual database name- Other functions execute instantly without API calls
- All functions are case-insensitive
The driver implements the following database/sql/driver interfaces:
Driver- Main driver registrationDriverContext- Context-aware driver operationsConnector- Connection factoryConn- Database connectionConnPrepareContext- Prepared statement creationStmt- Prepared statementStmtExecContext- Statement execution with contextStmtQueryContext- Statement querying with contextRows- Result set iterationResult- Execution results
| Go Type | D1 Type |
|---|---|
nil |
NULL |
int, int64 |
INTEGER |
float64 |
REAL |
bool |
INTEGER (0/1) |
string |
TEXT |
[]byte |
BLOB |
time.Time |
TEXT (RFC3339) |
The driver provides detailed error messages with context:
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// Handle no rows case
}
// Other error handling
}- Always close resources: Use
deferto close connections, statements, and rows - Use prepared statements: For repeated queries to improve performance
- Use contexts: Set appropriate timeouts for operations
- Connection pooling: Configure
SetMaxOpenConns()andSetMaxIdleConns() - Error handling: Always check and handle errors appropriately
db, err := sql.Open("d1", dsn)
if err != nil {
log.Fatal(err)
}
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions, please open an issue on GitHub.