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 for show clients #762

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading