upper/db

upper/dbは「A productive data access layer for Go」として開発された、PostgreSQL、CockroachDB、MySQL、SQLite、MongoDBに対応するGoのデータアクセス層(DAL)ライブラリです。ORM的な機能を持ちながら、一般的なデータベース操作のためのツールを提供し、高度なシナリオでは目立たずに機能します。SQL、NoSQLデータベースに関係なく、統一されたAPIを通じて異なるデータソースと連携でき、生産性と柔軟性のバランスを重視した設計になっています。

GoDatabaseORMDALMulti-databasePostgreSQLMySQLSQLiteMongoDB

GitHub概要

upper/db

Data Access Layer (DAL) for PostgreSQL, CockroachDB, MySQL, SQLite and MongoDB with ORM-like features.

ホームページ:https://upper.io/
スター3,616
ウォッチ56
フォーク240
作成日:2013年10月23日
言語:Go
ライセンス:MIT License

トピックス

cockroachdbdaldatabasedbgogolangmongodbmysqlnosqlormpostgresqlsqlsqliteupper

スター履歴

upper/db Star History
データ取得日時: 2025/8/13 01:43

ライブラリ

upper/db

概要

upper/dbは「A productive data access layer for Go」として開発された、PostgreSQL、CockroachDB、MySQL、SQLite、MongoDBに対応するGoのデータアクセス層(DAL)ライブラリです。ORM的な機能を持ちながら、一般的なデータベース操作のためのツールを提供し、高度なシナリオでは目立たずに機能します。SQL、NoSQLデータベースに関係なく、統一されたAPIを通じて異なるデータソースと連携でき、生産性と柔軟性のバランスを重視した設計になっています。

詳細

upper/db v4は、数年間のGo、SQLデータベース、各種APIの本番運用経験を集約した成果物です。従来のバニティインポートパス(upper.io/db.v3)から、より親しみやすいgithub.com/upper/db/v4パスに移行しました。SQLビルダーによるカスタムSQLクエリの構築、db.RecordとStoreインターフェースを通じた高度なパターンの実装、マルチデータベース対応の統一APIなど、企業レベルのアプリケーション開発に必要な機能を包括的にサポートします。

主な特徴

  • マルチデータベース対応: PostgreSQL、CockroachDB、MySQL、SQLite、MongoDB統一サポート
  • 統一API: SQL、NoSQLに関係なく一貫したインターフェース
  • SQLビルダー: 高度なケースに対応するカスタムクエリ構築
  • ORM的機能: db.RecordとStoreインターフェースによる便利な抽象化
  • 非侵入的設計: 高度なシナリオでは目立たずに機能
  • 生産性重視: 開始から即座に生産的な開発が可能

メリット・デメリット

メリット

  • 複数データベースの統一的な操作と開発効率の向上
  • SQLとNoSQLの両方に対応する一貫したAPI設計
  • 生SQLとSQLビルダーの選択的使い分けが可能
  • シンプルで直感的なAPIによる学習コストの低減
  • 企業レベルの本番運用実績による信頼性
  • Goらしいシンプルで明確な設計思想

デメリット

  • Go言語専用でマルチ言語対応なし
  • 重厚なORM機能は提供されない軽量設計
  • 複雑なリレーションシップ管理は手動実装が必要
  • データベース固有の高度な機能は抽象化されない場合がある
  • 大規模なレガシーシステムの移行には適さない可能性
  • 完全なORM置き換えとしては機能が限定的

参考ページ

書き方の例

セットアップ

# Go モジュール初期化
go mod init upper-db-example

# upper/db v4 インストール
go get github.com/upper/db/v4

# データベースドライバー追加(使用するデータベースに応じて)
go get github.com/upper/db/v4/adapter/postgresql  # PostgreSQL
go get github.com/upper/db/v4/adapter/mysql       # MySQL
go get github.com/upper/db/v4/adapter/sqlite      # SQLite
go get github.com/upper/db/v4/adapter/mongo       # MongoDB
// go.mod
module upper-db-example

go 1.21

require (
    github.com/upper/db/v4 v4.8.0
    github.com/upper/db/v4/adapter/postgresql v4.8.0
    github.com/upper/db/v4/adapter/mysql v4.8.0
    github.com/upper/db/v4/adapter/sqlite v4.8.0
    github.com/upper/db/v4/adapter/mongo v4.8.0
)

