Compare commits
3 Commits
database/i
...
database/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45d364280b | ||
|
|
fcd2a5ee43 | ||
|
|
989f5da01f |
@@ -433,21 +433,22 @@ func (x *Data_Redis) GetEnableMetrics() bool {
|
||||
type Data_MongoDB struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"`
|
||||
Username *string `protobuf:"bytes,2,opt,name=username,proto3,oneof" json:"username,omitempty"`
|
||||
Password *string `protobuf:"bytes,3,opt,name=password,proto3,oneof" json:"password,omitempty"`
|
||||
AuthMechanism *string `protobuf:"bytes,4,opt,name=auth_mechanism,json=authMechanism,proto3,oneof" json:"auth_mechanism,omitempty"` // 认证机制:SCRAM-SHA-1、SCRAM-SHA-256、MONGODB-X509、GSSAPI、PLAIN
|
||||
AuthMechanismProperties map[string]string `protobuf:"bytes,5,rep,name=auth_mechanism_properties,json=authMechanismProperties,proto3" json:"auth_mechanism_properties,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 认证机制属性
|
||||
AuthSource *string `protobuf:"bytes,6,opt,name=auth_source,json=authSource,proto3,oneof" json:"auth_source,omitempty"` // 认证源:admin、$external等
|
||||
ConnectTimeout *durationpb.Duration `protobuf:"bytes,50,opt,name=connect_timeout,json=connectTimeout,proto3" json:"connect_timeout,omitempty"` // 连接超时时间
|
||||
HeartbeatInterval *durationpb.Duration `protobuf:"bytes,51,opt,name=heartbeat_interval,json=heartbeatInterval,proto3" json:"heartbeat_interval,omitempty"` // 心跳间隔
|
||||
LocalThreshold *durationpb.Duration `protobuf:"bytes,52,opt,name=local_threshold,json=localThreshold,proto3" json:"local_threshold,omitempty"` // 本地延迟阈值
|
||||
MaxConnIdleTime *durationpb.Duration `protobuf:"bytes,53,opt,name=max_conn_idle_time,json=maxConnIdleTime,proto3" json:"max_conn_idle_time,omitempty"` // 最大连接空闲时间
|
||||
MaxStaleness *durationpb.Duration `protobuf:"bytes,54,opt,name=max_staleness,json=maxStaleness,proto3" json:"max_staleness,omitempty"` // 最大陈旧时间
|
||||
ServerSelectionTimeout *durationpb.Duration `protobuf:"bytes,55,opt,name=server_selection_timeout,json=serverSelectionTimeout,proto3" json:"server_selection_timeout,omitempty"` // 服务器选择超时时间
|
||||
SocketTimeout *durationpb.Duration `protobuf:"bytes,56,opt,name=socket_timeout,json=socketTimeout,proto3" json:"socket_timeout,omitempty"` // 套接字超时时间
|
||||
Timeout *durationpb.Duration `protobuf:"bytes,57,opt,name=timeout,proto3" json:"timeout,omitempty"` // 总超时时间
|
||||
EnableTracing bool `protobuf:"varint,100,opt,name=enable_tracing,json=enableTracing,proto3" json:"enable_tracing,omitempty"` // 打开链路追踪
|
||||
EnableMetrics bool `protobuf:"varint,101,opt,name=enable_metrics,json=enableMetrics,proto3" json:"enable_metrics,omitempty"` // 打开性能度量
|
||||
Database *string `protobuf:"bytes,2,opt,name=database,proto3,oneof" json:"database,omitempty"`
|
||||
Username *string `protobuf:"bytes,10,opt,name=username,proto3,oneof" json:"username,omitempty"`
|
||||
Password *string `protobuf:"bytes,11,opt,name=password,proto3,oneof" json:"password,omitempty"`
|
||||
AuthMechanism *string `protobuf:"bytes,20,opt,name=auth_mechanism,json=authMechanism,proto3,oneof" json:"auth_mechanism,omitempty"` // 认证机制:SCRAM-SHA-1、SCRAM-SHA-256、MONGODB-X509、GSSAPI、PLAIN
|
||||
AuthMechanismProperties map[string]string `protobuf:"bytes,21,rep,name=auth_mechanism_properties,json=authMechanismProperties,proto3" json:"auth_mechanism_properties,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 认证机制属性
|
||||
AuthSource *string `protobuf:"bytes,22,opt,name=auth_source,json=authSource,proto3,oneof" json:"auth_source,omitempty"` // 认证源:admin、$external等
|
||||
ConnectTimeout *durationpb.Duration `protobuf:"bytes,50,opt,name=connect_timeout,json=connectTimeout,proto3" json:"connect_timeout,omitempty"` // 连接超时时间
|
||||
HeartbeatInterval *durationpb.Duration `protobuf:"bytes,51,opt,name=heartbeat_interval,json=heartbeatInterval,proto3" json:"heartbeat_interval,omitempty"` // 心跳间隔
|
||||
LocalThreshold *durationpb.Duration `protobuf:"bytes,52,opt,name=local_threshold,json=localThreshold,proto3" json:"local_threshold,omitempty"` // 本地延迟阈值
|
||||
MaxConnIdleTime *durationpb.Duration `protobuf:"bytes,53,opt,name=max_conn_idle_time,json=maxConnIdleTime,proto3" json:"max_conn_idle_time,omitempty"` // 最大连接空闲时间
|
||||
MaxStaleness *durationpb.Duration `protobuf:"bytes,54,opt,name=max_staleness,json=maxStaleness,proto3" json:"max_staleness,omitempty"` // 最大陈旧时间
|
||||
ServerSelectionTimeout *durationpb.Duration `protobuf:"bytes,55,opt,name=server_selection_timeout,json=serverSelectionTimeout,proto3" json:"server_selection_timeout,omitempty"` // 服务器选择超时时间
|
||||
SocketTimeout *durationpb.Duration `protobuf:"bytes,56,opt,name=socket_timeout,json=socketTimeout,proto3" json:"socket_timeout,omitempty"` // 套接字超时时间
|
||||
Timeout *durationpb.Duration `protobuf:"bytes,57,opt,name=timeout,proto3" json:"timeout,omitempty"` // 总超时时间
|
||||
EnableTracing bool `protobuf:"varint,100,opt,name=enable_tracing,json=enableTracing,proto3" json:"enable_tracing,omitempty"` // 打开链路追踪
|
||||
EnableMetrics bool `protobuf:"varint,101,opt,name=enable_metrics,json=enableMetrics,proto3" json:"enable_metrics,omitempty"` // 打开性能度量
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -489,6 +490,13 @@ func (x *Data_MongoDB) GetUri() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Data_MongoDB) GetDatabase() string {
|
||||
if x != nil && x.Database != nil {
|
||||
return *x.Database
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Data_MongoDB) GetUsername() string {
|
||||
if x != nil && x.Username != nil {
|
||||
return *x.Username
|
||||
@@ -1835,7 +1843,7 @@ var File_conf_v1_kratos_conf_data_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_conf_v1_kratos_conf_data_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1econf/v1/kratos_conf_data.proto\x12\x04conf\x1a\x1egoogle/protobuf/duration.proto\x1a\x1dconf/v1/kratos_conf_tls.proto\"\x995\n" +
|
||||
"\x1econf/v1/kratos_conf_data.proto\x12\x04conf\x1a\x1egoogle/protobuf/duration.proto\x1a\x1dconf/v1/kratos_conf_tls.proto\"\xc75\n" +
|
||||
"\x04Data\x124\n" +
|
||||
"\bdatabase\x18\x01 \x01(\v2\x13.conf.Data.DatabaseH\x00R\bdatabase\x88\x01\x01\x12+\n" +
|
||||
"\x05redis\x18\n" +
|
||||
@@ -1886,14 +1894,16 @@ const file_conf_v1_kratos_conf_data_proto_rawDesc = "" +
|
||||
"\fread_timeout\x183 \x01(\v2\x19.google.protobuf.DurationR\vreadTimeout\x12>\n" +
|
||||
"\rwrite_timeout\x184 \x01(\v2\x19.google.protobuf.DurationR\fwriteTimeout\x12%\n" +
|
||||
"\x0eenable_tracing\x18d \x01(\bR\renableTracing\x12&\n" +
|
||||
"\x0eenable_metrics\x18\xe9\a \x01(\bR\renableMetrics\x1a\x99\b\n" +
|
||||
"\x0eenable_metrics\x18\xe9\a \x01(\bR\renableMetrics\x1a\xc7\b\n" +
|
||||
"\aMongoDB\x12\x10\n" +
|
||||
"\x03uri\x18\x01 \x01(\tR\x03uri\x12\x1f\n" +
|
||||
"\busername\x18\x02 \x01(\tH\x00R\busername\x88\x01\x01\x12\x1f\n" +
|
||||
"\bpassword\x18\x03 \x01(\tH\x01R\bpassword\x88\x01\x01\x12*\n" +
|
||||
"\x0eauth_mechanism\x18\x04 \x01(\tH\x02R\rauthMechanism\x88\x01\x01\x12k\n" +
|
||||
"\x19auth_mechanism_properties\x18\x05 \x03(\v2/.conf.Data.MongoDB.AuthMechanismPropertiesEntryR\x17authMechanismProperties\x12$\n" +
|
||||
"\vauth_source\x18\x06 \x01(\tH\x03R\n" +
|
||||
"\bdatabase\x18\x02 \x01(\tH\x00R\bdatabase\x88\x01\x01\x12\x1f\n" +
|
||||
"\busername\x18\n" +
|
||||
" \x01(\tH\x01R\busername\x88\x01\x01\x12\x1f\n" +
|
||||
"\bpassword\x18\v \x01(\tH\x02R\bpassword\x88\x01\x01\x12*\n" +
|
||||
"\x0eauth_mechanism\x18\x14 \x01(\tH\x03R\rauthMechanism\x88\x01\x01\x12k\n" +
|
||||
"\x19auth_mechanism_properties\x18\x15 \x03(\v2/.conf.Data.MongoDB.AuthMechanismPropertiesEntryR\x17authMechanismProperties\x12$\n" +
|
||||
"\vauth_source\x18\x16 \x01(\tH\x04R\n" +
|
||||
"authSource\x88\x01\x01\x12B\n" +
|
||||
"\x0fconnect_timeout\x182 \x01(\v2\x19.google.protobuf.DurationR\x0econnectTimeout\x12H\n" +
|
||||
"\x12heartbeat_interval\x183 \x01(\v2\x19.google.protobuf.DurationR\x11heartbeatInterval\x12B\n" +
|
||||
@@ -1908,6 +1918,7 @@ const file_conf_v1_kratos_conf_data_proto_rawDesc = "" +
|
||||
"\x1cAuthMechanismPropertiesEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\v\n" +
|
||||
"\t_databaseB\v\n" +
|
||||
"\t_usernameB\v\n" +
|
||||
"\t_passwordB\x11\n" +
|
||||
"\x0f_auth_mechanismB\x0e\n" +
|
||||
|
||||
@@ -48,12 +48,14 @@ message Data {
|
||||
message MongoDB {
|
||||
string uri = 1;
|
||||
|
||||
optional string username = 2;
|
||||
optional string password = 3;
|
||||
optional string database = 2;
|
||||
|
||||
optional string auth_mechanism = 4; // 认证机制:SCRAM-SHA-1、SCRAM-SHA-256、MONGODB-X509、GSSAPI、PLAIN
|
||||
map<string, string> auth_mechanism_properties = 5; // 认证机制属性
|
||||
optional string auth_source = 6; // 认证源:admin、$external等
|
||||
optional string username = 10;
|
||||
optional string password = 11;
|
||||
|
||||
optional string auth_mechanism = 20; // 认证机制:SCRAM-SHA-1、SCRAM-SHA-256、MONGODB-X509、GSSAPI、PLAIN
|
||||
map<string, string> auth_mechanism_properties = 21; // 认证机制属性
|
||||
optional string auth_source = 22; // 认证源:admin、$external等
|
||||
|
||||
google.protobuf.Duration connect_timeout = 50; // 连接超时时间
|
||||
google.protobuf.Duration heartbeat_interval = 51; // 心跳间隔
|
||||
|
||||
171
database/influxdb/utils.go
Normal file
171
database/influxdb/utils.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package influxdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/InfluxCommunity/influxdb3-go/v2/influxdb3"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func GetPointTag(point *influxdb3.Point, name string) *string {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
tagValue, ok := point.GetTag(name)
|
||||
if !ok || tagValue == "" {
|
||||
return nil
|
||||
}
|
||||
return &tagValue
|
||||
}
|
||||
|
||||
func GetBoolPointTag(point *influxdb3.Point, name string) *bool {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
tagValue, ok := point.GetTag(name)
|
||||
if !ok || tagValue == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := tagValue == "true"
|
||||
return &value
|
||||
}
|
||||
|
||||
func GetUint32PointTag(point *influxdb3.Point, name string) *uint32 {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
tagValue, ok := point.GetTag(name)
|
||||
if !ok || tagValue == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(tagValue, 10, 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
value32 := uint32(value)
|
||||
return &value32
|
||||
}
|
||||
|
||||
func GetUint64PointTag(point *influxdb3.Point, name string) *uint64 {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
tagValue, ok := point.GetTag(name)
|
||||
if !ok || tagValue == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(tagValue, 10, 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &value
|
||||
}
|
||||
|
||||
func GetEnumPointTag[T ~int32](point *influxdb3.Point, name string, valueMap map[string]int32) *T {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
tagValue, ok := point.GetTag(name)
|
||||
if !ok || tagValue == "" {
|
||||
return nil
|
||||
}
|
||||
enumValue, exists := valueMap[tagValue]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
enumType := T(enumValue)
|
||||
return &enumType
|
||||
}
|
||||
|
||||
func GetTimestampField(point *influxdb3.Point, name string) *timestamppb.Timestamp {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := point.GetField(name)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if timestamp, ok := value.(*timestamppb.Timestamp); ok {
|
||||
return timestamp
|
||||
}
|
||||
if timeValue, ok := value.(time.Time); ok {
|
||||
return timestamppb.New(timeValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUint32Field(point *influxdb3.Point, name string) *uint32 {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := point.GetUIntegerField(name)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
uint32Value := uint32(*value)
|
||||
if uint32Value == 0 {
|
||||
return nil
|
||||
}
|
||||
return &uint32Value
|
||||
}
|
||||
|
||||
func BoolToString(value *bool) string {
|
||||
if value == nil {
|
||||
return "false"
|
||||
}
|
||||
if *value {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func Uint64ToString(value *uint64) string {
|
||||
if value == nil {
|
||||
return "0"
|
||||
}
|
||||
return fmt.Sprintf("%d", *value)
|
||||
}
|
||||
|
||||
func BuildQueryWithParams(
|
||||
table string,
|
||||
filters map[string]interface{},
|
||||
operators map[string]string,
|
||||
fields []string,
|
||||
) string {
|
||||
var queryBuilder strings.Builder
|
||||
|
||||
// 构建 SELECT 语句
|
||||
queryBuilder.WriteString("SELECT ")
|
||||
if len(fields) > 0 {
|
||||
queryBuilder.WriteString(strings.Join(fields, ", "))
|
||||
} else {
|
||||
queryBuilder.WriteString("*")
|
||||
}
|
||||
queryBuilder.WriteString(fmt.Sprintf(" FROM %s", table))
|
||||
|
||||
// 构建 WHERE 条件
|
||||
if len(filters) > 0 {
|
||||
var operator string
|
||||
queryBuilder.WriteString(" WHERE ")
|
||||
var conditions []string
|
||||
for key, value := range filters {
|
||||
operator = "=" // 默认操作符
|
||||
if op, exists := operators[key]; exists {
|
||||
operator = op
|
||||
}
|
||||
conditions = append(conditions, fmt.Sprintf("%s %s %v", key, operator, value))
|
||||
}
|
||||
queryBuilder.WriteString(strings.Join(conditions, " AND "))
|
||||
}
|
||||
|
||||
return queryBuilder.String()
|
||||
}
|
||||
76
database/influxdb/utils_test.go
Normal file
76
database/influxdb/utils_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package influxdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildQueryWithParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
table string
|
||||
filters map[string]interface{}
|
||||
operators map[string]string
|
||||
fields []string
|
||||
expectedQuery string
|
||||
}{
|
||||
{
|
||||
name: "Basic query with filters and fields",
|
||||
table: "candles",
|
||||
filters: map[string]interface{}{"s": "'AAPL'", "o": 150.0},
|
||||
operators: map[string]string{"o": ">"},
|
||||
fields: []string{"s", "o", "h", "l", "c", "v"},
|
||||
expectedQuery: "SELECT s, o, h, l, c, v FROM candles WHERE s = 'AAPL' AND o > 150",
|
||||
},
|
||||
{
|
||||
name: "Query with no filters",
|
||||
table: "candles",
|
||||
filters: map[string]interface{}{},
|
||||
operators: map[string]string{},
|
||||
fields: []string{"s", "o", "h"},
|
||||
expectedQuery: "SELECT s, o, h FROM candles",
|
||||
},
|
||||
{
|
||||
name: "Query with no fields",
|
||||
table: "candles",
|
||||
filters: map[string]interface{}{"s": "'AAPL'"},
|
||||
operators: map[string]string{},
|
||||
fields: []string{},
|
||||
expectedQuery: "SELECT * FROM candles WHERE s = 'AAPL'",
|
||||
},
|
||||
{
|
||||
name: "Empty table name",
|
||||
table: "",
|
||||
filters: map[string]interface{}{"s": "'AAPL'"},
|
||||
operators: map[string]string{},
|
||||
fields: []string{"s", "o"},
|
||||
expectedQuery: "SELECT s, o FROM WHERE s = 'AAPL'",
|
||||
},
|
||||
{
|
||||
name: "Special characters in filters",
|
||||
table: "candles",
|
||||
filters: map[string]interface{}{"name": "'O'Reilly'"},
|
||||
operators: map[string]string{},
|
||||
fields: []string{"name"},
|
||||
expectedQuery: "SELECT name FROM candles WHERE name = 'O'Reilly'",
|
||||
},
|
||||
{
|
||||
name: "Query with interval filters",
|
||||
table: "candles",
|
||||
filters: map[string]interface{}{"time": "now() - interval '15 minutes'"},
|
||||
operators: map[string]string{"time": ">="},
|
||||
fields: []string{"*"},
|
||||
expectedQuery: "SELECT * FROM candles WHERE time >= now() - interval '15 minutes'",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
query := BuildQueryWithParams(tt.table, tt.filters, tt.operators, tt.fields)
|
||||
|
||||
if query != tt.expectedQuery {
|
||||
t.Errorf("expected query %s, got %s", tt.expectedQuery, query)
|
||||
}
|
||||
//t.Log(query)
|
||||
})
|
||||
}
|
||||
}
|
||||
51
database/mongodb/README.md
Normal file
51
database/mongodb/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# MongoDB
|
||||
|
||||
## 概念对比
|
||||
|
||||
| MongoDB存储结构 | RDBMS存储结构 |
|
||||
|-------------|-------------|
|
||||
| database | database |
|
||||
| collection | table |
|
||||
| document | row |
|
||||
| field | column |
|
||||
| index | 索引 |
|
||||
| primary key | primary key |
|
||||
|
||||
## Docker部署
|
||||
|
||||
下载镜像:
|
||||
|
||||
```bash
|
||||
docker pull bitnami/mongodb:latest
|
||||
docker pull bitnami/mongodb-exporter:latest
|
||||
```
|
||||
|
||||
带密码安装:
|
||||
|
||||
```bash
|
||||
docker run -itd \
|
||||
--name mongodb-server \
|
||||
-p 27017:27017 \
|
||||
-e MONGODB_ROOT_USER=root \
|
||||
-e MONGODB_ROOT_PASSWORD=123456 \
|
||||
-e MONGODB_USERNAME=test \
|
||||
-e MONGODB_PASSWORD=123456 \
|
||||
-e MONGODB_DATABASE=finances \
|
||||
bitnami/mongodb:latest
|
||||
```
|
||||
|
||||
不带密码安装:
|
||||
|
||||
```bash
|
||||
docker run -itd \
|
||||
--name mongodb-server \
|
||||
-p 27017:27017 \
|
||||
-e ALLOW_EMPTY_PASSWORD=yes \
|
||||
bitnami/mongodb:latest
|
||||
```
|
||||
|
||||
有两点需要注意:
|
||||
|
||||
1. 如果需要映射数据卷,需要把本地路径的所有权改到1001:`sudo chown -R 1001:1001 data/db`,否则会报错:
|
||||
`‘mkdir: cannot create directory ‘/bitnami/mongodb’: Permission denied’`;
|
||||
2. 从MongoDB 5.0开始,有些机器运行会报错:`Illegal instruction`,这是因为机器硬件不支持 **AVX 指令集** 的缘故,没办法,MongoDB降级吧。
|
||||
@@ -2,35 +2,172 @@ package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
mongoV2 "go.mongodb.org/mongo-driver/v2/mongo"
|
||||
optionsV2 "go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
|
||||
conf "github.com/tx7do/kratos-bootstrap/api/gen/go/conf/v1"
|
||||
)
|
||||
|
||||
// NewMongoClient 创建MongoDB客户端
|
||||
func NewMongoClient(ctx context.Context, cfg *conf.Bootstrap, l *log.Helper) *mongo.Client {
|
||||
if cfg.Data == nil || cfg.Data.Mongodb == nil {
|
||||
l.Warn("Mongodb config is nil")
|
||||
return nil
|
||||
}
|
||||
type Client struct {
|
||||
log *log.Helper
|
||||
|
||||
var opts []*options.ClientOptions
|
||||
|
||||
uri := fmt.Sprintf("mongodb://%s:%s@%s",
|
||||
cfg.Data.Mongodb.Username, cfg.Data.Mongodb.Password, cfg.Data.Mongodb.Address,
|
||||
)
|
||||
opts = append(opts, options.Client().ApplyURI(uri))
|
||||
|
||||
cli, err := mongo.Connect(ctx, opts...)
|
||||
if err != nil {
|
||||
l.Fatalf("failed opening connection to mongodb: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return cli
|
||||
cli *mongoV2.Client
|
||||
database string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewClient(logger log.Logger, cfg *conf.Bootstrap) (*Client, error) {
|
||||
c := &Client{
|
||||
log: log.NewHelper(log.With(logger, "module", "mongodb-client")),
|
||||
}
|
||||
|
||||
if err := c.createMongodbClient(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// createMongodbClient 创建MongoDB客户端
|
||||
func (c *Client) createMongodbClient(cfg *conf.Bootstrap) error {
|
||||
if cfg.Data == nil || cfg.Data.Mongodb == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var opts []*optionsV2.ClientOptions
|
||||
|
||||
if cfg.Data.Mongodb.GetUri() != "" {
|
||||
opts = append(opts, optionsV2.Client().ApplyURI(cfg.Data.Mongodb.GetUri()))
|
||||
}
|
||||
if cfg.Data.Mongodb.GetUsername() != "" && cfg.Data.Mongodb.GetPassword() != "" {
|
||||
credential := optionsV2.Credential{
|
||||
Username: cfg.Data.Mongodb.GetUsername(),
|
||||
Password: cfg.Data.Mongodb.GetPassword(),
|
||||
}
|
||||
|
||||
if cfg.Data.Mongodb.GetPassword() != "" {
|
||||
credential.PasswordSet = true
|
||||
}
|
||||
|
||||
opts = append(opts, optionsV2.Client().SetAuth(credential))
|
||||
}
|
||||
if cfg.Data.Mongodb.ConnectTimeout != nil {
|
||||
opts = append(opts, optionsV2.Client().SetConnectTimeout(cfg.Data.Mongodb.GetConnectTimeout().AsDuration()))
|
||||
}
|
||||
if cfg.Data.Mongodb.ServerSelectionTimeout != nil {
|
||||
opts = append(opts, optionsV2.Client().SetServerSelectionTimeout(cfg.Data.Mongodb.GetServerSelectionTimeout().AsDuration()))
|
||||
}
|
||||
if cfg.Data.Mongodb.Timeout != nil {
|
||||
opts = append(opts, optionsV2.Client().SetTimeout(cfg.Data.Mongodb.GetTimeout().AsDuration()))
|
||||
}
|
||||
|
||||
opts = append(opts, optionsV2.Client().SetBSONOptions(&optionsV2.BSONOptions{
|
||||
UseJSONStructTags: true, // 使用JSON结构标签
|
||||
}))
|
||||
|
||||
cli, err := mongoV2.Connect(opts...)
|
||||
if err != nil {
|
||||
c.log.Errorf("failed to create mongodb client: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
c.database = cfg.Data.Mongodb.GetDatabase()
|
||||
if cfg.Data.Mongodb.GetTimeout() != nil {
|
||||
c.timeout = cfg.Data.Mongodb.GetTimeout().AsDuration()
|
||||
} else {
|
||||
c.timeout = 10 * time.Second // 默认超时时间
|
||||
}
|
||||
|
||||
c.cli = cli
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close 关闭MongoDB客户端
|
||||
func (c *Client) Close() {
|
||||
if c.cli == nil {
|
||||
c.log.Warn("mongodb client is already closed or not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.cli.Disconnect(context.Background()); err != nil {
|
||||
c.log.Errorf("failed to disconnect mongodb client: %v", err)
|
||||
} else {
|
||||
c.log.Info("mongodb client disconnected successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// CheckConnect 检查MongoDB连接状态
|
||||
func (c *Client) CheckConnect() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
|
||||
defer cancel()
|
||||
|
||||
if err := c.cli.Ping(ctx, nil); err != nil {
|
||||
c.log.Errorf("failed to ping mongodb: %v", err)
|
||||
} else {
|
||||
c.log.Info("mongodb client is connected")
|
||||
}
|
||||
}
|
||||
|
||||
// InsertOne 插入单个文档
|
||||
func (c *Client) InsertOne(ctx context.Context, collection string, document interface{}) (*mongoV2.InsertOneResult, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
return c.cli.Database(c.database).Collection(collection).InsertOne(ctx, document)
|
||||
}
|
||||
|
||||
// InsertMany 插入多个文档
|
||||
func (c *Client) InsertMany(ctx context.Context, collection string, documents []interface{}) (*mongoV2.InsertManyResult, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
return c.cli.Database(c.database).Collection(collection).InsertMany(ctx, documents)
|
||||
}
|
||||
|
||||
// FindOne 查询单个文档
|
||||
func (c *Client) FindOne(ctx context.Context, collection string, filter interface{}, result interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
return c.cli.Database(c.database).Collection(collection).FindOne(ctx, filter).Decode(result)
|
||||
}
|
||||
|
||||
// Find 查询多个文档
|
||||
func (c *Client) Find(ctx context.Context, collection string, filter interface{}, results interface{}) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
cursor, err := c.cli.Database(c.database).Collection(collection).Find(ctx, filter)
|
||||
if err != nil {
|
||||
c.log.Errorf("failed to find documents in collection %s: %v", collection, err)
|
||||
return err
|
||||
}
|
||||
defer func(cursor *mongoV2.Cursor, ctx context.Context) {
|
||||
if err = cursor.Close(ctx); err != nil {
|
||||
c.log.Errorf("failed to close cursor: %v", err)
|
||||
}
|
||||
}(cursor, ctx)
|
||||
|
||||
return cursor.All(ctx, results)
|
||||
}
|
||||
|
||||
// UpdateOne 更新单个文档
|
||||
func (c *Client) UpdateOne(ctx context.Context, collection string, filter, update interface{}) (*mongoV2.UpdateResult, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
return c.cli.Database(c.database).Collection(collection).UpdateOne(ctx, filter, update)
|
||||
}
|
||||
|
||||
// DeleteOne 删除单个文档
|
||||
func (c *Client) DeleteOne(ctx context.Context, collection string, filter interface{}) (*mongoV2.DeleteResult, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||
defer cancel()
|
||||
|
||||
return c.cli.Database(c.database).Collection(collection).DeleteOne(ctx, filter)
|
||||
}
|
||||
|
||||
66
database/mongodb/client_test.go
Normal file
66
database/mongodb/client_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
conf "github.com/tx7do/kratos-bootstrap/api/gen/go/conf/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type Candle struct {
|
||||
Symbol *string `json:"s"`
|
||||
Open *float64 `json:"o"`
|
||||
High *float64 `json:"h"`
|
||||
Low *float64 `json:"l"`
|
||||
Close *float64 `json:"c"`
|
||||
Volume *float64 `json:"v"`
|
||||
StartTime *timestamppb.Timestamp `json:"st"`
|
||||
EndTime *timestamppb.Timestamp `json:"et"`
|
||||
}
|
||||
|
||||
func createTestClient() *Client {
|
||||
cli, _ := NewClient(
|
||||
log.DefaultLogger,
|
||||
&conf.Bootstrap{
|
||||
Data: &conf.Data{
|
||||
Mongodb: &conf.Data_MongoDB{
|
||||
Uri: "mongodb://root:123456@127.0.0.1:27017/?compressors=snappy,zlib,zstd",
|
||||
Database: trans.Ptr("finances"),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
return cli
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
client := createTestClient()
|
||||
assert.NotNil(t, client)
|
||||
|
||||
client.CheckConnect()
|
||||
}
|
||||
|
||||
func TestInsertOne(t *testing.T) {
|
||||
client := createTestClient()
|
||||
assert.NotNil(t, client)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
candle := Candle{
|
||||
StartTime: timestamppb.New(time.Now()),
|
||||
Symbol: trans.Ptr("AAPL"),
|
||||
Open: trans.Ptr(1.0),
|
||||
High: trans.Ptr(2.0),
|
||||
Low: trans.Ptr(3.0),
|
||||
Close: trans.Ptr(4.0),
|
||||
Volume: trans.Ptr(1000.0),
|
||||
}
|
||||
|
||||
_, err := client.InsertOne(ctx, "candles", candle)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
95
database/mongodb/consts.go
Normal file
95
database/mongodb/consts.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package mongodb
|
||||
|
||||
const (
|
||||
// 比较操作符
|
||||
|
||||
OperatorEq = "$eq" // 等于
|
||||
OperatorNe = "$ne" // 不等于
|
||||
OperatorGt = "$gt" // 大于
|
||||
OperatorGte = "$gte" // 大于等于
|
||||
OperatorLt = "$lt" // 小于
|
||||
OperatorLte = "$lte" // 小于等于
|
||||
|
||||
// 逻辑操作符
|
||||
|
||||
OperatorAnd = "$and" // 与
|
||||
OperatorOr = "$or" // 或
|
||||
OperatorNot = "$not" // 非
|
||||
OperatorNor = "$nor" // 非或
|
||||
|
||||
// 元素操作符
|
||||
|
||||
OperatorExists = "$exists" // 是否存在
|
||||
OperatorType = "$type" // 类型
|
||||
|
||||
// 评估操作符
|
||||
|
||||
OperatorExpr = "$expr" // 表达式
|
||||
OperatorJsonSchema = "$jsonSchema" // JSON Schema 验证
|
||||
OperatorMod = "$mod" // 取模
|
||||
OperatorRegex = "$regex" // 正则表达式
|
||||
OperatorText = "$text" // 文本搜索
|
||||
OperatorWhere = "$where" // JavaScript 表达式
|
||||
OperatorSearch = "$search" // 文本搜索
|
||||
|
||||
// 数组操作符
|
||||
|
||||
OperatorAll = "$all" // 匹配所有
|
||||
OperatorElemMatch = "$elemMatch" // 匹配数组中的元素
|
||||
OperatorSize = "$size" // 数组大小
|
||||
|
||||
// 集合操作符
|
||||
|
||||
OperatorIn = "$in" // 包含
|
||||
OperatorNin = "$nin" // 不包含
|
||||
|
||||
// 更新操作符
|
||||
|
||||
OperatorSet = "$set" // 设置字段值
|
||||
OperatorUnset = "$unset" // 删除字段
|
||||
OperatorInc = "$inc" // 增加值
|
||||
OperatorMul = "$mul" // 乘法
|
||||
OperatorRename = "$rename" // 重命名字段
|
||||
OperatorMin = "$min" // 设置最小值
|
||||
OperatorMax = "$max" // 设置最大值
|
||||
OperatorCurrentDate = "$currentDate" // 设置当前日期
|
||||
OperatorAddToSet = "$addToSet" // 添加到集合
|
||||
OperatorPop = "$pop" // 删除数组中的元素
|
||||
OperatorPull = "$pull" // 删除匹配的数组元素
|
||||
OperatorPush = "$push" // 添加数组元素
|
||||
OperatorEach = "$each" // 批量添加数组元素
|
||||
OperatorSlice = "$slice" // 截取数组
|
||||
OperatorSort = "$sort" // 排序数组
|
||||
OperatorPosition = "$position" // 指定数组位置
|
||||
|
||||
// 聚合操作符
|
||||
|
||||
OperatorGroup = "$group" // 分组
|
||||
OperatorMatch = "$match" // 匹配
|
||||
OperatorProject = "$project" // 投影
|
||||
OperatorSortAgg = "$sort" // 排序
|
||||
OperatorLimit = "$limit" // 限制
|
||||
OperatorSkip = "$skip" // 跳过
|
||||
OperatorUnwind = "$unwind" // 拆分数组
|
||||
OperatorLookup = "$lookup" // 关联查询
|
||||
OperatorAddFields = "$addFields" // 添加字段
|
||||
OperatorReplaceRoot = "$replaceRoot" // 替换根字段
|
||||
OperatorCount = "$count" // 计数
|
||||
OperatorFacet = "$facet" // 多面查询
|
||||
OperatorBucket = "$bucket" // 分桶
|
||||
OperatorBucketAuto = "$bucketAuto" // 自动分桶
|
||||
OperatorIndexStats = "$indexStats" // 索引统计
|
||||
OperatorOut = "$out" // 输出
|
||||
OperatorMerge = "$merge" // 合并
|
||||
|
||||
// 地理空间操作符
|
||||
|
||||
OperatorNear = "$near" // 查询距离某点最近的文档
|
||||
OperatorNearSphere = "$nearSphere" // 查询距离某点最近的文档(球面距离)
|
||||
OperatorGeoWithin = "$geoWithin" // 地理范围查询
|
||||
OperatorGeoIntersects = "$geoIntersects" // 地理相交查询
|
||||
|
||||
OperatorGeometry = "$geometry" // 几何图形
|
||||
OperatorMaxDistance = "$maxDistance" // 最大距离
|
||||
OperatorMinDistance = "$minDistance" // 最小距离
|
||||
)
|
||||
1
database/mongodb/errors.go
Normal file
1
database/mongodb/errors.go
Normal file
@@ -0,0 +1 @@
|
||||
package mongodb
|
||||
@@ -8,20 +8,26 @@ replace github.com/tx7do/kratos-bootstrap/api => ../../api
|
||||
|
||||
require (
|
||||
github.com/go-kratos/kratos/v2 v2.8.4
|
||||
github.com/tx7do/kratos-bootstrap/api v0.0.21
|
||||
go.mongodb.org/mongo-driver v1.17.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tx7do/go-utils v1.1.29
|
||||
github.com/tx7do/kratos-bootstrap/api v0.0.26
|
||||
go.mongodb.org/mongo-driver/v2 v2.2.2
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
|
||||
@@ -6,10 +7,22 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tx7do/go-utils v1.1.29 h1:kO1JDMVX++ZY4+aXGk3pOtDz5WBPDA3LxhIWkzXkvH8=
|
||||
github.com/tx7do/go-utils v1.1.29/go.mod h1:bmt7c85QmHURtd7h6QOu7k0QKOJTwjJ+cFP29nljdSw=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
@@ -19,20 +32,20 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.2.2 h1:9cYuS3fl1Xhqwpfazso10V7BHQD58kCgtzhfAmJYz9c=
|
||||
go.mongodb.org/mongo-driver/v2 v2.2.2/go.mod h1:qQkDMhCGWl3FN509DfdPd4GRBLU/41zqF/k8eTRceps=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -44,11 +57,16 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
217
database/mongodb/query.go
Normal file
217
database/mongodb/query.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
bsonV2 "go.mongodb.org/mongo-driver/v2/bson"
|
||||
optionsV2 "go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
type QueryBuilder struct {
|
||||
filter bsonV2.M
|
||||
opts *optionsV2.FindOptions
|
||||
}
|
||||
|
||||
func NewQuery() *QueryBuilder {
|
||||
return &QueryBuilder{
|
||||
filter: bsonV2.M{},
|
||||
opts: &optionsV2.FindOptions{},
|
||||
}
|
||||
}
|
||||
|
||||
// SetFilter 设置查询过滤条件
|
||||
func (qb *QueryBuilder) SetFilter(filter bsonV2.M) *QueryBuilder {
|
||||
qb.filter = filter
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetOr 设置多个条件的逻辑或
|
||||
func (qb *QueryBuilder) SetOr(conditions []bsonV2.M) *QueryBuilder {
|
||||
qb.filter[OperatorOr] = conditions
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetAnd 设置多个条件的逻辑与
|
||||
func (qb *QueryBuilder) SetAnd(conditions []bsonV2.M) *QueryBuilder {
|
||||
qb.filter[OperatorAnd] = conditions
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetNotEqual 设置字段的不等于条件
|
||||
func (qb *QueryBuilder) SetNotEqual(field string, value interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorNe: value}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetGreaterThan 设置字段的大于条件
|
||||
func (qb *QueryBuilder) SetGreaterThan(field string, value interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorGt: value}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetLessThan 设置字段的小于条件
|
||||
func (qb *QueryBuilder) SetLessThan(field string, value interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorLt: value}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetExists 设置字段是否存在条件
|
||||
func (qb *QueryBuilder) SetExists(field string, exists bool) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorExists: exists}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetType 设置字段的类型条件
|
||||
func (qb *QueryBuilder) SetType(field string, typeValue interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorType: typeValue}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetBetween 设置字段的范围查询条件
|
||||
func (qb *QueryBuilder) SetBetween(field string, start, end interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{
|
||||
OperatorGte: start,
|
||||
OperatorLte: end,
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetIn 设置字段的包含条件
|
||||
func (qb *QueryBuilder) SetIn(field string, values []interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorIn: values}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetNotIn 设置字段的排除条件
|
||||
func (qb *QueryBuilder) SetNotIn(field string, values []interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorNin: values}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetElemMatch 设置数组字段的匹配条件
|
||||
func (qb *QueryBuilder) SetElemMatch(field string, match bsonV2.M) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorElemMatch: match}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetAll 设置字段必须包含所有指定值的条件
|
||||
func (qb *QueryBuilder) SetAll(field string, values []interface{}) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorAll: values}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetSize 设置数组字段的大小条件
|
||||
func (qb *QueryBuilder) SetSize(field string, size int) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorSize: size}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetCurrentDate 设置字段为当前日期
|
||||
func (qb *QueryBuilder) SetCurrentDate(field string) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorCurrentDate: true}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetTextSearch 设置文本搜索条件
|
||||
func (qb *QueryBuilder) SetTextSearch(search string) *QueryBuilder {
|
||||
qb.filter[OperatorText] = bsonV2.M{OperatorSearch: search}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetMod 设置字段的模运算条件
|
||||
func (qb *QueryBuilder) SetMod(field string, divisor, remainder int) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorMod: []int{divisor, remainder}}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetRegex 设置正则表达式查询条件
|
||||
func (qb *QueryBuilder) SetRegex(field string, pattern string) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorRegex: pattern}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetGeoWithin 设置地理位置范围查询条件
|
||||
func (qb *QueryBuilder) SetGeoWithin(field string, geometry bsonV2.M) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorGeoWithin: geometry}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetGeoIntersects 设置地理位置相交查询条件
|
||||
func (qb *QueryBuilder) SetGeoIntersects(field string, geometry bsonV2.M) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{OperatorGeoIntersects: geometry}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetNear 设置地理位置附近查询条件
|
||||
func (qb *QueryBuilder) SetNear(field string, point bsonV2.M, maxDistance, minDistance float64) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{
|
||||
OperatorNear: bsonV2.M{
|
||||
OperatorGeometry: point,
|
||||
OperatorMaxDistance: maxDistance,
|
||||
OperatorMinDistance: minDistance,
|
||||
},
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetNearSphere 设置球面距离附近查询条件
|
||||
func (qb *QueryBuilder) SetNearSphere(field string, point bsonV2.M, maxDistance, minDistance float64) *QueryBuilder {
|
||||
qb.filter[field] = bsonV2.M{
|
||||
OperatorNearSphere: bsonV2.M{
|
||||
OperatorGeometry: point,
|
||||
OperatorMaxDistance: maxDistance,
|
||||
OperatorMinDistance: minDistance,
|
||||
},
|
||||
}
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetLimit 设置查询结果的限制数量
|
||||
func (qb *QueryBuilder) SetLimit(limit int64) *QueryBuilder {
|
||||
if qb.opts == nil {
|
||||
qb.opts = &optionsV2.FindOptions{}
|
||||
}
|
||||
qb.opts.Limit = &limit
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetSort 设置查询结果的排序条件
|
||||
func (qb *QueryBuilder) SetSort(sort bsonV2.D) *QueryBuilder {
|
||||
if qb.opts == nil {
|
||||
qb.opts = &optionsV2.FindOptions{}
|
||||
}
|
||||
qb.opts.Sort = sort
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetSortWithPriority 设置查询结果的排序条件,并指定优先级
|
||||
func (qb *QueryBuilder) SetSortWithPriority(sortFields []bsonV2.E) *QueryBuilder {
|
||||
if qb.opts == nil {
|
||||
qb.opts = &optionsV2.FindOptions{}
|
||||
}
|
||||
qb.opts.Sort = bsonV2.D(sortFields)
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetProjection 设置查询结果的字段投影
|
||||
func (qb *QueryBuilder) SetProjection(projection bsonV2.M) *QueryBuilder {
|
||||
qb.opts.Projection = projection
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetSkip 设置查询结果的跳过数量
|
||||
func (qb *QueryBuilder) SetSkip(skip int64) *QueryBuilder {
|
||||
qb.opts.Skip = &skip
|
||||
return qb
|
||||
}
|
||||
|
||||
// SetPage 设置分页功能,page 从 1 开始,size 为每页的文档数量
|
||||
func (qb *QueryBuilder) SetPage(page, size int64) *QueryBuilder {
|
||||
offset := (page - 1) * size
|
||||
qb.opts.Skip = &offset
|
||||
qb.opts.Limit = &size
|
||||
return qb
|
||||
}
|
||||
|
||||
// Build 返回最终的过滤条件和查询选项
|
||||
func (qb *QueryBuilder) Build() (bsonV2.M, *optionsV2.FindOptions) {
|
||||
return qb.filter, qb.opts
|
||||
}
|
||||
219
database/mongodb/query_test.go
Normal file
219
database/mongodb/query_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
bsonV2 "go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
func TestQueryBuilder(t *testing.T) {
|
||||
// 创建 QueryBuilder 实例
|
||||
qb := NewQuery()
|
||||
|
||||
// 测试 SetFilter
|
||||
filter := bsonV2.M{"name": "test"}
|
||||
qb.SetFilter(filter)
|
||||
assert.Equal(t, filter, qb.filter)
|
||||
|
||||
// 测试 SetLimit
|
||||
limit := int64(10)
|
||||
qb.SetLimit(limit)
|
||||
assert.NotNil(t, qb.opts.Limit)
|
||||
assert.Equal(t, &limit, qb.opts.Limit)
|
||||
|
||||
// 测试 SetSort
|
||||
sort := bsonV2.D{{Key: "name", Value: 1}}
|
||||
qb.SetSort(sort)
|
||||
assert.NotNil(t, qb.opts.Sort)
|
||||
assert.Equal(t, sort, qb.opts.Sort)
|
||||
|
||||
// 测试 Build
|
||||
finalFilter, finalOpts := qb.Build()
|
||||
assert.Equal(t, filter, finalFilter)
|
||||
assert.Equal(t, qb.opts, finalOpts)
|
||||
}
|
||||
|
||||
func TestQueryBuilderMethods(t *testing.T) {
|
||||
qb := NewQuery()
|
||||
|
||||
// 测试 SetFilter
|
||||
filter := bsonV2.M{"name": "test"}
|
||||
qb.SetFilter(filter)
|
||||
assert.Equal(t, filter, qb.filter)
|
||||
|
||||
// 测试 SetNotEqual
|
||||
qb.SetNotEqual("status", "inactive")
|
||||
assert.Equal(t, bsonV2.M{OperatorNe: "inactive"}, qb.filter["status"])
|
||||
|
||||
// 测试 SetGreaterThan
|
||||
qb.SetGreaterThan("age", 18)
|
||||
assert.Equal(t, bsonV2.M{OperatorGt: 18}, qb.filter["age"])
|
||||
|
||||
// 测试 SetLessThan
|
||||
qb.SetLessThan("age", 30)
|
||||
assert.Equal(t, bsonV2.M{OperatorLt: 30}, qb.filter["age"])
|
||||
|
||||
// 测试 SetExists
|
||||
qb.SetExists("email", true)
|
||||
assert.Equal(t, bsonV2.M{OperatorExists: true}, qb.filter["email"])
|
||||
|
||||
// 测试 SetType
|
||||
qb.SetType("age", "int")
|
||||
assert.Equal(t, bsonV2.M{OperatorType: "int"}, qb.filter["age"])
|
||||
|
||||
// 测试 SetBetween
|
||||
qb.SetBetween("price", 10, 100)
|
||||
assert.Equal(t, bsonV2.M{OperatorGte: 10, OperatorLte: 100}, qb.filter["price"])
|
||||
|
||||
// 测试 SetOr
|
||||
orConditions := []bsonV2.M{
|
||||
{"status": "active"},
|
||||
{"status": "pending"},
|
||||
}
|
||||
qb.SetOr(orConditions)
|
||||
assert.Equal(t, orConditions, qb.filter[OperatorOr])
|
||||
|
||||
// 测试 SetAnd
|
||||
andConditions := []bsonV2.M{
|
||||
{"age": bsonV2.M{OperatorGt: 18}},
|
||||
{"status": "active"},
|
||||
}
|
||||
qb.SetAnd(andConditions)
|
||||
assert.Equal(t, andConditions, qb.filter[OperatorAnd])
|
||||
|
||||
// 测试 SetLimit
|
||||
limit := int64(10)
|
||||
qb.SetLimit(limit)
|
||||
assert.NotNil(t, qb.opts.Limit)
|
||||
assert.Equal(t, &limit, qb.opts.Limit)
|
||||
|
||||
// 测试 SetSort
|
||||
sort := bsonV2.D{{Key: "name", Value: 1}}
|
||||
qb.SetSort(sort)
|
||||
assert.NotNil(t, qb.opts.Sort)
|
||||
assert.Equal(t, sort, qb.opts.Sort)
|
||||
|
||||
// 测试 SetSortWithPriority
|
||||
sortWithPriority := []bsonV2.E{{Key: "priority", Value: -1}, {Key: "name", Value: 1}}
|
||||
qb.SetSortWithPriority(sortWithPriority)
|
||||
assert.Equal(t, bsonV2.D(sortWithPriority), qb.opts.Sort)
|
||||
|
||||
// 测试 SetProjection
|
||||
projection := bsonV2.M{"name": 1, "age": 1}
|
||||
qb.SetProjection(projection)
|
||||
assert.Equal(t, projection, qb.opts.Projection)
|
||||
|
||||
// 测试 SetSkip
|
||||
skip := int64(5)
|
||||
qb.SetSkip(skip)
|
||||
assert.NotNil(t, qb.opts.Skip)
|
||||
assert.Equal(t, &skip, qb.opts.Skip)
|
||||
|
||||
// 测试 SetPage
|
||||
page, size := int64(2), int64(10)
|
||||
qb.SetPage(page, size)
|
||||
assert.Equal(t, &size, qb.opts.Limit)
|
||||
assert.Equal(t, int64(10), *qb.opts.Limit)
|
||||
assert.Equal(t, int64(10), *qb.opts.Skip)
|
||||
|
||||
// 测试 SetRegex
|
||||
qb.SetRegex("name", "^test")
|
||||
assert.Equal(t, bsonV2.M{OperatorRegex: "^test"}, qb.filter["name"])
|
||||
|
||||
// 测试 SetIn
|
||||
qb.SetIn("tags", []interface{}{"tag1", "tag2"})
|
||||
assert.Equal(t, bsonV2.M{OperatorIn: []interface{}{"tag1", "tag2"}}, qb.filter["tags"])
|
||||
|
||||
// 测试 Build
|
||||
finalFilter, finalOpts := qb.Build()
|
||||
assert.Equal(t, qb.filter, finalFilter)
|
||||
assert.Equal(t, qb.opts, finalOpts)
|
||||
}
|
||||
|
||||
func TestSetGeoWithin(t *testing.T) {
|
||||
qb := NewQuery()
|
||||
|
||||
field := "location"
|
||||
geometry := bsonV2.M{"type": "Polygon", "coordinates": []interface{}{
|
||||
[]interface{}{
|
||||
[]float64{40.0, -70.0},
|
||||
[]float64{41.0, -70.0},
|
||||
[]float64{41.0, -71.0},
|
||||
[]float64{40.0, -71.0},
|
||||
[]float64{40.0, -70.0},
|
||||
},
|
||||
}}
|
||||
|
||||
qb.SetGeoWithin(field, geometry)
|
||||
|
||||
expected := bsonV2.M{
|
||||
OperatorGeoWithin: bsonV2.M{
|
||||
OperatorGeometry: geometry,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, qb.filter[field])
|
||||
}
|
||||
|
||||
func TestSetGeoIntersects(t *testing.T) {
|
||||
qb := NewQuery()
|
||||
|
||||
field := "location"
|
||||
geometry := bsonV2.M{"type": "LineString", "coordinates": [][]float64{
|
||||
{40.0, -70.0},
|
||||
{41.0, -71.0},
|
||||
}}
|
||||
|
||||
qb.SetGeoIntersects(field, geometry)
|
||||
|
||||
expected := bsonV2.M{
|
||||
OperatorGeoIntersects: bsonV2.M{
|
||||
OperatorGeometry: geometry,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, qb.filter[field])
|
||||
}
|
||||
|
||||
func TestSetNear(t *testing.T) {
|
||||
qb := NewQuery()
|
||||
|
||||
field := "location"
|
||||
point := bsonV2.M{"type": "Point", "coordinates": []float64{40.7128, -74.0060}}
|
||||
maxDistance := 500.0
|
||||
minDistance := 50.0
|
||||
|
||||
qb.SetNear(field, point, maxDistance, minDistance)
|
||||
|
||||
expected := bsonV2.M{
|
||||
OperatorNear: bsonV2.M{
|
||||
OperatorGeometry: point,
|
||||
OperatorMaxDistance: maxDistance,
|
||||
OperatorMinDistance: minDistance,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, qb.filter[field])
|
||||
}
|
||||
|
||||
func TestSetNearSphere(t *testing.T) {
|
||||
qb := NewQuery()
|
||||
|
||||
field := "location"
|
||||
point := bsonV2.M{"type": "Point", "coordinates": []float64{40.7128, -74.0060}}
|
||||
maxDistance := 1000.0
|
||||
minDistance := 100.0
|
||||
|
||||
qb.SetNearSphere(field, point, maxDistance, minDistance)
|
||||
|
||||
expected := bsonV2.M{
|
||||
OperatorNearSphere: bsonV2.M{
|
||||
OperatorGeometry: point,
|
||||
OperatorMaxDistance: maxDistance,
|
||||
OperatorMinDistance: minDistance,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, qb.filter[field])
|
||||
}
|
||||
4
tag.bat
4
tag.bat
@@ -1,4 +1,4 @@
|
||||
git tag api/v0.0.25 --force
|
||||
git tag api/v0.0.26 --force
|
||||
|
||||
git tag utils/v0.1.4 --force
|
||||
|
||||
@@ -10,7 +10,7 @@ git tag tracer/v0.0.10 --force
|
||||
|
||||
git tag database/ent/v0.0.10 --force
|
||||
git tag database/gorm/v0.0.10 --force
|
||||
git tag database/mongodb/v0.0.10 --force
|
||||
git tag database/mongodb/v0.0.11 --force
|
||||
git tag database/influxdb/v0.0.11 --force
|
||||
git tag database/cassandra/v0.0.10 --force
|
||||
git tag database/clickhouse/v0.0.10 --force
|
||||
|
||||
Reference in New Issue
Block a user