| name | databricks-isv-go-sql-driver |
| description | PWAF-compliant Databricks SQL Driver for Go (databricks-sql-go): PAT, OAuth M2M, OAuth U2M (browser + token-env + custom OAuth app PKCE), WithUserAgentEntry. Use when building or testing integrations that run SQL queries via a Databricks SQL warehouse. |
Databricks SQL Driver for Go (ISV)
Use this skill when implementing or testing Databricks SQL Driver for Go (databricks-sql-go) integrations for PWAF-compliant SQL query execution via a Databricks SQL warehouse.
PWAF Documentation Links
Requirements
- Driver:
github.com/databricks/databricks-sql-go v1.5.2+
- Go: 1.20+
- SQL Warehouse: Required (
DATABRICKS_HTTP_PATH)
- Install:
go get github.com/databricks/databricks-sql-go@latest
Authentication Comparison
| Method | PWAF Status | Auto Token Refresh | Browser Required | Use Case |
|---|
| PAT | ⚠️ Limited | No | No | Testing only |
| OAuth M2M | ✅ Recommended | Yes (driver-native) | No | Production/automated |
| U2M Custom OAuth | ✅ Recommended (ISV) | No | Yes | Interactive (custom app) |
| U2M Token-Env | ✅ Supported | No | No | Headless/CI |
ISV Note: For user-interactive flows, use U2M Custom OAuth with your registered OAuth app (Account Console → App connections). This provides custom branding, audit trails, and scoped permissions. The driver's built-in U2M browser auth uses a generic databricks-sql-connector app which is not appropriate for ISV applications.
Host Normalization Helper
The driver's WithServerHostname expects a bare hostname (no https://):
import "strings"
func serverHostname(host string) string {
s := strings.TrimPrefix(host, "https://")
s = strings.TrimPrefix(s, "http://")
if idx := strings.Index(s, "/"); idx >= 0 {
s = s[:idx]
}
return s
}
Environment Variables Reference
| Variable | Required For | Description |
|---|
DATABRICKS_HOST | All | Workspace URL (e.g., https://myworkspace.cloud.databricks.com) |
DATABRICKS_HTTP_PATH | All | SQL warehouse HTTP path (e.g., /sql/1.0/warehouses/abc123) |
DATABRICKS_TOKEN | PAT | Personal access token |
DATABRICKS_CLIENT_ID | OAuth M2M | Service principal UUID |
DATABRICKS_CLIENT_SECRET | OAuth M2M | Service principal OAuth secret |
DATABRICKS_U2M_CLIENT_ID | U2M Custom OAuth | Custom OAuth app client ID from App connections |
DATABRICKS_U2M_CLIENT_SECRET | U2M Custom OAuth (optional) | Custom OAuth app client secret |
DATABRICKS_REDIRECT_URI | U2M Custom OAuth (optional) | Custom redirect URI (default: http://localhost:8040/callback) |
DATABRICKS_ACCESS_TOKEN | U2M Token-Env | Pre-obtained OAuth access token |
DATABRICKS_AUTH_TYPE | Multi-auth | Auth type selector: pat, oauth_m2m, u2m_browser, u2m_custom_oauth, u2m_token_env |
Important: Do not mix M2M and U2M environment variables. Use env -i for clean test environments.
Complete Examples
PAT Authentication (Testing Only)
package main
import (
"database/sql"
"fmt"
"os"
"strings"
dbsql "github.com/databricks/databricks-sql-go"
)
func main() {
host := os.Getenv("DATABRICKS_HOST")
httpPath := os.Getenv("DATABRICKS_HTTP_PATH")
token := os.Getenv("DATABRICKS_TOKEN")
connector, err := dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAccessToken(token),
dbsql.WithUserAgentEntry("YourCompany_YourProduct/1.0.0"),
)
if err != nil {
panic(err)
}
db := sql.OpenDB(connector)
defer db.Close()
rows, err := db.Query("SELECT COUNT(*) FROM samples.nyctaxi.trips")
if err != nil {
panic(err)
}
defer rows.Close()
var count int64
rows.Next()
rows.Scan(&count)
fmt.Printf("PAT OK: %d trips\n", count)
}
func serverHostname(host string) string {
s := strings.TrimPrefix(host, "https://")
s = strings.TrimPrefix(s, "http://")
if idx := strings.Index(s, "/"); idx >= 0 {
s = s[:idx]
}
return s
}
Env vars: DATABRICKS_HOST, DATABRICKS_HTTP_PATH, DATABRICKS_TOKEN
OAuth M2M Authentication (Production Recommended)
Uses the driver's built-in M2M authenticator that handles token fetch and refresh:
package main
import (
"database/sql"
"fmt"
"os"
"strings"
dbsql "github.com/databricks/databricks-sql-go"
"github.com/databricks/databricks-sql-go/auth/oauth/m2m"
)
func main() {
host := os.Getenv("DATABRICKS_HOST")
httpPath := os.Getenv("DATABRICKS_HTTP_PATH")
clientID := os.Getenv("DATABRICKS_CLIENT_ID")
clientSecret := os.Getenv("DATABRICKS_CLIENT_SECRET")
authenticator := m2m.NewAuthenticator(clientID, clientSecret, serverHostname(host))
connector, err := dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAuthenticator(authenticator),
dbsql.WithUserAgentEntry("YourCompany_YourProduct/1.0.0"),
)
if err != nil {
panic(err)
}
db := sql.OpenDB(connector)
defer db.Close()
rows, err := db.Query("SELECT COUNT(*) FROM samples.nyctaxi.trips")
if err != nil {
panic(err)
}
defer rows.Close()
var count int64
rows.Next()
rows.Scan(&count)
fmt.Printf("OAuth M2M OK: %d trips\n", count)
}
func serverHostname(host string) string {
s := strings.TrimPrefix(host, "https://")
s = strings.TrimPrefix(s, "http://")
if idx := strings.Index(s, "/"); idx >= 0 {
s = s[:idx]
}
return s
}
Env vars: DATABRICKS_HOST, DATABRICKS_HTTP_PATH, DATABRICKS_CLIENT_ID, DATABRICKS_CLIENT_SECRET
Setup: Create service principal in Account Console → Settings → Service principals. Generate OAuth secret. Grant CAN_USE on the SQL warehouse.
U2M Browser Authentication (Driver-Native)
Uses the driver's built-in U2M authenticator that opens the user's default browser:
package main
import (
"database/sql"
"fmt"
"os"
"strings"
"time"
dbsql "github.com/databricks/databricks-sql-go"
"github.com/databricks/databricks-sql-go/auth/oauth/u2m"
)
func main() {
host := os.Getenv("DATABRICKS_HOST")
httpPath := os.Getenv("DATABRICKS_HTTP_PATH")
authenticator, err := u2m.NewAuthenticator(serverHostname(host), 2*time.Minute)
if err != nil {
panic(err)
}
connector, err := dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAuthenticator(authenticator),
dbsql.WithUserAgentEntry("YourCompany_YourProduct/1.0.0"),
)
if err != nil {
panic(err)
}
db := sql.OpenDB(connector)
defer db.Close()
rows, err := db.Query("SELECT COUNT(*) FROM samples.nyctaxi.trips")
if err != nil {
panic(err)
}
defer rows.Close()
var count int64
rows.Next()
rows.Scan(&count)
fmt.Printf("U2M Browser OK: %d trips\n", count)
}
func serverHostname(host string) string {
s := strings.TrimPrefix(host, "https://")
s = strings.TrimPrefix(s, "http://")
if idx := strings.Index(s, "/"); idx >= 0 {
s = s[:idx]
}
return s
}
Env vars: DATABRICKS_HOST, DATABRICKS_HTTP_PATH
Notes:
- Uses Databricks' built-in OAuth app (client_id=
databricks-sql-connector)
- Do NOT set
DATABRICKS_CLIENT_ID or DATABRICKS_TOKEN
- Driver picks an available localhost port at runtime
U2M Token-Env Authentication (Headless/CI)
Pass a pre-obtained OAuth access token:
package main
import (
"database/sql"
"fmt"
"os"
"strings"
dbsql "github.com/databricks/databricks-sql-go"
)
func main() {
host := os.Getenv("DATABRICKS_HOST")
httpPath := os.Getenv("DATABRICKS_HTTP_PATH")
token := os.Getenv("DATABRICKS_ACCESS_TOKEN")
if token == "" {
token = os.Getenv("DATABRICKS_TOKEN")
}
connector, err := dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAccessToken(token),
dbsql.WithUserAgentEntry("YourCompany_YourProduct/1.0.0"),
)
if err != nil {
panic(err)
}
db := sql.OpenDB(connector)
defer db.Close()
rows, err := db.Query("SELECT COUNT(*) FROM samples.nyctaxi.trips")
if err != nil {
panic(err)
}
defer rows.Close()
var count int64
rows.Next()
rows.Scan(&count)
fmt.Printf("U2M Token-Env OK: %d trips\n", count)
}
func serverHostname(host string) string {
s := strings.TrimPrefix(host, "https://")
s = strings.TrimPrefix(s, "http://")
if idx := strings.Index(s, "/"); idx >= 0 {
s = s[:idx]
}
return s
}
Env vars: DATABRICKS_HOST, DATABRICKS_HTTP_PATH, DATABRICKS_ACCESS_TOKEN (or DATABRICKS_TOKEN)
U2M Custom OAuth App (PKCE) - Interactive
For user-interactive flows with a custom OAuth app:
package main
import (
"context"
"crypto/rand"
"crypto/sha256"
"database/sql"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"runtime"
"strings"
dbsql "github.com/databricks/databricks-sql-go"
)
func main() {
host := os.Getenv("DATABRICKS_HOST")
httpPath := os.Getenv("DATABRICKS_HTTP_PATH")
clientID := os.Getenv("DATABRICKS_U2M_CLIENT_ID")
clientSecret := os.Getenv("DATABRICKS_U2M_CLIENT_SECRET")
redirectURI := os.Getenv("DATABRICKS_REDIRECT_URI")
if redirectURI == "" {
redirectURI = "http://localhost:8040/callback"
}
token, err := runPKCEFlow(host, clientID, clientSecret, redirectURI)
if err != nil {
panic(err)
}
connector, err := dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAccessToken(token),
dbsql.WithUserAgentEntry("YourCompany_YourProduct/1.0.0"),
)
if err != nil {
panic(err)
}
db := sql.OpenDB(connector)
defer db.Close()
rows, err := db.Query("SELECT COUNT(*) FROM samples.nyctaxi.trips")
if err != nil {
panic(err)
}
defer rows.Close()
var count int64
rows.Next()
rows.Scan(&count)
fmt.Printf("U2M Custom OAuth OK: %d trips\n", count)
}
func runPKCEFlow(host, clientID, clientSecret, redirectURI string) (string, error) {
verifierBytes := make([]byte, 32)
rand.Read(verifierBytes)
codeVerifier := base64.RawURLEncoding.EncodeToString(verifierBytes)
hash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:])
stateBytes := make([]byte, 32)
rand.Read(stateBytes)
state := base64.RawURLEncoding.EncodeToString(stateBytes)
redirectURL, _ := url.Parse(redirectURI)
port := redirectURL.Port()
if port == "" {
port = "8040"
}
hostNorm := serverHostname(host)
authURL := fmt.Sprintf("https://%s/oidc/v1/authorize?"+
"client_id=%s&redirect_uri=%s&response_type=code&scope=all-apis&"+
"code_challenge=%s&code_challenge_method=S256&state=%s",
hostNorm, clientID, url.QueryEscape(redirectURI), codeChallenge, state)
codeChan := make(chan string, 1)
errChan := make(chan error, 1)
server := &http.Server{Addr: ":" + port}
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
errChan <- fmt.Errorf("state mismatch")
return
}
code := r.URL.Query().Get("code")
if code == "" {
errChan <- fmt.Errorf("no code in callback")
return
}
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<html><body style="font-family:sans-serif;text-align:center;padding:50px;">
<h1 style="color:green;">✓ Authentication Successful</h1>
<p>You can close this tab.</p></body></html>`))
codeChan <- code
})
go server.ListenAndServe()
defer server.Shutdown(context.Background())
openBrowser(authURL)
fmt.Println("Waiting for authentication in browser...")
var code string
select {
case code = <-codeChan:
case err := <-errChan:
return "", err
}
tokenURL := fmt.Sprintf("https://%s/oidc/v1/token", hostNorm)
data := url.Values{
"grant_type": {"authorization_code"},
"code": {code},
"redirect_uri": {redirectURI},
"code_verifier": {codeVerifier},
"client_id": {clientID},
}
if clientSecret != "" {
data.Set("client_secret", clientSecret)
}
resp, err := http.PostForm(tokenURL, data)
if err != nil {
return "", err
}
defer resp.Body.Close()
var result struct {
AccessToken string `json:"access_token"`
Error string `json:"error"`
}
json.NewDecoder(resp.Body).Decode(&result)
if result.Error != "" {
return "", fmt.Errorf("token error: %s", result.Error)
}
return result.AccessToken, nil
}
func serverHostname(host string) string {
s := strings.TrimPrefix(host, "https://")
s = strings.TrimPrefix(s, "http://")
if idx := strings.Index(s, "/"); idx >= 0 {
s = s[:idx]
}
return s
}
func openBrowser(url string) {
switch runtime.GOOS {
case "darwin":
exec.Command("open", url).Start()
case "linux":
exec.Command("xdg-open", url).Start()
case "windows":
exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
}
}
Env vars: DATABRICKS_HOST, DATABRICKS_HTTP_PATH, DATABRICKS_U2M_CLIENT_ID, optional DATABRICKS_U2M_CLIENT_SECRET, DATABRICKS_REDIRECT_URI
Setup: Create OAuth app in Account Console → Settings → App connections. Add redirect URI http://localhost:8040/callback.
Environment Variables Reference
| Variable | Used By | Description |
|---|
DATABRICKS_HOST | All | Workspace URL |
DATABRICKS_HTTP_PATH | All | SQL Warehouse path (e.g., /sql/1.0/warehouses/<id>) |
DATABRICKS_TOKEN | PAT, U2M token-env | Personal access token or pre-obtained OAuth token |
DATABRICKS_CLIENT_ID | OAuth M2M | Service principal UUID |
DATABRICKS_CLIENT_SECRET | OAuth M2M | Service principal OAuth secret |
DATABRICKS_U2M_CLIENT_ID | U2M Custom OAuth | Custom OAuth app client ID |
DATABRICKS_U2M_CLIENT_SECRET | U2M Custom OAuth | Custom OAuth app secret (if confidential) |
DATABRICKS_REDIRECT_URI | U2M Custom OAuth | Redirect URI (default: http://localhost:8040/callback) |
Auth Isolation
Run tests with a clean environment using env -i:
env -i PATH=$PATH HOME=$HOME \
DATABRICKS_HOST=$DATABRICKS_HOST \
DATABRICKS_HTTP_PATH=$DATABRICKS_HTTP_PATH \
DATABRICKS_CLIENT_ID=$DATABRICKS_CLIENT_ID \
DATABRICKS_CLIENT_SECRET=$DATABRICKS_CLIENT_SECRET \
./your_binary
Validation Query
Verify User-Agent telemetry is being recorded correctly:
SELECT event_time, user_agent, action_name, request_params
FROM system.access.audit
WHERE event_time > current_timestamp() - INTERVAL 1 HOUR
AND lower(user_agent) LIKE '%yourcompany%'
ORDER BY event_time DESC
LIMIT 10;
Required Imports
Quick reference for imports by auth type:
import (
"database/sql"
"fmt"
"os"
"strings"
dbsql "github.com/databricks/databricks-sql-go"
)
import "github.com/databricks/databricks-sql-go/auth/oauth/m2m"
import (
"time"
"github.com/databricks/databricks-sql-go/auth/oauth/u2m"
)
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
"os/exec"
"runtime"
)
import "database/sql/driver"
Error Handling Patterns
Basic Error Handling
import (
"database/sql"
"errors"
"strings"
)
func executeQuery(db *sql.DB, query string) error {
rows, err := db.Query(query)
if err != nil {
return handleSQLError(err)
}
defer rows.Close()
return nil
}
func handleSQLError(err error) error {
errMsg := err.Error()
switch {
case strings.Contains(errMsg, "401"):
return fmt.Errorf("authentication failed: check credentials")
case strings.Contains(errMsg, "403"):
return fmt.Errorf("permission denied: check service principal access")
case strings.Contains(errMsg, "503"):
return fmt.Errorf("warehouse unavailable: may be starting up")
case strings.Contains(errMsg, "429"):
return fmt.Errorf("rate limited: implement backoff")
case strings.Contains(errMsg, "invalid httpPath"):
return fmt.Errorf("invalid HTTP path: check DATABRICKS_HTTP_PATH")
default:
return err
}
}
Retry Pattern with Exponential Backoff
import (
"context"
"database/sql"
"strings"
"time"
)
type RetryConfig struct {
MaxRetries int
InitialBackoff time.Duration
MaxBackoff time.Duration
Multiplier float64
}
func DefaultRetryConfig() RetryConfig {
return RetryConfig{
MaxRetries: 3,
InitialBackoff: 2 * time.Second,
MaxBackoff: 30 * time.Second,
Multiplier: 2.0,
}
}
func QueryWithRetry(ctx context.Context, db *sql.DB, cfg RetryConfig, query string) (*sql.Rows, error) {
var lastErr error
backoff := cfg.InitialBackoff
for attempt := 0; attempt <= cfg.MaxRetries; attempt++ {
rows, err := db.QueryContext(ctx, query)
if err == nil {
return rows, nil
}
lastErr = err
if !isRetryable(err) || attempt >= cfg.MaxRetries {
return nil, err
}
fmt.Printf("[Retry %d/%d] Waiting %v. Error: %v\n", attempt+1, cfg.MaxRetries, backoff, err)
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(backoff):
backoff = time.Duration(float64(backoff) * cfg.Multiplier)
if backoff > cfg.MaxBackoff {
backoff = cfg.MaxBackoff
}
}
}
return nil, lastErr
}
func isRetryable(err error) bool {
msg := err.Error()
if strings.Contains(msg, "503") || strings.Contains(msg, "429") ||
strings.Contains(msg, "500") || strings.Contains(msg, "502") ||
strings.Contains(msg, "connection reset") ||
strings.Contains(msg, "TEMPORARILY_UNAVAILABLE") {
return true
}
if strings.Contains(msg, "401") || strings.Contains(msg, "403") ||
strings.Contains(msg, "AnalysisException") || strings.Contains(msg, "ParseException") {
return false
}
return false
}
Troubleshooting
Common Errors
| Error | Cause | Solution |
|---|
dial tcp: connection refused | Network/host issue | Verify DATABRICKS_HOST URL |
invalid httpPath | Missing warehouse path | Set DATABRICKS_HTTP_PATH=/sql/1.0/warehouses/<id> |
401 Unauthorized | Invalid credentials | Check token/secret |
403 PERMISSION_DENIED | SP lacks permission | Grant CAN_USE on SQL warehouse to service principal |
context deadline exceeded | Query timeout | Increase context timeout or check warehouse status |
503 Service Unavailable | Warehouse starting | Wait and retry with backoff |
OAuth-Specific Errors
| Error | Cause | Solution |
|---|
OAuth application not available | Account not enabled | Enable OAuth in account console |
redirect_uri mismatch | URI not registered | Add redirect URI in App connections |
invalid_client | Wrong client ID/secret | Verify credentials match registered OAuth app |
invalid_grant | Auth code expired/reused | Auth codes are single-use; restart OAuth flow |
U2M Browser Issues
| Symptom | Cause | Solution |
|---|
| Browser doesn't open | Missing env vars | Pass HOME, USER, DISPLAY via env -i |
localhost refused to connect | Stale browser tab | Normal if auth succeeded; server shuts down after token exchange |
| Port 8040 in use | Another process | Set DATABRICKS_REDIRECT_URI=http://localhost:8050/callback |
| Browser hangs after login | Callback not reached | Check redirect URI matches exactly; try different port |
authentication timed out | User didn't complete login | Retry; ensure browser can reach workspace |
Host Normalization Issues
| Symptom | Cause | Solution |
|---|
no such host | https:// prefix passed to WithServerHostname | Use serverHostname() helper to strip scheme |
| SSL errors | Incorrect port or scheme | Ensure WithPort(443) and bare hostname |
Multi-Auth Pattern
Support multiple auth types in a single binary using DATABRICKS_AUTH_TYPE:
authType := os.Getenv("DATABRICKS_AUTH_TYPE")
if authType == "" {
authType = "pat"
}
var connector driver.Connector
var err error
switch authType {
case "pat":
connector, err = dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAccessToken(os.Getenv("DATABRICKS_TOKEN")),
dbsql.WithUserAgentEntry(userAgent),
)
case "oauth_m2m", "m2m":
auth := m2m.NewAuthenticator(
os.Getenv("DATABRICKS_CLIENT_ID"),
os.Getenv("DATABRICKS_CLIENT_SECRET"),
serverHostname(host),
)
connector, err = dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAuthenticator(auth),
dbsql.WithUserAgentEntry(userAgent),
)
case "u2m_browser", "u2m":
auth, err := u2m.NewAuthenticator(serverHostname(host), 2*time.Minute)
if err != nil {
panic(err)
}
connector, err = dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAuthenticator(auth),
dbsql.WithUserAgentEntry(userAgent),
)
case "u2m_token_env":
token := os.Getenv("DATABRICKS_ACCESS_TOKEN")
if token == "" {
token = os.Getenv("DATABRICKS_TOKEN")
}
connector, err = dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAccessToken(token),
dbsql.WithUserAgentEntry(userAgent),
)
case "u2m_custom_oauth_app":
token, err := runPKCEFlow(host, clientID, clientSecret, redirectURI)
if err != nil {
panic(err)
}
connector, err = dbsql.NewConnector(
dbsql.WithServerHostname(serverHostname(host)),
dbsql.WithPort(443),
dbsql.WithHTTPPath(httpPath),
dbsql.WithAccessToken(token),
dbsql.WithUserAgentEntry(userAgent),
)
}
DATABRICKS_AUTH_TYPE | Auth flow | Env vars required |
|---|
pat (default) | Personal Access Token | DATABRICKS_TOKEN |
oauth_m2m or m2m | OAuth M2M (client credentials) | DATABRICKS_CLIENT_ID, DATABRICKS_CLIENT_SECRET |
u2m_browser or u2m | U2M browser sign-in | None (uses built-in OAuth app) |
u2m_token_env | U2M pre-obtained token | DATABRICKS_ACCESS_TOKEN or DATABRICKS_TOKEN |
u2m_custom_oauth_app | U2M custom OAuth app (PKCE) | DATABRICKS_U2M_CLIENT_ID, optional DATABRICKS_U2M_CLIENT_SECRET |
Note: Unlike the Databricks SDK for Go, the SQL driver does NOT read DATABRICKS_AUTH_TYPE internally, so you can safely use it as your app-level auth selector.
U2M Browser Environment Requirements
For U2M browser flows to work, pass these environment variables when running with env -i:
env -i PATH=$PATH HOME=$HOME USER=$USER DISPLAY=$DISPLAY \
DATABRICKS_HOST=$DATABRICKS_HOST \
DATABRICKS_HTTP_PATH=$DATABRICKS_HTTP_PATH \
./your_binary
HOME - Required for browser profile access
USER - Required by some browsers
DISPLAY - Required on Linux for GUI browser
Testing with Auth Isolation
Test each auth type with a clean environment to avoid "more than one authorization method" conflicts:
env -i PATH=$PATH HOME=$HOME \
DATABRICKS_HOST=$DATABRICKS_HOST \
DATABRICKS_HTTP_PATH=$DATABRICKS_HTTP_PATH \
DATABRICKS_TOKEN=$DATABRICKS_TOKEN \
DATABRICKS_AUTH_TYPE=pat \
./go_sql_all_auth
env -i PATH=$PATH HOME=$HOME \
DATABRICKS_HOST=$DATABRICKS_HOST \
DATABRICKS_HTTP_PATH=$DATABRICKS_HTTP_PATH \
DATABRICKS_CLIENT_ID=$DATABRICKS_CLIENT_ID \
DATABRICKS_CLIENT_SECRET=$DATABRICKS_CLIENT_SECRET \
DATABRICKS_AUTH_TYPE=oauth_m2m \
./go_sql_all_auth
env -i PATH=$PATH HOME=$HOME USER=$USER DISPLAY=$DISPLAY \
DATABRICKS_HOST=$DATABRICKS_HOST \
DATABRICKS_HTTP_PATH=$DATABRICKS_HTTP_PATH \
DATABRICKS_AUTH_TYPE=u2m_browser \
./go_sql_all_auth
env -i PATH=$PATH HOME=$HOME USER=$USER DISPLAY=$DISPLAY \
DATABRICKS_HOST=$DATABRICKS_HOST \
DATABRICKS_HTTP_PATH=$DATABRICKS_HTTP_PATH \
DATABRICKS_U2M_CLIENT_ID=$DATABRICKS_U2M_CLIENT_ID \
DATABRICKS_AUTH_TYPE=u2m_custom_oauth_app \
./go_sql_all_auth
PKCE Flow with Timeout
Add timeout handling to the PKCE flow for better UX:
func runPKCEFlow(host, clientID, clientSecret, redirectURI string) (string, error) {
codeChan := make(chan string, 1)
errChan := make(chan error, 1)
server := &http.Server{Addr: ":" + port}
go server.ListenAndServe()
defer server.Shutdown(context.Background())
openBrowser(authURL)
select {
case code := <-codeChan:
return exchangeCodeForToken(code, codeVerifier, clientID, clientSecret, redirectURI, host)
case err := <-errChan:
return "", err
case <-time.After(2 * time.Minute):
return "", fmt.Errorf("authentication timed out after 2 minutes")
}
}
Key Differences from Databricks SDK for Go
| Aspect | Databricks SQL Driver for Go | Databricks SDK for Go |
|---|
| Purpose | SQL queries via warehouse | REST APIs (UC, Jobs, Clusters) |
| SQL Warehouse | Required (DATABRICKS_HTTP_PATH) | Not needed |
| Interface | database/sql package | Typed Go methods |
| Auth Config | dbsql.NewConnector() options | databricks.Config{} struct |
| User-Agent | dbsql.WithUserAgentEntry() (per-connector) | useragent.WithPartner() (global) |
| U2M Browser | u2m.NewAuthenticator() (built-in) | Not built-in (use PKCE helper) |
| M2M Authenticator | m2m.NewAuthenticator() (built-in) | SDK handles automatically |
| Auth selector env var | Can use DATABRICKS_AUTH_TYPE | Must use custom (e.g., APP_AUTH_TYPE) |
Build Tags for Multiple Examples
When organizing multiple Go example files in the same package, use build tags to compile each example separately. This prevents "main redeclared" errors.
Adding Build Tags
Each example file should have a build tag at the top (before package declaration):
package main
Example File Tags
| File | Build Tag |
|---|
pat_example.go | //go:build pat_example |
oauth_m2m_example.go | //go:build oauth_m2m_example |
u2m_builtin_oauth_example.go | //go:build u2m_builtin_oauth_example |
u2m_custom_oauth_app_example.go | //go:build u2m_custom_oauth_app_example |
u2m_token_env_example.go | //go:build u2m_token_env_example |
go_all_auth_example.go | //go:build go_all_auth_example |
error_handling_example.go | //go:build error_handling_example |
retry_helper.go | //go:build retry_helper_example |
Shared Helper Files
For shared helpers (like PKCE helper) that are used by multiple examples, use OR conditions:
package main
Building with Tags
go build -tags=pat_example -o pat_example_bin .
go build -tags=oauth_m2m_example -o oauth_m2m_bin .
go build -tags=go_all_auth_example -o all_auth_bin .
go build -tags=u2m_custom_oauth_app_example -o u2m_bin . && ./u2m_bin
Why Build Tags?
Without build tags, Go tries to compile all .go files in a package together, causing conflicts when multiple files define func main() or the same constants/variables. Build tags solve this by selectively including files during compilation.
Implementation Learnings
- Host normalization is critical: Always use
serverHostname() helper to strip https:// before passing to WithServerHostname()
- Port 443 required: Always include
WithPort(443) for HTTPS connections
- User-Agent per connector: Unlike SDK's global setting, each connector needs
WithUserAgentEntry()
- U2M browser requires env vars: Pass
HOME, USER, DISPLAY via env -i for browser to launch
- M2M auto-refreshes: The
m2m.NewAuthenticator() handles token refresh automatically
- Build tags prevent conflicts: When multiple example files exist in same package, use build tags to compile individually
- PKCE helper is shared: The PKCE helper file needs conditional build tags to be included with relevant examples