# mongoctl

### 1.介绍

mongoctl是一个自动化生成MongoDB数据访问对象（Data Access Object）的工具。

### 2.优势

* 支持primitive.ObjectID、primitive.DateTime类型的自动填充。

* 支持int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64类型的自增长。

* 提供了对数据库字段的统一生成方案，避免了业务代码中随处可见的数据库字段的问题。

* 提供了包括InsertOne、InsertMany、UpdateOne、UpdateOneByID、UpdateMany、FindOne、FindOneByID、FindMany、DeleteOne、DeleteOneByID、DeleteMany、Count、Aggregate等多种数据库操作接口。

* 提供了对数据库操作接口的扩展能力。

* 提供了分包与不分包两种包解决方案。

* 支持目录和文件名风格的自定义。

### 3.安装

```bash
go install
```

### 4.用法

```bash
用法 mongoctl:
        mongoctl [flags] -model-dir=. -model-names=T,T -dao-dir=./dao
Flags:
  -counter-name string
        自增模型名称; 默认是 counter
  -dao-dir string
        生成代码所在目录; 必需
  -dao-pkg-path string
        生成代码所在目录的包名; 默认自动生成
  -file-style string
        代码文件名称风格; 选项: kebab | underscore | lower | camel | pascal; 默认是 underscore (default "underscore")
  -model-dir string
        模型所在目录; 必需
  -model-names string
        模型结构体名称; 必需
  -model-pkg-alias string
        模型包别名
  -model-pkg-path string
        模型包名; 默认自动生成
  -sub-pkg-enable
        每个模型创建一个子包; 默认关闭
  -sub-pkg-style string
        模型子包目录名称风格; 选项: kebab | underscore | lower | camel | pascal; 默认是 kebab (default "kebab")

```

### 5.标签

在模型定义中支持对gen标签的解析，目前支持以下标签解析：

| 标签名称     |                                                            | 示例                 | 说明                         |
| -------- | ---------------------------------------------------------- | ------------------ | -------------------------- |
| autoFill | primitive.ObjectID、primitive.DateTime                      | gen:"autoFill"     |                            |
| autoIncr | int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64 | gen:"autoIncr:uid" | 该自增为原子操作，会在生成代码时同步生成计数器代码。 |

### 6.示例

###### 6-1.创建模型

model/mail.go

```go
package model

import "go.mongodb.org/mongo-driver/bson/primitive"

//go:generate mongoctl -model-dir=. -model-names=Mail -dao-dir=../dao/
type Mail struct {
    ID       primitive.ObjectID `bson:"_id" gen:"autoFill"`       // 邮件ID
    Title    string             `bson:"title"`                    // 邮件标题
    Content  string             `bson:"content"`                  // 邮件内容
    Sender   int64              `bson:"sender"`                   // 邮件发送者
    Receiver int64              `bson:"receiver"`                 // 邮件接受者
    Status   int                `bson:"status"`                   // 邮件状态
    SendTime primitive.DateTime `bson:"send_time" gen:"autoFill"` // 发送时间
}
```

###### 6-2.生成dao文件

```bash
go generate ./...
```

###### 6-3.生成的dao文件示例

dao/internal/mail.go

