1package redis
2
3import (
4	"crypto/sha1"
5	"encoding/hex"
6	"io"
7	"strings"
8)
9
10type scripter interface {
11	Eval(script string, keys []string, args ...interface{}) *Cmd
12	EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
13	ScriptExists(hashes ...string) *BoolSliceCmd
14	ScriptLoad(script string) *StringCmd
15}
16
17var _ scripter = (*Client)(nil)
18var _ scripter = (*Ring)(nil)
19var _ scripter = (*ClusterClient)(nil)
20
21type Script struct {
22	src, hash string
23}
24
25func NewScript(src string) *Script {
26	h := sha1.New()
27	_, _ = io.WriteString(h, src)
28	return &Script{
29		src:  src,
30		hash: hex.EncodeToString(h.Sum(nil)),
31	}
32}
33
34func (s *Script) Hash() string {
35	return s.hash
36}
37
38func (s *Script) Load(c scripter) *StringCmd {
39	return c.ScriptLoad(s.src)
40}
41
42func (s *Script) Exists(c scripter) *BoolSliceCmd {
43	return c.ScriptExists(s.hash)
44}
45
46func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd {
47	return c.Eval(s.src, keys, args...)
48}
49
50func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd {
51	return c.EvalSha(s.hash, keys, args...)
52}
53
54// Run optimistically uses EVALSHA to run the script. If script does not exist
55// it is retried using EVAL.
56func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd {
57	r := s.EvalSha(c, keys, args...)
58	if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") {
59		return s.Eval(c, keys, args...)
60	}
61	return r
62}
63