基本的な使い方

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/upper/db/v4"
    "github.com/upper/db/v4/adapter/postgresql"
    "github.com/upper/db/v4/adapter/sqlite"
)

// User モデル
type User struct {
    ID        uint      `db:"id,omitempty"`
    Username  string    `db:"username"`
    Email     string    `db:"email"`
    CreatedAt time.Time `db:"created_at,omitempty"`
}

// Post モデル  
type Post struct {
    ID       uint   `db:"id,omitempty"`
    Title    string `db:"title"`
    Content  string `db:"content"`
    AuthorID uint   `db:"author_id"`
    User     *User  `db:"-"` // リレーション用(DBフィールドではない)
}

func main() {
    // PostgreSQL接続
    postgresSession, err := postgresql.Open(postgresql.ConnectionURL{
        Database: "testdb",
        Host:     "localhost",
        User:     "testuser",
        Password: "testpass",
    })
    if err != nil {
        log.Fatal("PostgreSQL接続エラー:", err)
    }
    defer postgresSession.Close()

    // SQLite接続(比較用)
    sqliteSession, err := sqlite.Open(sqlite.ConnectionURL{
        Database: "test.db",
    })
    if err != nil {
        log.Fatal("SQLite接続エラー:", err)
    }
    defer sqliteSession.Close()

    // データベース操作の例
    err = demonstrateBasicOperations(postgresSession)
    if err != nil {
        log.Fatal("操作エラー:", err)
    }
}

func demonstrateBasicOperations(sess db.Session) error {
    // テーブル参照取得
    usersCol := sess.Collection("users")
    postsCol := sess.Collection("posts")

    // ユーザー作成
    user := User{
        Username:  "john_doe",
        Email:     "[email protected]",
        CreatedAt: time.Now(),
    }

    id, err := usersCol.Insert(user)
    if err != nil {
        return fmt.Errorf("ユーザー挿入エラー: %w", err)
    }

    fmt.Printf("作成されたユーザーID: %v\n", id)

    // ユーザー検索
    var foundUser User
    err = usersCol.Find(db.Cond{"username": "john_doe"}).One(&foundUser)
    if err != nil {
        return fmt.Errorf("ユーザー検索エラー: %w", err)
    }

    fmt.Printf("検索されたユーザー: %+v\n", foundUser)

    // 投稿作成
    post := Post{
        Title:    "My First Post",
        Content:  "This is the content of my first post.",
        AuthorID: foundUser.ID,
    }

    _, err = postsCol.Insert(post)
    if err != nil {
        return fmt.Errorf("投稿挿入エラー: %w", err)
    }

    // 複数ユーザー取得
    var users []User
    err = usersCol.Find().All(&users)
    if err != nil {
        return fmt.Errorf("ユーザー一覧取得エラー: %w", err)
    }

    fmt.Printf("全ユーザー数: %d\n", len(users))

    return nil
}

SQLビルダーを使った高度なクエリ

package main

import (
    "fmt"
    "log"

    "github.com/upper/db/v4"
    "github.com/upper/db/v4/adapter/postgresql"
)

type UserWithPostCount struct {
    ID        uint   `db:"id"`
    Username  string `db:"username"`
    Email     string `db:"email"`
    PostCount int    `db:"post_count"`
}

type PostWithAuthor struct {
    ID          uint   `db:"id"`
    Title       string `db:"title"`
    Content     string `db:"content"`
    AuthorName  string `db:"author_name"`
    AuthorEmail string `db:"author_email"`
}

