Qmgo - The Go driver for MongoDB. It‘s based on official mongo-go-driver but easier to use like Mgo.

Qmgo

Build Status Coverage Status Go Report Card GitHub release GoDoc Join the chat at https://gitter.im/qiniu/qmgo

English | 简体中文

Qmgo is a Go driver for MongoDB . It is based on MongoDB official driver, but easier to use like mgo (such as the chain call).

  • Qmgo allows users to use the new features of MongoDB in a more elegant way.

  • Qmgo is the first choice for migrating from mgo to the new MongoDB driver with minimal code changes.

Requirements

-Go 1.10 and above.

-MongoDB 2.6 and above.

Features

  • CRUD to documents, with all official supported options
  • Sort、limit、count、select、distinct
  • Transactions
  • Hooks
  • Automatically default and custom fields
  • Predefine operator keys
  • Aggregate、indexes operation、cursor
  • Validation tags
  • Plugin

Installation

  • Use go mod to automatically install dependencies by import github.com/qiniu/qmgo

Or

  • Use go get github.com/qiniu/qmgo

Usage

  • Start

    import and create a new connection

    import (
        "context"
      
        "github.com/qiniu/qmgo"
    )
    
    ctx := context.Background()
    client, err := qmgo.NewClient(ctx, &qmgo.Config{Uri: "mongodb://localhost:27017"})
    db := client.Database("class")
    coll := db.Collection("user")

    If your connection points to a fixed database and collection, recommend using the following way to initialize the connection. All operations can be based on cli:

    cli, err := qmgo.Open(ctx, &qmgo.Config{Uri: "mongodb://localhost:27017", Database: "class", Coll: "user"})

    The following examples will be based on cli, if you use the first way for initialization, replace cli with clientdb or coll

    Make sure to defer a call to Disconnect after instantiating your client:

    defer func() {
    if err = cli.Close(ctx); err != nil {
            panic(err)
        }
    }()
  • Create index

    Before doing the operation, we first initialize some data:

    type UserInfo struct {
        Name   string `bson:"name"`
        Age    uint16 `bson:"age"`
        Weight uint32 `bson:"weight"`
    }
    
    var userInfo = UserInfo{
        Name: "xm",
        Age: 7,
        Weight: 40,
    }

    Create index

    cli.CreateOneIndex(context.Background(), options.IndexModel{Key: []string{"name"}, Unique: true})
    cli.CreateIndexes(context.Background(), []options.IndexModel{{Key: []string{"id2", "id3"}}})
  • Insert a document

    // insert one document
    result, err := cli.InsertOne(ctx, userInfo)
  • Find a document

    // find one document
      one := UserInfo{}
      err = cli.Find(ctx, bson.M{"name": userInfo.Name}).One(&one)
  • Delete documents

    err = cli.Remove(ctx, bson.M{"age": 7})
  • Insert multiple data

    // multiple insert
    var userInfos = []UserInfo{
        UserInfo{Name: "a1", Age: 6, Weight: 20},
        UserInfo{Name: "b2", Age: 6, Weight: 25},
        UserInfo{Name: "c3", Age: 6, Weight: 30},
        UserInfo{Name: "d4", Age: 6, Weight: 35},
        UserInfo{Name: "a1", Age: 7, Weight: 40},
        UserInfo{Name: "a1", Age: 8, Weight: 45},
    }
    result, err = cli.Collection.InsertMany(ctx, userInfos)
  • Search all, sort and limit

    // find all, sort and limit
    batch := []UserInfo{}
    cli.Find(ctx, bson.M{"age": 6}).Sort("weight").Limit(7).All(&batch)
  • Count

    count, err := cli.Find(ctx, bson.M{"age": 6}).Count()
  • Update

    // UpdateOne one
    err := cli.UpdateOne(ctx, bson.M{"name": "d4"}, bson.M{"$set": bson.M{"age": 7}})
    
    // UpdateAll
    result, err := cli.UpdateAll(ctx, bson.M{"age": 6}, bson.M{"$set": bson.M{"age": 10}})
  • Select

    err := cli.Find(ctx, bson.M{"age": 10}).Select(bson.M{"age": 1}).One(&one)
  • Aggregate

    matchStage := bson.D{{"$match", []bson.E{{"weight", bson.D{{"$gt", 30}}}}}}
    groupStage := bson.D{{"$group", bson.D{{"_id", "$name"}, {"total", bson.D{{"$sum", "$age"}}}}}}
    var showsWithInfo []bson.M
    err = cli.Aggregate(context.Background(), Pipeline{matchStage, groupStage}).All(&showsWithInfo)
  • Support All mongoDB Options when create connection

    poolMonitor := &event.PoolMonitor{
        Event: func(evt *event.PoolEvent) {
            switch evt.Type {
            case event.GetSucceeded:
                fmt.Println("GetSucceeded")
            case event.ConnectionReturned:
                fmt.Println("ConnectionReturned")
            }
        },
    }
    opt := options.Client().SetPoolMonitor(poolMonitor)  // more options use the chain options.
    cli, err := Open(ctx, &Config{Uri: URI, Database: DATABASE, Coll: COLL}, opt) 
    
  • Transactions

    The super simple and powerful transaction, with features like timeoutretry:

    callback := func(sessCtx context.Context) (interface{}, error) {
        // Important: make sure the sessCtx used in every operation in the whole transaction
        if _, err := cli.InsertOne(sessCtx, bson.D{{"abc", int32(1)}}); err != nil {
            return nil, err
        }
        if _, err := cli.InsertOne(sessCtx, bson.D{{"xyz", int32(999)}}); err != nil {
            return nil, err
        }
        return nil, nil
    }
    result, err = cli.DoTransaction(ctx, callback)

    More about transaction

  • Predefine operator keys

    // aggregate
    matchStage := bson.D{{operator.Match, []bson.E{{"weight", bson.D{{operator.Gt, 30}}}}}}
    groupStage := bson.D{{operator.Group, bson.D{{"_id", "$name"}, {"total", bson.D{{operator.Sum, "$age"}}}}}}
    var showsWithInfo []bson.M
    err = cli.Aggregate(context.Background(), Pipeline{matchStage, groupStage}).All(&showsWithInfo)
  • Hooks

    Qmgo flexible hooks:

    type User struct {
        Name         string    `bson:"name"`
        Age          int       `bson:"age"`
    }
    func (u *User) BeforeInsert() error {
        fmt.Println("before insert called")
        return nil
    }
    func (u *User) AfterInsert() error {
        fmt.Println("after insert called")
        return nil
    }
    
    u := &User{Name: "Alice", Age: 7}
    _, err := cli.InsertOne(context.Background(), u)

    More about hooks

  • Automatically fields

    Qmgo support two ways to make specific fields automatically update in specific API

    • Default fields

    Inject field.DefaultField in document struct, Qmgo will update createAtupdateAt and _id in update and insert operation.

    type User struct {
      field.DefaultField `bson:",inline"`
    
      Name string `bson:"name"`
      Age  int    `bson:"age"`
    }
    
    u := &User{Name: "Lucas", Age: 7}
    _, err := cli.InsertOne(context.Background(), u)
    // Fields with tag createAt、updateAt and _id will be generated automatically 
    • Custom fields

    Define the custom fields, Qmgo will update them in update and insert operation.

    type User struct {
        Name string `bson:"name"`
        Age  int    `bson:"age"`
    
        MyId         string    `bson:"myId"`
        CreateTimeAt time.Time `bson:"createTimeAt"`
        UpdateTimeAt int64     `bson:"updateTimeAt"`
    }
    // Define the custom fields
    func (u *User) CustomFields() field.CustomFieldsBuilder {
        return field.NewCustom().SetCreateAt("CreateTimeAt").SetUpdateAt("UpdateTimeAt").SetId("MyId")
    }
    
    u := &User{Name: "Lucas", Age: 7}
    _, err := cli.InsertOne(context.Background(), u)
    // CreateTimeAt、UpdateTimeAt and MyId will be generated automatically 
    
    // suppose Id and ui is ready
    err = cli.ReplaceOne(context.Background(), bson.M{"_id": Id}, &ui)
    // UpdateTimeAt will update

    Check examples here

    More about automatically fields

  • Validation tags

    Qmgo Validation tags is Based on go-playground/validator.

    So Qmgo support all validations on structs in go-playground/validator, such as:

    type User struct {
        FirstName string            `bson:"fname"`
        LastName  string            `bson:"lname"`
        Age       uint8             `bson:"age" validate:"gte=0,lte=130" `    // Age must in [0,130]
        Email     string            `bson:"e-mail" validate:"required,email"` //  Email can't be empty string, and must has email format
        CreateAt  time.Time         `bson:"createAt" validate:"lte"`          // CreateAt must lte than current time
        Relations map[string]string `bson:"relations" validate:"max=2"`       // Relations can't has more than 2 elements
    }

    Qmgo tags only supported in following API: InsertOne、InsertyMany、Upsert、UpsertId、ReplaceOne

  • Plugin

    • Implement following method:
    func Do(doc interface{}, opType operator.OpType, opts ...interface{}) error{
      // do anything
    }
    • Call Register() in package middleware, register the method Do

      Qmgo will call Do before and after the operation

    middleware.Register(Do)

    Example

    The hookautomatically fields and validation tags in Qmgo run on plugin.

