Back to Documentation

Go SDK

High-performance Go client for entropyDB with goroutine support

Overview

The Go SDK provides:

  • High Performance: Optimized for concurrency
  • Context Support: Cancellation and timeouts
  • Connection Pooling: Efficient resource management
  • Query Builder: Type-safe query construction
  • Zero-Copy: Memory-efficient operations

Installation

# Install the SDK
go get github.com/entropydb/entropydb-go

# Import in your code
import (
    "github.com/entropydb/entropydb-go"
    "github.com/entropydb/entropydb-go/query"
)

Basic Usage

package main

import (
    "context"
    "fmt"
    "log"

    entropydb "github.com/entropydb/entropydb-go"
)

type User struct {
    ID       int64
    Username string
    Email    string
}

func main() {
    // Connect to EntropyDB (Host/Port point at entropydb.pg-protocol.port; User/Password are the
    // email/password you registered with -- see the PostgreSQL wire protocol docs).
    config := entropydb.Config{
        Host:     "localhost",
        Port:     5432,
        Database: "mydb",
        User:     "admin@example.com",
        Password: "password",
        PoolSize: 20,
    }

    client, err := entropydb.NewClient(config)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    ctx := context.Background()

    // Create table. Note: no SERIAL/auto-increment -- EntropyDB's SQL engine doesn't support
    // sequence/identity columns today, so IDs are assigned by the caller.
    _, err = client.Exec(ctx, `
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER,
            username VARCHAR(64),
            email VARCHAR(255),
            PRIMARY KEY (id)
        )
    `)
    if err != nil {
        log.Fatal(err)
    }

    // Insert data
    result, err := client.Exec(ctx,
        "INSERT INTO users (id, username, email) VALUES ($1, $2, $3)",
        1, "alice", "alice@example.com")
    if err != nil {
        log.Fatal(err)
    }

    rowsAffected, _ := result.RowsAffected()
    fmt.Printf("Inserted %d row(s)\n", rowsAffected)

    // Query single row
    var user User
    err = client.QueryRow(ctx,
        "SELECT id, username, email FROM users WHERE username = $1",
        "alice").Scan(&user.ID, &user.Username, &user.Email)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("User: %+v\n", user)

    // Query multiple rows
    rows, err := client.Query(ctx,
        "SELECT id, username, email FROM users WHERE email LIKE $1",
        "%@example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Username, &u.Email); err != nil {
            log.Fatal(err)
        }
        users = append(users, u)
    }

    fmt.Printf("Found %d users\n", len(users))
}

Connection Pooling

package main

import (
    "fmt"
    "time"

    entropydb "github.com/entropydb/entropydb-go"
)

func main() {
    config := entropydb.Config{
        Host:     "localhost",
        Port:     5432,
        Database: "mydb",
        User:     "admin@example.com",
        Password: "password",

        // Pool configuration -- Client wraps a real database/sql.DB, so these map directly to
        // SetMaxOpenConns/SetMaxIdleConns/SetConnMaxLifetime/SetConnMaxIdleTime.
        PoolSize:        20, // Maximum open connections
        MinConns:        5,  // Minimum idle connections
        MaxConnLifetime: 30 * time.Minute,
        MaxConnIdleTime: 5 * time.Minute,
        ConnectTimeout:  10 * time.Second,
    }

    client, err := entropydb.NewClient(config)
    if err != nil {
        panic(err)
    }
    defer client.Close()

    // Get pool stats -- a real database/sql.DBStats, not a custom type.
    stats := client.Stats()
    fmt.Printf("In use: %d\n", stats.InUse)
    fmt.Printf("Idle: %d\n", stats.Idle)
    fmt.Printf("Open connections: %d\n", stats.OpenConnections)
}

Query Builder

package main

import (
    "context"
    "time"

    entropydb "github.com/entropydb/entropydb-go"
    "github.com/entropydb/entropydb-go/query"
)

