feat: OpsxDB 桌面数据库客户端首版入库
含工作台/连库多引擎字段与 SSH·SSL、结果编辑、Redis 键浏览、 Tauri 桌面与 docs/落地页;排除本地构建产物与 sidecar 二进制。
This commit is contained in:
commit
f8740d27f7
240 files changed
+21625
No files matched your search
@@ -0,0 +1,597 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func redisOpts(c ConnReq) (*redis.Options, error) {
|
||||
host := c.Host
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port := c.Port
|
||||
if port == 0 {
|
||||
port = 6379
|
||||
}
|
||||
db := 0
|
||||
if c.Database != "" {
|
||||
if n, err := strconv.Atoi(c.Database); err == nil {
|
||||
db = n
|
||||
}
|
||||
}
|
||||
opts := &redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||
Password: c.Password,
|
||||
Username: c.User,
|
||||
DB: db,
|
||||
DialTimeout: 5 * time.Second,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
if SSLEnabled(c) {
|
||||
tlsCfg, err := tlsConfigFrom(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.TLSConfig = tlsCfg
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func openRedis(c ConnReq) (*redis.Client, error) {
|
||||
opts, err := redisOpts(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return redis.NewClient(opts), nil
|
||||
}
|
||||
|
||||
func testRedis(c ConnReq) error {
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
return cli.Ping(ctx).Err()
|
||||
}
|
||||
|
||||
func listDatabasesRedis(c ConnReq) ([]string, error) {
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
// INFO keyspace 可拿已用 DB;统一暴露 0-15 便于切换
|
||||
_ = ctx
|
||||
out := make([]string, 16)
|
||||
for i := 0; i < 16; i++ {
|
||||
out[i] = strconv.Itoa(i)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func listTablesRedis(c ConnReq) ([]string, error) {
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||||
defer cancel()
|
||||
var keys []string
|
||||
var cursor uint64
|
||||
for {
|
||||
batch, next, err := cli.Scan(ctx, cursor, "*", 200).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = append(keys, batch...)
|
||||
cursor = next
|
||||
if cursor == 0 || len(keys) >= 500 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if keys == nil {
|
||||
keys = []string{}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func execRedis(parent context.Context, c ConnReq) ExecResult {
|
||||
start := time.Now()
|
||||
sql := strings.TrimSpace(c.SQL)
|
||||
if sql == "" {
|
||||
return ExecResult{Error: "empty sql", TimeMs: 0}
|
||||
}
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
defer cli.Close()
|
||||
if parent == nil {
|
||||
parent = context.Background()
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(parent, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fields := splitRedisCmd(sql)
|
||||
if len(fields) == 0 {
|
||||
return ExecResult{Error: "empty command", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
cmd := strings.ToUpper(fields[0])
|
||||
args := fields[1:]
|
||||
|
||||
switch cmd {
|
||||
case "PING":
|
||||
val, err := cli.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: []string{"result"}, Rows: []map[string]interface{}{{"result": val}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()}
|
||||
case "GET":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "GET key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
val, err := cli.Get(ctx, args[0]).Result()
|
||||
if err == redis.Nil {
|
||||
return ExecResult{Columns: []string{"key", "value"}, Rows: []map[string]interface{}{}, RowCount: 0, TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: []string{"key", "value"}, Rows: []map[string]interface{}{{"key": args[0], "value": val}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()}
|
||||
case "SET":
|
||||
if len(args) < 2 {
|
||||
return ExecResult{Error: "SET key value", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
if err := cli.Set(ctx, args[0], strings.Join(args[1:], " "), 0).Err(); err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: []string{"affected_rows"}, Rows: []map[string]interface{}{{"affected_rows": 1}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()}
|
||||
case "DEL":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "DEL key [key...]", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
n, err := cli.Del(ctx, args...).Result()
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: []string{"affected_rows"}, Rows: []map[string]interface{}{{"affected_rows": n}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()}
|
||||
case "KEYS":
|
||||
pat := "*"
|
||||
if len(args) > 0 {
|
||||
pat = args[0]
|
||||
}
|
||||
keys, err := cli.Keys(ctx, pat).Result()
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
rows := make([]map[string]interface{}, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
rows = append(rows, map[string]interface{}{"key": k})
|
||||
}
|
||||
return ExecResult{Columns: []string{"key"}, Rows: rows, RowCount: len(rows), TimeMs: time.Since(start).Milliseconds()}
|
||||
case "SCAN":
|
||||
keys, err := listTablesRedis(c)
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
rows := make([]map[string]interface{}, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
rows = append(rows, map[string]interface{}{"key": k})
|
||||
}
|
||||
return ExecResult{Columns: []string{"key"}, Rows: rows, RowCount: len(rows), TimeMs: time.Since(start).Milliseconds()}
|
||||
case "HGETALL":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "HGETALL key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
c2 := c
|
||||
c2.Key = args[0]
|
||||
c2.Limit = redisValueRowCap
|
||||
v := GetRedisKey(c2)
|
||||
if v.Error != "" && v.Type == "" {
|
||||
return ExecResult{Error: v.Error, TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error}
|
||||
case "LRANGE":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "LRANGE key [start stop]", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
c2 := c
|
||||
c2.Key = args[0]
|
||||
c2.Limit = redisValueRowCap
|
||||
v := GetRedisKey(c2)
|
||||
return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error}
|
||||
case "SMEMBERS":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "SMEMBERS key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
c2 := c
|
||||
c2.Key = args[0]
|
||||
v := GetRedisKey(c2)
|
||||
return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error}
|
||||
case "ZRANGE":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "ZRANGE key [start stop]", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
c2 := c
|
||||
c2.Key = args[0]
|
||||
v := GetRedisKey(c2)
|
||||
return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error}
|
||||
case "TYPE":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "TYPE key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
t, err := cli.Type(ctx, args[0]).Result()
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: []string{"key", "type"}, Rows: []map[string]interface{}{{"key": args[0], "type": t}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()}
|
||||
case "TTL":
|
||||
if len(args) < 1 {
|
||||
return ExecResult{Error: "TTL key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
n, err := cli.TTL(ctx, args[0]).Result()
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{Columns: []string{"key", "ttl_sec"}, Rows: []map[string]interface{}{{"key": args[0], "ttl_sec": int64(n.Seconds())}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()}
|
||||
default:
|
||||
return ExecResult{Error: fmt.Sprintf("暂不支持命令 %s(可用: PING/GET/SET/DEL/KEYS/SCAN/TYPE/TTL/HGETALL/LRANGE/SMEMBERS/ZRANGE)", cmd), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
}
|
||||
|
||||
func splitRedisCmd(s string) []string {
|
||||
var out []string
|
||||
var cur strings.Builder
|
||||
inQ := false
|
||||
esc := false
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case esc:
|
||||
cur.WriteRune(r)
|
||||
esc = false
|
||||
case r == '\\':
|
||||
esc = true
|
||||
case r == '"':
|
||||
inQ = !inQ
|
||||
case !inQ && (r == ' ' || r == '\t' || r == '\n'):
|
||||
if cur.Len() > 0 {
|
||||
out = append(out, cur.String())
|
||||
cur.Reset()
|
||||
}
|
||||
default:
|
||||
cur.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if cur.Len() > 0 {
|
||||
out = append(out, cur.String())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func isRedis(d string) bool {
|
||||
return driverKey(d) == "redis"
|
||||
}
|
||||
|
||||
// IsRedisDriver exported for router guards.
|
||||
func IsRedisDriver(d string) bool {
|
||||
return isRedis(d)
|
||||
}
|
||||
|
||||
func listColumnsRedis(c ConnReq, key string) ([]ColumnInfo, error) {
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
typ, err := cli.Type(ctx, key).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ttl, _ := cli.TTL(ctx, key).Result()
|
||||
return []ColumnInfo{
|
||||
{Name: key, Type: typ, Comment: fmt.Sprintf("ttl=%ds", int64(ttl.Seconds()))},
|
||||
}, nil
|
||||
}
|
||||
|
||||
const redisKeyListCap = 500
|
||||
const redisValueRowCap = 500
|
||||
|
||||
func redisKeyName(c ConnReq) string {
|
||||
if c.Key != "" {
|
||||
return c.Key
|
||||
}
|
||||
return c.Table
|
||||
}
|
||||
|
||||
func redisScanPattern(c ConnReq) string {
|
||||
p := strings.TrimSpace(c.Pattern)
|
||||
if p == "" {
|
||||
return "*"
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func redisValueLimit(c ConnReq) int {
|
||||
n := c.Limit
|
||||
if n <= 0 {
|
||||
n = redisValueRowCap
|
||||
}
|
||||
if n > MaxRowLimit {
|
||||
n = MaxRowLimit
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func ttlSeconds(d time.Duration) int64 {
|
||||
// go-redis maps Redis TTL -1 (no expire) / -2 (missing) to -1ns/-2ns
|
||||
// (some versions use -1s/-2s). Truncating with d/time.Second turns those into 0.
|
||||
switch d {
|
||||
case time.Duration(-1), -1 * time.Second:
|
||||
return -1
|
||||
case time.Duration(-2), -2 * time.Second:
|
||||
return -2
|
||||
}
|
||||
return int64(d / time.Second)
|
||||
}
|
||||
|
||||
// ListRedisKeys SCAN + pipeline TYPE/TTL. Cap ~500.
|
||||
func ListRedisKeys(c ConnReq) RedisKeysResult {
|
||||
start := time.Now()
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return RedisKeysResult{Keys: []RedisKeyInfo{}, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pat := redisScanPattern(c)
|
||||
var names []string
|
||||
var cursor uint64
|
||||
truncated := false
|
||||
for {
|
||||
batch, next, err := cli.Scan(ctx, cursor, pat, 200).Result()
|
||||
if err != nil {
|
||||
return RedisKeysResult{Keys: []RedisKeyInfo{}, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
names = append(names, batch...)
|
||||
cursor = next
|
||||
if len(names) >= redisKeyListCap {
|
||||
truncated = true
|
||||
names = names[:redisKeyListCap]
|
||||
break
|
||||
}
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if names == nil {
|
||||
names = []string{}
|
||||
}
|
||||
|
||||
out := make([]RedisKeyInfo, len(names))
|
||||
if len(names) == 0 {
|
||||
return RedisKeysResult{Keys: out, Truncated: truncated, TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
|
||||
// Pipeline TYPE + TTL (avoid N+1)
|
||||
pipe := cli.Pipeline()
|
||||
typeCmds := make([]*redis.StatusCmd, len(names))
|
||||
ttlCmds := make([]*redis.DurationCmd, len(names))
|
||||
for i, k := range names {
|
||||
typeCmds[i] = pipe.Type(ctx, k)
|
||||
ttlCmds[i] = pipe.TTL(ctx, k)
|
||||
}
|
||||
_, _ = pipe.Exec(ctx)
|
||||
for i, k := range names {
|
||||
typ := "none"
|
||||
if t, err := typeCmds[i].Result(); err == nil {
|
||||
typ = t
|
||||
}
|
||||
ttl := int64(-2)
|
||||
if d, err := ttlCmds[i].Result(); err == nil {
|
||||
ttl = ttlSeconds(d)
|
||||
}
|
||||
out[i] = RedisKeyInfo{Name: k, Type: typ, TTL: ttl}
|
||||
}
|
||||
return RedisKeysResult{Keys: out, Truncated: truncated, TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
|
||||
// GetRedisKey typed value → ResultPane columns/rows.
|
||||
func GetRedisKey(c ConnReq) RedisKeyValue {
|
||||
start := time.Now()
|
||||
key := redisKeyName(c)
|
||||
if key == "" {
|
||||
return RedisKeyValue{Error: "缺少 key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return RedisKeyValue{Name: key, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
typ, err := cli.Type(ctx, key).Result()
|
||||
if err != nil {
|
||||
return RedisKeyValue{Name: key, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
ttl := int64(-2)
|
||||
if d, err := cli.TTL(ctx, key).Result(); err == nil {
|
||||
ttl = ttlSeconds(d)
|
||||
}
|
||||
limit := redisValueLimit(c)
|
||||
base := RedisKeyValue{Name: key, Type: typ, TTL: ttl, Limit: limit}
|
||||
|
||||
switch typ {
|
||||
case "none":
|
||||
base.Columns = []string{"value"}
|
||||
base.Rows = []map[string]interface{}{}
|
||||
base.RowCount = 0
|
||||
base.Error = "key 不存在"
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
case "string":
|
||||
val, err := cli.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
base.Columns = []string{"value"}
|
||||
base.Rows = []map[string]interface{}{}
|
||||
base.RowCount = 0
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
if err != nil {
|
||||
base.Error = errMsg(err)
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
base.Columns = []string{"value"}
|
||||
base.Rows = []map[string]interface{}{{"value": val}}
|
||||
base.RowCount = 1
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
case "hash":
|
||||
fields, err := cli.HGetAll(ctx, key).Result()
|
||||
if err != nil {
|
||||
base.Error = errMsg(err)
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
base.Columns = []string{"field", "value"}
|
||||
rows := make([]map[string]interface{}, 0, len(fields))
|
||||
for f, v := range fields {
|
||||
rows = append(rows, map[string]interface{}{"field": f, "value": v})
|
||||
if len(rows) >= limit {
|
||||
base.Truncated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
base.Rows = rows
|
||||
base.RowCount = len(rows)
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
case "list":
|
||||
vals, err := cli.LRange(ctx, key, 0, int64(limit)-1).Result()
|
||||
if err != nil {
|
||||
base.Error = errMsg(err)
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
truncated := false
|
||||
n, _ := cli.LLen(ctx, key).Result()
|
||||
if n > int64(len(vals)) {
|
||||
truncated = true
|
||||
}
|
||||
base.Columns = []string{"index", "value"}
|
||||
rows := make([]map[string]interface{}, 0, len(vals))
|
||||
for i, v := range vals {
|
||||
rows = append(rows, map[string]interface{}{"index": i, "value": v})
|
||||
}
|
||||
base.Rows = rows
|
||||
base.RowCount = len(rows)
|
||||
base.Truncated = truncated
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
case "set":
|
||||
base.Columns = []string{"member"}
|
||||
var rows []map[string]interface{}
|
||||
var scursor uint64
|
||||
truncated := false
|
||||
for {
|
||||
batch, next, err := cli.SScan(ctx, key, scursor, "*", 200).Result()
|
||||
if err != nil {
|
||||
base.Error = errMsg(err)
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
for _, m := range batch {
|
||||
rows = append(rows, map[string]interface{}{"member": m})
|
||||
if len(rows) >= limit {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
scursor = next
|
||||
if truncated || scursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if rows == nil {
|
||||
rows = []map[string]interface{}{}
|
||||
}
|
||||
base.Rows = rows
|
||||
base.RowCount = len(rows)
|
||||
base.Truncated = truncated
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
case "zset":
|
||||
vals, err := cli.ZRangeWithScores(ctx, key, 0, int64(limit-1)).Result()
|
||||
if err != nil {
|
||||
base.Error = errMsg(err)
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
truncated := false
|
||||
n, _ := cli.ZCard(ctx, key).Result()
|
||||
if n > int64(len(vals)) {
|
||||
truncated = true
|
||||
}
|
||||
base.Columns = []string{"score", "member"}
|
||||
rows := make([]map[string]interface{}, 0, len(vals))
|
||||
for _, z := range vals {
|
||||
rows = append(rows, map[string]interface{}{"score": z.Score, "member": z.Member})
|
||||
}
|
||||
base.Rows = rows
|
||||
base.RowCount = len(rows)
|
||||
base.Truncated = truncated
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
default:
|
||||
// stream / recon / module types: meta only
|
||||
base.Columns = []string{"info"}
|
||||
base.Rows = []map[string]interface{}{{"info": fmt.Sprintf("类型 %s 暂不展开,可用命令查看", typ)}}
|
||||
base.RowCount = 1
|
||||
base.TimeMs = time.Since(start).Milliseconds()
|
||||
return base
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteRedisKey DEL one key.
|
||||
func DeleteRedisKey(c ConnReq) ExecResult {
|
||||
start := time.Now()
|
||||
key := redisKeyName(c)
|
||||
if key == "" {
|
||||
return ExecResult{Error: "缺少 key", TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
cli, err := openRedis(c)
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
defer cli.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
n, err := cli.Del(ctx, key).Result()
|
||||
if err != nil {
|
||||
return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()}
|
||||
}
|
||||
return ExecResult{
|
||||
Columns: []string{"affected_rows"},
|
||||
Rows: []map[string]interface{}{{"affected_rows": n}},
|
||||
RowCount: 1,
|
||||
TimeMs: time.Since(start).Milliseconds(),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user