func demonstrateSQLBuilder(sess db.Session) error {
    // SQLビルダーの取得
    sqlBuilder := sess.SQL()

    // ユーザーと投稿数のJOINクエリ
    var usersWithCounts []UserWithPostCount
    err := sqlBuilder.
        Select("u.id", "u.username", "u.email", "COUNT(p.id) as post_count").
        From("users u").
        LeftJoin("posts p ON u.id = p.author_id").
        GroupBy("u.id", "u.username", "u.email").
        OrderBy("post_count DESC").
        All(&usersWithCounts)

    if err != nil {
        return fmt.Errorf("JOIN クエリエラー: %w", err)
    }

    fmt.Printf("ユーザーと投稿数:\n")
    for _, user := range usersWithCounts {
        fmt.Printf("  %s (%s): %d posts\n", user.Username, user.Email, user.PostCount)
    }

    // 投稿と著者情報のJOINクエリ
    var postsWithAuthors []PostWithAuthor
    err = sqlBuilder.
        Select("p.id", "p.title", "p.content", "u.username as author_name", "u.email as author_email").
        From("posts p").
        Join("users u ON p.author_id = u.id").
        Where("p.title LIKE ?", "%First%").
        OrderBy("p.id DESC").
        All(&postsWithAuthors)

    if err != nil {
        return fmt.Errorf("投稿JOIN クエリエラー: %w", err)
    }

    fmt.Printf("\n投稿と著者情報:\n")
    for _, post := range postsWithAuthors {
        fmt.Printf("  %s by %s (%s)\n", post.Title, post.AuthorName, post.AuthorEmail)
    }

    // 条件付き更新
    res, err := sqlBuilder.
        Update("users").
        Set("email = ?", "[email protected]").
        Where("username = ?", "john_doe").
        Exec()

    if err != nil {
        return fmt.Errorf("更新エラー: %w", err)
    }

    rowsAffected, _ := res.RowsAffected()
    fmt.Printf("\n更新された行数: %d\n", rowsAffected)

    return nil
}

// 複雑な検索条件を使った例
func demonstrateAdvancedQueries(sess db.Session) error {
    postsCol := sess.Collection("posts")

    // 複雑な条件での検索
    var posts []Post
    err := postsCol.Find(
        db.Or(
            db.Cond{"title LIKE": "%Go%"},
            db.Cond{"title LIKE": "%Database%"},
        ),
        db.Cond{"author_id >": 0},
    ).OrderBy("-id").Limit(10).All(&posts)

    if err != nil {
        return fmt.Errorf("高度な検索エラー: %w", err)
    }

    fmt.Printf("検索結果: %d件\n", len(posts))

    // ページネーション
    const pageSize = 5
    page := 2

    var paginatedPosts []Post
    err = postsCol.Find().
        OrderBy("id").
        Limit(pageSize).
        Offset((page - 1) * pageSize).
        All(&paginatedPosts)

    if err != nil {
        return fmt.Errorf("ページネーションエラー: %w", err)
    }

    fmt.Printf("ページ %d の投稿: %d件\n", page, len(paginatedPosts))

    return nil
}

func main() {
    sess, err := postgresql.Open(postgresql.ConnectionURL{
        Database: "testdb",
        Host:     "localhost",
        User:     "testuser",
        Password: "testpass",
    })
    if err != nil {
        log.Fatal("接続エラー:", err)
    }
    defer sess.Close()

    if err := demonstrateSQLBuilder(sess); err != nil {
        log.Fatal("SQLビルダーエラー:", err)
    }

    if err := demonstrateAdvancedQueries(sess); err != nil {
        log.Fatal("高度なクエリエラー:", err)
    }
}

マルチデータベース対応とトランザクション

package main

import (
    "fmt"
    "log"

    "github.com/upper/db/v4"
    "github.com/upper/db/v4/adapter/postgresql"
    "github.com/upper/db/v4/adapter/mysql"
    "github.com/upper/db/v4/adapter/sqlite"
    "github.com/upper/db/v4/adapter/mongo"
)

// データベース抽象化インターフェース
type DatabaseService interface {
    CreateUser(user User) (interface{}, error)
    GetUserByUsername(username string) (*User, error)
    GetAllUsers() ([]User, error)
    Close() error
}

// PostgreSQL実装
type PostgreSQLService struct {
    session db.Session
}

func NewPostgreSQLService() (*PostgreSQLService, error) {
    sess, err := postgresql.Open(postgresql.ConnectionURL{
        Database: "testdb",
        Host:     "localhost", 
        User:     "testuser",
        Password: "testpass",
    })
    if err != nil {
        return nil, err
    }
    return &PostgreSQLService{session: sess}, nil
}

func (s *PostgreSQLService) CreateUser(user User) (interface{}, error) {
    return s.session.Collection("users").Insert(user)
}

