Skip to content

Adapters

Adapters bridge the gap between scaf’s code generation and specific database drivers. Each adapter knows how to generate code that executes queries using a particular database library.

flowchart LR
    subgraph CodeGen["Code Generation"]
        scaf[".scaf file"]
        lang["Language (Go)"]
        adapter["Adapter (neogo)"]
    end
    
    subgraph Generated["Generated Code"]
        code["scaf.go"]
        driver["Database Driver"]
    end
    
    scaf --> lang
    lang --> adapter
    adapter --> code
    code --> driver

The flow:

  1. Language (e.g., Go) handles the overall code structure
  2. Adapter (e.g., neogo) generates database-specific function bodies
  3. Generated code uses the database driver directly—no scaf runtime dependency
AdapterDatabaseDriverLanguageStatus
neogoNeo4jneogoGo✅ Available
pgxPostgreSQLpgxGo🚧 Planned
mysqlMySQLgo-sql-driverGo🚧 Planned
sqliteSQLitemodernc.org/sqliteGo🚧 Planned

The neogo adapter generates Go code that uses the neogo driver for Neo4j.

neo4j:
uri: bolt://localhost:7687
username: neo4j
password: password
generate:
lang: go
adapter: neogo # Inferred from neo4j + go if not specified
out: ./queries
package: queries

Given this scaf query:

fn GetUser `
MATCH (u:User {id: $userId})
RETURN u.name AS name, u.email AS email
`

The neogo adapter generates:

func GetUser(ctx context.Context, db neogo.Driver, userId string) (name string, email string, err error) {
err = db.Exec().
Cypher(`MATCH (u:User {id: $userId}) RETURN u.name AS name, u.email AS email`).
RunWithParams(ctx, map[string]any{"userId": userId}, "name", &name, "email", &email)
if err != nil {
return "", "", err
}
return name, email, nil
}
  • Standalone functions — No receiver type, functions are package-level
  • Context-aware — First parameter is always context.Context
  • Driver injection — Second parameter is neogo.Driver for flexibility
  • Error handling — All functions return error as the last value
  • Parameter mapping — Query $params become function parameters
  • Return binding — Query RETURN fields become return values
package main
import (
"context"
"log"
"github.com/rlch/neogo"
"myproject/queries"
)
func main() {
ctx := context.Background()
db, err := neogo.New(ctx, "bolt://localhost:7687",
neogo.WithAuth("neo4j", "password"))
if err != nil {
log.Fatal(err)
}
defer db.Close(ctx)
name, email, err := queries.GetUser(ctx, db, "user-123")
if err != nil {
log.Fatal(err)
}
log.Printf("User: %s <%s>", name, email)
}

The pgx adapter will generate Go code using pgx for PostgreSQL.

func GetUser(ctx context.Context, pool *pgxpool.Pool, userId int) (name string, email string, err error) {
row := pool.QueryRow(ctx,
`SELECT name, email FROM users WHERE id = $1`,
userId)
err = row.Scan(&name, &email)
if err != nil {
return "", "", err
}
return name, email, nil
}

If adapter is not specified in config, scaf infers it from the database and language:

DatabaseLanguageDefault Adapter
neo4jgoneogo
postgresgopgx
mysqlgomysql
sqlitegosqlite
generate:
adapter: neogo # Explicitly set adapter

Or via CLI:

Terminal window
scaf generate --adapter=neogo

Adapters implement the golang.Binding interface:

type Binding interface {
// Name returns the adapter identifier
Name() string
// Imports returns required import paths
Imports() []string
// ReceiverType returns method receiver type (empty for functions)
ReceiverType() string
// PrependParams returns params added before query params
PrependParams() []BindingParam
// ReturnsError returns true if functions return error
ReturnsError() bool
// GenerateBody generates the function body
GenerateBody(ctx *BodyContext) (string, error)
}

Register your adapter:

package myadapter
import "github.com/rlch/scaf/language/go"
func init() {
golang.RegisterBinding(&MyBinding{})
}

Import in your generate command:

import _ "myproject/adapters/myadapter"