go-mongox:簡單高效,讓文件操作和 bson 資料構造更流暢

陳明勇發表於2024-01-07

Go 語言中使用 MongoDB 官方框架進行集合操作時,深深感到構建 bson 資料是一件非常繁瑣的工作。欄位、逗號,括號等符號的排列,讓我感覺彷彿是在進行一場拼圖遊戲。因此我在想,有沒有一個能讓我絲滑,高效操作 MongoDB 的第三方框架呢,遺憾的是,並沒有找到符合我預期的框架,索性我就自己動手開發了一個,這就是 go-mongox 框架的由來。

如果你也有類似我的這種感受,相信 go-mongox 框架能給你帶來不一樣的體驗。

go-mongox 基於 泛型MongoDB 官方框架進行了二次封裝,它透過使用鏈式呼叫的方式,讓我們能夠絲滑地操作文件。同時,其還提供了多種型別的 bson 構造器,幫助我們高效的構建 bson 資料。

倉庫地址:github.com/chenmingyong0423/go-mon...

該框架處於初期階段,希望透過集思廣益的方式,邀請各位開發者共同參與,提出寶貴的建議和意見,共同打造一個更強大、更靈活的框架。期待著您的積極參與和寶貴反饋,共同推動go-mongox不斷進步。

功能

  • 文件的 crud 操作
  • 聚合操作
  • 構造 bson 資料
  • ······(敬請期待)

安裝

go get github.com/chenmingyong0423/go-mongox@latest

基於泛型的 collection 形態初始化

package main

import (
    "context"

    "github.com/chenmingyong0423/go-mongox"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

type Post struct {
    Id      primitive.ObjectID `bson:"_id,omitempty"`
    Title   string `bson:"title"`
    Author  string `bson:"author"`
    Content string `bson:"content"`
}

func main() {
    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := newCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[Post](mongoCollection)
}

// 示例程式碼,不是最佳的建立方式
func newCollection() *mongo.Collection {
    client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{
        Username:   "test",
        Password:   "test",
        AuthSource: "db-test",
    }))
    if err != nil {
        panic(err)
    }
    err = client.Ping(context.Background(), readpref.Primary())
    if err != nil {
        panic(err)
    }
    collection := client.Database("db-test").Collection("test_post")
    return collection
}

透過 mongox.NewCollection 函式,我們可以建立一個基於泛型的 collection 裝飾器。

Creator 創造器

Creator 是一個創造器,用於執行插入相關的操作。

插入單個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/creator/insert_one.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "go.mongodb.org/mongo-driver/mongo/options"

    "github.com/chenmingyong0423/go-mongox"
)

func main() {
    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)

    ctx := context.Background()
    // 插入一個文件
    doc := collection.Post{Title: "go 語言 go-mongox 庫的使用教程", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"}
    oneResult, err := postCollection.Creator().InsertOne(ctx, doc)
    if err != nil {
        panic(err)
    }
    fmt.Println(oneResult.InsertedID != nil) // true

    // 攜帶 option 引數
    oneResult, err = postCollection.Creator().InsertOne(ctx, doc, options.InsertOne().SetComment("test"))
    if err != nil {
        panic(err)
    }
    fmt.Println(oneResult.InsertedID != nil) // true
}

基於 postCollection 例項,我們可以透過 Creator() 方法建立一個創造器,然後進行插入操作。

InsertOne 方法與官方的 API 同名,作用是插入一條資料。如果我們需要設定 options 引數,可以將其作為該方法的第三個引數傳遞。

插入多個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/creator/insert_many.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "github.com/chenmingyong0423/go-mongox"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)

    ctx := context.Background()

    docs := []collection.Post{
        {Title: "go-mongox", Author: "陳明勇", Content: "..."},
        {Title: "builder", Author: "陳明勇", Content: "..."},
    }
    manyResult, err := postCollection.Creator().InsertMany(ctx, docs)
    if err != nil {
        panic(err)
    }
    fmt.Println(len(manyResult.InsertedIDs) == 2) // true

    // 攜帶 option 引數
    manyResult, err = postCollection.Creator().InsertMany(ctx, docs, options.InsertMany().SetComment("test"))
    if err != nil {
        panic(err)
    }
    fmt.Println(len(manyResult.InsertedIDs) == 2) // true
}

