Cursor

クエリを実行し、行ごとに定義された構造体を返すカーソルを返します。大きな結果セットで全体の結果をメモリに読み込むのが非効率的な場合に特に有用です。

使用方法

type userObj struct {
    ID   int
    Name string
}

ctx := context.Background()
db, err := bob.Open("postgres", "...")
if err != nil {
    // ...
}

q := psql.Select(...)
// user は userObj{} 型
cursor, err := bob.Cursor(ctx, db, q, scan.StructMapper[userObj]())
if err != nil {
    // ...
}
defer cursor.Close() // 必ずクローズする

for cursor.Next() {
    user, err := cursor.Get() // 次の行を具体的な型にスキャン
    if err != nil {
        // エラーハンドリング
        break
    }
    // user を処理
}

特徴

  • メモリ効率: 大きな結果セットを一度にメモリに読み込まない
  • 逐次処理: 行ごとに順次処理
  • 型安全: 構造体マッピングをサポート
  • リソース管理: 必ず Close() を呼ぶ必要がある

使用例

基本的な使用

type User struct {
    ID    int    `db:"id"`
    Name  string `db:"name"`
    Email string `db:"email"`
}

query := psql.Select("id", "name", "email").
    From("users").
    Where(psql.Quote("active").EQ(psql.Arg(true)))

cursor, err := bob.Cursor(ctx, db, query, scan.StructMapper[User]())
if err != nil {
    return err
}
defer cursor.Close()

var processedCount int
for cursor.Next() {
    user, err := cursor.Get()
    if err != nil {
        return fmt.Errorf("failed to scan user: %w", err)
    }
    
    // ユーザーを処理
    fmt.Printf("Processing user: %s\n", user.Name)
    processedCount++
}

fmt.Printf("Processed %d users\n", processedCount)

大量データの処理

type UserExport struct {
    ID        int       `db:"id"`
    Name      string    `db:"name"`
    Email     string    `db:"email"`
    CreatedAt time.Time `db:"created_at"`
}

func ExportUsers(ctx context.Context, db bob.Executor, writer io.Writer) error {
    query := psql.Select("id", "name", "email", "created_at").
        From("users").
        OrderBy(psql.Quote("id").Asc())
    
    cursor, err := bob.Cursor(ctx, db, query, scan.StructMapper[UserExport]())
    if err != nil {
        return err
    }
    defer cursor.Close()
    
    // CSV ヘッダーを書き込み
    fmt.Fprintln(writer, "ID,Name,Email,CreatedAt")
    
    for cursor.Next() {
        user, err := cursor.Get()
        if err != nil {
            return err
        }
        
        // CSV 行を書き込み
        fmt.Fprintf(writer, "%d,%s,%s,%s\n", 
            user.ID, 
            user.Name, 
            user.Email, 
            user.CreatedAt.Format("2006-01-02"))
    }
    
    return nil
}

エラーハンドリング付きの処理

type User struct {
    ID    int    `db:"id"`
    Name  string `db:"name"`
    Email string `db:"email"`
}

func ProcessUsersWithCursor(ctx context.Context, db bob.Executor) error {
    query := psql.Select("id", "name", "email").From("users")
    
    cursor, err := bob.Cursor(ctx, db, query, scan.StructMapper[User]())
    if err != nil {
        return fmt.Errorf("failed to create cursor: %w", err)
    }
    defer cursor.Close()
    
    for cursor.Next() {
        user, err := cursor.Get()
        if err != nil {
            return fmt.Errorf("failed to scan user: %w", err)
        }
        
        // ユーザーごとの処理
        if err := processUser(user); err != nil {
            log.Printf("Failed to process user %d: %v", user.ID, err)
            continue // エラーがあっても続行
        }
    }
    
    return nil
}

func processUser(user User) error {
    // ユーザー処理のロジック
    return nil
}

注意点

  • リソース管理: defer cursor.Close() を必ず使用する
  • メモリ効率: 大きな結果セットに最適
  • エラーハンドリング: cursor.Next()cursor.Get() の両方でエラーを確認
  • パフォーマンス: 小さな結果セットの場合は All の方が効率的

Cursor は、大量のデータを効率的に処理する場合に最適な選択肢です。