- update log

- update ip
master
李光春 1 year ago
parent 240eb1e72c
commit b9541a6be8

@ -1,3 +1,8 @@
## v2022-01-05
- update log
- update ip
## v2022-12-28
- update log

@ -28,7 +28,6 @@ require (
github.com/oschwald/geoip2-golang v1.8.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/qiniu/go-sdk/v7 v7.14.0
github.com/qiniu/qmgo v1.1.4
github.com/robfig/cron/v3 v3.0.1
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda
github.com/shirou/gopsutil v3.21.11+incompatible

@ -144,7 +144,6 @@ github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
@ -329,7 +328,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ks3sdklib/aws-sdk-go v1.2.0 h1:Hhe7Ku2gs/TykWy4hoSVTqlLu2p+AApeatKHysgRgVM=
github.com/ks3sdklib/aws-sdk-go v1.2.0/go.mod h1:DVzr6V4XzDjdy+H+1ptuIDIy1MQgI+28SvUpOkJXJD8=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -461,8 +459,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.14.0 h1:6icihMTKHoKMmeU1mqtIoHUv7c1LrLjYm8wTQaYDqmw=
github.com/qiniu/go-sdk/v7 v7.14.0/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
github.com/qiniu/qmgo v1.1.4 h1:6UJBn4laLXRc5kqzhijiPW/TUcOEa1GuW2Q5bV9yCDE=
github.com/qiniu/qmgo v1.1.4/go.mod h1:gTj5P+fOyGwtTkumPa8YTFspsf0Ndpw+MtRPwU1FHL4=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
@ -569,7 +565,6 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=

@ -1,5 +1,5 @@
package go_library
func Version() string {
return "1.0.68"
return "1.0.69"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 MiB

After

Width:  |  Height:  |  Size: 63 MiB

Binary file not shown.

@ -13,10 +13,9 @@ type ApiClientFun func() *ApiClient
// ApiClient 接口
type ApiClient struct {
gormClient *dorm.GormClient // 数据库驱动
mongoClient *dorm.MongoClient // 数据库驱动
zapLog *ZapLog // 日志服务
config struct {
gormClient *dorm.GormClient // 数据库驱动
zapLog *ZapLog // 日志服务
config struct {
systemHostname string // 主机名
systemOs string // 系统类型
systemVersion string // 系统版本
@ -30,18 +29,11 @@ type ApiClient struct {
systemOutsideIp string // 外网ip
goVersion string // go版本
sdkVersion string // sdk版本
mongoVersion string // mongo版本
mongoSdkVersion string // mongo sdk版本
}
gormConfig struct {
stats bool // 状态
tableName string // 表名
}
mongoConfig struct {
stats bool // 状态
databaseName string // 库名
collectionName string // 表名
}
}
// ApiClientConfig 接口实例配置
@ -73,11 +65,7 @@ func NewApiClient(config *ApiClientConfig) (*ApiClient, error) {
c.setConfig(ctx)
gormClient, gormTableName := config.GormClientFun()
//mongoClient, mongoDatabaseName, mongoCollectionName := config.MongoClientFun()
//if (gormClient == nil || gormClient.GetDb() == nil) || (mongoClient == nil || mongoClient.GetDb() == nil) {
// return nil, dbClientFunNoConfig
//}
if gormClient == nil || gormClient.GetDb() == nil {
return nil, dbClientFunNoConfig
}
@ -99,32 +87,6 @@ func NewApiClient(config *ApiClientConfig) (*ApiClient, error) {
c.gormConfig.stats = true
}
// 配置非关系数据库
//if mongoClient != nil || mongoClient.GetDb() != nil {
//
// c.mongoClient = mongoClient
//
// if mongoDatabaseName == "" {
// return nil, errors.New("没有设置库名")
// } else {
// c.mongoConfig.databaseName = mongoDatabaseName
// }
//
// if mongoCollectionName == "" {
// return nil, errors.New("没有设置表名")
// } else {
// c.mongoConfig.collectionName = mongoCollectionName
// }
//
// // 创建时间序列集合
// c.mongoCreateCollection(ctx)
//
// // 创建索引
// c.mongoCreateIndexes(ctx)
//
// c.mongoConfig.stats = true
//}
return c, nil
}
@ -133,9 +95,6 @@ func (c *ApiClient) Middleware(ctx context.Context, request gorequest.Response,
if c.gormConfig.stats {
c.gormMiddleware(ctx, request, sdkVersion)
}
//if c.mongoConfig.stats {
// c.mongoMiddleware(ctx, request, sdkVersion)
//}
}
// MiddlewareXml 中间件
@ -143,9 +102,6 @@ func (c *ApiClient) MiddlewareXml(ctx context.Context, request gorequest.Respons
if c.gormConfig.stats {
c.gormMiddlewareXml(ctx, request, sdkVersion)
}
//if c.mongoConfig.stats {
// c.mongoMiddlewareXml(ctx, request, sdkVersion)
//}
}
// MiddlewareCustom 中间件
@ -153,7 +109,4 @@ func (c *ApiClient) MiddlewareCustom(ctx context.Context, api string, request go
if c.gormConfig.stats {
c.gormMiddlewareCustom(ctx, api, request, sdkVersion)
}
//if c.mongoConfig.stats {
// c.mongoMiddlewareCustom(ctx, api, request, sdkVersion)
//}
}

@ -4,7 +4,6 @@ import (
"context"
"github.com/dtapps/go-library"
"github.com/dtapps/go-library/utils/goip"
"go.mongodb.org/mongo-driver/version"
"runtime"
)
@ -26,6 +25,4 @@ func (c *ApiClient) setConfig(ctx context.Context) {
c.config.sdkVersion = go_library.Version()
c.config.goVersion = runtime.Version()
c.config.mongoSdkVersion = version.Driver
}

@ -68,7 +68,12 @@ func (c *ApiClient) gormRecord(ctx context.Context, data apiPostgresqlLog) {
// GormDelete 删除
func (c *ApiClient) GormDelete(ctx context.Context, hour int64) error {
err := c.gormClient.GetDb().Table(c.gormConfig.tableName).Where("request_time < ?", gotime.Current().BeforeHour(hour).Format()).Delete(&apiPostgresqlLog{}).Error
return c.GormCustomTableDelete(ctx, c.gormConfig.tableName, hour)
}
// GormCustomTableDelete 删除数据 - 自定义表名
func (c *ApiClient) GormCustomTableDelete(ctx context.Context, tableName string, hour int64) error {
err := c.gormClient.GetDb().Table(tableName).Where("request_time < ?", gotime.Current().BeforeHour(hour).Format()).Delete(&apiPostgresqlLog{}).Error
if err != nil {
c.zapLog.WithTraceId(ctx).Sugar().Errorf("删除失败:%s", err)
}

@ -19,11 +19,10 @@ type GinClientFun func() *GinClient
// GinClient 框架
type GinClient struct {
gormClient *dorm.GormClient // 数据库驱动
mongoClient *dorm.MongoClient // 数据库驱动
ipService *goip.Client // ip服务
zapLog *ZapLog // 日志服务
config struct {
gormClient *dorm.GormClient // 数据库驱动
ipService *goip.Client // ip服务
zapLog *ZapLog // 日志服务
config struct {
systemHostname string // 主机名
systemOs string // 系统类型
systemVersion string // 系统版本
@ -37,18 +36,11 @@ type GinClient struct {
systemOutsideIp string // 外网ip
goVersion string // go版本
sdkVersion string // sdk版本
mongoVersion string // mongo版本
mongoSdkVersion string // mongo sdk版本
}
gormConfig struct {
stats bool // 状态
tableName string // 表名
}
mongoConfig struct {
stats bool // 状态
databaseName string // 库名
collectionName string // 表名
}
}
// GinClientConfig 框架实例配置
@ -83,11 +75,7 @@ func NewGinClient(config *GinClientConfig) (*GinClient, error) {
c.setConfig(ctx)
gormClient, gormTableName := config.GormClientFun()
//mongoClient, mongoDatabaseName, mongoCollectionName := config.MongoClientFun()
//if (gormClient == nil || gormClient.GetDb() == nil) || (mongoClient == nil || mongoClient.GetDb() == nil) {
// return nil, dbClientFunNoConfig
//}
if gormClient == nil || gormClient.GetDb() == nil {
return nil, dbClientFunNoConfig
}
@ -108,32 +96,6 @@ func NewGinClient(config *GinClientConfig) (*GinClient, error) {
c.gormConfig.stats = true
}
// 配置非关系数据库
//if mongoClient != nil || mongoClient.GetDb() != nil {
//
// c.mongoClient = mongoClient
//
// if mongoDatabaseName == "" {
// return nil, errors.New("没有设置库名")
// } else {
// c.mongoConfig.databaseName = mongoDatabaseName
// }
//
// if mongoCollectionName == "" {
// return nil, errors.New("没有设置表名")
// } else {
// c.mongoConfig.collectionName = mongoCollectionName
// }
//
// // 创建时间序列集合
// //c.mongoCreateCollection(ctx)
//
// // 创建索引
// c.mongoCreateIndexes(ctx)
//
// c.mongoConfig.stats = true
//}
return c, nil
}

@ -4,7 +4,6 @@ import (
"context"
"github.com/dtapps/go-library"
"github.com/dtapps/go-library/utils/goip"
"go.mongodb.org/mongo-driver/version"
"runtime"
)
@ -26,6 +25,4 @@ func (c *GinClient) setConfig(ctx context.Context) {
c.config.sdkVersion = go_library.Version()
c.config.goVersion = runtime.Version()
c.config.mongoSdkVersion = version.Driver
}

@ -151,7 +151,12 @@ func (c *GinClient) gormRecordXml(ginCtx *gin.Context, traceId string, requestTi
// GormDelete 删除
func (c *GinClient) GormDelete(ctx context.Context, hour int64) error {
err := c.gormClient.GetDb().Table(c.gormConfig.tableName).Where("request_time < ?", gotime.Current().BeforeHour(hour).Format()).Delete(&ginPostgresqlLog{}).Error
return c.GormCustomTableDelete(ctx, c.gormConfig.tableName, hour)
}
// GormCustomTableDelete 删除数据 - 自定义表名
func (c *GinClient) GormCustomTableDelete(ctx context.Context, tableName string, hour int64) error {
err := c.gormClient.GetDb().Table(tableName).Where("request_time < ?", gotime.Current().BeforeHour(hour).Format()).Delete(&ginPostgresqlLog{}).Error
if err != nil {
c.zapLog.WithTraceId(ctx).Sugar().Errorf("删除失败:%s", err)
}

@ -1,2 +0,0 @@
# idea
.idea

@ -1,22 +0,0 @@
language: go
go:
- "1.14"
env:
- GO111MODULE=on
services:
- mongodb
before_script:
- sleep 15 # https://docs.travis-ci.com/user/database-setup/#mongodb-does-not-immediately-accept-connections
- echo "replication:" | sudo tee -a /etc/mongod.conf
- |-
echo " replSetName: \"rs0\"" | sudo tee -a /etc/mongod.conf
- sudo service mongod restart
- sleep 15
- mongo --eval 'rs.initiate()'
- sleep 5
script:
- mongod --version
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 The Qmgo Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -1,382 +0,0 @@
# 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)
[![Join the chat at https://gitter.im/qiniu/qmgo](https://badges.gitter.im/qiniu/qmgo.svg)](https://gitter.im/qiniu/qmgo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
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)

@ -1,382 +0,0 @@
# Qmgo
`Qmgo` 是一款`Go`语言的`MongoDB` `driver`,它基于[MongoDB 官方 driver](https://github.com/mongodb/mongo-go-driver) 开发实现,同时使用更易用的接口设计,比如参考[mgo](https://github.com/go-mgo/mgo) (比如`mgo`的链式调用)。
- `Qmgo`让您以更优雅的姿势使用`MongoDB`的新特性。
- `Qmgo`是从`mgo`迁移到新`MongoDB driver`的第一选择,对代码的改动影响最小。
## 要求
- `Go 1.10` 及以上。
- `MongoDB 2.6` 及以上。
## 功能
- 文档的增删改查, 均支持官方driver支持的所有options
- `Sort`、`limit`、`count`、`select`、`distinct`
- 事务
- `Hooks`
- 自动化更新的默认和定制fields
- 预定义操作符
- 聚合`Aggregate`、索引操作、`cursor`
- `validation tags` 基于tag的字段验证
- 可自定义插件化编程
## 安装
推荐方式是使用`go mod`,通过在源码中`import github.com/qiniu/qmgo` 来自动安装依赖。
当然,通过下面方式同样可行:
```
go get github.com/qiniu/qmgo
```
## Usage
- 开始
`import`并新建连接
```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")
```
如果你的连接是指向固定的 database 和 collection我们推荐使用下面的更方便的方法初始化连接后续操作都基于`cli`而不用再关心 database 和 collection
```go
cli, err := qmgo.Open(ctx, &qmgo.Config{Uri: "mongodb://localhost:27017", Database: "class", Coll: "user"})
```
**_后面都会基于`cli`来举例,如果你使用第一种传统的方式进行初始化,根据上下文,将`cli`替换成`client`、`db` 或 `coll`即可_**
在初始化成功后,请`defer`来关闭连接
```go
defer func() {
if err = cli.Close(ctx); err != nil {
panic(err)
}
}()
```
- 创建索引
做操作前,我们先初始化一些数据:
```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,
}
```
创建索引
```go
cli.CreateOneIndex(context.Background(), options.IndexModel{Key: []string{"name"}})
cli.CreateIndexes(context.Background(), []options.IndexModel{{Key: []string{"id2", "id3"}}})
```
- 插入一个文档
```go
// insert one document
result, err := cli.InsertOne(ctx, userInfo)
```
- 查找一个文档
```go
// find one document
one := UserInfo{}
err = cli.Find(ctx, bson.M{"name": userInfo.Name}).One(&one)
```
- 删除文档
```go
err = cli.Remove(ctx, bson.M{"age": 7})
```
- 插入多条数据
```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)
```
- 批量查找、`Sort`和`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)
```
- 建立连接时支持所有 mongoDB 的`Options`
```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)
```
- 事务
有史以来最简单和强大的事务, 同时还有超时和重试等功能:
```go
callback := func(sessCtx context.Context) (interface{}, error) {
// 重要确保事务中的每一个操作都使用传入的sessCtx参数
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)
```
[关于事务的更多内容](https://github.com/qiniu/qmgo/wiki/Transactions)
- 预定义操作符
```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 灵活的 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)
```
[Hooks 详情介绍](<https://github.com/qiniu/qmgo/wiki/Hooks--(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)>)
- 自动化更新fields
Qmgo支持2种方式来自动化更新特定的字段
- 默认 fields
在文档结构体里注入 `field.DefaultField`, `Qmgo` 会自动在更新和插入操作时更新 `createAt`、`updateAt` and `_id` field的值.
````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)
// tag为createAt、updateAt 和 _id 的字段会自动更新插入
````
- Custom fields
可以自定义field名, `Qmgo` 会自动在更新和插入操作时更新他们.
```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"`
}
// 指定自定义field的field名
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 会自动更新并插入DB
// 假设Id和ui已经初始化
err = cli.ReplaceOne(context.Background(), bson.M{"_id": Id}, &ui)
// UpdateTimeAt 会被自动更新
```
[例子介绍](https://github.com/qiniu/qmgo/blob/master/field_test.go)
[自动化 fields 详情介绍](https://github.com/qiniu/qmgo/wiki/Automatically-update-fields)
- `Validation tags` 基于tag的字段验证
功能基于[go-playground/validator](https://github.com/go-playground/validator)实现。
所以`Qmgo`支持所有[go-playground/validator 的struct验证规则](https://github.com/go-playground/validator#usage-and-documentation),比如:
```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
}
```
本功能只对以下API有效
` InsertOne、InsertyMany、Upsert、UpsertId、ReplaceOne `
- 插件化编程
- 实现以下方法
```go
func Do(ctx context.Context, doc interface{}, opType operator.OpType, opts ...interface{}) error{
// do anything
}
```
- 调用middleware包的Register方法注入`Do`
Qmgo会在支持的[操作](operator/operate_type.go)执行前后调用`Do`
```go
middleware.Register(Do)
```
[Example](middleware/middleware_test.go)
Qmgo的hook、自动更新field和validation tags都基于plugin的方式实现
## `qmgo` vs `go.mongodb.org/mongo-driver`
下面我们举一个多文件查找、`sort`和`limit`的例子, 说明`qmgo`和`mgo`的相似,以及对`go.mongodb.org/mongo-driver`的改进
官方`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)
```
`Qmgo`和`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`
[Qmgo 和 Mgo 的差异](https://github.com/qiniu/qmgo/wiki/Differences-between-Qmgo-and-Mgo)
## Contributing
非常欢迎您对`Qmgo`的任何贡献,非常感谢您的帮助!
## 沟通交流:
- 加入 [qmgo discussions](https://github.com/qiniu/qmgo/discussions)

@ -1,83 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
opts "github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
// Pipeline define the pipeline for aggregate
type Pipeline []bson.D
// Aggregate is a handle to a aggregate
type Aggregate struct {
ctx context.Context
pipeline interface{}
collection *mongo.Collection
options []opts.AggregateOptions
}
// All iterates the cursor from aggregate and decodes each document into results.
func (a *Aggregate) All(results interface{}) error {
opts := options.Aggregate()
if len(a.options) > 0 {
opts = a.options[0].AggregateOptions
}
c, err := a.collection.Aggregate(a.ctx, a.pipeline, opts)
if err != nil {
return err
}
return c.All(a.ctx, results)
}
// One iterates the cursor from aggregate and decodes current document into result.
func (a *Aggregate) One(result interface{}) error {
opts := options.Aggregate()
if len(a.options) > 0 {
opts = a.options[0].AggregateOptions
}
c, err := a.collection.Aggregate(a.ctx, a.pipeline, opts)
if err != nil {
return err
}
cr := Cursor{
ctx: a.ctx,
cursor: c,
err: err,
}
defer cr.Close()
if !cr.Next(result) {
return ErrNoSuchDocuments
}
return err
}
// Iter return the cursor after aggregate
func (a *Aggregate) Iter() CursorI {
opts := options.Aggregate()
if len(a.options) > 0 {
opts = a.options[0].AggregateOptions
}
c, err := a.collection.Aggregate(a.ctx, a.pipeline, opts)
return &Cursor{
ctx: a.ctx,
cursor: c,
err: err,
}
}

@ -1,29 +0,0 @@
/*
Copyright 2021 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import "go.mongodb.org/mongo-driver/bson"
// alias mongo drive bson primitives
// thus user don't need to import go.mongodb.org/mongo-driver/mongo, it's all in qmgo
type (
// M is an alias of bson.M
M = bson.M
// A is an alias of bson.A
A = bson.A
// D is an alias of bson.D
D = bson.D
// E is an alias of bson.E
E = bson.E
)

@ -1,185 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// BulkResult is the result type returned by Bulk.Run operation.
type BulkResult struct {
// The number of documents inserted.
InsertedCount int64
// The number of documents matched by filters in update and replace operations.
MatchedCount int64
// The number of documents modified by update and replace operations.
ModifiedCount int64
// The number of documents deleted.
DeletedCount int64
// The number of documents upserted by update and replace operations.
UpsertedCount int64
// A map of operation index to the _id of each upserted document.
UpsertedIDs map[int64]interface{}
}
// Bulk is context for batching operations to be sent to database in a single
// bulk write.
//
// Bulk is not safe for concurrent use.
//
// Notes:
//
// Individual operations inside a bulk do not trigger middlewares or hooks
// at present.
//
// Different from original mgo, the qmgo implementation of Bulk does not emulate
// bulk operations individually on old versions of MongoDB servers that do not
// natively support bulk operations.
//
// Only operations supported by the official driver are exposed, that is why
// InsertMany is missing from the methods.
type Bulk struct {
coll *Collection
queue []mongo.WriteModel
ordered *bool
}
// Bulk returns a new context for preparing bulk execution of operations.
func (c *Collection) Bulk() *Bulk {
return &Bulk{
coll: c,
queue: nil,
ordered: nil,
}
}
// SetOrdered marks the bulk as ordered or unordered.
//
// If ordered, writes does not continue after one individual write fails.
// Default is ordered.
func (b *Bulk) SetOrdered(ordered bool) *Bulk {
b.ordered = &ordered
return b
}
// InsertOne queues an InsertOne operation for bulk execution.
func (b *Bulk) InsertOne(doc interface{}) *Bulk {
wm := mongo.NewInsertOneModel().SetDocument(doc)
b.queue = append(b.queue, wm)
return b
}
// Remove queues a Remove operation for bulk execution.
func (b *Bulk) Remove(filter interface{}) *Bulk {
wm := mongo.NewDeleteOneModel().SetFilter(filter)
b.queue = append(b.queue, wm)
return b
}
// RemoveId queues a RemoveId operation for bulk execution.
func (b *Bulk) RemoveId(id interface{}) *Bulk {
b.Remove(bson.M{"_id": id})
return b
}
// RemoveAll queues a RemoveAll operation for bulk execution.
func (b *Bulk) RemoveAll(filter interface{}) *Bulk {
wm := mongo.NewDeleteManyModel().SetFilter(filter)
b.queue = append(b.queue, wm)
return b
}
// Upsert queues an Upsert operation for bulk execution.
// The replacement should be document without operator
func (b *Bulk) Upsert(filter interface{}, replacement interface{}) *Bulk {
wm := mongo.NewReplaceOneModel().SetFilter(filter).SetReplacement(replacement).SetUpsert(true)
b.queue = append(b.queue, wm)
return b
}
// UpsertOne queues an UpsertOne operation for bulk execution.
// The update should contain operator
func (b *Bulk) UpsertOne(filter interface{}, update interface{}) *Bulk {
wm := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
b.queue = append(b.queue, wm)
return b
}
// UpsertId queues an UpsertId operation for bulk execution.
// The replacement should be document without operator
func (b *Bulk) UpsertId(id interface{}, replacement interface{}) *Bulk {
b.Upsert(bson.M{"_id": id}, replacement)
return b
}
// UpdateOne queues an UpdateOne operation for bulk execution.
// The update should contain operator
func (b *Bulk) UpdateOne(filter interface{}, update interface{}) *Bulk {
wm := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update)
b.queue = append(b.queue, wm)
return b
}
// UpdateId queues an UpdateId operation for bulk execution.
// The update should contain operator
func (b *Bulk) UpdateId(id interface{}, update interface{}) *Bulk {
b.UpdateOne(bson.M{"_id": id}, update)
return b
}
// UpdateAll queues an UpdateAll operation for bulk execution.
// The update should contain operator
func (b *Bulk) UpdateAll(filter interface{}, update interface{}) *Bulk {
wm := mongo.NewUpdateManyModel().SetFilter(filter).SetUpdate(update)
b.queue = append(b.queue, wm)
return b
}
// Run executes the collected operations in a single bulk operation.
//
// A successful call resets the Bulk. If an error is returned, the internal
// queue of operations is unchanged, containing both successful and failed
// operations.
func (b *Bulk) Run(ctx context.Context) (*BulkResult, error) {
opts := options.BulkWriteOptions{
Ordered: b.ordered,
}
result, err := b.coll.collection.BulkWrite(ctx, b.queue, &opts)
if err != nil {
// In original mgo, queue is not reset in case of error.
return nil, err
}
// Empty the queue for possible reuse, as per mgo's behavior.
b.queue = nil
return &BulkResult{
InsertedCount: result.InsertedCount,
MatchedCount: result.MatchedCount,
ModifiedCount: result.ModifiedCount,
DeletedCount: result.DeletedCount,
UpsertedCount: result.UpsertedCount,
UpsertedIDs: result.UpsertedIDs,
}, nil
}

@ -1,371 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo"
opts "go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
// Config for initial mongodb instance
type Config struct {
// URI example: [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
// URI Reference: https://docs.mongodb.com/manual/reference/connection-string/
Uri string `json:"uri"`
Database string `json:"database"`
Coll string `json:"coll"`
// ConnectTimeoutMS specifies a timeout that is used for creating connections to the server.
// If set to 0, no timeout will be used.
// The default is 30 seconds.
ConnectTimeoutMS *int64 `json:"connectTimeoutMS"`
// MaxPoolSize specifies that maximum number of connections allowed in the driver's connection pool to each server.
// If this is 0, it will be set to math.MaxInt64,
// The default is 100.
MaxPoolSize *uint64 `json:"maxPoolSize"`
// MinPoolSize specifies the minimum number of connections allowed in the driver's connection pool to each server. If
// this is non-zero, each server's pool will be maintained in the background to ensure that the size does not fall below
// the minimum. This can also be set through the "minPoolSize" URI option (e.g. "minPoolSize=100"). The default is 0.
MinPoolSize *uint64 `json:"minPoolSize"`
// SocketTimeoutMS specifies how long the driver will wait for a socket read or write to return before returning a
// network error. If this is 0 meaning no timeout is used and socket operations can block indefinitely.
// The default is 300,000 ms.
SocketTimeoutMS *int64 `json:"socketTimeoutMS"`
// ReadPreference determines which servers are considered suitable for read operations.
// default is PrimaryMode
ReadPreference *ReadPref `json:"readPreference"`
// can be used to provide authentication options when configuring a Client.
Auth *Credential `json:"auth"`
}
// Credential can be used to provide authentication options when configuring a Client.
//
// AuthMechanism: the mechanism to use for authentication. Supported values include "SCRAM-SHA-256", "SCRAM-SHA-1",
// "MONGODB-CR", "PLAIN", "GSSAPI", "MONGODB-X509", and "MONGODB-AWS". This can also be set through the "authMechanism"
// URI option. (e.g. "authMechanism=PLAIN"). For more information, see
// https://docs.mongodb.com/manual/core/authentication-mechanisms/.
// AuthSource: the name of the database to use for authentication. This defaults to "$external" for MONGODB-X509,
// GSSAPI, and PLAIN and "admin" for all other mechanisms. This can also be set through the "authSource" URI option
// (e.g. "authSource=otherDb").
//
// Username: the username for authentication. This can also be set through the URI as a username:password pair before
// the first @ character. For example, a URI for user "user", password "pwd", and host "localhost:27017" would be
// "mongodb://user:pwd@localhost:27017". This is optional for X509 authentication and will be extracted from the
// client certificate if not specified.
//
// Password: the password for authentication. This must not be specified for X509 and is optional for GSSAPI
// authentication.
//
// PasswordSet: For GSSAPI, this must be true if a password is specified, even if the password is the empty string, and
// false if no password is specified, indicating that the password should be taken from the context of the running
// process. For other mechanisms, this field is ignored.
type Credential struct {
AuthMechanism string `json:"authMechanism"`
AuthSource string `json:"authSource"`
Username string `json:"username"`
Password string `json:"password"`
PasswordSet bool `json:"passwordSet"`
}
// ReadPref determines which servers are considered suitable for read operations.
type ReadPref struct {
// MaxStaleness is the maximum amount of time to allow a server to be considered eligible for selection.
// Supported from version 3.4.
MaxStalenessMS int64 `json:"maxStalenessMS"`
// indicates the user's preference on reads.
// PrimaryMode as default
Mode readpref.Mode `json:"mode"`
}
// QmgoClient specifies the instance to operate mongoDB
type QmgoClient struct {
*Collection
*Database
*Client
}
// Open creates client instance according to config
// QmgoClient can operates all qmgo.client 、qmgo.database and qmgo.collection
func Open(ctx context.Context, conf *Config, o ...options.ClientOptions) (cli *QmgoClient, err error) {
client, err := NewClient(ctx, conf, o...)
if err != nil {
fmt.Println("new client fail", err)
return
}
db := client.Database(conf.Database)
coll := db.Collection(conf.Coll)
cli = &QmgoClient{
Client: client,
Database: db,
Collection: coll,
}
return
}
// Client creates client to mongo
type Client struct {
client *mongo.Client
conf Config
registry *bsoncodec.Registry
}
// NewClient creates Qmgo MongoDB client
func NewClient(ctx context.Context, conf *Config, o ...options.ClientOptions) (cli *Client, err error) {
opt, err := newConnectOpts(conf, o...)
if err != nil {
return nil, err
}
client, err := client(ctx, opt)
if err != nil {
fmt.Println("new client fail", err)
return
}
cli = &Client{
client: client,
conf: *conf,
registry: opt.Registry,
}
return
}
// client creates connection to MongoDB
func client(ctx context.Context, opt *opts.ClientOptions) (client *mongo.Client, err error) {
client, err = mongo.Connect(ctx, opt)
if err != nil {
fmt.Println(err)
return
}
// half of default connect timeout
pCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
if err = client.Ping(pCtx, readpref.Primary()); err != nil {
fmt.Println(err)
return
}
return
}
// newConnectOpts creates client options from conf
// Qmgo will follow this way official mongodb driver do
// - the configuration in uri takes precedence over the configuration in the setter
// - Check the validity of the configuration in the uri, while the configuration in the setter is basically not checked
func newConnectOpts(conf *Config, o ...options.ClientOptions) (*opts.ClientOptions, error) {
option := opts.Client()
for _, apply := range o {
option = opts.MergeClientOptions(apply.ClientOptions)
}
if conf.ConnectTimeoutMS != nil {
timeoutDur := time.Duration(*conf.ConnectTimeoutMS) * time.Millisecond
option.SetConnectTimeout(timeoutDur)
}
if conf.SocketTimeoutMS != nil {
timeoutDur := time.Duration(*conf.SocketTimeoutMS) * time.Millisecond
option.SetSocketTimeout(timeoutDur)
} else {
option.SetSocketTimeout(300 * time.Second)
}
if conf.MaxPoolSize != nil {
option.SetMaxPoolSize(*conf.MaxPoolSize)
}
if conf.MinPoolSize != nil {
option.SetMinPoolSize(*conf.MinPoolSize)
}
if conf.ReadPreference != nil {
readPreference, err := newReadPref(*conf.ReadPreference)
if err != nil {
return nil, err
}
option.SetReadPreference(readPreference)
}
if conf.Auth != nil {
auth, err := newAuth(*conf.Auth)
if err != nil {
return nil, err
}
option.SetAuth(auth)
}
option.ApplyURI(conf.Uri)
return option, nil
}
// newAuth create options.Credential from conf.Auth
func newAuth(auth Credential) (credential opts.Credential, err error) {
if auth.AuthMechanism != "" {
credential.AuthMechanism = auth.AuthMechanism
}
if auth.AuthSource != "" {
credential.AuthSource = auth.AuthSource
}
if auth.Username != "" {
// Validate and process the username.
if strings.Contains(auth.Username, "/") {
err = ErrNotSupportedUsername
return
}
credential.Username, err = url.QueryUnescape(auth.Username)
if err != nil {
err = ErrNotSupportedUsername
return
}
}
credential.PasswordSet = auth.PasswordSet
if auth.Password != "" {
if strings.Contains(auth.Password, ":") {
err = ErrNotSupportedPassword
return
}
if strings.Contains(auth.Password, "/") {
err = ErrNotSupportedPassword
return
}
credential.Password, err = url.QueryUnescape(auth.Password)
if err != nil {
err = ErrNotSupportedPassword
return
}
credential.Password = auth.Password
}
return
}
// newReadPref create readpref.ReadPref from config
func newReadPref(pref ReadPref) (*readpref.ReadPref, error) {
readPrefOpts := make([]readpref.Option, 0, 1)
if pref.MaxStalenessMS != 0 {
readPrefOpts = append(readPrefOpts, readpref.WithMaxStaleness(time.Duration(pref.MaxStalenessMS)*time.Millisecond))
}
mode := readpref.PrimaryMode
if pref.Mode != 0 {
mode = pref.Mode
}
readPreference, err := readpref.New(mode, readPrefOpts...)
return readPreference, err
}
// Close closes sockets to the topology referenced by this Client.
func (c *Client) Close(ctx context.Context) error {
err := c.client.Disconnect(ctx)
return err
}
// Ping confirm connection is alive
func (c *Client) Ping(timeout int64) error {
var err error
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
if err = c.client.Ping(ctx, readpref.Primary()); err != nil {
return err
}
return nil
}
// Database create connection to database
func (c *Client) Database(name string, options ...*options.DatabaseOptions) *Database {
opts := opts.Database()
if len(options) > 0 {
if options[0].DatabaseOptions != nil {
opts = options[0].DatabaseOptions
}
}
return &Database{database: c.client.Database(name, opts), registry: c.registry}
}
// Session create one session on client
// Watch out, close session after operation done
func (c *Client) Session(opt ...*options.SessionOptions) (*Session, error) {
sessionOpts := opts.Session()
if len(opt) > 0 && opt[0].SessionOptions != nil {
sessionOpts = opt[0].SessionOptions
}
s, err := c.client.StartSession(sessionOpts)
return &Session{session: s}, err
}
// DoTransaction do whole transaction in one function
// precondition
// - version of mongoDB server >= v4.0
// - Topology of mongoDB server is not Single
// At the same time, please pay attention to the following
// - make sure all operations in callback use the sessCtx as context parameter
// - if operations in callback takes more than(include equal) 120s, the operations will not take effect,
// - if operation in callback return qmgo.ErrTransactionRetry,
// the whole transaction will retry, so this transaction must be idempotent
// - if operations in callback return qmgo.ErrTransactionNotSupported,
// - If the ctx parameter already has a Session attached to it, it will be replaced by this session.
func (c *Client) DoTransaction(ctx context.Context, callback func(sessCtx context.Context) (interface{}, error), opts ...*options.TransactionOptions) (interface{}, error) {
if !c.transactionAllowed() {
return nil, ErrTransactionNotSupported
}
s, err := c.Session()
if err != nil {
return nil, err
}
defer s.EndSession(ctx)
return s.StartTransaction(ctx, callback, opts...)
}
// ServerVersion get the version of mongoDB server, like 4.4.0
func (c *Client) ServerVersion() string {
var buildInfo bson.Raw
err := c.client.Database("admin").RunCommand(
context.Background(),
bson.D{{"buildInfo", 1}},
).Decode(&buildInfo)
if err != nil {
fmt.Println("run command err", err)
return ""
}
v, err := buildInfo.LookupErr("version")
if err != nil {
fmt.Println("look up err", err)
return ""
}
return v.StringValue()
}
// transactionAllowed check if transaction is allowed
func (c *Client) transactionAllowed() bool {
vr, err := CompareVersions("4.0", c.ServerVersion())
if err != nil {
return false
}
if vr > 0 {
fmt.Println("transaction is not supported because mongo server version is below 4.0")
return false
}
// TODO dont know why need to do `cli, err := Open(ctx, &c.conf)` in topology() to get topo,
// Before figure it out, we only use this function in UT
//topo, err := c.topology()
//if topo == description.Single {
// fmt.Println("transaction is not supported because mongo server topology is single")
// return false
//}
return true
}

@ -1,577 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
"fmt"
"reflect"
"strings"
"github.com/qiniu/qmgo/middleware"
"github.com/qiniu/qmgo/operator"
opts "github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx"
)
// Collection is a handle to a MongoDB collection
type Collection struct {
collection *mongo.Collection
registry *bsoncodec.Registry
}
// Find find by condition filterreturn QueryI
func (c *Collection) Find(ctx context.Context, filter interface{}, opts ...opts.FindOptions) QueryI {
return &Query{
ctx: ctx,
collection: c.collection,
filter: filter,
opts: opts,
registry: c.registry,
}
}
// InsertOne insert one document into the collection
// If InsertHook in opts is set, hook works on it, otherwise hook try the doc as hook
// Reference: https://docs.mongodb.com/manual/reference/command/insert/
func (c *Collection) InsertOne(ctx context.Context, doc interface{}, opts ...opts.InsertOneOptions) (result *InsertOneResult, err error) {
h := doc
insertOneOpts := options.InsertOne()
if len(opts) > 0 {
if opts[0].InsertOneOptions != nil {
insertOneOpts = opts[0].InsertOneOptions
}
if opts[0].InsertHook != nil {
h = opts[0].InsertHook
}
}
if err = middleware.Do(ctx, doc, operator.BeforeInsert, h); err != nil {
return
}
res, err := c.collection.InsertOne(ctx, doc, insertOneOpts)
if res != nil {
result = &InsertOneResult{InsertedID: res.InsertedID}
}
if err != nil {
return
}
if err = middleware.Do(ctx, doc, operator.AfterInsert, h); err != nil {
return
}
return
}
// InsertMany executes an insert command to insert multiple documents into the collection.
// If InsertHook in opts is set, hook works on it, otherwise hook try the doc as hook
// Reference: https://docs.mongodb.com/manual/reference/command/insert/
func (c *Collection) InsertMany(ctx context.Context, docs interface{}, opts ...opts.InsertManyOptions) (result *InsertManyResult, err error) {
h := docs
insertManyOpts := options.InsertMany()
if len(opts) > 0 {
if opts[0].InsertManyOptions != nil {
insertManyOpts = opts[0].InsertManyOptions
}
if opts[0].InsertHook != nil {
h = opts[0].InsertHook
}
}
if err = middleware.Do(ctx, docs, operator.BeforeInsert, h); err != nil {
return
}
sDocs := interfaceToSliceInterface(docs)
if sDocs == nil {
return nil, ErrNotValidSliceToInsert
}
res, err := c.collection.InsertMany(ctx, sDocs, insertManyOpts)
if res != nil {
result = &InsertManyResult{InsertedIDs: res.InsertedIDs}
}
if err != nil {
return
}
if err = middleware.Do(ctx, docs, operator.AfterInsert, h); err != nil {
return
}
return
}
// interfaceToSliceInterface convert interface to slice interface
func interfaceToSliceInterface(docs interface{}) []interface{} {
if reflect.Slice != reflect.TypeOf(docs).Kind() {
return nil
}
s := reflect.ValueOf(docs)
if s.Len() == 0 {
return nil
}
var sDocs []interface{}
for i := 0; i < s.Len(); i++ {
sDocs = append(sDocs, s.Index(i).Interface())
}
return sDocs
}
// Upsert updates one documents if filter match, inserts one document if filter is not match, Error when the filter is invalid
// The replacement parameter must be a document that will be used to replace the selected document. It cannot be nil
// and cannot contain any update operators
// Reference: https://docs.mongodb.com/manual/reference/operator/update/
// If replacement has "_id" field and the document is existed, please initial it with existing id(even with Qmgo default field feature).
// Otherwise, "the (immutable) field '_id' altered" error happens.
func (c *Collection) Upsert(ctx context.Context, filter interface{}, replacement interface{}, opts ...opts.UpsertOptions) (result *UpdateResult, err error) {
h := replacement
officialOpts := options.Replace().SetUpsert(true)
if len(opts) > 0 {
if opts[0].ReplaceOptions != nil {
opts[0].ReplaceOptions.SetUpsert(true)
officialOpts = opts[0].ReplaceOptions
}
if opts[0].UpsertHook != nil {
h = opts[0].UpsertHook
}
}
if err = middleware.Do(ctx, replacement, operator.BeforeUpsert, h); err != nil {
return
}
res, err := c.collection.ReplaceOne(ctx, filter, replacement, officialOpts)
if res != nil {
result = translateUpdateResult(res)
}
if err != nil {
return
}
if err = middleware.Do(ctx, replacement, operator.AfterUpsert, h); err != nil {
return
}
return
}
// UpsertId updates one documents if id match, inserts one document if id is not match and the id will inject into the document
// The replacement parameter must be a document that will be used to replace the selected document. It cannot be nil
// and cannot contain any update operators
// Reference: https://docs.mongodb.com/manual/reference/operator/update/
func (c *Collection) UpsertId(ctx context.Context, id interface{}, replacement interface{}, opts ...opts.UpsertOptions) (result *UpdateResult, err error) {
h := replacement
officialOpts := options.Replace().SetUpsert(true)
if len(opts) > 0 {
if opts[0].ReplaceOptions != nil {
opts[0].ReplaceOptions.SetUpsert(true)
officialOpts = opts[0].ReplaceOptions
}
if opts[0].UpsertHook != nil {
h = opts[0].UpsertHook
}
}
if err = middleware.Do(ctx, replacement, operator.BeforeUpsert, h); err != nil {
return
}
res, err := c.collection.ReplaceOne(ctx, bson.M{"_id": id}, replacement, officialOpts)
if res != nil {
result = translateUpdateResult(res)
}
if err != nil {
return
}
if err = middleware.Do(ctx, replacement, operator.AfterUpsert, h); err != nil {
return
}
return
}
// UpdateOne executes an update command to update at most one document in the collection.
// Reference: https://docs.mongodb.com/manual/reference/operator/update/
func (c *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts ...opts.UpdateOptions) (err error) {
updateOpts := options.Update()
if len(opts) > 0 {
if opts[0].UpdateOptions != nil {
updateOpts = opts[0].UpdateOptions
}
if opts[0].UpdateHook != nil {
if err = middleware.Do(ctx, opts[0].UpdateHook, operator.BeforeUpdate); err != nil {
return
}
}
}
res, err := c.collection.UpdateOne(ctx, filter, update, updateOpts)
if res != nil && res.MatchedCount == 0 {
// UpdateOne support upsert function
if updateOpts.Upsert == nil || !*updateOpts.Upsert {
err = ErrNoSuchDocuments
}
}
if err != nil {
return err
}
if len(opts) > 0 && opts[0].UpdateHook != nil {
if err = middleware.Do(ctx, opts[0].UpdateHook, operator.AfterUpdate); err != nil {
return
}
}
return err
}
// UpdateId executes an update command to update at most one document in the collection.
// Reference: https://docs.mongodb.com/manual/reference/operator/update/
func (c *Collection) UpdateId(ctx context.Context, id interface{}, update interface{}, opts ...opts.UpdateOptions) (err error) {
updateOpts := options.Update()
if len(opts) > 0 {
if opts[0].UpdateOptions != nil {
updateOpts = opts[0].UpdateOptions
}
if opts[0].UpdateHook != nil {
if err = middleware.Do(ctx, opts[0].UpdateHook, operator.BeforeUpdate); err != nil {
return
}
}
}
res, err := c.collection.UpdateOne(ctx, bson.M{"_id": id}, update, updateOpts)
if res != nil && res.MatchedCount == 0 {
err = ErrNoSuchDocuments
}
if err != nil {
return err
}
if len(opts) > 0 && opts[0].UpdateHook != nil {
if err = middleware.Do(ctx, opts[0].UpdateHook, operator.AfterUpdate); err != nil {
return
}
}
return err
}
// UpdateAll executes an update command to update documents in the collection.
// The matchedCount is 0 in UpdateResult if no document updated
// Reference: https://docs.mongodb.com/manual/reference/operator/update/
func (c *Collection) UpdateAll(ctx context.Context, filter interface{}, update interface{}, opts ...opts.UpdateOptions) (result *UpdateResult, err error) {
updateOpts := options.Update()
if len(opts) > 0 {
if opts[0].UpdateOptions != nil {
updateOpts = opts[0].UpdateOptions
}
if opts[0].UpdateHook != nil {
if err = middleware.Do(ctx, opts[0].UpdateHook, operator.BeforeUpdate); err != nil {
return
}
}
}
res, err := c.collection.UpdateMany(ctx, filter, update, updateOpts)
if res != nil {
result = translateUpdateResult(res)
}
if err != nil {
return
}
if len(opts) > 0 && opts[0].UpdateHook != nil {
if err = middleware.Do(ctx, opts[0].UpdateHook, operator.AfterUpdate); err != nil {
return
}
}
return
}
// ReplaceOne executes an update command to update at most one document in the collection.
// If UpdateHook in opts is set, hook works on it, otherwise hook try the doc as hook
// Expect type of the doc is the define of user's document
func (c *Collection) ReplaceOne(ctx context.Context, filter interface{}, doc interface{}, opts ...opts.ReplaceOptions) (err error) {
h := doc
replaceOpts := options.Replace()
if len(opts) > 0 {
if opts[0].ReplaceOptions != nil {
replaceOpts = opts[0].ReplaceOptions
}
if opts[0].UpdateHook != nil {
h = opts[0].UpdateHook
}
}
if err = middleware.Do(ctx, doc, operator.BeforeReplace, h); err != nil {
return
}
res, err := c.collection.ReplaceOne(ctx, filter, doc, replaceOpts)
if res != nil && res.MatchedCount == 0 {
err = ErrNoSuchDocuments
}
if err != nil {
return err
}
if err = middleware.Do(ctx, doc, operator.AfterReplace, h); err != nil {
return
}
return err
}
// Remove executes a delete command to delete at most one document from the collection.
// if filter is bson.M{}DeleteOne will delete one document in collection
// Reference: https://docs.mongodb.com/manual/reference/command/delete/
func (c *Collection) Remove(ctx context.Context, filter interface{}, opts ...opts.RemoveOptions) (err error) {
deleteOptions := options.Delete()
if len(opts) > 0 {
if opts[0].DeleteOptions != nil {
deleteOptions = opts[0].DeleteOptions
}
if opts[0].RemoveHook != nil {
if err = middleware.Do(ctx, opts[0].RemoveHook, operator.BeforeRemove); err != nil {
return err
}
}
}
res, err := c.collection.DeleteOne(ctx, filter, deleteOptions)
if res != nil && res.DeletedCount == 0 {
err = ErrNoSuchDocuments
}
if err != nil {
return err
}
if len(opts) > 0 && opts[0].RemoveHook != nil {
if err = middleware.Do(ctx, opts[0].RemoveHook, operator.AfterRemove); err != nil {
return err
}
}
return err
}
// RemoveId executes a delete command to delete at most one document from the collection.
func (c *Collection) RemoveId(ctx context.Context, id interface{}, opts ...opts.RemoveOptions) (err error) {
deleteOptions := options.Delete()
if len(opts) > 0 {
if opts[0].DeleteOptions != nil {
deleteOptions = opts[0].DeleteOptions
}
if opts[0].RemoveHook != nil {
if err = middleware.Do(ctx, opts[0].RemoveHook, operator.BeforeRemove); err != nil {
return err
}
}
}
res, err := c.collection.DeleteOne(ctx, bson.M{"_id": id}, deleteOptions)
if res != nil && res.DeletedCount == 0 {
err = ErrNoSuchDocuments
}
if err != nil {
return err
}
if len(opts) > 0 && opts[0].RemoveHook != nil {
if err = middleware.Do(ctx, opts[0].RemoveHook, operator.AfterRemove); err != nil {
return err
}
}
return err
}
// RemoveAll executes a delete command to delete documents from the collection.
// If filter is bson.M{}all ducuments in Collection will be deleted
// Reference: https://docs.mongodb.com/manual/reference/command/delete/
func (c *Collection) RemoveAll(ctx context.Context, filter interface{}, opts ...opts.RemoveOptions) (result *DeleteResult, err error) {
deleteOptions := options.Delete()
if len(opts) > 0 {
if opts[0].DeleteOptions != nil {
deleteOptions = opts[0].DeleteOptions
}
if opts[0].RemoveHook != nil {
if err = middleware.Do(ctx, opts[0].RemoveHook, operator.BeforeRemove); err != nil {
return
}
}
}
res, err := c.collection.DeleteMany(ctx, filter, deleteOptions)
if res != nil {
result = &DeleteResult{DeletedCount: res.DeletedCount}
}
if err != nil {
return
}
if len(opts) > 0 && opts[0].RemoveHook != nil {
if err = middleware.Do(ctx, opts[0].RemoveHook, operator.AfterRemove); err != nil {
return
}
}
return
}
// Aggregate executes an aggregate command against the collection and returns a AggregateI to get resulting documents.
func (c *Collection) Aggregate(ctx context.Context, pipeline interface{}, opts ...opts.AggregateOptions) AggregateI {
return &Aggregate{
ctx: ctx,
collection: c.collection,
pipeline: pipeline,
options: opts,
}
}
// ensureIndex create multiple indexes on the collection and returns the names of
// Exampleindexes = []string{"idx1", "-idx2", "idx3,idx4"}
// Three indexes will be created, index idx1 with ascending order, index idx2 with descending order, idex3 and idex4 are Compound ascending sort index
// Reference: https://docs.mongodb.com/manual/reference/command/createIndexes/
func (c *Collection) ensureIndex(ctx context.Context, indexes []opts.IndexModel) error {
var indexModels []mongo.IndexModel
for _, idx := range indexes {
var model mongo.IndexModel
var keysDoc bsonx.Doc
for _, field := range idx.Key {
key, n := SplitSortField(field)
keysDoc = keysDoc.Append(key, bsonx.Int32(n))
}
model = mongo.IndexModel{
Keys: keysDoc,
Options: idx.IndexOptions,
}
indexModels = append(indexModels, model)
}
if len(indexModels) == 0 {
return nil
}
res, err := c.collection.Indexes().CreateMany(ctx, indexModels)
if err != nil || len(res) == 0 {
fmt.Println("<MongoDB.C>: ", c.collection.Name(), " Index: ", indexes, " error: ", err, "res: ", res)
return err
}
return nil
}
// EnsureIndexes Deprecated
// Recommend to use CreateIndexes / CreateOneIndex for more function)
// EnsureIndexes creates unique and non-unique indexes in collection
// the combination of indexes is different from CreateIndexes:
// if uniques/indexes is []string{"name"}, means create index "name"
// if uniques/indexes is []string{"name,-age","uid"} means create Compound indexes: name and -age, then create one index: uid
func (c *Collection) EnsureIndexes(ctx context.Context, uniques []string, indexes []string) (err error) {
var uniqueModel []opts.IndexModel
var indexesModel []opts.IndexModel
for _, v := range uniques {
vv := strings.Split(v, ",")
indexOpts := options.Index()
indexOpts.SetUnique(true)
model := opts.IndexModel{Key: vv, IndexOptions: indexOpts}
uniqueModel = append(uniqueModel, model)
}
if err = c.CreateIndexes(ctx, uniqueModel); err != nil {
return
}
for _, v := range indexes {
vv := strings.Split(v, ",")
model := opts.IndexModel{Key: vv}
indexesModel = append(indexesModel, model)
}
if err = c.CreateIndexes(ctx, indexesModel); err != nil {
return
}
return
}
// CreateIndexes creates multiple indexes in collection
// If the Key in opts.IndexModel is []string{"name"}, means create index: name
// If the Key in opts.IndexModel is []string{"name","-age"} means create Compound indexes: name and -age
func (c *Collection) CreateIndexes(ctx context.Context, indexes []opts.IndexModel) (err error) {
err = c.ensureIndex(ctx, indexes)
return
}
// CreateOneIndex creates one index
// If the Key in opts.IndexModel is []string{"name"}, means create index name
// If the Key in opts.IndexModel is []string{"name","-age"} means create Compound index: name and -age
func (c *Collection) CreateOneIndex(ctx context.Context, index opts.IndexModel) error {
return c.ensureIndex(ctx, []opts.IndexModel{index})
}
// DropAllIndexes drop all indexes on the collection except the index on the _id field
// if there is only _id field index on the collection, the function call will report an error
func (c *Collection) DropAllIndexes(ctx context.Context) (err error) {
_, err = c.collection.Indexes().DropAll(ctx)
return err
}
// DropIndex drop indexes in collection, indexes that be dropped should be in line with inputting indexes
// The indexes is []string{"name"} means drop index: name
// The indexes is []string{"name","-age"} means drop Compound indexes: name and -age
func (c *Collection) DropIndex(ctx context.Context, indexes []string) error {
_, err := c.collection.Indexes().DropOne(ctx, generateDroppedIndex(indexes))
if err != nil {
return err
}
return err
}
// generate indexes that store in mongo which may consist more than one index(like []string{"index1","index2"} is stored as "index1_1_index2_1")
func generateDroppedIndex(index []string) string {
var res string
for _, e := range index {
key, sort := SplitSortField(e)
n := key + "_" + fmt.Sprint(sort)
if len(res) == 0 {
res = n
} else {
res += "_" + n
}
}
return res
}
// DropCollection drops collection
// it's safe even collection is not exists
func (c *Collection) DropCollection(ctx context.Context) error {
return c.collection.Drop(ctx)
}
// CloneCollection creates a copy of the Collection
func (c *Collection) CloneCollection() (*mongo.Collection, error) {
return c.collection.Clone()
}
// GetCollectionName returns the name of collection
func (c *Collection) GetCollectionName() string {
return c.collection.Name()
}
// Watch returns a change stream for all changes on the corresponding collection. See
// https://docs.mongodb.com/manual/changeStreams/ for more information about change streams.
func (c *Collection) Watch(ctx context.Context, pipeline interface{}, opts ...*opts.ChangeStreamOptions) (*mongo.ChangeStream, error) {
changeStreamOption := options.ChangeStream()
if len(opts) > 0 && opts[0].ChangeStreamOptions != nil {
changeStreamOption = opts[0].ChangeStreamOptions
}
return c.collection.Watch(ctx, pipeline, changeStreamOption)
}
// translateUpdateResult translates mongo update result to qmgo define UpdateResult
func translateUpdateResult(res *mongo.UpdateResult) (result *UpdateResult) {
result = &UpdateResult{
MatchedCount: res.MatchedCount,
ModifiedCount: res.ModifiedCount,
UpsertedCount: res.UpsertedCount,
UpsertedID: res.UpsertedID,
}
return
}

@ -1,77 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
)
// Cursor struct define
type Cursor struct {
ctx context.Context
cursor *mongo.Cursor
err error
}
// Next gets the next document for this cursor. It returns true if there were no errors and the cursor has not been
// exhausted.
func (c *Cursor) Next(result interface{}) bool {
if c.err != nil {
return false
}
var err error
if c.cursor.Next(c.ctx) {
err = c.cursor.Decode(result)
if err == nil {
return true
}
}
return false
}
// All iterates the cursor and decodes each document into results. The results parameter must be a pointer to a slice.
// recommend to use All() in struct Query or Aggregate
func (c *Cursor) All(results interface{}) error {
if c.err != nil {
return c.err
}
return c.cursor.All(c.ctx, results)
}
// ID returns the ID of this cursor, or 0 if the cursor has been closed or exhausted.
//func (c *Cursor) ID() int64 {
// if c.err != nil {
// return 0
// }
// return c.cursor.ID()
//}
// Close closes this cursor. Next and TryNext must not be called after Close has been called.
// When the cursor object is no longer in use, it should be actively closed
func (c *Cursor) Close() error {
if c.err != nil {
return c.err
}
return c.cursor.Close(c.ctx)
}
// Err return the last error of Cursor, if no error occurs, return nil
func (c *Cursor) Err() error {
if c.err != nil {
return c.err
}
return c.cursor.Err()
}

@ -1,82 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
opts "github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// Database is a handle to a MongoDB database
type Database struct {
database *mongo.Database
registry *bsoncodec.Registry
}
// Collection gets collection from database
func (d *Database) Collection(name string) *Collection {
var cp *mongo.Collection
cp = d.database.Collection(name)
return &Collection{
collection: cp,
registry: d.registry,
}
}
// GetDatabaseName returns the name of database
func (d *Database) GetDatabaseName() string {
return d.database.Name()
}
// DropDatabase drops database
func (d *Database) DropDatabase(ctx context.Context) error {
return d.database.Drop(ctx)
}
// RunCommand executes the given command against the database.
//
// The runCommand parameter must be a document for the command to be executed. It cannot be nil.
// This must be an order-preserving type such as bson.D. Map types such as bson.M are not valid.
// If the command document contains a session ID or any transaction-specific fields, the behavior is undefined.
//
// The opts parameter can be used to specify options for this operation (see the options.RunCmdOptions documentation).
func (d *Database) RunCommand(ctx context.Context, runCommand interface{}, opts ...opts.RunCommandOptions) *mongo.SingleResult {
option := options.RunCmd()
if len(opts) > 0 && opts[0].RunCmdOptions != nil {
option = opts[0].RunCmdOptions
}
return d.database.RunCommand(ctx, runCommand, option)
}
// CreateCollection executes a create command to explicitly create a new collection with the specified name on the
// server. If the collection being created already exists, this method will return a mongo.CommandError. This method
// requires driver version 1.4.0 or higher.
//
// The opts parameter can be used to specify options for the operation (see the options.CreateCollectionOptions
// documentation).
func (db *Database) CreateCollection(ctx context.Context, name string, opts ...opts.CreateCollectionOptions) error {
var option = make([]*options.CreateCollectionOptions,0,len(opts))
for _,opt := range opts{
if opt.CreateCollectionOptions != nil{
option = append(option,opt.CreateCollectionOptions)
}
}
return db.database.CreateCollection(ctx,name,option...)
}

@ -1,60 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"errors"
"strings"
"go.mongodb.org/mongo-driver/mongo"
)
var (
// ErrQueryNotSlicePointer return if result argument is not a pointer to a slice
ErrQueryNotSlicePointer = errors.New("result argument must be a pointer to a slice")
// ErrQueryNotSliceType return if result argument is not slice address
ErrQueryNotSliceType = errors.New("result argument must be a slice address")
// ErrQueryResultTypeInconsistent return if result type is not equal mongodb value type
ErrQueryResultTypeInconsistent = errors.New("result type is not equal mongodb value type")
// ErrQueryResultValCanNotChange return if the value of result can not be changed
ErrQueryResultValCanNotChange = errors.New("the value of result can not be changed")
// ErrNoSuchDocuments return if no document found
ErrNoSuchDocuments = mongo.ErrNoDocuments
// ErrTransactionRetry return if transaction need to retry
ErrTransactionRetry = errors.New("retry transaction")
// ErrTransactionNotSupported return if transaction not supported
ErrTransactionNotSupported = errors.New("transaction not supported")
// ErrNotSupportedUsername return if username is invalid
ErrNotSupportedUsername = errors.New("username not supported")
// ErrNotSupportedPassword return if password is invalid
ErrNotSupportedPassword = errors.New("password not supported")
// ErrNotValidSliceToInsert return if insert argument is not valid slice
ErrNotValidSliceToInsert = errors.New("must be valid slice to insert")
// ErrReplacementContainUpdateOperators return if replacement document contain update operators
ErrReplacementContainUpdateOperators = errors.New("replacement document cannot contain keys beginning with '$'")
)
// IsErrNoDocuments check if err is no documents, both mongo-go-driver error and qmgo custom error
// Deprecated, simply call if err == ErrNoSuchDocuments or if err == mongo.ErrNoDocuments
func IsErrNoDocuments(err error) bool {
if err == ErrNoSuchDocuments {
return true
}
return false
}
// IsDup check if err is mongo E11000 (duplicate err)。
func IsDup(err error) bool {
return err != nil && strings.Contains(err.Error(), "E11000")
}

@ -1,147 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package field
import (
"fmt"
"go.mongodb.org/mongo-driver/bson/primitive"
"reflect"
"time"
)
// CustomFields defines struct of supported custom fields
type CustomFields struct {
createAt string
updateAt string
id string
}
// CustomFieldsHook defines the interface, CustomFields return custom field user want to change
type CustomFieldsHook interface {
CustomFields() CustomFieldsBuilder
}
// CustomFieldsBuilder defines the interface which user use to set custom fields
type CustomFieldsBuilder interface {
SetUpdateAt(fieldName string) CustomFieldsBuilder
SetCreateAt(fieldName string) CustomFieldsBuilder
SetId(fieldName string) CustomFieldsBuilder
}
// NewCustom creates new Builder which is used to set the custom fields
func NewCustom() CustomFieldsBuilder {
return &CustomFields{}
}
// SetUpdateAt set the custom UpdateAt field
func (c *CustomFields) SetUpdateAt(fieldName string) CustomFieldsBuilder {
c.updateAt = fieldName
return c
}
// SetCreateAt set the custom CreateAt field
func (c *CustomFields) SetCreateAt(fieldName string) CustomFieldsBuilder {
c.createAt = fieldName
return c
}
// SetId set the custom Id field
func (c *CustomFields) SetId(fieldName string) CustomFieldsBuilder {
c.id = fieldName
return c
}
// CustomCreateTime changes the custom create time
func (c CustomFields) CustomCreateTime(doc interface{}) {
if c.createAt == "" {
return
}
fieldName := c.createAt
setTime(doc, fieldName, false)
return
}
// CustomUpdateTime changes the custom update time
func (c CustomFields) CustomUpdateTime(doc interface{}) {
if c.updateAt == "" {
return
}
fieldName := c.updateAt
setTime(doc, fieldName, true)
return
}
// CustomUpdateTime changes the custom update time
func (c CustomFields) CustomId(doc interface{}) {
if c.id == "" {
return
}
fieldName := c.id
setId(doc, fieldName)
return
}
// setTime changes the custom time fields
// The overWrite defines if change value when the filed has valid value
func setTime(doc interface{}, fieldName string, overWrite bool) {
if reflect.Ptr != reflect.TypeOf(doc).Kind() {
fmt.Println("not a point type")
return
}
e := reflect.ValueOf(doc).Elem()
ca := e.FieldByName(fieldName)
if ca.CanSet() {
tt := time.Now()
switch a := ca.Interface().(type) {
case time.Time:
if ca.Interface().(time.Time).IsZero() {
ca.Set(reflect.ValueOf(tt))
} else if overWrite {
ca.Set(reflect.ValueOf(tt))
}
case int64:
if ca.Interface().(int64) == 0 {
ca.SetInt(tt.Unix())
} else if overWrite {
ca.SetInt(tt.Unix())
}
default:
fmt.Println("unsupported type to setTime", a)
}
}
}
// setId changes the custom Id fields
func setId(doc interface{}, fieldName string) {
if reflect.Ptr != reflect.TypeOf(doc).Kind() {
fmt.Println("not a point type")
return
}
e := reflect.ValueOf(doc).Elem()
ca := e.FieldByName(fieldName)
if ca.CanSet() {
switch a := ca.Interface().(type) {
case primitive.ObjectID:
if ca.Interface().(primitive.ObjectID).IsZero() {
ca.Set(reflect.ValueOf(primitive.NewObjectID()))
}
case string:
if ca.String() == "" {
ca.SetString(primitive.NewObjectID().Hex())
}
default:
fmt.Println("unsupported type to setId", a)
}
}
}

@ -1,54 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package field
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// DefaultFieldHook defines the interface to change default fields by hook
type DefaultFieldHook interface {
DefaultUpdateAt()
DefaultCreateAt()
DefaultId()
}
// DefaultField defines the default fields to handle when operation happens
// import the DefaultField in document struct to make it working
type DefaultField struct {
Id primitive.ObjectID `bson:"_id"`
CreateAt time.Time `bson:"createAt"`
UpdateAt time.Time `bson:"updateAt"`
}
// DefaultUpdateAt changes the default updateAt field
func (df *DefaultField) DefaultUpdateAt() {
df.UpdateAt = time.Now().Local()
}
// DefaultCreateAt changes the default createAt field
func (df *DefaultField) DefaultCreateAt() {
if df.CreateAt.IsZero() {
df.CreateAt = time.Now().Local()
}
}
// DefaultId changes the default _id field
func (df *DefaultField) DefaultId() {
if df.Id.IsZero() {
df.Id = primitive.NewObjectID()
}
}

@ -1,139 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package field
import (
"context"
"reflect"
"time"
"github.com/qiniu/qmgo/operator"
)
var nilTime time.Time
// filedHandler defines the relations between field type and handler
var fieldHandler = map[operator.OpType]func(doc interface{}) error{
operator.BeforeInsert: beforeInsert,
operator.BeforeUpdate: beforeUpdate,
operator.BeforeReplace: beforeUpdate,
operator.BeforeUpsert: beforeUpsert,
}
//func init() {
// middleware.Register(Do)
//}
// Do call the specific method to handle field based on fType
// Don't use opts here
func Do(ctx context.Context, doc interface{}, opType operator.OpType, opts ...interface{}) error {
to := reflect.TypeOf(doc)
if to == nil {
return nil
}
switch reflect.TypeOf(doc).Kind() {
case reflect.Slice:
return sliceHandle(doc, opType)
case reflect.Ptr:
v := reflect.ValueOf(doc).Elem()
switch v.Kind() {
case reflect.Slice:
return sliceHandle(v.Interface(), opType)
default:
return do(doc, opType)
}
}
//fmt.Println("not support type")
return nil
}
// sliceHandle handles the slice docs
func sliceHandle(docs interface{}, opType operator.OpType) error {
// []interface{}{UserType{}...}
if h, ok := docs.([]interface{}); ok {
for _, v := range h {
if err := do(v, opType); err != nil {
return err
}
}
return nil
}
// []UserType{}
s := reflect.ValueOf(docs)
for i := 0; i < s.Len(); i++ {
if err := do(s.Index(i).Interface(), opType); err != nil {
return err
}
}
return nil
}
// beforeInsert handles field before insert
// If value of field createAt is valid in doc, upsert doesn't change it
// If value of field id is valid in doc, upsert doesn't change it
// Change the value of field updateAt anyway
func beforeInsert(doc interface{}) error {
if ih, ok := doc.(DefaultFieldHook); ok {
ih.DefaultId()
ih.DefaultCreateAt()
ih.DefaultUpdateAt()
}
if ih, ok := doc.(CustomFieldsHook); ok {
fields := ih.CustomFields()
fields.(*CustomFields).CustomId(doc)
fields.(*CustomFields).CustomCreateTime(doc)
fields.(*CustomFields).CustomUpdateTime(doc)
}
return nil
}
// beforeUpdate handles field before update
func beforeUpdate(doc interface{}) error {
if ih, ok := doc.(DefaultFieldHook); ok {
ih.DefaultUpdateAt()
}
if ih, ok := doc.(CustomFieldsHook); ok {
fields := ih.CustomFields()
fields.(*CustomFields).CustomUpdateTime(doc)
}
return nil
}
// beforeUpsert handles field before upsert
// If value of field createAt is valid in doc, upsert doesn't change it
// If value of field id is valid in doc, upsert doesn't change it
// Change the value of field updateAt anyway
func beforeUpsert(doc interface{}) error {
if ih, ok := doc.(DefaultFieldHook); ok {
ih.DefaultId()
ih.DefaultCreateAt()
ih.DefaultUpdateAt()
}
if ih, ok := doc.(CustomFieldsHook); ok {
fields := ih.CustomFields()
fields.(*CustomFields).CustomId(doc)
fields.(*CustomFields).CustomCreateTime(doc)
fields.(*CustomFields).CustomUpdateTime(doc)
}
return nil
}
// do check if opType is supported and call fieldHandler
func do(doc interface{}, opType operator.OpType) error {
if f, ok := fieldHandler[opType]; !ok {
return nil
} else {
return f(doc)
}
}

@ -1,218 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hook
import (
"context"
"github.com/qiniu/qmgo/operator"
"reflect"
)
// hookHandler defines the relations between hook type and handler
var hookHandler = map[operator.OpType]func(ctx context.Context, hook interface{}) error{
operator.BeforeInsert: beforeInsert,
operator.AfterInsert: afterInsert,
operator.BeforeUpdate: beforeUpdate,
operator.AfterUpdate: afterUpdate,
operator.BeforeQuery: beforeQuery,
operator.AfterQuery: afterQuery,
operator.BeforeRemove: beforeRemove,
operator.AfterRemove: afterRemove,
operator.BeforeUpsert: beforeUpsert,
operator.AfterUpsert: afterUpsert,
operator.BeforeReplace: beforeUpdate,
operator.AfterReplace: afterUpdate,
}
//
//func init() {
// middleware.Register(Do)
//}
// Do call the specific method to handle hook based on hType
// If opts has valid value, use it instead of original hook
func Do(ctx context.Context, hook interface{}, opType operator.OpType, opts ...interface{}) error {
if len(opts) > 0 {
hook = opts[0]
}
to := reflect.TypeOf(hook)
if to == nil {
return nil
}
switch to.Kind() {
case reflect.Slice:
return sliceHandle(ctx, hook, opType)
case reflect.Ptr:
v := reflect.ValueOf(hook).Elem()
switch v.Kind() {
case reflect.Slice:
return sliceHandle(ctx, v.Interface(), opType)
default:
return do(ctx, hook, opType)
}
default:
return do(ctx, hook, opType)
}
}
// sliceHandle handles the slice hooks
func sliceHandle(ctx context.Context, hook interface{}, opType operator.OpType) error {
// []interface{}{UserType{}...}
if h, ok := hook.([]interface{}); ok {
for _, v := range h {
if err := do(ctx, v, opType); err != nil {
return err
}
}
return nil
}
// []UserType{}
s := reflect.ValueOf(hook)
for i := 0; i < s.Len(); i++ {
if err := do(ctx, s.Index(i).Interface(), opType); err != nil {
return err
}
}
return nil
}
// BeforeInsertHook InsertHook defines the insert hook interface
type BeforeInsertHook interface {
BeforeInsert(ctx context.Context) error
}
type AfterInsertHook interface {
AfterInsert(ctx context.Context) error
}
// beforeInsert calls custom BeforeInsert
func beforeInsert(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(BeforeInsertHook); ok {
return ih.BeforeInsert(ctx)
}
return nil
}
// afterInsert calls custom AfterInsert
func afterInsert(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(AfterInsertHook); ok {
return ih.AfterInsert(ctx)
}
return nil
}
// BeforeUpdateHook defines the Update hook interface
type BeforeUpdateHook interface {
BeforeUpdate(ctx context.Context) error
}
type AfterUpdateHook interface {
AfterUpdate(ctx context.Context) error
}
// beforeUpdate calls custom BeforeUpdate
func beforeUpdate(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(BeforeUpdateHook); ok {
return ih.BeforeUpdate(ctx)
}
return nil
}
// afterUpdate calls custom AfterUpdate
func afterUpdate(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(AfterUpdateHook); ok {
return ih.AfterUpdate(ctx)
}
return nil
}
// BeforeQueryHook QueryHook defines the query hook interface
type BeforeQueryHook interface {
BeforeQuery(ctx context.Context) error
}
type AfterQueryHook interface {
AfterQuery(ctx context.Context) error
}
// beforeQuery calls custom BeforeQuery
func beforeQuery(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(BeforeQueryHook); ok {
return ih.BeforeQuery(ctx)
}
return nil
}
// afterQuery calls custom AfterQuery
func afterQuery(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(AfterQueryHook); ok {
return ih.AfterQuery(ctx)
}
return nil
}
// BeforeRemoveHook RemoveHook defines the remove hook interface
type BeforeRemoveHook interface {
BeforeRemove(ctx context.Context) error
}
type AfterRemoveHook interface {
AfterRemove(ctx context.Context) error
}
// beforeRemove calls custom BeforeRemove
func beforeRemove(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(BeforeRemoveHook); ok {
return ih.BeforeRemove(ctx)
}
return nil
}
// afterRemove calls custom AfterRemove
func afterRemove(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(AfterRemoveHook); ok {
return ih.AfterRemove(ctx)
}
return nil
}
// BeforeUpsertHook UpsertHook defines the upsert hook interface
type BeforeUpsertHook interface {
BeforeUpsert(ctx context.Context) error
}
type AfterUpsertHook interface {
AfterUpsert(ctx context.Context) error
}
// beforeUpsert calls custom BeforeUpsert
func beforeUpsert(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(BeforeUpsertHook); ok {
return ih.BeforeUpsert(ctx)
}
return nil
}
// afterUpsert calls custom AfterUpsert
func afterUpsert(ctx context.Context, hook interface{}) error {
if ih, ok := hook.(AfterUpsertHook); ok {
return ih.AfterUpsert(ctx)
}
return nil
}
// do check if opType is supported and call hookHandler
func do(ctx context.Context, hook interface{}, opType operator.OpType) error {
if f, ok := hookHandler[opType]; !ok {
return nil
} else {
return f(ctx, hook)
}
}

@ -1,75 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import "go.mongodb.org/mongo-driver/mongo/options"
// CollectionI
// 集合操作接口
//type CollectionI interface {
// Find(filter interface{}) QueryI
// InsertOne(doc interface{}) (*mongo.InsertOneResult, error)
// InsertMany(docs ...interface{}) (*mongo.InsertManyResult, error)
// Upsert(filter interface{}, replacement interface{}) (*mongo.UpdateResult, error)
// UpdateOne(filter interface{}, update interface{}) error
// UpdateAll(filter interface{}, update interface{}) (*mongo.UpdateResult, error)
// DeleteOne(filter interface{}) error
// RemoveAll(selector interface{}) (*mongo.DeleteResult, error)
// EnsureIndex(indexes []string, isUnique bool)
// EnsureIndexes(uniques []string, indexes []string)
//}
// Change holds fields for running a findAndModify command via the Query.Apply method.
type Change struct {
Update interface{} // update/replace document
Replace bool // Whether to replace the document rather than updating
Remove bool // Whether to remove the document found rather than updating
Upsert bool // Whether to insert in case the document isn't found, take effect when Remove is false
ReturnNew bool // Should the modified document be returned rather than the old one, take effect when Remove is false
}
// CursorI Cursor interface
type CursorI interface {
Next(result interface{}) bool
Close() error
Err() error
All(results interface{}) error
//ID() int64
}
// QueryI Query interface
type QueryI interface {
Collation(collation *options.Collation) QueryI
Sort(fields ...string) QueryI
Select(selector interface{}) QueryI
Skip(n int64) QueryI
BatchSize(n int64) QueryI
NoCursorTimeout(n bool) QueryI
Limit(n int64) QueryI
One(result interface{}) error
All(result interface{}) error
Count() (n int64, err error)
EstimatedCount() (n int64, err error)
Distinct(key string, result interface{}) error
Cursor() CursorI
Apply(change Change, result interface{}) error
Hint(hint interface{}) QueryI
}
// AggregateI define the interface of aggregate
type AggregateI interface {
All(results interface{}) error
One(result interface{}) error
Iter() CursorI
}

@ -1,36 +0,0 @@
package middleware
import (
"context"
"github.com/qiniu/qmgo/field"
"github.com/qiniu/qmgo/hook"
"github.com/qiniu/qmgo/operator"
"github.com/qiniu/qmgo/validator"
)
// callback define the callback function type
type callback func(ctx context.Context, doc interface{}, opType operator.OpType, opts ...interface{}) error
// middlewareCallback the register callback slice
// some callbacks initial here without Register() for order
var middlewareCallback = []callback{
hook.Do,
field.Do,
validator.Do,
}
// Register register callback into middleware
func Register(cb callback) {
middlewareCallback = append(middlewareCallback, cb)
}
// Do call every registers
// The doc is always the document to operate
func Do(ctx context.Context, content interface{}, opType operator.OpType, opts ...interface{}) error {
for _, cb := range middlewareCallback {
if err := cb(ctx, content, opType, opts...); err != nil {
return err
}
}
return nil
}

@ -1,159 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package operator
// Aggregation Pipeline Operators
// refer: https://docs.mongodb.com/manual/reference/operator/aggregation/
const (
// Arithmetic Expression Operators
Abs = "$abs"
Add = "$add"
Ceil = "$ceil"
Divide = "$divide"
Exp = "$exp"
Floor = "$floor"
Ln = "$ln"
Log = "$log"
Log10 = "$log10"
Multiply = "$multiply"
Pow = "$pow"
Round = "$round"
Sqrt = "$sqrt"
Subtract = "$subtract"
Trunc = "$trunc"
// Array Expression Operators
ArrayElemAt = "$arrayElemAt"
ArrayToObject = "$arrayToObject"
ConcatArrays = "$concatArrays"
Filter = "$filter"
IndexOfArray = "$indexOfArray"
IsArray = "$isArray"
Map = "$map"
ObjectToArray = "$objectToArray"
Range = "$range"
Reduce = "$reduce"
ReverseArray = "$reverseArray"
Zip = "$zip"
// Comparison Expression Operators
Cmp = "$cmp"
// Conditional Expression Operators
Cond = "$cond"
IfNull = "$ifNull"
Switch = "$switch"
// Custom Aggregation Expression Operators
Accumulator = "$accumulator"
Function = "$function"
// Data Size Operators
BinarySize = "$binarySize"
BsonSize = "$bsonSize"
// Date Expression Operators
DateFromParts = "$dateFromParts"
DateFromString = "$dateFromString"
DateToParts = "$dateToParts"
DateToString = "$dateToString"
DayOfMonth = "$dayOfMonth"
DayOfWeek = "$dayOfWeek"
DayOfYear = "$dayOfYear"
Hour = "$hour"
IsoDayOfWeek = "$isoDayOfWeek"
IsoWeek = "$isoWeek"
IsoWeekYear = "$isoWeekYear"
Millisecond = "$millisecond"
Minute = "$minute"
Month = "$month"
Second = "$second"
ToDate = "$toDate"
Week = "$week"
Year = "$year"
// Literal Expression Operator
Literal = "$literal"
// Object Expression Operators
MergeObjects = "$mergeObjects"
// Set Expression Operators
AllElementsTrue = "$allElementsTrue"
AnyElementTrue = "$anyElementTrue"
SetDifference = "$setDifference"
SetEquals = "$setEquals"
SetIntersection = "$setIntersection"
SetIsSubset = "$setIsSubset"
SetUnion = "$setUnion"
// String Expression Operators
Concat = "$concat"
IndexOfBytes = "$indexOfBytes"
IndexOfCP = "$indexOfCP"
Ltrim = "$ltrim"
RegexFind = "$regexFind"
RegexFindAll = "$regexFindAll"
RegexMatch = "$regexMatch"
Rtrim = "$rtrim"
Split = "$split"
StrLenBytes = "$strLenBytes"
StrLenCP = "$strLenCP"
Strcasecmp = "$strcasecmp"
Substr = "$substr"
SubstrBytes = "$substrBytes"
SubstrCP = "$substrCP"
ToLower = "$toLower"
ToString = "$toString"
Trim = "$trim"
ToUpper = "$toUpper"
ReplaceOne = "$replaceOne"
ReplaceAll = "$replaceAll"
// Trigonometry Expression Operators
Sin = "$sin"
Cos = "$cos"
Tan = "$tan"
Asin = "$asin"
Acos = "$acos"
Atan = "$atan"
Atan2 = "$atan2"
Asinh = "$asinh"
Acosh = "$acosh"
Atanh = "$atanh"
DegreesToRadians = "$degreesToRadians"
RadiansToDegrees = "$radiansToDegrees"
// Type Expression Operators
Convert = "$convert"
ToBool = "$toBool"
ToDecimal = "$toDecimal"
ToDouble = "$toDouble"
ToInt = "$toInt"
ToLong = "$toLong"
ToObjectID = "$toObjectId"
IsNumber = "$isNumber"
// Accumulators ($group)
Avg = "$avg"
First = "$first"
Last = "$last"
StdDevPop = "$stdDevPop"
StdDevSamp = "$stdDevSamp"
Sum = "$sum"
// Variable Expression Operators
Let = "$let"
)

@ -1,50 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package operator
// define the aggregation pipeline stages
// refer: https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
const (
// Collection Aggregate Stages
AddFields = "$addFields"
Bucket = "$bucket"
BucketAuto = "$bucketAuto"
CollStats = "$collStats"
Count = "$count"
Facet = "$facet"
GeoNear = "$geoNear"
GraphLookup = "$graphLookup"
Group = "$group"
IndexStats = "$indexStats"
Limit = "$limit"
ListSessions = "$listSessions"
Lookup = "$lookup"
Match = "$match"
Merge = "$merge"
Out = "$out"
PlanCacheStats = "$planCacheStats"
Project = "$project"
Redact = "$redact"
ReplaceRoot = "$replaceRoot"
ReplaceWith = "$replaceWith"
Sample = "$sample"
Skip = "$skip"
SortByCount = "$sortByCount"
UnionWith = "$unionWith"
Unwind = "$unwind"
// Database Aggregate stages
CurrentOp = "$currentOp"
ListLocalSessions = "$listLocalSessions"
)

@ -1,18 +0,0 @@
package operator
type OpType string
const (
BeforeInsert OpType = "beforeInsert"
AfterInsert OpType = "afterInsert"
BeforeUpdate OpType = "beforeUpdate"
AfterUpdate OpType = "afterUpdate"
BeforeQuery OpType = "beforeQuery"
AfterQuery OpType = "afterQuery"
BeforeRemove OpType = "beforeRemove"
AfterRemove OpType = "afterRemove"
BeforeUpsert OpType = "beforeUpsert"
AfterUpsert OpType = "afterUpsert"
BeforeReplace OpType = "beforeReplace"
AfterReplace OpType = "afterReplace"
)

@ -1,71 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package operator
// define the query and projection operators
// refer: https://docs.mongodb.com/manual/reference/operator/query/
const (
// Comparison
Eq = "$eq"
Gt = "$gt"
Gte = "$gte"
In = "$in"
Lt = "$lt"
Lte = "$lte"
Ne = "$ne"
Nin = "$nin"
// Logical
And = "$and"
Not = "$not"
Nor = "$nor"
Or = "$or"
// Element
Exists = "$exists"
Type = "$type"
// Evaluation
Expr = "$expr"
JsonSchema = "$jsonSchema"
Mod = "$mod"
Regex = "$regex"
Text = "$text"
Where = "$where"
// Geo spatial
GeoIntersects = "$geoIntersects"
GeoWithin = "$geoWithin"
Near = "$near"
NearSphere = "$nearSphere"
// Array
All = "$all"
ElemMatch = "$elemMatch"
Size = "$size"
// Bitwise
BitsAllClear = "$bitsAllClear"
BitsAllSet = "$bitsAllSet"
BitsAnyClear = "$bitsAnyClear"
BitsAnySet = "$bitsAnySet"
// Comments
Comment = "$comment"
// Projection operators
Dollar = "$"
Meta = "$meta"
Slice = "$slice"
)

@ -1,30 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package operator
//Query Modifiers
// refer:https://docs.mongodb.com/manual/reference/operator/query-modifier/
const (
// Modifiers
Explain = "$explain"
Hint = "$hint"
MaxTimeMS = "$maxTimeMS"
OrderBy = "$orderby"
Query = "$query"
ReturnKey = "$returnKey"
ShowDiskLoc = "$showDiskLoc"
// Sort Order
Natural = "$natural"
)

@ -1,44 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package operator
// define the update operators
// refer: https://docs.mongodb.com/manual/reference/operator/update/
const (
// Fields
CurrentDate = "$currentDate"
Inc = "$inc"
Min = "$min"
Max = "$max"
Mul = "$mul"
Rename = "$rename"
Set = "$set"
SetOnInsert = "$setOnInsert"
Unset = "$unset"
// Array Operators
AddToSet = "$addToSet"
Pop = "$pop"
Pull = "$pull"
Push = "$push"
PullAll = "$pullAll"
// Array modifiers
Each = "$each"
Position = "$position"
Sort = "$sort"
// Array bitwise
Bit = "$bit"
)

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type AggregateOptions struct {
*options.AggregateOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type ChangeStreamOptions struct {
*options.ChangeStreamOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type ClientOptions struct {
*options.ClientOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type CreateCollectionOptions struct {
*options.CreateCollectionOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type DatabaseOptions struct {
*options.DatabaseOptions
}

@ -1,21 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type IndexModel struct {
Key []string // Index key fields; prefix name with dash (-) for descending order
*options.IndexOptions
}

@ -1,25 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type InsertOneOptions struct {
InsertHook interface{}
*options.InsertOneOptions
}
type InsertManyOptions struct {
InsertHook interface{}
*options.InsertManyOptions
}

@ -1,18 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
type FindOptions struct {
QueryHook interface{}
}

@ -1,21 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type RemoveOptions struct {
RemoveHook interface{}
*options.DeleteOptions
}

@ -1,8 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type ReplaceOptions struct {
UpdateHook interface{}
*options.ReplaceOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type RunCommandOptions struct {
*options.RunCmdOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type SessionOptions struct {
*options.SessionOptions
}

@ -1,7 +0,0 @@
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type TransactionOptions struct {
*options.TransactionOptions
}

@ -1,21 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type UpdateOptions struct {
UpdateHook interface{}
*options.UpdateOptions
}

@ -1,21 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import "go.mongodb.org/mongo-driver/mongo/options"
type UpsertOptions struct {
UpsertHook interface{}
*options.ReplaceOptions
}

@ -1,414 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
"fmt"
"reflect"
"github.com/qiniu/qmgo/middleware"
"github.com/qiniu/qmgo/operator"
qOpts "github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// Query struct definition
type Query struct {
filter interface{}
sort interface{}
project interface{}
hint interface{}
limit *int64
skip *int64
batchSize *int64
noCursorTimeout *bool
collation *options.Collation
ctx context.Context
collection *mongo.Collection
opts []qOpts.FindOptions
registry *bsoncodec.Registry
}
func (q *Query) Collation(collation *options.Collation) QueryI {
newQ := q
newQ.collation = collation
return newQ
}
func (q *Query) NoCursorTimeout(n bool) QueryI {
newQ := q
newQ.noCursorTimeout = &n
return newQ
}
// BatchSize sets the value for the BatchSize field.
// Means the maximum number of documents to be included in each batch returned by the server.
func (q *Query) BatchSize(n int64) QueryI {
newQ := q
newQ.batchSize = &n
return newQ
}
// Sort is Used to set the sorting rules for the returned results
// Format: "age" or "+age" means to sort the age field in ascending order, "-age" means in descending order
// When multiple sort fields are passed in at the same time, they are arranged in the order in which the fields are passed in.
// For example, {"age", "-name"}, first sort by age in ascending order, then sort by name in descending order
func (q *Query) Sort(fields ...string) QueryI {
if len(fields) == 0 {
// A nil bson.D will not correctly serialize, but this case is no-op
// so an early return will do.
return q
}
var sorts bson.D
for _, field := range fields {
key, n := SplitSortField(field)
if key == "" {
panic("Sort: empty field name")
}
sorts = append(sorts, bson.E{Key: key, Value: n})
}
newQ := q
newQ.sort = sorts
return newQ
}
// Select is used to determine which fields are displayed or not displayed in the returned results
// Format: bson.M{"age": 1} means that only the age field is displayed
// bson.M{"age": 0} means to display other fields except age
// When _id is not displayed and is set to 0, it will be returned to display
func (q *Query) Select(projection interface{}) QueryI {
newQ := q
newQ.project = projection
return newQ
}
// Skip skip n records
func (q *Query) Skip(n int64) QueryI {
newQ := q
newQ.skip = &n
return newQ
}
// Hint sets the value for the Hint field.
// This should either be the index name as a string or the index specification
// as a document. The default value is nil, which means that no hint will be sent.
func (q *Query) Hint(hint interface{}) QueryI {
newQ := q
newQ.hint = hint
return newQ
}
// Limit limits the maximum number of documents found to n
// The default value is 0, and 0 means no limit, and all matching results are returned
// When the limit value is less than 0, the negative limit is similar to the positive limit, but the cursor is closed after returning a single batch result.
// Reference https://docs.mongodb.com/manual/reference/method/cursor.limit/index.html
func (q *Query) Limit(n int64) QueryI {
newQ := q
newQ.limit = &n
return newQ
}
// One query a record that meets the filter conditions
// If the search fails, an error will be returned
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.collation != nil {
opt.SetCollation(q.collation)
}
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
}
// All query multiple records that meet the filter conditions
// The static type of result must be a slice pointer
func (q *Query) All(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.Find()
if q.collation != nil {
opt.SetCollation(q.collation)
}
if q.sort != nil {
opt.SetSort(q.sort)
}
if q.project != nil {
opt.SetProjection(q.project)
}
if q.limit != nil {
opt.SetLimit(*q.limit)
}
if q.skip != nil {
opt.SetSkip(*q.skip)
}
if q.hint != nil {
opt.SetHint(q.hint)
}
if q.batchSize != nil {
opt.SetBatchSize(int32(*q.batchSize))
}
if q.noCursorTimeout != nil {
opt.SetNoCursorTimeout(*q.noCursorTimeout)
}
var err error
var cursor *mongo.Cursor
cursor, err = q.collection.Find(q.ctx, q.filter, opt)
c := Cursor{
ctx: q.ctx,
cursor: cursor,
err: err,
}
err = c.All(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
}
// Count count the number of eligible entries
func (q *Query) Count() (n int64, err error) {
opt := options.Count()
if q.limit != nil {
opt.SetLimit(*q.limit)
}
if q.skip != nil {
opt.SetSkip(*q.skip)
}
return q.collection.CountDocuments(q.ctx, q.filter, opt)
}
// EstimatedCount count the number of the collection by using the metadata
func (q *Query) EstimatedCount() (n int64, err error) {
return q.collection.EstimatedDocumentCount(q.ctx)
}
// Distinct gets the unique value of the specified field in the collection and return it in the form of slice
// result should be passed a pointer to slice
// The function will verify whether the static type of the elements in the result slice is consistent with the data type obtained in mongodb
// reference https://docs.mongodb.com/manual/reference/command/distinct/
func (q *Query) Distinct(key string, result interface{}) error {
resultVal := reflect.ValueOf(result)
if resultVal.Kind() != reflect.Ptr {
return ErrQueryNotSlicePointer
}
resultElmVal := resultVal.Elem()
if resultElmVal.Kind() != reflect.Interface && resultElmVal.Kind() != reflect.Slice {
return ErrQueryNotSliceType
}
opt := options.Distinct()
res, err := q.collection.Distinct(q.ctx, key, q.filter, opt)
if err != nil {
return err
}
registry := q.registry
if registry == nil {
registry = bson.DefaultRegistry
}
valueType, valueBytes, err_ := bson.MarshalValueWithRegistry(registry, res)
if err_ != nil {
fmt.Printf("bson.MarshalValue err: %+v\n", err_)
return err_
}
rawValue := bson.RawValue{Type: valueType, Value: valueBytes}
err = rawValue.Unmarshal(result)
if err != nil {
fmt.Printf("rawValue.Unmarshal err: %+v\n", err)
return ErrQueryResultTypeInconsistent
}
return nil
}
// Cursor gets a Cursor object, which can be used to traverse the query result set
// After obtaining the CursorI object, you should actively call the Close interface to close the cursor
func (q *Query) Cursor() CursorI {
opt := options.Find()
if q.sort != nil {
opt.SetSort(q.sort)
}
if q.project != nil {
opt.SetProjection(q.project)
}
if q.limit != nil {
opt.SetLimit(*q.limit)
}
if q.skip != nil {
opt.SetSkip(*q.skip)
}
if q.batchSize != nil {
opt.SetBatchSize(int32(*q.batchSize))
}
if q.noCursorTimeout != nil {
opt.SetNoCursorTimeout(*q.noCursorTimeout)
}
var err error
var cur *mongo.Cursor
cur, err = q.collection.Find(q.ctx, q.filter, opt)
return &Cursor{
ctx: q.ctx,
cursor: cur,
err: err,
}
}
// Apply runs the findAndModify command, which allows updating, replacing
// or removing a document matching a query and atomically returning either the old
// version (the default) or the new version of the document (when ReturnNew is true)
//
// The Sort and Select query methods affect the result of Apply. In case
// multiple documents match the query, Sort enables selecting which document to
// act upon by ordering it first. Select enables retrieving only a selection
// of fields of the new or old document.
//
// When Change.Replace is true, it means replace at most one document in the collection
// and the update parameter must be a document and cannot contain any update operators;
// if no objects are found and Change.Upsert is false, it will returns ErrNoDocuments.
// When Change.Remove is true, it means delete at most one document in the collection
// and returns the document as it appeared before deletion; if no objects are found,
// it will returns ErrNoDocuments.
// When both Change.Replace and Change.Remove are falseit means update at most one document
// in the collection and the update parameter must be a document containing update operators;
// if no objects are found and Change.Upsert is false, it will returns ErrNoDocuments.
//
// reference: https://docs.mongodb.com/manual/reference/command/findAndModify/
func (q *Query) Apply(change Change, result interface{}) error {
var err error
if change.Remove {
err = q.findOneAndDelete(change, result)
} else if change.Replace {
err = q.findOneAndReplace(change, result)
} else {
err = q.findOneAndUpdate(change, result)
}
return err
}
// findOneAndDelete
// reference: https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndDelete/
func (q *Query) findOneAndDelete(change Change, result interface{}) error {
opts := options.FindOneAndDelete()
if q.sort != nil {
opts.SetSort(q.sort)
}
if q.project != nil {
opts.SetProjection(q.project)
}
return q.collection.FindOneAndDelete(q.ctx, q.filter, opts).Decode(result)
}
// findOneAndReplace
// reference: https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/
func (q *Query) findOneAndReplace(change Change, result interface{}) error {
opts := options.FindOneAndReplace()
if q.sort != nil {
opts.SetSort(q.sort)
}
if q.project != nil {
opts.SetProjection(q.project)
}
if change.Upsert {
opts.SetUpsert(change.Upsert)
}
if change.ReturnNew {
opts.SetReturnDocument(options.After)
}
err := q.collection.FindOneAndReplace(q.ctx, q.filter, change.Update, opts).Decode(result)
if change.Upsert && !change.ReturnNew && err == mongo.ErrNoDocuments {
return nil
}
return err
}
// findOneAndUpdate
// reference: https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/
func (q *Query) findOneAndUpdate(change Change, result interface{}) error {
opts := options.FindOneAndUpdate()
if q.sort != nil {
opts.SetSort(q.sort)
}
if q.project != nil {
opts.SetProjection(q.project)
}
if change.Upsert {
opts.SetUpsert(change.Upsert)
}
if change.ReturnNew {
opts.SetReturnDocument(options.After)
}
err := q.collection.FindOneAndUpdate(q.ctx, q.filter, change.Update, opts).Decode(result)
if change.Upsert && !change.ReturnNew && err == mongo.ErrNoDocuments {
return nil
}
return err
}

@ -1,39 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
// InsertOneResult is the result type returned by an InsertOne operation.
type InsertOneResult struct {
// The _id of the inserted document. A value generated by the driver will be of type primitive.ObjectID.
InsertedID interface{}
}
// InsertManyResult is a result type returned by an InsertMany operation.
type InsertManyResult struct {
// The _id values of the inserted documents. Values generated by the driver will be of type primitive.ObjectID.
InsertedIDs []interface{}
}
// UpdateResult is the result type returned from UpdateOne, UpdateMany, and ReplaceOne operations.
type UpdateResult struct {
MatchedCount int64 // The number of documents matched by the filter.
ModifiedCount int64 // The number of documents modified by the operation.
UpsertedCount int64 // The number of documents upsert by the operation.
UpsertedID interface{} // The _id field of the upsert document, or nil if no upsert was done.
}
// DeleteResult is the result type returned by DeleteOne and DeleteMany operations.
type DeleteResult struct {
DeletedCount int64 // The number of documents deleted.
}

@ -1,74 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"context"
opts "github.com/qiniu/qmgo/options"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/mongo/driver"
)
// Session is an struct that represents a MongoDB logical session
type Session struct {
session mongo.Session
}
// StartTransaction starts transaction
//precondition
//- version of mongoDB server >= v4.0
//- Topology of mongoDB server is not Single
//At the same time, please pay attention to the following
//- make sure all operations in callback use the sessCtx as context parameter
//- Dont forget to call EndSession if session is not used anymore
//- if operations in callback takes more than(include equal) 120s, the operations will not take effect,
//- if operation in callback return qmgo.ErrTransactionRetry,
// the whole transaction will retry, so this transaction must be idempotent
//- if operations in callback return qmgo.ErrTransactionNotSupported,
//- If the ctx parameter already has a Session attached to it, it will be replaced by this session.
func (s *Session) StartTransaction(ctx context.Context, cb func(sessCtx context.Context) (interface{}, error), opts ...*opts.TransactionOptions) (interface{}, error) {
transactionOpts := options.Transaction()
if len(opts) > 0 && opts[0].TransactionOptions != nil {
transactionOpts = opts[0].TransactionOptions
}
result, err := s.session.WithTransaction(ctx, wrapperCustomCb(cb), transactionOpts)
if err != nil {
return nil, err
}
return result, nil
}
// EndSession will abort any existing transactions and close the session.
func (s *Session) EndSession(ctx context.Context) {
s.session.EndSession(ctx)
}
// AbortTransaction aborts the active transaction for this session. This method will return an error if there is no
// active transaction for this session or the transaction has been committed or aborted.
func (s *Session) AbortTransaction(ctx context.Context) error {
return s.session.AbortTransaction(ctx)
}
// wrapperCustomF wrapper caller's callback function to mongo dirver's
func wrapperCustomCb(cb func(ctx context.Context) (interface{}, error)) func(sessCtx mongo.SessionContext) (interface{}, error) {
return func(sessCtx mongo.SessionContext) (interface{}, error) {
result, err := cb(sessCtx)
if err == ErrTransactionRetry {
return nil, mongo.CommandError{Labels: []string{driver.TransientTransactionError}}
}
return result, err
}
}

@ -1,83 +0,0 @@
/*
Copyright 2020 The Qmgo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package qmgo
import (
"math"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Now return Millisecond current time
func Now() time.Time {
return time.Unix(0, time.Now().UnixNano()/1e6*1e6)
}
// NewObjectID generates a new ObjectID.
// Watch out: the way it generates objectID is different from mgo
func NewObjectID() primitive.ObjectID {
return primitive.NewObjectID()
}
// SplitSortField handle sort symbol: "+"/"-" in front of field
// if "+" return sort as 1
// if "-" return sort as -1
func SplitSortField(field string) (key string, sort int32) {
sort = 1
key = field
if len(field) != 0 {
switch field[0] {
case '+':
key = strings.TrimPrefix(field, "+")
sort = 1
case '-':
key = strings.TrimPrefix(field, "-")
sort = -1
}
}
return key, sort
}
// CompareVersions compares two version number strings (i.e. positive integers separated by
// periods). Comparisons are done to the lesser precision of the two versions. For example, 3.2 is
// considered equal to 3.2.11, whereas 3.2.0 is considered less than 3.2.11.
//
// Returns a positive int if version1 is greater than version2, a negative int if version1 is less
// than version2, and 0 if version1 is equal to version2.
func CompareVersions(v1 string, v2 string) (int, error) {
n1 := strings.Split(v1, ".")
n2 := strings.Split(v2, ".")
for i := 0; i < int(math.Min(float64(len(n1)), float64(len(n2)))); i++ {
i1, err := strconv.Atoi(n1[i])
if err != nil {
return 0, err
}
i2, err := strconv.Atoi(n2[i])
if err != nil {
return 0, err
}
difference := i1 - i2
if difference != 0 {
return difference, nil
}
}
return 0, nil
}

@ -1,96 +0,0 @@
package validator
import (
"context"
"reflect"
"time"
"github.com/go-playground/validator/v10"
"github.com/qiniu/qmgo/operator"
)
// use a single instance of Validate, it caches struct info
var validate = validator.New()
// SetValidate let validate can use custom rules
func SetValidate(v *validator.Validate) {
validate = v
}
// validatorNeeded checks if the validator is needed to opType
func validatorNeeded(opType operator.OpType) bool {
switch opType {
case operator.BeforeInsert, operator.BeforeUpsert, operator.BeforeReplace:
return true
}
return false
}
// Do calls validator check
// Don't use opts here
func Do(ctx context.Context, doc interface{}, opType operator.OpType, opts ...interface{}) error {
if !validatorNeeded(opType) {
return nil
}
to := reflect.TypeOf(doc)
if to == nil {
return nil
}
switch reflect.TypeOf(doc).Kind() {
case reflect.Slice:
return sliceHandle(doc, opType)
case reflect.Ptr:
v := reflect.ValueOf(doc).Elem()
switch v.Kind() {
case reflect.Slice:
return sliceHandle(v.Interface(), opType)
default:
return do(doc)
}
default:
return do(doc)
}
}
// sliceHandle handles the slice docs
func sliceHandle(docs interface{}, opType operator.OpType) error {
// []interface{}{UserType{}...}
if h, ok := docs.([]interface{}); ok {
for _, v := range h {
if err := do(v); err != nil {
return err
}
}
return nil
}
// []UserType{}
s := reflect.ValueOf(docs)
for i := 0; i < s.Len(); i++ {
if err := do(s.Index(i).Interface()); err != nil {
return err
}
}
return nil
}
// do check if opType is supported and call fieldHandler
func do(doc interface{}) error {
if !validatorStruct(doc) {
return nil
}
return validate.Struct(doc)
}
// validatorStruct check if kind of doc is validator supported struct
// same implement as validator
func validatorStruct(doc interface{}) bool {
val := reflect.ValueOf(doc)
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}
if val.Kind() != reflect.Struct || val.Type() == reflect.TypeOf(time.Time{}) {
return false
}
return true
}

@ -398,15 +398,6 @@ github.com/qiniu/go-sdk/v7/internal/hostprovider
github.com/qiniu/go-sdk/v7/internal/log
github.com/qiniu/go-sdk/v7/reqid
github.com/qiniu/go-sdk/v7/storage
# github.com/qiniu/qmgo v1.1.4
## explicit; go 1.16
github.com/qiniu/qmgo
github.com/qiniu/qmgo/field
github.com/qiniu/qmgo/hook
github.com/qiniu/qmgo/middleware
github.com/qiniu/qmgo/operator
github.com/qiniu/qmgo/options
github.com/qiniu/qmgo/validator
# github.com/robfig/cron/v3 v3.0.1
## explicit; go 1.12
github.com/robfig/cron/v3

Loading…
Cancel
Save