Skip to content

Commit

Permalink
feature: 分库分表: 哈希分库分表范围查询支持 (#181)
Browse files Browse the repository at this point in the history

Co-authored-by: stonehuang <stonehuang@canway.net>
  • Loading branch information
Stone-afk and stonehuang authored Apr 14, 2023
1 parent 3fae3b6 commit 96d0b3c
Show file tree
Hide file tree
Showing 10 changed files with 1,856 additions and 219 deletions.
6 changes: 5 additions & 1 deletion .CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
- [eorm: 分库分表: Merger抽象与批量查询实现](https://github.com/ecodeclub/eorm/pull/160)
- [eorm: 增强的 ShardingAlgorithm 设计与实现](https://github.com/ecodeclub/eorm/pull/161)
- [eorm: 分库分表: Merger排序实现](https://github.com/ecodeclub/eorm/pull/166)
- [eorm: Datasource 抽象](https://github.com/ecodeclub/eorm/pull/167)
- [eorm: 分库分表: hash、shadow_hash算法不符合预期](https://github.com/ecodeclub/eorm/pull/174)
- [eorm: 分库分表: Merger分页实现](https://github.com/ecodeclub/eorm/pull/175)
- [eorm: BasicTypeValue重命名](https://github.com/ecodeclub/eorm/pull/177)
- [eorm: Datasource 抽象](https://github.com/ecodeclub/eorm/pull/167)
- [eorm: 分库分表: hash、shadow_hash算法不符合预期](https://github.com/ecodeclub/eorm/pull/174)
- [eorm: 分库分表: Merger分页实现](https://github.com/ecodeclub/eorm/pull/175)
- [eorm: BasicTypeValue重命名](https://github.com/ecodeclub/eorm/pull/177)
- [eorm: 分库分表: 范围查询支持](https://github.com/ecodeclub/eorm/pull/178)
- [eorm: 修复单条查询时连接泄露问题](https://github.com/ecodeclub/eorm/pull/188)

## v0.0.1:
Expand Down
2 changes: 1 addition & 1 deletion builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (b *builder) buildBinaryExpr(e binaryExpr) error {
if err != nil {
return err
}
b.writeString(e.op.text)
b.writeString(e.op.Text)
return b.buildSubExpr(e.right)
}

Expand Down
4 changes: 3 additions & 1 deletion expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package eorm

import operator "github.com/ecodeclub/eorm/internal/operator"

// Expr is the top interface. It represents everything.
type Expr interface {
expr() (string, error)
Expand Down Expand Up @@ -49,7 +51,7 @@ func (RawExpr) selected() {}

type binaryExpr struct {
left Expr
op op
op operator.Op
right Expr
}

Expand Down
150 changes: 146 additions & 4 deletions internal/integration/sharding_select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import (
"testing"
"time"

"github.com/ecodeclub/eorm"
"github.com/ecodeclub/eorm/internal/datasource"
"github.com/ecodeclub/eorm/internal/datasource/masterslave"

"github.com/ecodeclub/eorm"
"github.com/ecodeclub/eorm/internal/model"
operator "github.com/ecodeclub/eorm/internal/operator"
"github.com/ecodeclub/eorm/internal/sharding"
"github.com/ecodeclub/eorm/internal/sharding/hash"
"github.com/ecodeclub/eorm/internal/test"
Expand All @@ -45,7 +45,7 @@ func (s *ShardingSelectTestSuite) SetupSuite() {
s.ShardingSuite.SetupSuite()
for _, item := range s.data {
shardingRes, err := s.algorithm.Sharding(
context.Background(), sharding.Request{SkValues: map[string]any{"OrderId": item.OrderId}})
context.Background(), sharding.Request{Op: operator.OpEQ, SkValues: map[string]any{"OrderId": item.OrderId}})
require.NoError(t, err)
require.NotNil(t, shardingRes.Dsts)
for _, dst := range shardingRes.Dsts {
Expand Down Expand Up @@ -184,6 +184,146 @@ func (s *ShardingSelectTestSuite) TestSardingSelectorGetMulti() {
{OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"},
},
},
{
name: "where gt",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").GT(1))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
{OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"},
{OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"},
{OrderId: 181, ItemId: 11, UsingCol1: "Kawhi", UsingCol2: "Leonard"},
},
},
{
name: "where lt",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").LT(150))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
},
},
{
name: "where gt eq",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").GTEQ(123))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
{OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"},
{OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"},
{OrderId: 181, ItemId: 11, UsingCol1: "Kawhi", UsingCol2: "Leonard"},
},
},
{
name: "where lt eq",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").LTEQ(123))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
},
},
{
name: "where in",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").In(8, 11, 123, 234))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
{OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"},
},
},
{
name: "where eq or gt",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").EQ(8).
Or(eorm.C("OrderId").GT(240)))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"},
},
},
{
name: "where in or eq",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").In(8, 11, 123).
Or(eorm.C("OrderId").EQ(181)))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
{OrderId: 181, ItemId: 11, UsingCol1: "Kawhi", UsingCol2: "Leonard"},
},
},
{
name: "where in or gt",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").In(8, 11).
Or(eorm.C("OrderId").GT(200)))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 8, ItemId: 6, UsingCol1: "Kobe", UsingCol2: "Bryant"},
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"},
{OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"},
},
},
{
name: "where between",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").GTEQ(123).And(eorm.C("OrderId").LTEQ(253)))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 123, ItemId: 10, UsingCol1: "LeBron", UsingCol2: "James"},
{OrderId: 234, ItemId: 12, UsingCol1: "Kevin", UsingCol2: "Durant"},
{OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"},
{OrderId: 181, ItemId: 11, UsingCol1: "Kawhi", UsingCol2: "Leonard"},
},
},
{
name: "where eq and lt or gt",
s: func() *eorm.ShardingSelector[test.OrderDetail] {
builder := eorm.NewShardingSelector[test.OrderDetail](s.shardingDB).
Where(eorm.C("OrderId").EQ(11).And(eorm.C("OrderId").LT(123)).
Or(eorm.C("OrderId").GT(234)))
return builder
}(),
wantRes: []*test.OrderDetail{
{OrderId: 11, ItemId: 8, UsingCol1: "James", UsingCol2: "Harden"},
{OrderId: 253, ItemId: 8, UsingCol1: "Stephen", UsingCol2: "Curry"},
},
},
}

for _, tc := range testCases {
Expand All @@ -204,7 +344,7 @@ func (s *ShardingSelectTestSuite) TearDownSuite() {
t := s.T()
for _, item := range s.data {
shardingRes, err := s.algorithm.Sharding(
context.Background(), sharding.Request{SkValues: map[string]any{"OrderId": item.OrderId}})
context.Background(), sharding.Request{Op: operator.OpEQ, SkValues: map[string]any{"OrderId": item.OrderId}})
require.NoError(t, err)
require.NotNil(t, shardingRes.Dsts)
for _, dst := range shardingRes.Dsts {
Expand Down Expand Up @@ -248,6 +388,8 @@ func TestMySQL8ShardingSelect(t *testing.T) {
},
},
data: []*test.OrderDetail{
{8, 6, "Kobe", "Bryant"},
{11, 8, "James", "Harden"},
{123, 10, "LeBron", "James"},
{234, 12, "Kevin", "Durant"},
{253, 8, "Stephen", "Curry"},
Expand Down
43 changes: 43 additions & 0 deletions internal/operator/operator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2021 ecodeclub
//
// 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

type Op struct {
Symbol string
Text string
}

var (
OpLT = Op{Symbol: "<", Text: "<"}
OpLTEQ = Op{Symbol: "<=", Text: "<="}
OpGT = Op{Symbol: ">", Text: ">"}
OpGTEQ = Op{Symbol: ">=", Text: ">="}
OpEQ = Op{Symbol: "=", Text: "="}
OpNEQ = Op{Symbol: "!=", Text: "!="}
OpAdd = Op{Symbol: "+", Text: "+"}
// OpIn = Op{Symbol: "IN", Text: " IN "}
// OpMinus = Op{Symbol:"-", Text: "-"}
OpMulti = Op{Symbol: "*", Text: "*"}
// OpDiv = Op{Symbol:"/", Text: "/"}
OpAnd = Op{Symbol: "AND", Text: " AND "}
OpOr = Op{Symbol: "OR", Text: " OR "}
OpNot = Op{Symbol: "NOT", Text: "NOT "}
OpIn = Op{Symbol: "IN", Text: " IN "}
OpNotIN = Op{Symbol: "NOT IN", Text: " NOT IN "}
OpFalse = Op{Symbol: "FALSE", Text: "FALSE"}
OpLike = Op{Symbol: "LIKE", Text: " LIKE "}
OpNotLike = Op{Symbol: "NOT LIKE", Text: " NOT LIKE "}
OpExist = Op{Symbol: "EXIST", Text: "EXIST "}
)
38 changes: 22 additions & 16 deletions internal/sharding/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ package hash
import (
"context"
"fmt"
"strings"

"github.com/ecodeclub/eorm/internal/errs"
operator "github.com/ecodeclub/eorm/internal/operator"
"github.com/ecodeclub/eorm/internal/sharding"
)

Expand All @@ -34,7 +34,6 @@ type Hash struct {
}

func (h *Hash) Broadcast(ctx context.Context) []sharding.Dst {

if !h.DBPattern.NotSharding && h.TablePattern.NotSharding && h.DsPattern.NotSharding { // 只分库
return h.onlyDBroadcast(ctx)
} else if h.DBPattern.NotSharding && !h.TablePattern.NotSharding && h.DsPattern.NotSharding { // 只分表
Expand Down Expand Up @@ -124,21 +123,28 @@ func (h *Hash) Sharding(ctx context.Context, req sharding.Request) (sharding.Res
if !ok {
return sharding.Result{Dsts: h.Broadcast(ctx)}, nil
}
dbName := h.DBPattern.Name
if !h.DBPattern.NotSharding && strings.Contains(dbName, "%d") {
dbName = fmt.Sprintf(dbName, skVal.(int)%h.DBPattern.Base)
}
tbName := h.TablePattern.Name
if !h.TablePattern.NotSharding && strings.Contains(tbName, "%d") {
tbName = fmt.Sprintf(tbName, skVal.(int)%h.TablePattern.Base)
}
dsName := h.DsPattern.Name
if !h.DsPattern.NotSharding && strings.Contains(dsName, "%d") {
dsName = fmt.Sprintf(dsName, skVal.(int)%h.DsPattern.Base)
switch req.Op {
case operator.OpEQ:
dbName := h.DBPattern.Name
if !h.DBPattern.NotSharding {
dbName = fmt.Sprintf(dbName, skVal.(int)%h.DBPattern.Base)
}
tbName := h.TablePattern.Name
if !h.TablePattern.NotSharding {
tbName = fmt.Sprintf(tbName, skVal.(int)%h.TablePattern.Base)
}
dsName := h.DsPattern.Name
if !h.DsPattern.NotSharding {
dsName = fmt.Sprintf(dsName, skVal.(int)%h.DsPattern.Base)
}
return sharding.Result{
Dsts: []sharding.Dst{{Name: dsName, DB: dbName, Table: tbName}},
}, nil
case operator.OpGT, operator.OpLT, operator.OpGTEQ, operator.OpLTEQ:
return sharding.Result{Dsts: h.Broadcast(ctx)}, nil
default:
return sharding.EmptyResult, errs.NewUnsupportedOperatorError(req.Op.Text)
}
return sharding.Result{
Dsts: []sharding.Dst{{Name: dsName, DB: dbName, Table: tbName}},
}, nil
}

func (h *Hash) ShardingKeys() []string {
Expand Down
2 changes: 2 additions & 0 deletions internal/sharding/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package sharding
import (
"context"

operator "github.com/ecodeclub/eorm/internal/operator"
"github.com/ecodeclub/eorm/internal/query"
)

Expand Down Expand Up @@ -60,5 +61,6 @@ func (r Dst) NotEquals(l Dst) bool {
}

type Request struct {
Op operator.Op
SkValues map[string]any
}
Loading

0 comments on commit 96d0b3c

Please sign in to comment.