func example(ctx context.Context, client *entropydb.Client) error {
    // Build a SELECT: Build() returns (sqlText string, args []any) -- pass both straight through.
    sqlText, args := query.Select("id", "username", "email").
        From("users").
        Where("email LIKE ?", "%@example.com").
        And("created_at > ?", time.Now().AddDate(0, 0, -7)).
        OrderBy("created_at DESC").
        Limit(10).
        Build()
    // sqlText: SELECT id, username, email FROM users WHERE email LIKE $1 AND created_at > $2 ORDER BY created_at DESC LIMIT 10

    rows, err := client.Query(ctx, sqlText, args...)
    if err != nil {
        return err
    }
    defer rows.Close()

    // Build an INSERT (RETURNING is rendered faithfully but isn't executable against EntropyDB
    // today -- its SQL engine doesn't implement RETURNING yet, a real, disclosed limitation).
    insertSQL, insertArgs := query.Insert("users").
        Columns("id", "username", "email").
        Values(2, "bob", "bob@example.com").
        Build()
    if _, err := client.Exec(ctx, insertSQL, insertArgs...); err != nil {
        return err
    }

    // Build an UPDATE
    updateSQL, updateArgs := query.Update("users").
        Set("email", "newemail@example.com").
        Where("username = ?", "bob").
        Build()
    if _, err := client.Exec(ctx, updateSQL, updateArgs...); err != nil {
        return err
    }

    // Build a DELETE
    deleteSQL, deleteArgs := query.Delete("users").
        Where("created_at < ?", time.Now().AddDate(0, 0, -365)).
        Build()
    if _, err := client.Exec(ctx, deleteSQL, deleteArgs...); err != nil {
        return err
    }

    // A JOIN with GROUP BY/HAVING
    joinSQL, joinArgs := query.Select("u.username", "o.total").
        From("users u").
        Join("orders o", "u.id = o.user_id").
        Where("o.status = ?", "completed").
        GroupBy("u.username", "o.total").
        Having("o.total > ?", 100).
        Build()
    _, err = client.Query(ctx, joinSQL, joinArgs...)
    return err
}

Transactions

package main

import (
    "context"
    "github.com/entropydb/entropydb-go"
)

func transferFunds(ctx context.Context, client *entropydb.Client, fromID, toID int64, amount float64) error {
    // Begin transaction
    tx, err := client.BeginTx(ctx, &entropydb.TxOptions{
        IsolationLevel: entropydb.Serializable,
    })
    if err != nil {
        return err
    }
    defer tx.Rollback(ctx)
    
    // Debit from source account
    _, err = tx.Exec(ctx,
        "UPDATE accounts SET balance = balance - $1 WHERE id = $2",
        amount, fromID)
    if err != nil {
        return err
    }
    
    // Credit to destination account
    _, err = tx.Exec(ctx,
        "UPDATE accounts SET balance = balance + $1 WHERE id = $2",
        amount, toID)
    if err != nil {
        return err
    }
    
    // Record transaction
    _, err = tx.Exec(ctx,
        "INSERT INTO transfers (from_account, to_account, amount) VALUES ($1, $2, $3)",
        fromID, toID, amount)
    if err != nil {
        return err
    }
    
    // Commit transaction
    return tx.Commit(ctx)
}

// Using context timeout
func queryWithTimeout() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    rows, err := client.Query(ctx, "SELECT * FROM large_table")
    if err != nil {
        if ctx.Err() == context.DeadlineExceeded {
            log.Println("Query timed out")
        }
    }
}

Concurrency with Goroutines

package main

import (
    "context"
    "sync"
)

func concurrentInserts(client *entropydb.Client, records []User) error {
    ctx := context.Background()
    var wg sync.WaitGroup
    errChan := make(chan error, len(records))
    
    // Process records concurrently
    for _, user := range records {
        wg.Add(1)
        go func(u User) {
            defer wg.Done()
            
            _, err := client.Exec(ctx,
                "INSERT INTO users (username, email) VALUES ($1, $2)",
                u.Username, u.Email)
            if err != nil {
                errChan <- err
            }
        }(user)
    }
    
    // Wait for all goroutines
    wg.Wait()
    close(errChan)
    
    // Check for errors
    for err := range errChan {
        if err != nil {
            return err
        }
    }
    
    return nil
}

// Worker pool pattern
func workerPool(client *entropydb.Client) {
    ctx := context.Background()
    jobs := make(chan int, 100)
    results := make(chan string, 100)
    
    // Start workers
    var wg sync.WaitGroup
    for w := 0; w < 10; w++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for id := range jobs {
                var username string
                err := client.QueryRow(ctx,
                    "SELECT username FROM users WHERE id = $1",
                    id).Scan(&username)
                if err == nil {
                    results <- username
                }
            }
        }()
    }
    
    // Send jobs
    for i := 1; i <= 100; i++ {
        jobs <- i
    }
    close(jobs)
    
    // Wait for completion
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // Collect results
    for username := range results {
        fmt.Println(username)
    }
}

Best Practices

Performance

  • • Use connection pooling
  • • Enable statement caching
  • • Use context for cancellation
  • • Batch operations when possible

Concurrency

  • • Share client, not connections
  • • Use worker pools for bulk operations
  • • Handle errors from goroutines
  • • Set appropriate timeouts

Next Steps