```go
// --------------------------------------------------------------------------------------------
// The following code is automatically generated by the mongo-dao-generator tool.
// Please do not modify this code manually to avoid being overwritten in the next generation.
// For more tool details, please click the link to view https://github.com/dobyte/mongo-dao-generator
// --------------------------------------------------------------------------------------------

package internal

import (
    "context"
    "errors"
    models "github.com/dobyte/mongo-dao-generator/example/model"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "time"
)

type MailFilterFunc func(cols *MailColumns) interface{}
type MailUpdateFunc func(cols *MailColumns) interface{}
type MailPipelineFunc func(cols *MailColumns) interface{}
type MailCountOptionsFunc func(cols *MailColumns) *options.CountOptions
type MailAggregateOptionsFunc func(cols *MailColumns) *options.AggregateOptions
type MailFindOneOptionsFunc func(cols *MailColumns) *options.FindOneOptions
type MailFindManyOptionsFunc func(cols *MailColumns) *options.FindOptions
type MailUpdateOptionsFunc func(cols *MailColumns) *options.UpdateOptions
type MailDeleteOptionsFunc func(cols *MailColumns) *options.DeleteOptions
type MailInsertOneOptionsFunc func(cols *MailColumns) *options.InsertOneOptions
type MailInsertManyOptionsFunc func(cols *MailColumns) *options.InsertManyOptions

type Mail struct {
    Columns    *MailColumns
    Database   *mongo.Database
    Collection *mongo.Collection
}

type MailColumns struct {
    ID       string // 邮件ID
    Title    string // 邮件标题
    Content  string // 邮件内容
    Sender   string // 邮件发送者
    Receiver string // 邮件接受者
    Status   string // 邮件状态
    SendTime string // 发送时间
}

var mailColumns = &MailColumns{
    ID:       "_id",       // 邮件ID
    Title:    "title",     // 邮件标题
    Content:  "content",   // 邮件内容
    Sender:   "sender",    // 邮件发送者
    Receiver: "receiver",  // 邮件接受者
    Status:   "status",    // 邮件状态
    SendTime: "send_time", // 发送时间
}

func NewMail(db *mongo.Database) *Mail {
    return &Mail{
        Columns:    mailColumns,
        Database:   db,
        Collection: db.Collection("mail"),
    }
}

// Count returns the number of documents in the collection.
func (dao *Mail) Count(ctx context.Context, filterFunc MailFilterFunc, optionsFunc ...MailCountOptionsFunc) (int64, error) {
    var (
        opts   *options.CountOptions
        filter = filterFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.CountDocuments(ctx, filter, opts)
}

// Aggregate executes an aggregate command against the collection and returns a cursor over the resulting documents.
func (dao *Mail) Aggregate(ctx context.Context, pipelineFunc MailPipelineFunc, optionsFunc ...MailAggregateOptionsFunc) (*mongo.Cursor, error) {
    var (
        opts     *options.AggregateOptions
        pipeline = pipelineFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.Aggregate(ctx, pipeline, opts)
}

// InsertOne executes an insert command to insert a single document into the collection.
func (dao *Mail) InsertOne(ctx context.Context, model *models.Mail, optionsFunc ...MailInsertOneOptionsFunc) (*mongo.InsertOneResult, error) {
    if model == nil {
        return nil, errors.New("model is nil")
    }

    if err := dao.autofill(ctx, model); err != nil {
        return nil, err
    }

    var opts *options.InsertOneOptions

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.InsertOne(ctx, model, opts)
}

// InsertMany executes an insert command to insert multiple documents into the collection.
func (dao *Mail) InsertMany(ctx context.Context, models []*models.Mail, optionsFunc ...MailInsertManyOptionsFunc) (*mongo.InsertManyResult, error) {
    if len(models) == 0 {
        return nil, errors.New("models is empty")
    }

    documents := make([]interface{}, 0, len(models))
    for i := range models {
        model := models[i]
        if err := dao.autofill(ctx, model); err != nil {
            return nil, err
        }
        documents = append(documents, model)
    }

    var opts *options.InsertManyOptions

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.InsertMany(ctx, documents, opts)
}

// UpdateOne executes an update command to update at most one document in the collection.
func (dao *Mail) UpdateOne(ctx context.Context, filterFunc MailFilterFunc, updateFunc MailUpdateFunc, optionsFunc ...MailUpdateOptionsFunc) (*mongo.UpdateResult, error) {
    var (
        opts   *options.UpdateOptions
        filter = filterFunc(dao.Columns)
        update = updateFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.UpdateOne(ctx, filter, update, opts)
}

// UpdateOneByID executes an update command to update at most one document in the collection.
func (dao *Mail) UpdateOneByID(ctx context.Context, id string, updateFunc MailUpdateFunc, optionsFunc ...MailUpdateOptionsFunc) (*mongo.UpdateResult, error) {
    objectID, err := primitive.ObjectIDFromHex(id)
    if err != nil {
        return nil, err
    }

    return dao.UpdateOne(ctx, func(cols *MailColumns) interface{} {
        return bson.M{"_id": objectID}
    }, updateFunc, optionsFunc...)
}

// UpdateMany executes an update command to update documents in the collection.
func (dao *Mail) UpdateMany(ctx context.Context, filterFunc MailFilterFunc, updateFunc MailUpdateFunc, optionsFunc ...MailUpdateOptionsFunc) (*mongo.UpdateResult, error) {
    var (
        opts   *options.UpdateOptions
        filter = filterFunc(dao.Columns)
        update = updateFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.UpdateMany(ctx, filter, update, opts)
}

// FindOne executes a find command and returns a model for one document in the collection.
func (dao *Mail) FindOne(ctx context.Context, filterFunc MailFilterFunc, optionsFunc ...MailFindOneOptionsFunc) (*models.Mail, error) {
    var (
        opts   *options.FindOneOptions
        model  = &models.Mail{}
        filter = filterFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    err := dao.Collection.FindOne(ctx, filter, opts).Decode(model)
    if err != nil {
        if err == mongo.ErrNoDocuments {
            return nil, nil
        }
        return nil, err
    }

    return model, nil
}

// FindOneByID executes a find command and returns a model for one document in the collection.
func (dao *Mail) FindOneByID(ctx context.Context, id string, optionsFunc ...MailFindOneOptionsFunc) (*models.Mail, error) {
    objectID, err := primitive.ObjectIDFromHex(id)
    if err != nil {
        return nil, err
    }

    return dao.FindOne(ctx, func(cols *MailColumns) interface{} {
        return bson.M{"_id": objectID}
    }, optionsFunc...)
}

// FindMany executes a find command and returns many models the matching documents in the collection.
func (dao *Mail) FindMany(ctx context.Context, filterFunc MailFilterFunc, optionsFunc ...MailFindManyOptionsFunc) ([]*models.Mail, error) {
    var (
        opts   *options.FindOptions
        filter = filterFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    cur, err := dao.Collection.Find(ctx, filter, opts)
    if err != nil {
        return nil, err
    }

    models := make([]*models.Mail, 0)

    if err = cur.All(ctx, &models); err != nil {
        return nil, err
    }

    return models, nil
}

// DeleteOne executes a delete command to delete at most one document from the collection.
func (dao *Mail) DeleteOne(ctx context.Context, filterFunc MailFilterFunc, optionsFunc ...MailDeleteOptionsFunc) (*mongo.DeleteResult, error) {
    var (
        opts   *options.DeleteOptions
        filter = filterFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.DeleteOne(ctx, filter, opts)
}

// DeleteOneByID executes a delete command to delete at most one document from the collection.
func (dao *Mail) DeleteOneByID(ctx context.Context, id string, optionsFunc ...MailDeleteOptionsFunc) (*mongo.DeleteResult, error) {
    objectID, err := primitive.ObjectIDFromHex(id)
    if err != nil {
        return nil, err
    }

    return dao.DeleteOne(ctx, func(cols *MailColumns) interface{} {
        return bson.M{"_id": objectID}
    }, optionsFunc...)
}

// DeleteMany executes a delete command to delete documents from the collection.
func (dao *Mail) DeleteMany(ctx context.Context, filterFunc MailFilterFunc, optionsFunc ...MailDeleteOptionsFunc) (*mongo.DeleteResult, error) {
    var (
        opts   *options.DeleteOptions
        filter = filterFunc(dao.Columns)
    )

    if len(optionsFunc) > 0 {
        opts = optionsFunc[0](dao.Columns)
    }

    return dao.Collection.DeleteMany(ctx, filter, opts)
}

// autofill when inserting data
func (dao *Mail) autofill(ctx context.Context, model *models.Mail) error {
    if model.ID.IsZero() {
        model.ID = primitive.NewObjectID()
    }

    if model.SendTime == 0 {
        model.SendTime = primitive.NewDateTimeFromTime(time.Now())
    }

    return nil
}
```

