Skip to content

Commit

Permalink
Add ORDER BY for show clients (#762)
Browse files Browse the repository at this point in the history
* Add ORDER BY for `show clients`

* Update interactor_test.go: nitpick

* drop fmt
  • Loading branch information
visill authored Sep 10, 2024
1 parent 61a4619 commit ee97a26
Show file tree
Hide file tree
Showing 8 changed files with 540 additions and 335 deletions.
8 changes: 8 additions & 0 deletions docs/sharding/console/sql_commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,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
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
85 changes: 84 additions & 1 deletion pkg/clientinteractor/interactor_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package clientinteractor_test

import (
"context"
"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 +182,79 @@ 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)
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

0 comments on commit ee97a26

Please sign in to comment.