Qmgo vs go.mongodb.org/mongo-driver

Below we give an example of multi-file search、sort and limit to illustrate the similarities between qmgo and mgo and the improvement compare to go.mongodb.org/mongo-driver. How do we do ingo.mongodb.org/mongo-driver:

// go.mongodb.org/mongo-driver
// find all, sort and limit
findOptions := options.Find()
findOptions.SetLimit(7) // set limit
var sorts D
sorts = append(sorts, E{Key: "weight", Value: 1})
findOptions.SetSort(sorts) // set sort

batch := []UserInfo{}
cur, err := coll.Find(ctx, bson.M{"age": 6}, findOptions)
cur.All(ctx, &batch)

How do we do in Qmgo and mgo:

// qmgo
// find all, sort and limit
batch := []UserInfo{}
cli.Find(ctx, bson.M{"age": 6}).Sort("weight").Limit(7).All(&batch)

// mgo
// find all, sort and limit
coll.Find(bson.M{"age": 6}).Sort("weight").Limit(7).All(&batch)

Qmgo vs mgo

Differences between qmgo and mgo

Contributing

The Qmgo project welcomes all contributors. We appreciate your help!

Communication:

avatar

Owner
Qiniu Cloud
Connect Data, Redefine Value.
Qiniu Cloud
Comments
  • Hooks v2

    Hooks v2

    #71 implement the Hooks v1 in qmgo: More about hooks | Hooks详情介绍

    But more jobs need to do:

    • #73 Can't change documents in hooks, which also make feature: default and custom fields in update operation is blocked now #62
    • Now we use options to pass in hooks, Is there any more elegant way? specially in current version of qmgo, no document is passed in in Update operation.
  • No Document In Result

    No Document In Result

    cli.Database("missamelle").Collection("t_user").Find(ctx, bson.M{"openID": openID}).One(&user)

    不好意思,打扰了。 我第一次使用MongoDB。我发现这里如果查询不到数据会返回一个 no document in result 的 error, 我想知道,one方法是只会返回这一种error 吗? 如果是的话我将使用 if err != nil 来判断数据是否存在。

    这是与其它mysql orm不太一样的地方。还望解答一下

  • Support options in database operation

    Support options in database operation

    I have a server that requires data (which requires Primary ReadPreference), but also serves many Changestreams across multiple different collections (SecondaryPreferred would be the preferred ReadConcern here).

    Would maintaining two clients, one with ReadPref Primary and one with ReadPref SecondaryPreferred?, be the best way to handle this? Or does qmgo have a more elegant way of handling this?

  • Cursor Next 读取数据不可靠,有可能使用前一个对象的值

    Cursor Next 读取数据不可靠,有可能使用前一个对象的值

    type Demo struct {
    	Name    string       `bson:"name,omitempty"`
    }
    
    demo := Demo{}
    for it.Next(&demo) {
        demos = append(demos, demo)
    }
    

    由于 demo 对象是重用的,name 没有值的有概率使用上一次的值。

    有两种方式可以解决这个问题:

    • 使用后重新初始化

      demo := Demo{}
      for it.Next(&demo) {
          demos = append(demos, demo)
          demo := Demo{}
      }
      
    • Next 方法里统一处理,清掉 struct

      	func (c *Cursor) Next(result interface{}) bool {
      		if c.err != nil {
      			return false
      		}
      		var err error
      		if c.cursor.Next(c.ctx) {
      			p := reflect.ValueOf(result).Elem()
      			p.Set(reflect.Zero(p.Type()))
      			err = c.cursor.Decode(result)
      			if err == nil {
      				return true
      			}
      		}
      		return false
      	}
      

    https://github.com/qiniu/qmgo/blob/1472099f6cd6832694e1062413d97d174ba2c807/cursor.go#L31

    第一种方式对开发要求较高,写的时候要记得有这件事,容易遗漏。

    第二种方式比较简单,但比较隐晦,不知是否合理,希望能给一下建议。

  • uint32 and similar fields are always marshaled into BSON int64

    uint32 and similar fields are always marshaled into BSON int64

    • ~There can be nil maps that break reflect-based setters like mergo~ #117
    • It seems uint32 is unconditionally marshaled into NumberLong, it breaks one of our in-house data extraction scripts. https://github.com/mongodb/mongo-go-driver/pull/193 (closed by upstream but has a workaround)
  • add options(createcollections_options files),support abundant options(like Time Series) in mongodb5.x version

    add options(createcollections_options files),support abundant options(like Time Series) in mongodb5.x version

    In mongodb5.0x version,support Time Series Collections, https://docs.mongodb.com/manual/core/timeseries-collections/ . Add CreateCollectionOptions for wrapping mgo new feature.

  • 使用 DoTransaction 必须赋予 admin.root 权限,否则 transactionAllowed 检查无法通过

    使用 DoTransaction 必须赋予 admin.root 权限,否则 transactionAllowed 检查无法通过

    环境说明

    版本:docker mongo:4.2.6

    问题描述

    image

    image

    image

    期望结果

    没必要前置检查 transactionAllowed,还指向 admin 数据库,一些有管控的公司,基本上不允许访问 admin 数据。

    可以去掉 transactionAllowed 的判断,直接得到服务器的命令不支持错误提示更好。

  • (BadValue) $in needs an array

    (BadValue) $in needs an array

    Describe the bug

    var filter struct {
      arr []string `bson:"arr"`
    }
    
    var result map[string]interface{}
    
    err := coll.Find(ctx, bson.M{
      "name": bson.M{operator.In: filter.arr},
    }).One(&result)
    
    // err: (BadValue) $in needs an array
    

    To Reproduce Hope treat as empty array

  • 关于在Update方法中设置upsert的问题

    关于在Update方法中设置upsert的问题

    我希望能够实现如果文档不存在就插入的功能,在update的option中设置了upsert为true。 实际应用中,当匹配不到条件时,能够执行插入功能,但是会返回 mongo: no documents in result 错误信息。告诉我没有匹配结果。 按理说如果设置了upsert为true,那么就算没有匹配结果也应该是正常的,不应该返回这个错误吧。

    这是业务代码

    now := time.Now()
    filter := bson.M{"year": now.Year(), "month": int(now.Month()), "day": now.Day(), "operate": operate}
    update := bson.M{operator.Push: bson.M{"posts": item}}
    
    var upset bool = true
      option := options.UpdateOptions{
      UpdateOptions: &mongoOptions.UpdateOptions{Upsert: &upset},
    }
    
    err := db.Mongo.Collection(this.table()).UpdateOne(context.TODO(), filter, update, option)
    if err != nil {
      fmt.Println(err)
    }
    

    在源码的 collection.go 的217行,对匹配条件进行判断,如果匹配结果数量为0,就会返回未匹配错误,并没有对upsert进行判断。

    res, err := c.collection.UpdateOne(ctx, filter, update, updateOpts)
    if res != nil && res.MatchedCount == 0 {
      err = ErrNoSuchDocuments
    }
    if err != nil {
      return err
    }
    

    当文档未匹配时,res的MatchedCount为0,UpsertedCount为1。 是否应该对upsert的结果进行判断,来决定要不要返回 ErrNoSuchDocuments 错误呢?

    例如进行如下修改,如果没有设置upsert,或者设置了upsert但值为false的话,才返回未匹配错误。

    res, err := c.collection.UpdateOne(ctx, filter, update, updateOpts)
    if res != nil && res.MatchedCount == 0 {
      if updateOpts.Upsert == nil || *updateOpts.Upsert == false {
        err = ErrNoSuchDocuments
      }
      // 其他判断条件 如 if UpsertedCount == 0 { err = upsert错误 }
    }
    if err != nil {
      return err
    }
    
  • Rename the second parameter of middleware.Do

    Rename the second parameter of middleware.Do

    Describe the bug Query对象的One方法和All方法中,执行middleware.Do时,将hook传给了doc

    func (q *Query) One(result interface{}) error { if len(q.opts) > 0 { // 这里 if err := middleware.Do(q.ctx, q.opts[0].QueryHook, operator.BeforeQuery); err != nil { return err } } opt := options.FindOne()

    if q.sort != nil {
    	opt.SetSort(q.sort)
    }
    if q.project != nil {
    	opt.SetProjection(q.project)
    }
    if q.skip != nil {
    	opt.SetSkip(*q.skip)
    }
    if q.hint != nil {
    	opt.SetHint(q.hint)
    }
    
    err := q.collection.FindOne(q.ctx, q.filter, opt).Decode(result)
    
    if err != nil {
    	return err
    }
    if len(q.opts) > 0 {
                 // 还有这里
    	if err := middleware.Do(q.ctx, q.opts[0].QueryHook, operator.AfterQuery); err != nil {
    		return err
    	}
    }
    return nil
    

    }

  • Add method to list indexes for one collection

    Add method to list indexes for one collection

    Sometimes I need to test index usage to make sure the index created is useful. In my work, there is a staging env running on public net, and we want to sync it's db indexes to local env, but I cannnot find a method in qmgo that can list all indexes in the same format when using the create method, so I want to write one. Do you have any advice? If you approve this feature, I will work on it.

    @jiangz222

  • Optimize implement of `upsert` in `bulk`

    Optimize implement of `upsert` in `bulk`

    After pr 256, Upsert And UpsertOne in bulk have different impelement. Upsert use official Replecement API. UpsertOne use official Update API. Consider to change Upsert in bulk.

Go-mongodb - Practice Go with MongoDB because why not

Practice Mongo DB with Go Because why not. Dependencies gin-gonic go mongodb dri

Jan 5, 2022
The MongoDB driver for Go

The MongoDB driver for Go This fork has had a few improvements by ourselves as well as several PR's merged from the original mgo repo that are current

Jan 8, 2023
The Go driver for MongoDB
The Go driver for MongoDB

MongoDB Go Driver The MongoDB supported driver for Go. Requirements Installation Usage Bugs / Feature Reporting Testing / Development Continuous Integ

Dec 31, 2022
Data access layer for PostgreSQL, CockroachDB, MySQL, SQLite and MongoDB with ORM-like features.
Data access layer for PostgreSQL, CockroachDB, MySQL, SQLite and MongoDB with ORM-like features.

upper/db is a productive data access layer (DAL) for Go that provides agnostic tools to work with different data sources

Jan 3, 2023
Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package

Go-MySQL-Driver A MySQL-Driver for Go's database/sql package Features Requirements Installation Usage DSN (Data Source Name) Password Protocol Address

Jan 4, 2023
Go driver for PostgreSQL over SSH. This driver can connect to postgres on a server via SSH using the local ssh-agent, password, or private-key.

pqssh Go driver for PostgreSQL over SSH. This driver can connect to postgres on a server via SSH using the local ssh-agent, password, or private-key.

Nov 6, 2022
Package pbpgx provides a toolkit for easier Protocol Buffers interaction with PostgreSQL databases.

PBPGX Package pbpgx provides a toolkit for easier Protocol Buffers interaction with PostgreSQL databases. Pbpgx supports the Protocol Buffer types gen

Jun 27, 2022
Simple key-value store abstraction and implementations for Go (Redis, Consul, etcd, bbolt, BadgerDB, LevelDB, Memcached, DynamoDB, S3, PostgreSQL, MongoDB, CockroachDB and many more)

gokv Simple key-value store abstraction and implementations for Go Contents Features Simple interface Implementations Value types Marshal formats Road

Dec 24, 2022
💲 Golang, Go Fiber, RabbitMQ, MongoDB, Docker, Kubernetes, GitHub Actions
💲 Golang, Go Fiber, RabbitMQ, MongoDB, Docker, Kubernetes, GitHub Actions

Bank Projeto para simular empréstimos financeiros em um banco para clientes Tecnologias Utilizadas Golang MongoDB RabbitMQ Github Actions Docker Hub D

Dec 9, 2022
Examples and code to assign a name to your MongoDB, MySQL, PostgreSQL, RabbitMQ, and redis connection.
Examples and code to assign a name to your MongoDB, MySQL, PostgreSQL, RabbitMQ, and redis connection.

your connection deserves a name ?? When your app interacts with an external system, assign a name to the connection. An external system in this contex

Dec 14, 2022
A MongoDB compatible embeddable database and toolkit for Go.
A MongoDB compatible embeddable database and toolkit for Go.

lungo A MongoDB compatible embeddable database and toolkit for Go. Installation Example Motivation Architecture Features License Installation To get s

Jan 3, 2023
Go-odm, a Golang Object Document Mapping for MongoDB.
Go-odm, a Golang Object Document Mapping for MongoDB.

A project of SENROK Open Source Go ODM Go-odm, a Golang Object Document Mapping for MongoDB. Table of contents Features Installation Get started Docum

Nov 4, 2022
Golang MongoDB Integration Examples

Get Program Get a copy of the program: git clone https://github.com/hmdhszd/Go

Feb 1, 2022
Mirror of Apache Calcite - Avatica Go SQL Driver

Apache Avatica/Phoenix SQL Driver Apache Calcite's Avatica Go is a Go database/sql driver for the Avatica server. Avatica is a sub-project of Apache C

Nov 3, 2022
Firebird RDBMS sql driver for Go (golang)

firebirdsql (Go firebird sql driver) Firebird RDBMS http://firebirdsql.org SQL driver for Go Requirements Firebird 2.5 or higher Golang 1.13 or higher

Dec 20, 2022
Microsoft ActiveX Object DataBase driver for go that using exp/sql

go-adodb Microsoft ADODB driver conforming to the built-in database/sql interface Installation This package can be installed with the go get command:

Dec 30, 2022
Microsoft SQL server driver written in go language

A pure Go MSSQL driver for Go's database/sql package Install Requires Go 1.8 or above. Install with go get github.com/denisenkom/go-mssqldb . Connecti

Dec 26, 2022
Oracle driver for Go using database/sql

go-oci8 Description Golang Oracle database driver conforming to the Go database/sql interface Installation Install Oracle full client or Instant Clien

Dec 30, 2022
sqlite3 driver for go using database/sql

go-sqlite3 Latest stable version is v1.14 or later not v2. NOTE: The increase to v2 was an accident. There were no major changes or features. Descript

Jan 8, 2023