InsertMany 方法與官方的 API 同名,作用是插入多條資料。如果我們需要設定 options 引數,可以將其作為該方法的第三個引數傳遞。

Finder 查詢器

Finder 是一個查詢器,用於執行查詢相關的操作。

查詢單個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/finder/find_one.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "go.mongodb.org/mongo-driver/mongo/options"

    "github.com/chenmingyong0423/go-mongox"
    "github.com/chenmingyong0423/go-mongox/bsonx"
    "github.com/chenmingyong0423/go-mongox/builder/query"
)

func main() {
    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    oneResult, err := postCollection.Creator().InsertOne(context.Background(), collection.Post{Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"})
    if err != nil {
        panic(err)
    }

    // 查詢單個文件
    post, err := postCollection.Finder().Filter(query.Id(oneResult.InsertedID)).FindOne(context.Background())
    if err != nil {
        panic(err)
    }
    fmt.Println(post)

    // 設定 *options.FindOneOptions 引數
    post2, err := postCollection.Finder().
        Filter(query.Id(oneResult.InsertedID)).
        FindOne(context.Background(), options.FindOne().SetProjection(bsonx.M("content", 0)))
    if err != nil {
        panic(err)
    }
    fmt.Println(post2)

    // - map 作為 filter 條件
    post3, err := postCollection.Finder().Filter(map[string]any{"_id": oneResult.InsertedID}).FindOne(context.Background())
    if err != nil {
        panic(err)
    }
    fmt.Println(post3)

    // - 複雜條件查詢
    // -- 使用 query 包構造複雜的 bson: bson.D{bson.E{Key: "title", Value: bson.M{"$eq": "go"}}, bson.E{Key: "author", Value: bson.M{"$eq": "陳明勇"}}}
    post4, err := postCollection.Finder().
        Filter(query.BsonBuilder().Eq("title", "go-mongox").Eq("author", "陳明勇").Build()).
        FindOne(context.Background())
    if err != nil {
        panic(err)
    }
    fmt.Println(post4)
}

基於 postCollection 例項,我們可以透過 Finder() 方法建立一個查詢器,然後進行查詢操作。

FindOne 方法與官方的 API 同名,作用是查詢單個文件。我們可以透過 Filter 方法設定 查詢條件,如果我們需要設定 options 引數,可以將其作為 FindOne 方法的第二個引數傳遞。

對於簡單的查詢條件,我們可以使用 query 包提供的函式進行構造,例如 query.Id(id);對於複雜的查詢條件,我們可以使用 query 包提供的 BsonBuilder構造器進行構造。query 包的用法會在下面的內容進行詳細地介紹。

查詢多個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/finder/find.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "github.com/chenmingyong0423/go-mongox"
    "github.com/chenmingyong0423/go-mongox/bsonx"
    "github.com/chenmingyong0423/go-mongox/builder/query"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    ctx := context.Background()

    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    _, err := postCollection.Creator().InsertMany(ctx, []collection.Post{
        {Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"},
        {Title: "mongo", Author: "陳明勇", Content: "..."},
    })
    if err != nil {
        panic(err)
    }

    // 查詢多個文件
    // bson.D{bson.E{Key: "title", Value: bson.M{"$in": []string{"go-mongox", "mongo"}}}}
    posts, err := postCollection.Finder().
        Filter(query.In("title", []string{"go-mongox", "mongo"}...)).
        Find(ctx)
    if err != nil {
        panic(err)
    }
    for _, post := range posts {
        fmt.Println(post)
    }

    // 設定 *options.FindOptions 引數
    // bson.D{bson.E{Key: "title", Value: bson.M{"$in": []string{"go-mongox", "mongo"}}}}
    posts2, err := postCollection.Finder().
        Filter(query.In("title", []string{"go-mongox", "mongo"}...)).
        Find(ctx, options.Find().SetProjection(bsonx.M("content", 0)))
    if err != nil {
        panic(err)
    }
    for _, post := range posts2 {
        fmt.Println(post)
    }
}

Find 方法與官方的 API 同名,作用是查詢多個文件。如果我們需要設定 options 引數,可以將其作為 Find 方法的第二個引數傳遞。

在上面的例子中,為了構造 $in 查詢語句,我們使用了 query 包提供的泛型函式 In

Updater 更新器

Updater 是一個更新器,用於執行更新相關的操作。

更新單個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/updater/update_one.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "go.mongodb.org/mongo-driver/mongo/options"

    "github.com/chenmingyong0423/go-mongox/builder/query"

    "github.com/chenmingyong0423/go-mongox"
    "github.com/chenmingyong0423/go-mongox/bsonx"
    "github.com/chenmingyong0423/go-mongox/builder/update"
    "github.com/chenmingyong0423/go-mongox/types"
)

func main() {
    ctx := context.Background()

    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    oneResult, err := postCollection.Creator().InsertOne(ctx, collection.Post{Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"})
    if err != nil {
        panic(err)
    }

    // 更新單個文件
    // 透過 update 包構建 bson 更新語句
    updateResult, err := postCollection.Updater().
        Filter(query.Id(oneResult.InsertedID)).
        Updates(update.Set("title", "golang")).
        UpdateOne(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Println(updateResult.ModifiedCount == 1) // true

    // - 使用 map 構造更新資料,並設定 *options.UpdateOptions,執行 upsert 操作
    updateResult2, err := postCollection.Updater().
        Filter(bsonx.Id("custom-id")).
        UpdatesWithOperator(types.Set, bsonx.M("title", "mongo")).
        UpdateOne(ctx, options.Update().SetUpsert(true))
    if err != nil {
        panic(err)
    }
    fmt.Println(updateResult2.UpsertedID.(string) == "custom-id") // true
}

基於 postCollection 例項,我們可以透過 Updater() 方法建立一個更新器,然後進行更新操作。

UpdateOne 方法與官方的 API 同名,作用是更新單個文件。我們可以透過 Filter 方法設定 文件匹配的條件,如果我們需要設定 options 引數,可以將其作為 UpdateOne 方法的第二個引數傳遞。

對於更新操作引數,我們可以使用以下兩個方法進行設定:

  • Updates 方法:該方法接收 bsonmap 等合法的更新操作語句。上面的例子使用了 update 包裡的 Set 對更新操作語句進行構造。
  • UpdatesWithOperator 方法:該方法的第一個引數為更新運算子,第二個引數為預期更新的資料。上面的例子使用了 bsonx 包構造 bson 資料。

更新多個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/updater/update_many.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "github.com/chenmingyong0423/go-mongox"
    "github.com/chenmingyong0423/go-mongox/builder/query"
    "github.com/chenmingyong0423/go-mongox/builder/update"
)

func main() {
    ctx := context.Background()

    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    _, err := postCollection.Creator().InsertMany(ctx, []collection.Post{
        {Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"},
        {Title: "builder", Author: "陳明勇", Content: "..."},
    })
    if err != nil {
        panic(err)
    }

    // 更新多個文件
    updateResult, err := postCollection.Updater().
        Filter(query.In("title", []string{"go-mongox", "builder"}...)).
        Updates(update.Set("title", "golang")).
        UpdateMany(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Println(updateResult.ModifiedCount == 2) // true
}

UpdateMany 方法與官方的 API 同名,作用是更新多個文件。

Deleter 刪除器

Deleter 是一個刪除器,用於執行刪除相關的操作。

刪除單個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/deleter/delete_one.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "github.com/chenmingyong0423/go-mongox/builder/query"
    "go.mongodb.org/mongo-driver/mongo/options"

    "github.com/chenmingyong0423/go-mongox"
)

func main() {
    ctx := context.Background()

    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    _, err := postCollection.Creator().InsertMany(ctx, []collection.Post{
        {Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"},
        {Title: "builder", Author: "陳明勇", Content: "..."},
    })
    if err != nil {
        panic(err)
    }

    // 刪除單個文件
    deleteResult, err := postCollection.Deleter().Filter(query.Eq("title", "go-mongox")).DeleteOne(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Println(deleteResult.DeletedCount == 1) // true

    // 攜帶 option 引數
    deleteResult2, err := postCollection.Deleter().Filter(query.Eq("title", "builder")).DeleteOne(ctx, options.Delete().SetComment("test"))
    if err != nil {
        panic(err)
    }
    fmt.Println(deleteResult2.DeletedCount == 1) // true
}

基於 postCollection 例項,我們可以透過 Deleter() 方法建立一個刪除器,然後進行刪除操作。

DeleteOne 方法與官方的 API 同名,作用是刪除單個文件。我們可以透過 Filter 方法設定 文件匹配的條件。如果我們需要設定 options 引數,可以將其作為 DeleteOne 方法的第二個引數傳遞。

刪除多個文件

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/deleter/delete_many.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "github.com/chenmingyong0423/go-mongox"
    "github.com/chenmingyong0423/go-mongox/builder/query"
)

func main() {
    ctx := context.Background()

    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    _, err := postCollection.Creator().InsertMany(ctx, []collection.Post{
        {Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"},
        {Title: "builder", Author: "陳明勇", Content: "..."},
    })
    if err != nil {
        panic(err)
    }

    // 刪除多個文件
    // - 透過 query 包構造複雜的 bson 語句
    deleteResult, err := postCollection.Deleter().
        Filter(query.In("title", "go-mongox", "builder")).
        DeleteMany(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Println(deleteResult.DeletedCount == 2) // true
}

DeleteMany 方法與官方的 API 同名,作用是刪除多個文件。

Aggregator 聚合器

Aggregator 是一個聚合器,用於執行聚合相關的操作。

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/collection/aggregator/aggregator.go
package main

import (
    "chenmingyong0423/blog/tutorial-code/go-mongox/collection"
    "context"
    "fmt"

    "github.com/chenmingyong0423/go-mongox"
    "github.com/chenmingyong0423/go-mongox/bsonx"
    "github.com/chenmingyong0423/go-mongox/builder/aggregation"
    "go.mongodb.org/mongo-driver/mongo"
)

func main() {
    ctx := context.Background()

    // 你需要預先建立一個 *mongo.Collection 物件
    mongoCollection := collection.NewCollection()
    // 使用 Post 結構體作為泛型引數建立一個 collection
    postCollection := mongox.NewCollection[collection.Post](mongoCollection)
    _, err := postCollection.Creator().InsertMany(ctx, []collection.Post{
        {Title: "go-mongox", Author: "陳明勇", Content: "go-mongox 旨在提供更方便和高效的MongoDB資料操作體驗。"},
        {Title: "builder", Author: "陳明勇", Content: "..."},
    })
    if err != nil {
        panic(err)
    }

    // 聚合操作
    // - 使用 aggregation 包構造 pipeline
    posts, err := postCollection.
        // 去除 content 欄位
        Aggregator().Pipeline(aggregation.StageBsonBuilder().Project(bsonx.M("content", 0)).Build()).
        Aggregate(ctx)
    if err != nil {
        panic(err)
    }
    for _, post := range posts {
        fmt.Println(post)
    }

    // 如果我們透過聚合操作更改欄位的名稱,那麼我們可以使用 AggregationWithCallback 方法,然後透過 callback 函式將結果對映到我們預期的結構體中
    type DiffPost struct {
        Id          string `bson:"_id"`
        Title       string `bson:"title"`
        Name        string `bson:"name"` // author → name
        Content     string `bson:"content"`
        Outstanding bool   `bson:"outstanding"`
    }
    result := make([]*DiffPost, 0)
    //  將 author 欄位更名為 name,排除 content 欄位,新增 outstanding 欄位,返回結果為 []*DiffPost
    err = postCollection.Aggregator().
        Pipeline(
            aggregation.StageBsonBuilder().Project(
                bsonx.NewD().Add("name", "$author").Add("author", 1).Add("_id", 1).Add("title", 1).Add("outstanding", aggregation.Eq("$author", "陳明勇")).Build(),
            ).Build(),
        ).
        AggregateWithCallback(ctx, func(ctx context.Context, cursor *mongo.Cursor) error {
            return cursor.All(ctx, &result)
        })

    for _, post := range result {
        fmt.Println(post)
    }
}

基於 postCollection 例項,我們可以透過 Aggregator() 方法建立一個聚合器,然後進行聚合操作。

我們可以透過 Pipeline 方法設定 pipeline 引數。

對於執行聚合操作,有以下兩個方法:

  • Aggregate 方法:與官方的 API 同名。
  • AggregateWithCallback 方法:因為我們在建立 collection 裝飾器時,使用泛型繫結了一個結構體,如果我們執行聚合操作之後,返回的資料與所繫結的結構體對映不上,這時可以使用該方法將結果對映到指定的結構體裡。

go-mongox 框架提供了以下幾種型別的構造器:

  • universal: 簡單而又通用的 bson 資料建構函式。
  • query: 查詢構造,用於構造查詢操作所需的 bson 資料。
  • update: 更新構造,用於構造更新操作所需的 bson 資料。
  • aggregation: 聚合操作構造,包含兩種,一種是用於構造聚合 stage 階段所需的 bson 資料,另一種是用於構造除了 stage 階段以外的 bson 資料。

universal 通用構造

我們可以使用 bsonx 包裡的一些函式進行 bson 資料的構造,例如 bsonx.Mbsonx.Idbsonx.D 等等。

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/builder/universal.go
package main

import (
    "fmt"

    "github.com/chenmingyong0423/go-mongox/bsonx"
)

func main() {
    // bson.M{"姓名": "陳明勇"}
    m := bsonx.M("姓名", "陳明勇")
    fmt.Printf("%#v\n\n", m)

    // bson.M{"_id": "陳明勇"}
    id := bsonx.Id("陳明勇")
    fmt.Printf("%#v\n\n", id)

    // bson.E{Key:"姓名", Value:"陳明勇"}
    e := bsonx.E("姓名", "陳明勇")
    fmt.Printf("%#v\n\n", e)

    // bson.D{bson.E{Key:"姓名", Value:"陳明勇"}, bson.E{Key:"手機號", Value:"1888***1234"}}
    d := bsonx.D(bsonx.E("姓名", "陳明勇"), bsonx.E("手機號", "1888***1234"))
    fmt.Printf("%#v\n\n", d)
    // 我們還可以使用 bsonx.DBuilder 來構建 bson.D
    d2 := bsonx.NewD().Add("姓名", "陳明勇").Add("手機號", "1888***1234").Build()
    fmt.Printf("%#v\n\n", d2)

    // bson.A{"陳明勇", "1888***1234"}
    a := bsonx.A("陳明勇", "1888***1234")
    fmt.Printf("%#v", a)
}

bsonx 包暫時提供了這些建構函式,後面會持續新增更多有用的函式。

query 查詢構造

query 包可以幫我們構造出查詢相關的 bson 資料,例如 $in$gt$and 等等。

構造型別包含兩種:

  • 直接構造
    query 包提供許多建構函式,能夠幫我們快速地構造簡單的 bson 資料,例如 query.Idquery.Inquery.Eq 等。

  • 構造器構造
    簡單的 bson 資料,我們只需要使用 query 包提供的函式即可。但當我們需要構造複雜或多級條件的 bson 資料時,我們可以使用 BsonBuilder 構造器。

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/builder/query.go
package main

import (
    "fmt"

    "github.com/chenmingyong0423/go-mongox/bsonx"

    "github.com/chenmingyong0423/go-mongox/builder/query"
)

func main() {
    // 透過函式直接構造
    // bson.D{bson.E{Key:"姓名", Value:bson.D{bson.E{Key:"$eq", Value:"陳明勇"}}}}
    d := query.Eq("姓名", "陳明勇")
    fmt.Printf("%#v\n\n", d)

    //  bson.D{bson.E{Key:"age", Value:bson.D{{Key:"$in", Value:[]int{18, 19, 20}}}}}
    d = query.In("age", 18, 19, 20)
    fmt.Printf("%#v\n\n", d)

    // elemMatch
    // bson.D{bson.E{Key: "result", Value: bson.D{bson.E{Key: "$elemMatch", Value: bson.D{bson.E{Key: "$gte", Value: 80}, bson.E{Key: "$lt", Value: 85}}}}}}
    d = query.ElemMatch("result", bsonx.D(bsonx.E("$gte", 80), bsonx.E("$lt", 85)))
    fmt.Printf("%#v\n\n", d)

    // 透過構造器構造
    // bson.D{bson.E{Key:"age", Value:bson.D{{Key:"$gt", Value:18}, bson.E{Key:"$lt", Value:25}}}}
    d = query.BsonBuilder().Gt("age", 18).Lt("age", 25).Build()
    fmt.Printf("%#v\n\n", d)

    // bson.d{bson.E{Key: "$and", Value: []any{bson.D{{Key: "x", Value: bson.D{{Key: "$ne", Value: 0}}}}, bson.D{{Key: "y", Value: bson.D{{Key: "$gt", Value: 0}}}}}}
    d = query.BsonBuilder().And(
        query.BsonBuilder().Ne("x", 0).Build(),
        query.BsonBuilder().Gt("y", 0).Build(),
    ).Build()
    fmt.Printf("%#v\n\n", d)

    // bson.D{bson.E{Key:"qty", Value:bson.D{{Key:"$exists", Value:true}, bson.E{Key:"$nin", Value:[]int{5, 15}}}}}
    d = query.BsonBuilder().Exists("qty", true).NinInt("qty", 5, 15).Build()
    fmt.Printf("%#v", d)
}

query 包提供的方法不止這些,以上只是列舉出一些典型的例子,還有更多的用法等著你去探索。

update 更新構造器

update 包可以幫我們構造出更新操作相關的 bson 資料,例如 $set$$inc$push 等等。

構造型別包含兩種:

  • 直接構造
    update 包提供許多建構函式,能夠幫我們快速地構造簡單的 bson 資料,例如 update.SetSimpleupdate.Incupdate.Push 等。

  • 構造器構造
    簡單的 bson 資料,我們只需要使用 update 包提供的函式即可。但當我們需要構造複雜或多級條件的 bson 資料時,我們可以使用 BsonBuilder 構造器。

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/builder/update.go
package main

import (
    "fmt"

    "github.com/chenmingyong0423/go-mongox/builder/update"
)

func main() {
    // 透過函式直接構造
    // bson.D{bson.E{Key:"$set", Value:bson.M{"name":"陳明勇"}}}
    u := update.Set("name", "陳明勇")
    fmt.Printf("%#v\n\n", u)

    // bson.D{bson.E{Key:"$inc", Value:bson.D{bson.E{Key:"ratings", Value:-1}}}}
    u = update.BsonBuilder().Inc("ratings", -1).Build()
    fmt.Printf("%#v\n\n", u)

    // bson.D{bson.E{Key:"$push", Value:bson.M{"scores":95}}}
    u = update.BsonBuilder().Push("scores", 95).Build()
    fmt.Printf("%#v\n\n", u)

    // 透過構造器構造
    // bson.D{bson.E{Key:"$set", Value:bson.D{bson.E{Key:"name", Value:"陳明勇"}, bson.E{Key:"sex", Value:"男"}}}}
    u = update.BsonBuilder().Set("name", "陳明勇").Set("sex", "男").Build()
    fmt.Printf("%#v\n\n", u)
    // bson.D{bson.E{Key:"$set", Value:bson.D{bson.E{Key:"name", Value:"陳明勇"}}}, bson.E{Key:"$inc", Value:bson.D{bson.E{Key:"rating Value:-1}}}, bson.E{Key:"$push", Value:bson.D{bson.E{Key:"scores", Value:95}}}}
    u = update.BsonBuilder().Set("name", "陳明勇").Inc("ratings", -1).Push("scores", 95).Build()
    fmt.Printf("%#v\n\n", u)
}

update 包提供的方法不止這些,以上只是列舉出一些典型的例子,還有更多的用法等著你去探索。

aggregation 聚合構造器

aggregation 包提供了兩個 builder

  • StageBsonBuilder:用於構造 stage 階段所需的 bson 資料
  • BsonBuilder:用於構造除了 stage 階段以外的 bson 資料。
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go-mongox/builder/aggregation.go
package main

import (
    "fmt"

    "github.com/chenmingyong0423/go-mongox/bsonx"
    "github.com/chenmingyong0423/go-mongox/builder/aggregation"
    "github.com/chenmingyong0423/go-mongox/types"
)

func main() {
    // bson.D{bson.E{Key:"total", Value:bson.D{bson.E{Key:"$gt", Value:[]interface {}{"$price", "$fee"}}}}}
    gt := aggregation.Gt("total", []any{"$price", "$fee"}...)
    fmt.Printf("%#v\n\n", gt)

    // mongo.Pipeline{bson.D{bson.E{Key:"$project", Value:bson.D{bson.E{Key:"name", Value:1}, bson.E{Key:"qtyGt250", Value:bson.D{bson.E{Key:"total", Value:bson.D{bson.E{Key:"$gt", Value:[]interface {}{"$price", "$fee"}}}}}}}}}}
    pipeline := aggregation.StageBsonBuilder().
        Project(bsonx.NewD().Add("name", 1).Add("qtyGt250", gt).Build()).
        Build()
    fmt.Printf("%#v\n\n", pipeline)

    // bson.D{bson.E{Key:"result", Value:bson.D{bson.E{Key:"$or", Value:[]interface {}{bson.D{bson.E{Key:"$gt", Value:[]interface {}{"score", 70}}, bson.E{Key:"score", Value:bson.D{bson.E{Key:"$lt", Value:[]interface {}{90}}}}}, bson.D{bson.E{Key:"views", Value:bson.D{bson.E{Key:"$gte", Value:[]interface {}{90}}}}}}}}}}
    or := aggregation.BsonBuilder().Or("result", aggregation.BsonBuilder().GtWithoutKey("score", 70).Lt("score", 90).Build(), aggregation.BsonBuilder().Gte("views", 90).Build()).Build()
    fmt.Printf("%#v\n\n", or)

    // mongo.Pipeline{bson.D{bson.E{Key:"$match", Value:bson.D{bson.E{Key:"result", Value:bson.D{bson.E{Key:"$or", Value:[]interface {}{bson.D{bson.E{Key:"$gt", Value:[]interface {}{"score", 70}}, bson.E{Key:"score", Value:bson.D{bson.E{Key:"$lt", Value:[]interface {}{90}}}}}, bson.D{bson.E{Key:"views", Value:bson.D{bson.E{Key:"$gte", Value:[]interface {}{90}}}}}}}}}}}}, bson.D{bson.E{Key:"$group", Value:bson.D{bson.E{Key:"_id", Value:interface {}(nil)}, bson.E{Key:"count", Value:bson.D{bson.E{Key:"$sum", Value:1}}}}}}}
    pipeline = aggregation.StageBsonBuilder().
        Match(or).
        Group(nil, aggregation.Sum("count", 1)...).Build()
    fmt.Printf("%#v\n\n", pipeline)

    // mongo.Pipeline{bson.D{bson.E{Key:"$unwind", Value:"$size"}}}
    pipeline = aggregation.StageBsonBuilder().Unwind("$size", nil).Build()
    fmt.Printf("%#v\n\n", pipeline)

    // mongo.Pipeline{bson.D{bson.E{Key:"$unwind", Value:bson.D{bson.E{Key:"path", Value:"$size"}, bson.E{Key:"includeArrayIndex", Value:"arrayIndex"}, bson.E{Key:"preserveNullAndEmptyArrays", Value:true}}}}}
    pipeline = aggregation.StageBsonBuilder().Unwind("$size", &types.UnWindOptions{
        IncludeArrayIndex:          "arrayIndex",
        PreserveNullAndEmptyArrays: true,
    }).Build()
    fmt.Printf("%#v", pipeline)
}

aggregation 包提供的方法不止這些,以上只是列舉出一些典型的例子,還有更多的用法等著你去探索。

本文對 go-mongox 框架進行了詳細的介紹,它有兩個核心,一個是基於泛型的 colletion 形態,另一個就是 bson 構造器了。這兩個核心是單獨存在的,你可以使用其中之一,也可以同時使用。

倉庫地址:github.com/chenmingyong0423/go-mon...

該框架處於初期階段,希望透過集思廣益的方式,邀請各位開發者共同參與,提出寶貴的建議和意見,共同打造一個更強大、更靈活的框架。期待著您的積極參與和寶貴反饋,共同推動go-mongox不斷進步。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章