Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ORDER BY selecting for clients #753

Closed
wants to merge 19 commits into from
8 changes: 8 additions & 0 deletions docs/sharding/console/sql_commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ spqr-console=> SHOW backend_connections WHERE hostname='hostname:6432'
824682937984 | no data | app-prod-spqr1 | hostname:6432 | test_app_app | testdb | 0 | 7622 | IDLE
```

Exists feature - order by col asc/desc for clients.

```sql
SHOW clients ORDER BY <column> (ASC/DESC)
```

The ORDER BY column feature works with clients, not works with other entities.

### KILL CLIENT

This command is used to terminate a specific client connection in a SPQR Router.
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/pg-sharding/spqr

go 1.21
go 1.23.0

require (
github.com/BurntSushi/toml v1.4.0
github.com/caio/go-tdigest v3.1.0+incompatible
github.com/cucumber/godog v0.14.1
github.com/docker/docker v26.1.5+incompatible
github.com/docker/docker v27.2.0+incompatible
github.com/go-faster/city v1.0.1
github.com/go-ldap/ldap/v3 v3.4.8
github.com/gofrs/uuid v4.4.0+incompatible
Expand Down Expand Up @@ -36,7 +36,7 @@ require (
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/sync v0.8.0
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
google.golang.org/grpc v1.65.0
google.golang.org/grpc v1.66.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
)
Expand Down Expand Up @@ -97,13 +97,13 @@ require (
go.opentelemetry.io/otel/trace v1.22.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
gonum.org/v1/gonum v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
)
22 changes: 10 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ 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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g=
github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
Expand Down Expand Up @@ -164,8 +164,6 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pg-sharding/lyx v0.0.0-20240813162238-718ac25dcade h1:BbQCEohK5H9j1Uv8+f7lQKmMWJJZTbXEF0zn0/xyD5c=
github.com/pg-sharding/lyx v0.0.0-20240813162238-718ac25dcade/go.mod h1:2dPBQAhqv/30mhzj2yBXQkXhsGJQ8GhM+oWOfbGua58=
github.com/pg-sharding/lyx v0.0.0-20240819153240-bbdc782d01c1 h1:AwlQkwnrqRyL8lqZTTAzfQ09niEc+6oFiDvQkMImTPE=
github.com/pg-sharding/lyx v0.0.0-20240819153240-bbdc782d01c1/go.mod h1:2dPBQAhqv/30mhzj2yBXQkXhsGJQ8GhM+oWOfbGua58=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
Expand Down Expand Up @@ -292,8 +290,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -362,12 +360,12 @@ gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
62 changes: 50 additions & 12 deletions pkg/clientinteractor/interactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,16 +720,17 @@ func GetColumnsMap(desc TableDesc) map[string]int {
//
// Returns:
// - error: An error if any occurred during the operation.
func (pi *PSQLInteractor) Clients(ctx context.Context, clients []client.ClientInfo, condition spqrparser.WhereClauseNode) error {
func (pi *PSQLInteractor) Clients(ctx context.Context, clients []client.ClientInfo, query *spqrparser.Show) error {
desc := ClientDesc{}
header := desc.GetHeader()
rowDesc := GetColumnsMap(desc)

condition := query.Where
order := query.Order
if err := pi.WriteHeader(header...); err != nil {
spqrlog.Zero.Error().Err(err).Msg("")
return err
}

var data [][]string
for _, cl := range clients {
if len(cl.Shards()) > 0 {
for _, sh := range cl.Shards() {
Expand All @@ -745,11 +746,7 @@ func (pi *PSQLInteractor) Clients(ctx context.Context, clients []client.ClientIn
if !match {
continue
}

if err := pi.WriteDataRow(row...); err != nil {
spqrlog.Zero.Error().Err(err).Msg("")
return err
}
data = append(data, row)
}
} else {
row := desc.GetRow(cl, "no backend connection", cl.RAddr())
Expand All @@ -762,17 +759,58 @@ func (pi *PSQLInteractor) Clients(ctx context.Context, clients []client.ClientIn
continue
}

if err := pi.WriteDataRow(row...); err != nil {
spqrlog.Zero.Error().Err(err).Msg("")
return err
}
data = append(data, row)
}

}
switch order.(type) {
case spqrparser.Order:
ord := order.(spqrparser.Order)
var asc_desc int

switch ord.OptAscDesc.(type) {
case spqrparser.SortByAsc:
asc_desc = ASC
case spqrparser.SortByDesc:
asc_desc = DESC
case spqrparser.SortByDefault:
asc_desc = ASC
default:
return fmt.Errorf("wrong sorting option (asc/desc)")
}
sortable := SortableWithContext{data, rowDesc[ord.Col.ColName], asc_desc}
sort.Sort(sortable)
}
for i := 0; i < len(data); i++ {
if err := pi.WriteDataRow(data[i]...); err != nil {
spqrlog.Zero.Error().Err(err).Msg("")
return err
}
}
return pi.CompleteMsg(len(clients))
}

const (
ASC = iota
DESC
)

type SortableWithContext struct {
Data [][]string
Col_index int
Order int
}

func (a SortableWithContext) Len() int { return len(a.Data) }
func (a SortableWithContext) Swap(i, j int) { a.Data[i], a.Data[j] = a.Data[j], a.Data[i] }
func (a SortableWithContext) Less(i, j int) bool {
if a.Order == ASC {
return a.Data[i][a.Col_index] < a.Data[j][a.Col_index]
} else {
return a.Data[i][a.Col_index] > a.Data[j][a.Col_index]
}
}

// TODO : unit tests

// Distributions sends distribution data to the PSQL client.
Expand Down
87 changes: 86 additions & 1 deletion pkg/clientinteractor/interactor_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package clientinteractor_test

import (
"context"
"fmt"
"sort"
"testing"

"github.com/golang/mock/gomock"
pkgclient "github.com/pg-sharding/spqr/pkg/client"
mock "github.com/pg-sharding/spqr/pkg/mock/clientinteractor"
"testing"

proto "github.com/pg-sharding/spqr/pkg/protos"
"github.com/pg-sharding/spqr/router/client"
mockcl "github.com/pg-sharding/spqr/router/mock/client"
"github.com/stretchr/testify/assert"

"github.com/pg-sharding/spqr/pkg/clientinteractor"
Expand Down Expand Up @@ -175,3 +183,80 @@ func TestGetColumnsMap(t *testing.T) {
}

}

func TestSortableWithContext(t *testing.T) {
data := [][]string{[]string{"a", "b"}, []string{"b", "a"}}
rev_data := [][]string{[]string{"b", "a"}, []string{"a", "b"}}
sortable := clientinteractor.SortableWithContext{data, 0, clientinteractor.DESC}
sort.Sort(sortable)
assert.Equal(t, data, rev_data)
}

func TestClientsOrderBy(t *testing.T) {

ctrl := gomock.NewController(t)

var v1, v2, v3, v4, v5, v6 proto.UsedShardInfo
var i1, i2, i3, i4, i5, i6 proto.DBInstaceInfo

i1.Hostname = "abracadabra1"
i2.Hostname = "abracadabra2"
i3.Hostname = "abracadabra14"
i4.Hostname = "abracadabra52"
i5.Hostname = "abracadabras"
i6.Hostname = "abracadabrav"

v1.Instance = &i1
v2.Instance = &i2
v3.Instance = &i3
v4.Instance = &i4
v5.Instance = &i5
v6.Instance = &i6

var a, b, c proto.ClientInfo

a.ClientId = 1
a.Dbname = "Barnaul"
a.Dsname = "Rjaken"
a.Shards = []*proto.UsedShardInfo{
&v1, &v2,
}

b.ClientId = 2
b.Dbname = "Moscow"
b.Dsname = "Space"
b.Shards = []*proto.UsedShardInfo{
&v3, &v4,
}

c.ClientId = 2
c.Dbname = "Ekaterinburg"
c.Dsname = "Hill"
c.Shards = []*proto.UsedShardInfo{
&v5, &v6,
}

ca := mockcl.NewMockRouterClient(ctrl)
cb := client.NewNoopClient(&b, "addr")
cc := client.NewNoopClient(&c, "addr")
interactor := clientinteractor.NewPSQLInteractor(ca)
fmt.Println("sadsad")
ci := []pkgclient.ClientInfo{
pkgclient.ClientInfoImpl{Client: ca},
pkgclient.ClientInfoImpl{Client: cb},
pkgclient.ClientInfoImpl{Client: cc},
}

ca.EXPECT().Send(gomock.Any()).AnyTimes()
ca.EXPECT().Shards().AnyTimes()
ca.EXPECT().ID().AnyTimes()
ca.EXPECT().Usr().AnyTimes()
ca.EXPECT().DB().AnyTimes()
err := interactor.Clients(context.TODO(), ci, &spqrparser.Show{
Cmd: spqrparser.ClientsStr,
Where: spqrparser.WhereClauseEmpty{},
Order: spqrparser.Order{OptAscDesc: spqrparser.SortByAsc{},
Col: spqrparser.ColumnRef{ColName: "user"}},
})
assert.Nil(t, err)
}
2 changes: 1 addition & 1 deletion pkg/meta/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ func ProcessShow(ctx context.Context, stmt *spqrparser.Show, mngr EntityMgr, ci
return err
}

return cli.Clients(ctx, resp, stmt.Where)
return cli.Clients(ctx, resp, stmt)
case spqrparser.PoolsStr:
var respPools []pool.Pool
if err := ci.ForEachPool(func(p pool.Pool) error {
Expand Down
19 changes: 19 additions & 0 deletions yacc/console/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ type ColumnRef struct {
TableAlias string
ColName string
}
type OptAscDesc interface{}

type SortByDefault struct {
OptAscDesc
}
type SortByAsc struct {
OptAscDesc
}
type SortByDesc struct {
OptAscDesc
}
type OrderClause interface{}

type Order struct {
OrderClause
OptAscDesc OptAscDesc
Col ColumnRef
}

type WhereClauseNode interface {
}
Expand Down Expand Up @@ -31,6 +49,7 @@ type WhereClauseOp struct {
type Show struct {
Cmd string
Where WhereClauseNode
Order OrderClause
}

type Set struct {
Expand Down
Loading
Loading