382 lines
11 KiB
Markdown
382 lines
11 KiB
Markdown
# Qmgo
|
||
|
||
[![Build Status](https://travis-ci.org/qiniu/qmgo.png?branch=master)](https://travis-ci.org/qiniu/qmgo)
|
||
[![Coverage Status](https://codecov.io/gh/qiniu/qmgo/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/qmgo)
|
||
[![Go Report Card](https://goreportcard.com/badge/github.com/qiniu/qmgo)](https://goreportcard.com/report/github.com/qiniu/qmgo)
|
||
[![GitHub release](https://img.shields.io/github/v/tag/qiniu/qmgo.svg?label=release)](https://github.com/qiniu/qmgo/releases)
|
||
[![GoDoc](https://pkg.go.dev/badge/github.com/qiniu/qmgo?status.svg)](https://pkg.go.dev/github.com/qiniu/qmgo?tab=doc)
|
||
|
||
English | [简体中文](README_ZH.md)
|
||
|
||
`Qmgo` is a `Go` `driver` for `MongoDB` . It is based on [MongoDB official driver](https://github.com/mongodb/mongo-go-driver), but easier to use like [mgo](https://github.com/go-mgo/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
|
||
```go
|
||
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`:
|
||
|
||
```go
|
||
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 `client`、`db` or `coll`***
|
||
|
||
Make sure to defer a call to Disconnect after instantiating your client:
|
||
|
||
```go
|
||
defer func() {
|
||
if err = cli.Close(ctx); err != nil {
|
||
panic(err)
|
||
}
|
||
}()
|
||
```
|
||
|
||
- Create index
|
||
|
||
Before doing the operation, we first initialize some data:
|
||
|
||
```go
|
||
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
|
||
|
||
```go
|
||
cli.CreateOneIndex(context.Background(), options.IndexModel{Key: []string{"name"}})
|
||
cli.CreateIndexes(context.Background(), []options.IndexModel{{Key: []string{"id2", "id3"}}})
|
||
```
|
||
|
||
- Insert a document
|
||
|
||
```go
|
||
// insert one document
|
||
result, err := cli.InsertOne(ctx, userInfo)
|
||
```
|
||
|
||
- Find a document
|
||
|
||
```go
|
||
// find one document
|
||
one := UserInfo{}
|
||
err = cli.Find(ctx, bson.M{"name": userInfo.Name}).One(&one)
|
||
```
|
||
|
||
- Delete documents
|
||
|
||
```go
|
||
err = cli.Remove(ctx, bson.M{"age": 7})
|
||
```
|
||
|
||
- Insert multiple data
|
||
|
||
```go
|
||
// 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
|
||
```go
|
||
// find all, sort and limit
|
||
batch := []UserInfo{}
|
||
cli.Find(ctx, bson.M{"age": 6}).Sort("weight").Limit(7).All(&batch)
|
||
```
|
||
- Count
|
||
|
||
````go
|
||
count, err := cli.Find(ctx, bson.M{"age": 6}).Count()
|
||
````
|
||
|
||
- Update
|
||
|
||
````go
|
||
// 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
|
||
|
||
````go
|
||
err := cli.Find(ctx, bson.M{"age": 10}).Select(bson.M{"age": 1}).One(&one)
|
||
````
|
||
|
||
- Aggregate
|
||
|
||
```go
|
||
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
|
||
|
||
````go
|
||
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 `timeout`、`retry`:
|
||
````go
|
||
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](https://github.com/qiniu/qmgo/wiki/Transactions)
|
||
|
||
- Predefine operator keys
|
||
|
||
````go
|
||
// 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:
|
||
|
||
````go
|
||
type User struct {
|
||
Name string `bson:"name"`
|
||
Age int `bson:"age"`
|
||
}
|
||
func (u *User) BeforeInsert(ctx context.Context) error {
|
||
fmt.Println("before insert called")
|
||
return nil
|
||
}
|
||
func (u *User) AfterInsert(ctx context.Context) error {
|
||
fmt.Println("after insert called")
|
||
return nil
|
||
}
|
||
|
||
u := &User{Name: "Alice", Age: 7}
|
||
_, err := cli.InsertOne(context.Background(), u)
|
||
````
|
||
[More about hooks](https://github.com/qiniu/qmgo/wiki/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 `createAt`、`updateAt` and `_id` in update and insert operation.
|
||
|
||
````go
|
||
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.
|
||
|
||
```go
|
||
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](https://github.com/qiniu/qmgo/blob/master/field_test.go)
|
||
|
||
[More about automatically fields](https://github.com/qiniu/qmgo/wiki/Automatically-update-fields)
|
||
|
||
- Validation tags
|
||
|
||
Qmgo Validation tags is Based on [go-playground/validator](https://github.com/go-playground/validator).
|
||
|
||
So Qmgo support [all validations on structs in go-playground/validator](https://github.com/go-playground/validator#usage-and-documentation), such as:
|
||
|
||
```go
|
||
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:
|
||
|
||
```go
|
||
func Do(ctx context.Context, 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](operator/operate_type.go)
|
||
|
||
```go
|
||
middleware.Register(Do)
|
||
```
|
||
[Example](middleware/middleware_test.go)
|
||
|
||
The `hook`、`automatically 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 in`go.mongodb.org/mongo-driver`:
|
||
|
||
```go
|
||
// 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`:
|
||
|
||
```go
|
||
// 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](https://github.com/qiniu/qmgo/wiki/Differences-between-Qmgo-and-Mgo)
|
||
|
||
## Contributing
|
||
|
||
The Qmgo project welcomes all contributors. We appreciate your help!
|
||
|
||
## Communication:
|
||
|
||
- Join [qmgo discussions](https://github.com/qiniu/qmgo/discussions)
|
||
|