Repository: redis/redis

Issue:
SCRIPT LIST command desirable
@antirez Currently if you know the `SHA1` for a loaded script you can check for its existence with `SCRIPT EXISTS`.  If you don't know the `SHA1` value there is no way to get a list of scripts in the cache.  It would be useful if it were possible to list the `SHA1` values in the cache, and ideally the script associated with the `SHA1`.

The situation is that scripts get updated/versioned and loaded into Redis.  Old ones need to be deleted, but the `SHA1` values are not necessarily recorded.  With a listing function and the ability to see the script that was loaded, old scripts can be manually deleted.

Secondarily, if the data is available, a `SCRIPT GET SHA1` would be useful.


Discussion:
three years later...
@allanwax @usernameisnull, redis philosophy is that it intentionally doesn't want to let you get the script code back (i.e. `SCRIPT GET SHA1`), the design is that the scripts are the responsibility of the client application, and are only cached by redis to improve performance.
The application should always fall back to `SCRIPT LOAD` or `EVAL` if `EVALSHA` or `SCRIPT EXIST` fail.

> If you don't know the SHA1 value there is no way to get a list of scripts in the cache

The application must!! know the SHA1 value, and actually have the script code ready to be re-loaded! redis should not be the one that gives you the SHA1 of a script you wish to execute! it's not a coincidence that scripts are not named! doing that would introduce a versioning problem (you'll need to make sure you run the right version of your named script), which is what redis aims to avoid!

You should not attempt or wish to delete old scripts from the cache! so i also don't see any reason for a `SCRIPT LIST` command.

Please describe what's the use case for your request.
When you're troubleshooting/debugging a 3rd party application that uses Redis heavily and trying to determine why it isn't producing the desired output, being able to see what it is creating in Redis would be very helpful.  (I can see the SHA from the `MONITOR` command, so the ` SCRIPT LIST` command isn't _as_ critical... But `SCRIPT GET` would be great as I don't really know what is _in_ the script.)

Just my 2 cents, but saying it's the application's "philosophy" to intentionally blind you because you _should know already_ doesn't make sense to me in the context of modern app development that depends so heavily on third party libraries/code.
that makes sense, but i'm afraid it's a slippery slope, and this could be abused.
maybe a viable solution is `DEBUG SCRIPT <SHA>` which will print the script the the log file?
this will be less likely to be abused.
@yossigo WDYT?
@oranagra I think your suggestion is a good compromise.
@rocksteady-david and others. would that solve your problems?
we can add
`DEBUG SCRIPT LIST`
`DEBUG SCRIPT <SHA>`
both of which, print their output to the redis log file, and reply `+OK`
Yeah, that seems reasonable to me.
Why the redis log and not the response? I think that seems a little unusual and arbitrary, and isn't how most command work - even dangerous commands. If this is intended for security reasons, the threat that we're trying to protect against should be explicit - and I wonder whether ACL isn't the more obvious and appropriate solution. Having to access the redis logs is going to make it much harder to make such a command useful, and has some security implications of its own, such as "who has access to the log?", and "how do we correlate specific queries?"
@mgravell the intention here is NOT to add another command and a capability for client applications, it is actually the opposite.
I don't want this command to be used by anyone, note that it'll be under the DEBUG command, (not the SCRIPT command).
It's not about security, but rather about preventing people from abusing the philosophy about EVAL scripts, which dictates that they are part of the client application (not hosted by Redis).
So such a command is only intended to use for debugging, specifically when one's hosting a 3rd party redis application or a library.
play a bit, loaded two script using `script load`. DEBUG SCRIPT always reply +OK
```
[root@binblog redis]# cat 1.lua 
local is_exist = redis.call("exists", KEYS[1])
if is_exist == 1 then
    redis.call("incr", KEYS[1])
else
    redis.call("set", KEYS[1], ARGV[1], "ex", ARGV[2])
end
[root@binblog redis]# src/redis-cli -x script load < 2.lua
```

debug script error_sha
```
21782:M 20 Apr 2022 17:39:10.074 # script not found: error_sha
```

