You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

753 lines
20 KiB

  1. package redis
  2. import (
  3. "context"
  4. "encoding"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net"
  9. "reflect"
  10. "runtime"
  11. "strings"
  12. "time"
  13. "github.com/redis/go-redis/v9/internal"
  14. )
  15. // KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
  16. // otherwise you will receive an error: (error) ERR syntax error.
  17. // For example:
  18. //
  19. // rdb.Set(ctx, key, value, redis.KeepTTL)
  20. const KeepTTL = -1
  21. func usePrecise(dur time.Duration) bool {
  22. return dur < time.Second || dur%time.Second != 0
  23. }
  24. func formatMs(ctx context.Context, dur time.Duration) int64 {
  25. if dur > 0 && dur < time.Millisecond {
  26. internal.Logger.Printf(
  27. ctx,
  28. "specified duration is %s, but minimal supported value is %s - truncating to 1ms",
  29. dur, time.Millisecond,
  30. )
  31. return 1
  32. }
  33. return int64(dur / time.Millisecond)
  34. }
  35. func formatSec(ctx context.Context, dur time.Duration) int64 {
  36. if dur > 0 && dur < time.Second {
  37. internal.Logger.Printf(
  38. ctx,
  39. "specified duration is %s, but minimal supported value is %s - truncating to 1s",
  40. dur, time.Second,
  41. )
  42. return 1
  43. }
  44. return int64(dur / time.Second)
  45. }
  46. func appendArgs(dst, src []interface{}) []interface{} {
  47. if len(src) == 1 {
  48. return appendArg(dst, src[0])
  49. }
  50. dst = append(dst, src...)
  51. return dst
  52. }
  53. func appendArg(dst []interface{}, arg interface{}) []interface{} {
  54. switch arg := arg.(type) {
  55. case []string:
  56. for _, s := range arg {
  57. dst = append(dst, s)
  58. }
  59. return dst
  60. case []interface{}:
  61. dst = append(dst, arg...)
  62. return dst
  63. case map[string]interface{}:
  64. for k, v := range arg {
  65. dst = append(dst, k, v)
  66. }
  67. return dst
  68. case map[string]string:
  69. for k, v := range arg {
  70. dst = append(dst, k, v)
  71. }
  72. return dst
  73. case time.Time, time.Duration, encoding.BinaryMarshaler, net.IP:
  74. return append(dst, arg)
  75. case nil:
  76. return dst
  77. default:
  78. // scan struct field
  79. v := reflect.ValueOf(arg)
  80. if v.Type().Kind() == reflect.Ptr {
  81. if v.IsNil() {
  82. // error: arg is not a valid object
  83. return dst
  84. }
  85. v = v.Elem()
  86. }
  87. if v.Type().Kind() == reflect.Struct {
  88. return appendStructField(dst, v)
  89. }
  90. return append(dst, arg)
  91. }
  92. }
  93. // appendStructField appends the field and value held by the structure v to dst, and returns the appended dst.
  94. func appendStructField(dst []interface{}, v reflect.Value) []interface{} {
  95. typ := v.Type()
  96. for i := 0; i < typ.NumField(); i++ {
  97. tag := typ.Field(i).Tag.Get("redis")
  98. if tag == "" || tag == "-" {
  99. continue
  100. }
  101. name, opt, _ := strings.Cut(tag, ",")
  102. if name == "" {
  103. continue
  104. }
  105. field := v.Field(i)
  106. // miss field
  107. if omitEmpty(opt) && isEmptyValue(field) {
  108. continue
  109. }
  110. if field.CanInterface() {
  111. dst = append(dst, name, field.Interface())
  112. }
  113. }
  114. return dst
  115. }
  116. func omitEmpty(opt string) bool {
  117. for opt != "" {
  118. var name string
  119. name, opt, _ = strings.Cut(opt, ",")
  120. if name == "omitempty" {
  121. return true
  122. }
  123. }
  124. return false
  125. }
  126. func isEmptyValue(v reflect.Value) bool {
  127. switch v.Kind() {
  128. case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
  129. return v.Len() == 0
  130. case reflect.Bool:
  131. return !v.Bool()
  132. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  133. return v.Int() == 0
  134. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  135. return v.Uint() == 0
  136. case reflect.Float32, reflect.Float64:
  137. return v.Float() == 0
  138. case reflect.Interface, reflect.Pointer:
  139. return v.IsNil()
  140. case reflect.Struct:
  141. if v.Type() == reflect.TypeOf(time.Time{}) {
  142. return v.IsZero()
  143. }
  144. // Only supports the struct time.Time,
  145. // subsequent iterations will follow the func Scan support decoder.
  146. }
  147. return false
  148. }
  149. type Cmdable interface {
  150. Pipeline() Pipeliner
  151. Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
  152. TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
  153. TxPipeline() Pipeliner
  154. Command(ctx context.Context) *CommandsInfoCmd
  155. CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd
  156. CommandGetKeys(ctx context.Context, commands ...interface{}) *StringSliceCmd
  157. CommandGetKeysAndFlags(ctx context.Context, commands ...interface{}) *KeyFlagsCmd
  158. ClientGetName(ctx context.Context) *StringCmd
  159. Echo(ctx context.Context, message interface{}) *StringCmd
  160. Ping(ctx context.Context) *StatusCmd
  161. Quit(ctx context.Context) *StatusCmd
  162. Unlink(ctx context.Context, keys ...string) *IntCmd
  163. BgRewriteAOF(ctx context.Context) *StatusCmd
  164. BgSave(ctx context.Context) *StatusCmd
  165. ClientKill(ctx context.Context, ipPort string) *StatusCmd
  166. ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd
  167. ClientList(ctx context.Context) *StringCmd
  168. ClientInfo(ctx context.Context) *ClientInfoCmd
  169. ClientPause(ctx context.Context, dur time.Duration) *BoolCmd
  170. ClientUnpause(ctx context.Context) *BoolCmd
  171. ClientID(ctx context.Context) *IntCmd
  172. ClientUnblock(ctx context.Context, id int64) *IntCmd
  173. ClientUnblockWithError(ctx context.Context, id int64) *IntCmd
  174. ClientMaintNotifications(ctx context.Context, enabled bool, endpointType string) *StatusCmd
  175. ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd
  176. ConfigResetStat(ctx context.Context) *StatusCmd
  177. ConfigSet(ctx context.Context, parameter, value string) *StatusCmd
  178. ConfigRewrite(ctx context.Context) *StatusCmd
  179. DBSize(ctx context.Context) *IntCmd
  180. FlushAll(ctx context.Context) *StatusCmd
  181. FlushAllAsync(ctx context.Context) *StatusCmd
  182. FlushDB(ctx context.Context) *StatusCmd
  183. FlushDBAsync(ctx context.Context) *StatusCmd
  184. Info(ctx context.Context, section ...string) *StringCmd
  185. LastSave(ctx context.Context) *IntCmd
  186. Save(ctx context.Context) *StatusCmd
  187. Shutdown(ctx context.Context) *StatusCmd
  188. ShutdownSave(ctx context.Context) *StatusCmd
  189. ShutdownNoSave(ctx context.Context) *StatusCmd
  190. SlaveOf(ctx context.Context, host, port string) *StatusCmd
  191. SlowLogGet(ctx context.Context, num int64) *SlowLogCmd
  192. Time(ctx context.Context) *TimeCmd
  193. DebugObject(ctx context.Context, key string) *StringCmd
  194. MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd
  195. ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
  196. ACLCmdable
  197. BitMapCmdable
  198. ClusterCmdable
  199. GenericCmdable
  200. GeoCmdable
  201. HashCmdable
  202. HyperLogLogCmdable
  203. ListCmdable
  204. ProbabilisticCmdable
  205. PubSubCmdable
  206. ScriptingFunctionsCmdable
  207. SearchCmdable
  208. SetCmdable
  209. SortedSetCmdable
  210. StringCmdable
  211. StreamCmdable
  212. TimeseriesCmdable
  213. JSONCmdable
  214. VectorSetCmdable
  215. }
  216. type StatefulCmdable interface {
  217. Cmdable
  218. Auth(ctx context.Context, password string) *StatusCmd
  219. AuthACL(ctx context.Context, username, password string) *StatusCmd
  220. Select(ctx context.Context, index int) *StatusCmd
  221. SwapDB(ctx context.Context, index1, index2 int) *StatusCmd
  222. ClientSetName(ctx context.Context, name string) *BoolCmd
  223. ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd
  224. Hello(ctx context.Context, ver int, username, password, clientName string) *MapStringInterfaceCmd
  225. }
  226. var (
  227. _ Cmdable = (*Client)(nil)
  228. _ Cmdable = (*Tx)(nil)
  229. _ Cmdable = (*Ring)(nil)
  230. _ Cmdable = (*ClusterClient)(nil)
  231. _ Cmdable = (*Pipeline)(nil)
  232. )
  233. type cmdable func(ctx context.Context, cmd Cmder) error
  234. type statefulCmdable func(ctx context.Context, cmd Cmder) error
  235. //------------------------------------------------------------------------------
  236. func (c statefulCmdable) Auth(ctx context.Context, password string) *StatusCmd {
  237. cmd := NewStatusCmd(ctx, "auth", password)
  238. _ = c(ctx, cmd)
  239. return cmd
  240. }
  241. // AuthACL Perform an AUTH command, using the given user and pass.
  242. // Should be used to authenticate the current connection with one of the connections defined in the ACL list
  243. // when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
  244. func (c statefulCmdable) AuthACL(ctx context.Context, username, password string) *StatusCmd {
  245. cmd := NewStatusCmd(ctx, "auth", username, password)
  246. _ = c(ctx, cmd)
  247. return cmd
  248. }
  249. func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) *IntCmd {
  250. cmd := NewIntCmd(ctx, "wait", numSlaves, int(timeout/time.Millisecond))
  251. cmd.setReadTimeout(timeout)
  252. _ = c(ctx, cmd)
  253. return cmd
  254. }
  255. func (c cmdable) WaitAOF(ctx context.Context, numLocal, numSlaves int, timeout time.Duration) *IntCmd {
  256. cmd := NewIntCmd(ctx, "waitAOF", numLocal, numSlaves, int(timeout/time.Millisecond))
  257. cmd.setReadTimeout(timeout)
  258. _ = c(ctx, cmd)
  259. return cmd
  260. }
  261. func (c statefulCmdable) Select(ctx context.Context, index int) *StatusCmd {
  262. cmd := NewStatusCmd(ctx, "select", index)
  263. _ = c(ctx, cmd)
  264. return cmd
  265. }
  266. func (c statefulCmdable) SwapDB(ctx context.Context, index1, index2 int) *StatusCmd {
  267. cmd := NewStatusCmd(ctx, "swapdb", index1, index2)
  268. _ = c(ctx, cmd)
  269. return cmd
  270. }
  271. // ClientSetName assigns a name to the connection.
  272. func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCmd {
  273. cmd := NewBoolCmd(ctx, "client", "setname", name)
  274. _ = c(ctx, cmd)
  275. return cmd
  276. }
  277. // ClientSetInfo sends a CLIENT SETINFO command with the provided info.
  278. func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd {
  279. err := info.Validate()
  280. if err != nil {
  281. panic(err.Error())
  282. }
  283. var cmd *StatusCmd
  284. if info.LibName != nil {
  285. libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, internal.ReplaceSpaces(runtime.Version()))
  286. cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName)
  287. } else {
  288. cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer)
  289. }
  290. _ = c(ctx, cmd)
  291. return cmd
  292. }
  293. // Validate checks if only one field in the struct is non-nil.
  294. func (info LibraryInfo) Validate() error {
  295. if info.LibName != nil && info.LibVer != nil {
  296. return errors.New("both LibName and LibVer cannot be set at the same time")
  297. }
  298. if info.LibName == nil && info.LibVer == nil {
  299. return errors.New("at least one of LibName and LibVer should be set")
  300. }
  301. return nil
  302. }
  303. // Hello sets the resp protocol used.
  304. func (c statefulCmdable) Hello(ctx context.Context,
  305. ver int, username, password, clientName string,
  306. ) *MapStringInterfaceCmd {
  307. args := make([]interface{}, 0, 7)
  308. args = append(args, "hello", ver)
  309. if password != "" {
  310. if username != "" {
  311. args = append(args, "auth", username, password)
  312. } else {
  313. args = append(args, "auth", "default", password)
  314. }
  315. }
  316. if clientName != "" {
  317. args = append(args, "setname", clientName)
  318. }
  319. cmd := NewMapStringInterfaceCmd(ctx, args...)
  320. _ = c(ctx, cmd)
  321. return cmd
  322. }
  323. //------------------------------------------------------------------------------
  324. func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd {
  325. cmd := NewCommandsInfoCmd(ctx, "command")
  326. _ = c(ctx, cmd)
  327. return cmd
  328. }
  329. // FilterBy is used for the `CommandList` command parameter.
  330. type FilterBy struct {
  331. Module string
  332. ACLCat string
  333. Pattern string
  334. }
  335. func (c cmdable) CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd {
  336. args := make([]interface{}, 0, 5)
  337. args = append(args, "command", "list")
  338. if filter != nil {
  339. if filter.Module != "" {
  340. args = append(args, "filterby", "module", filter.Module)
  341. } else if filter.ACLCat != "" {
  342. args = append(args, "filterby", "aclcat", filter.ACLCat)
  343. } else if filter.Pattern != "" {
  344. args = append(args, "filterby", "pattern", filter.Pattern)
  345. }
  346. }
  347. cmd := NewStringSliceCmd(ctx, args...)
  348. _ = c(ctx, cmd)
  349. return cmd
  350. }
  351. func (c cmdable) CommandGetKeys(ctx context.Context, commands ...interface{}) *StringSliceCmd {
  352. args := make([]interface{}, 2+len(commands))
  353. args[0] = "command"
  354. args[1] = "getkeys"
  355. copy(args[2:], commands)
  356. cmd := NewStringSliceCmd(ctx, args...)
  357. _ = c(ctx, cmd)
  358. return cmd
  359. }
  360. func (c cmdable) CommandGetKeysAndFlags(ctx context.Context, commands ...interface{}) *KeyFlagsCmd {
  361. args := make([]interface{}, 2+len(commands))
  362. args[0] = "command"
  363. args[1] = "getkeysandflags"
  364. copy(args[2:], commands)
  365. cmd := NewKeyFlagsCmd(ctx, args...)
  366. _ = c(ctx, cmd)
  367. return cmd
  368. }
  369. // ClientGetName returns the name of the connection.
  370. func (c cmdable) ClientGetName(ctx context.Context) *StringCmd {
  371. cmd := NewStringCmd(ctx, "client", "getname")
  372. _ = c(ctx, cmd)
  373. return cmd
  374. }
  375. func (c cmdable) Echo(ctx context.Context, message interface{}) *StringCmd {
  376. cmd := NewStringCmd(ctx, "echo", message)
  377. _ = c(ctx, cmd)
  378. return cmd
  379. }
  380. func (c cmdable) Ping(ctx context.Context) *StatusCmd {
  381. cmd := NewStatusCmd(ctx, "ping")
  382. _ = c(ctx, cmd)
  383. return cmd
  384. }
  385. func (c cmdable) Do(ctx context.Context, args ...interface{}) *Cmd {
  386. cmd := NewCmd(ctx, args...)
  387. _ = c(ctx, cmd)
  388. return cmd
  389. }
  390. func (c cmdable) Quit(_ context.Context) *StatusCmd {
  391. panic("not implemented")
  392. }
  393. //------------------------------------------------------------------------------
  394. func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd {
  395. cmd := NewStatusCmd(ctx, "bgrewriteaof")
  396. _ = c(ctx, cmd)
  397. return cmd
  398. }
  399. func (c cmdable) BgSave(ctx context.Context) *StatusCmd {
  400. cmd := NewStatusCmd(ctx, "bgsave")
  401. _ = c(ctx, cmd)
  402. return cmd
  403. }
  404. func (c cmdable) ClientKill(ctx context.Context, ipPort string) *StatusCmd {
  405. cmd := NewStatusCmd(ctx, "client", "kill", ipPort)
  406. _ = c(ctx, cmd)
  407. return cmd
  408. }
  409. // ClientKillByFilter is new style syntax, while the ClientKill is old
  410. //
  411. // CLIENT KILL <option> [value] ... <option> [value]
  412. func (c cmdable) ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd {
  413. args := make([]interface{}, 2+len(keys))
  414. args[0] = "client"
  415. args[1] = "kill"
  416. for i, key := range keys {
  417. args[2+i] = key
  418. }
  419. cmd := NewIntCmd(ctx, args...)
  420. _ = c(ctx, cmd)
  421. return cmd
  422. }
  423. func (c cmdable) ClientList(ctx context.Context) *StringCmd {
  424. cmd := NewStringCmd(ctx, "client", "list")
  425. _ = c(ctx, cmd)
  426. return cmd
  427. }
  428. func (c cmdable) ClientPause(ctx context.Context, dur time.Duration) *BoolCmd {
  429. cmd := NewBoolCmd(ctx, "client", "pause", formatMs(ctx, dur))
  430. _ = c(ctx, cmd)
  431. return cmd
  432. }
  433. func (c cmdable) ClientUnpause(ctx context.Context) *BoolCmd {
  434. cmd := NewBoolCmd(ctx, "client", "unpause")
  435. _ = c(ctx, cmd)
  436. return cmd
  437. }
  438. func (c cmdable) ClientID(ctx context.Context) *IntCmd {
  439. cmd := NewIntCmd(ctx, "client", "id")
  440. _ = c(ctx, cmd)
  441. return cmd
  442. }
  443. func (c cmdable) ClientUnblock(ctx context.Context, id int64) *IntCmd {
  444. cmd := NewIntCmd(ctx, "client", "unblock", id)
  445. _ = c(ctx, cmd)
  446. return cmd
  447. }
  448. func (c cmdable) ClientUnblockWithError(ctx context.Context, id int64) *IntCmd {
  449. cmd := NewIntCmd(ctx, "client", "unblock", id, "error")
  450. _ = c(ctx, cmd)
  451. return cmd
  452. }
  453. func (c cmdable) ClientInfo(ctx context.Context) *ClientInfoCmd {
  454. cmd := NewClientInfoCmd(ctx, "client", "info")
  455. _ = c(ctx, cmd)
  456. return cmd
  457. }
  458. // ClientMaintNotifications enables or disables maintenance notifications for maintenance upgrades.
  459. // When enabled, the client will receive push notifications about Redis maintenance events.
  460. func (c cmdable) ClientMaintNotifications(ctx context.Context, enabled bool, endpointType string) *StatusCmd {
  461. args := []interface{}{"client", "maint_notifications"}
  462. if enabled {
  463. if endpointType == "" {
  464. endpointType = "none"
  465. }
  466. args = append(args, "on", "moving-endpoint-type", endpointType)
  467. } else {
  468. args = append(args, "off")
  469. }
  470. cmd := NewStatusCmd(ctx, args...)
  471. _ = c(ctx, cmd)
  472. return cmd
  473. }
  474. // ------------------------------------------------------------------------------------------------
  475. func (c cmdable) ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd {
  476. cmd := NewMapStringStringCmd(ctx, "config", "get", parameter)
  477. _ = c(ctx, cmd)
  478. return cmd
  479. }
  480. func (c cmdable) ConfigResetStat(ctx context.Context) *StatusCmd {
  481. cmd := NewStatusCmd(ctx, "config", "resetstat")
  482. _ = c(ctx, cmd)
  483. return cmd
  484. }
  485. func (c cmdable) ConfigSet(ctx context.Context, parameter, value string) *StatusCmd {
  486. cmd := NewStatusCmd(ctx, "config", "set", parameter, value)
  487. _ = c(ctx, cmd)
  488. return cmd
  489. }
  490. func (c cmdable) ConfigRewrite(ctx context.Context) *StatusCmd {
  491. cmd := NewStatusCmd(ctx, "config", "rewrite")
  492. _ = c(ctx, cmd)
  493. return cmd
  494. }
  495. func (c cmdable) DBSize(ctx context.Context) *IntCmd {
  496. cmd := NewIntCmd(ctx, "dbsize")
  497. _ = c(ctx, cmd)
  498. return cmd
  499. }
  500. func (c cmdable) FlushAll(ctx context.Context) *StatusCmd {
  501. cmd := NewStatusCmd(ctx, "flushall")
  502. _ = c(ctx, cmd)
  503. return cmd
  504. }
  505. func (c cmdable) FlushAllAsync(ctx context.Context) *StatusCmd {
  506. cmd := NewStatusCmd(ctx, "flushall", "async")
  507. _ = c(ctx, cmd)
  508. return cmd
  509. }
  510. func (c cmdable) FlushDB(ctx context.Context) *StatusCmd {
  511. cmd := NewStatusCmd(ctx, "flushdb")
  512. _ = c(ctx, cmd)
  513. return cmd
  514. }
  515. func (c cmdable) FlushDBAsync(ctx context.Context) *StatusCmd {
  516. cmd := NewStatusCmd(ctx, "flushdb", "async")
  517. _ = c(ctx, cmd)
  518. return cmd
  519. }
  520. func (c cmdable) Info(ctx context.Context, sections ...string) *StringCmd {
  521. args := make([]interface{}, 1+len(sections))
  522. args[0] = "info"
  523. for i, section := range sections {
  524. args[i+1] = section
  525. }
  526. cmd := NewStringCmd(ctx, args...)
  527. _ = c(ctx, cmd)
  528. return cmd
  529. }
  530. func (c cmdable) InfoMap(ctx context.Context, sections ...string) *InfoCmd {
  531. args := make([]interface{}, 1+len(sections))
  532. args[0] = "info"
  533. for i, section := range sections {
  534. args[i+1] = section
  535. }
  536. cmd := NewInfoCmd(ctx, args...)
  537. _ = c(ctx, cmd)
  538. return cmd
  539. }
  540. func (c cmdable) LastSave(ctx context.Context) *IntCmd {
  541. cmd := NewIntCmd(ctx, "lastsave")
  542. _ = c(ctx, cmd)
  543. return cmd
  544. }
  545. func (c cmdable) Save(ctx context.Context) *StatusCmd {
  546. cmd := NewStatusCmd(ctx, "save")
  547. _ = c(ctx, cmd)
  548. return cmd
  549. }
  550. func (c cmdable) shutdown(ctx context.Context, modifier string) *StatusCmd {
  551. var args []interface{}
  552. if modifier == "" {
  553. args = []interface{}{"shutdown"}
  554. } else {
  555. args = []interface{}{"shutdown", modifier}
  556. }
  557. cmd := NewStatusCmd(ctx, args...)
  558. _ = c(ctx, cmd)
  559. if err := cmd.Err(); err != nil {
  560. if err == io.EOF {
  561. // Server quit as expected.
  562. cmd.err = nil
  563. }
  564. } else {
  565. // Server did not quit. String reply contains the reason.
  566. cmd.err = errors.New(cmd.val)
  567. cmd.val = ""
  568. }
  569. return cmd
  570. }
  571. func (c cmdable) Shutdown(ctx context.Context) *StatusCmd {
  572. return c.shutdown(ctx, "")
  573. }
  574. func (c cmdable) ShutdownSave(ctx context.Context) *StatusCmd {
  575. return c.shutdown(ctx, "save")
  576. }
  577. func (c cmdable) ShutdownNoSave(ctx context.Context) *StatusCmd {
  578. return c.shutdown(ctx, "nosave")
  579. }
  580. func (c cmdable) SlaveOf(ctx context.Context, host, port string) *StatusCmd {
  581. cmd := NewStatusCmd(ctx, "slaveof", host, port)
  582. _ = c(ctx, cmd)
  583. return cmd
  584. }
  585. func (c cmdable) SlowLogGet(ctx context.Context, num int64) *SlowLogCmd {
  586. cmd := NewSlowLogCmd(context.Background(), "slowlog", "get", num)
  587. _ = c(ctx, cmd)
  588. return cmd
  589. }
  590. func (c cmdable) Sync(_ context.Context) {
  591. panic("not implemented")
  592. }
  593. func (c cmdable) Time(ctx context.Context) *TimeCmd {
  594. cmd := NewTimeCmd(ctx, "time")
  595. _ = c(ctx, cmd)
  596. return cmd
  597. }
  598. func (c cmdable) DebugObject(ctx context.Context, key string) *StringCmd {
  599. cmd := NewStringCmd(ctx, "debug", "object", key)
  600. _ = c(ctx, cmd)
  601. return cmd
  602. }
  603. func (c cmdable) MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd {
  604. args := []interface{}{"memory", "usage", key}
  605. if len(samples) > 0 {
  606. if len(samples) != 1 {
  607. panic("MemoryUsage expects single sample count")
  608. }
  609. args = append(args, "SAMPLES", samples[0])
  610. }
  611. cmd := NewIntCmd(ctx, args...)
  612. cmd.SetFirstKeyPos(2)
  613. _ = c(ctx, cmd)
  614. return cmd
  615. }
  616. //------------------------------------------------------------------------------
  617. // ModuleLoadexConfig struct is used to specify the arguments for the MODULE LOADEX command of redis.
  618. // `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]`
  619. type ModuleLoadexConfig struct {
  620. Path string
  621. Conf map[string]interface{}
  622. Args []interface{}
  623. }
  624. func (c *ModuleLoadexConfig) toArgs() []interface{} {
  625. args := make([]interface{}, 3, 3+len(c.Conf)*3+len(c.Args)*2)
  626. args[0] = "MODULE"
  627. args[1] = "LOADEX"
  628. args[2] = c.Path
  629. for k, v := range c.Conf {
  630. args = append(args, "CONFIG", k, v)
  631. }
  632. for _, arg := range c.Args {
  633. args = append(args, "ARGS", arg)
  634. }
  635. return args
  636. }
  637. // ModuleLoadex Redis `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` command.
  638. func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd {
  639. cmd := NewStringCmd(ctx, conf.toArgs()...)
  640. _ = c(ctx, cmd)
  641. return cmd
  642. }
  643. /*
  644. Monitor - represents a Redis MONITOR command, allowing the user to capture
  645. and process all commands sent to a Redis server. This mimics the behavior of
  646. MONITOR in the redis-cli.
  647. Notes:
  648. - Using MONITOR blocks the connection to the server for itself. It needs a dedicated connection
  649. - The user should create a channel of type string
  650. - This runs concurrently in the background. Trigger via the Start and Stop functions
  651. See further: Redis MONITOR command: https://redis.io/commands/monitor
  652. */
  653. func (c cmdable) Monitor(ctx context.Context, ch chan string) *MonitorCmd {
  654. cmd := newMonitorCmd(ctx, ch)
  655. _ = c(ctx, cmd)
  656. return cmd
  657. }