func (s *PostgreSQLService) GetUserByUsername(username string) (*User, error) {
    var user User
    err := s.session.Collection("users").Find(db.Cond{"username": username}).One(&user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func (s *PostgreSQLService) GetAllUsers() ([]User, error) {
    var users []User
    err := s.session.Collection("users").Find().All(&users)
    return users, err
}

func (s *PostgreSQLService) Close() error {
    return s.session.Close()
}

// MongoDB実装(NoSQL例)
type MongoService struct {
    session db.Session
}

func NewMongoService() (*MongoService, error) {
    sess, err := mongo.Open(mongo.ConnectionURL{
        Database: "testdb",
        Host:     "localhost",
    })
    if err != nil {
        return nil, err
    }
    return &MongoService{session: sess}, nil
}

func (s *MongoService) CreateUser(user User) (interface{}, error) {
    return s.session.Collection("users").Insert(user)
}

func (s *MongoService) GetUserByUsername(username string) (*User, error) {
    var user User
    err := s.session.Collection("users").Find(db.Cond{"username": username}).One(&user)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func (s *MongoService) GetAllUsers() ([]User, error) {
    var users []User
    err := s.session.Collection("users").Find().All(&users)
    return users, err
}

func (s *MongoService) Close() error {
    return s.session.Close()
}

// トランザクション処理の例
func demonstrateTransactions(sess db.Session) error {
    return sess.Tx(func(tx db.Session) error {
        usersCol := tx.Collection("users")
        postsCol := tx.Collection("posts")

        // ユーザー作成
        user := User{
            Username: "tx_user",
            Email:    "[email protected]",
        }

        userID, err := usersCol.Insert(user)
        if err != nil {
            return fmt.Errorf("トランザクション内ユーザー作成エラー: %w", err)
        }

        // 投稿作成
        post := Post{
            Title:    "Transaction Post",
            Content:  "Created within a transaction",
            AuthorID: userID.(uint),
        }

        _, err = postsCol.Insert(post)
        if err != nil {
            return fmt.Errorf("トランザクション内投稿作成エラー: %w", err)
        }

        fmt.Println("トランザクション処理完了")
        return nil
    })
}

// バッチ処理の例
func demonstrateBatchOperations(sess db.Session) error {
    usersCol := sess.Collection("users")

    // バッチ挿入
    users := []User{
        {Username: "batch_user1", Email: "[email protected]"},
        {Username: "batch_user2", Email: "[email protected]"},
        {Username: "batch_user3", Email: "[email protected]"},
    }

    // 各ユーザーを挿入
    for _, user := range users {
        _, err := usersCol.Insert(user)
        if err != nil {
            return fmt.Errorf("バッチ挿入エラー: %w", err)
        }
    }

    // バッチ更新
    res, err := sess.SQL().
        Update("users").
        Set("email = CONCAT(username, '@batch.com')").
        Where("username LIKE ?", "batch_%").
        Exec()

    if err != nil {
        return fmt.Errorf("バッチ更新エラー: %w", err)
    }

    rowsAffected, _ := res.RowsAffected()
    fmt.Printf("バッチ更新された行数: %d\n", rowsAffected)

    return nil
}

func main() {
    // PostgreSQL サービス
    pgService, err := NewPostgreSQLService()
    if err != nil {
        log.Fatal("PostgreSQL サービス作成エラー:", err)
    }
    defer pgService.Close()

    // 統一インターフェースでの操作
    services := []DatabaseService{pgService}

    for i, service := range services {
        fmt.Printf("=== データベースサービス %d ===\n", i+1)

        // ユーザー作成
        user := User{
            Username: fmt.Sprintf("service_user_%d", i+1),
            Email:    fmt.Sprintf("service%[email protected]", i+1),
        }

        _, err := service.CreateUser(user)
        if err != nil {
            log.Printf("ユーザー作成エラー: %v", err)
            continue
        }

        // ユーザー取得
        foundUser, err := service.GetUserByUsername(user.Username)
        if err != nil {
            log.Printf("ユーザー取得エラー: %v", err)
            continue
        }

        fmt.Printf("作成・取得されたユーザー: %+v\n", foundUser)
    }

    // トランザクションとバッチ処理のデモ
    if err := demonstrateTransactions(pgService.session); err != nil {
        log.Printf("トランザクションエラー: %v", err)
    }

    if err := demonstrateBatchOperations(pgService.session); err != nil {
        log.Printf("バッチ処理エラー: %v", err)
    }
}