debug script 7b61bd2b4d3fc183238f999ba83bf47b8c62a923
```
21782:M 20 Apr 2022 17:39:20.635 # sha 7b61bd2b4d3fc183238f999ba83bf47b8c62a923
local is_exist2 = redis.call("exists", KEYS[1])
if is_exist2 == 1 then
    redis.call("incr", KEYS[1])
else
    redis.call("set", KEYS[1], ARGV[1], "ex", ARGV[2])
end
```

debug script list, there are two script
```
21782:M 20 Apr 2022 17:39:25.858 # sha c9e1a03257cfd4aa298ef33e2dfd47b1da68b2ca
local is_exist = redis.call("exists", KEYS[1])
if is_exist == 1 then
    redis.call("incr", KEYS[1])
else
    redis.call("set", KEYS[1], ARGV[1], "ex", ARGV[2])
end

21782:M 20 Apr 2022 17:39:25.858 # sha 7b61bd2b4d3fc183238f999ba83bf47b8c62a923
local is_exist2 = redis.call("exists", KEYS[1])
if is_exist2 == 1 then
    redis.call("incr", KEYS[1])
else
    redis.call("set", KEYS[1], ARGV[1], "ex", ARGV[2])
end
```

Patch:
diff --git a/src/debug.c b/src/debug.c
index 4b5f730610f..b774ccc656b 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -15,6 +15,7 @@
 #include "fpconv_dtoa.h"
 #include "cluster.h"
 #include "threads_mngr.h"
+#include "script.h"
 
 #include <arpa/inet.h>
 #include <signal.h>
@@ -1013,6 +1014,29 @@ NULL
     } else if (!strcasecmp(c->argv[1]->ptr, "dict-resizing") && c->argc == 3) {
         server.dict_resizing = atoi(c->argv[2]->ptr);
         addReply(c, shared.ok);
+    } else if (!strcasecmp(c->argv[1]->ptr,"script") && c->argc == 3) {
+        if (!strcasecmp(c->argv[2]->ptr,"list")) {
+            dictIterator *di = dictGetIterator(getLuaScripts());
+            dictEntry *de;
+            while ((de = dictNext(di)) != NULL) {
+                luaScript *script = dictGetVal(de);
+                sds *sha = dictGetKey(de);
+                serverLog(LL_WARNING, "SCRIPT SHA: %s\n%s", (char*)sha, (char*)script->body->ptr);
+            }
+            dictReleaseIterator(di);
+        } else if (sdslen(c->argv[2]->ptr) == 40) {
+            dictEntry *de;
+            if ((de = dictFind(getLuaScripts(), c->argv[2]->ptr)) == NULL) {
+                addReplyErrorObject(c, shared.noscripterr);
+                return;
+            }
+            luaScript *script = dictGetVal(de);
+            serverLog(LL_WARNING, "SCRIPT SHA: %s\n%s", (char*)c->argv[2]->ptr, (char*)script->body->ptr);
+        } else {
+            addReplySubcommandSyntaxError(c);
+            return;
+        }
+        addReply(c,shared.ok);
     } else if(!handleDebugClusterCommand(c)) {
         addReplySubcommandSyntaxError(c);
         return;
diff --git a/src/eval.c b/src/eval.c
index 02b47209833..1cea9e6db02 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1737,3 +1737,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
         rctx->start_time = getMonotonicUs();
     }
 }
+
+dict *getLuaScripts(void) {
+    return lctx.lua_scripts;
+}
diff --git a/src/script.h b/src/script.h
index 12d01f7549d..8d604e493de 100644
--- a/src/script.h
+++ b/src/script.h
@@ -74,6 +74,7 @@ extern scriptFlag scripts_flags_def[];
 
 void luaEnvInit(void);
 lua_State *createLuaState(void);
+dict *getLuaScripts(void);
 uint64_t scriptFlagsToCmdFlags(uint64_t cmd_flags, uint64_t script_flags);
 int scriptPrepareForRun(scriptRunCtx *r_ctx, client *engine_client, client *caller, const char *funcname, uint64_t script_flags, int ro);
 void scriptResetRun(scriptRunCtx *r_ctx);