dao/mail.go

```go
package dao

import (
    "github.com/dobyte/mongo-dao-generator/example/dao/internal"
    "go.mongodb.org/mongo-driver/mongo"
)

type MailColumns = internal.MailColumns

type Mail struct {
    *internal.Mail
}

func NewMail(db *mongo.Database) *Mail {
    return &Mail{Mail: internal.NewMail(db)}
}
```

###### 6-4.使用生成的dao文件

```go
package main

import (
    "context"
    "github.com/dobyte/mongo-dao-generator/example/dao"
    "github.com/dobyte/mongo-dao-generator/example/model"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
    "log"
    "time"
)

func main() {
    var (
        uri     = "mongodb://root:12345678@127.0.0.1:27017"
        opts    = options.Client().ApplyURI(uri)
        baseCtx = context.Background()
    )

    ctx, cancel := context.WithTimeout(baseCtx, 5*time.Second)
    client, err := mongo.Connect(ctx, opts)
    cancel()
    if err != nil {
        log.Fatalf("connect mongo server failed: %v", err)
    }

    ctx, cancel = context.WithTimeout(baseCtx, 5*time.Second)
    defer cancel()
    err = client.Ping(ctx, readpref.Primary())
    cancel()
    if err != nil {
        log.Fatalf("ping mongo server failed: %v", err)
    }

    db := client.Database("dao_test")

    mailDao := dao.NewMail(db)

    _, err = mailDao.InsertOne(baseCtx, &model.Mail{
        Title:    "mongo-dao-generator introduction",
        Content:  "the mongo-dao-generator is a tool for automatically generating MongoDB Data Access Object.",
        Sender:   1,
        Receiver: 2,
        Status:   1,
    })
    if err != nil {
        log.Fatalf("failed to insert into mongo database: %v", err)
    }

    mail, err := mailDao.FindOne(baseCtx, func(cols *dao.MailColumns) interface{} {
        return bson.M{cols.Receiver: 2}
    })
    if err != nil {
        log.Fatalf("failed to find a row of data from mongo database: %v", err)
    }

    log.Printf("%+v", mail)
}
```

运行结果:

```bash
2023/02/17 16:05:31 &{ID:ObjectID("63ef354a4ddc485f0d9c5ea3") Title:mongo-dao-generator introduction Content:the mongo-dao-generator is a tool for automatically generating MongoDB Data Access Object. Sender:1 Receiver:2 Status:1 SendTime:1676621130323}
```
