4

I have tried to declare a function without the local keyword and then call that function from anther script but it gives me an error when I run the command.

test = function ()    
    return 'test'
end



# from some other script
test()

Edit:

I can't believe I still have no answer to this. I'll include more details of my setup.

I am using node with the redis-scripto package to load the scripts into redis. Here is an example.

var Scripto = require('redis-scripto');
var scriptManager = new Scripto(redis);

scriptManager.loadFromDir('./lua_scripts');

var keys    = [key1, key2];
var values  = [val];

scriptManager.run('run_function', keys, values, function(err, result) {
console.log(err, result)
})

And the lua scripts.

-- ./lua_scripts/dict_2_bulk.lua

-- turns a dictionary table into a bulk reply table
dict2bulk = function (dict)
    local result = {}
    for k, v in pairs(dict) do
        table.insert(result, k)
        table.insert(result, v)
    end
    return result
end


-- run_function.lua 

return dict2bulk({ test=1 })

Throws the following error.

[Error: ERR Error running script (call to f_d06f7fd783cc537d535ec59228a18f70fccde663): @enable_strict_lua:14: user_script:1: Script attempted to access unexisting global variable 'dict2bulk' ] undefined
Tim Fairbrother
  • 928
  • 2
  • 9
  • 20
  • I'll ask the obvious: in the second script, do you (or anywhere else) 'require' the script that specifies the function? That is, does that code actually run? – Nathan Feb 12 '14 at 03:59
  • The scripts are loaded into redis via the script load command. There is no requires from within the scripts themselves. Do I need them? – Tim Fairbrother Feb 12 '14 at 04:05
  • if I use require, it gives me an error. Script attempted to access unexisting global variable 'require'. I'm not sure require will work from within Redis. – Tim Fairbrother Feb 12 '14 at 04:18
  • Also, the script runs. I can run normal redis commands that execute successfully but it doesn't know anything about the functions that I have declared in other scripts. – Tim Fairbrother Feb 12 '14 at 04:43
  • @Nathan, can you please tell me how I am supposed to use require inside Redis when there is no filesystem? – Tim Fairbrother Feb 12 '14 at 05:35
  • Sorry, I didn't realize that Redis didn't allow the use of require. But your code should work as long as the script that creates the function is being called. Have you confirmed that it is? – Nathan Feb 12 '14 at 06:54
  • Nothing I have tried works. I tried to keep this a redis specific question but maybe I need to include more details on the redis client. – Tim Fairbrother Feb 12 '14 at 22:49
  • This is probably the case. If you can confirm that the scripts are running, and in the correct order, then it has to be a redis issue. Definitely include some more details about that, but I, personally, have no redis knowledge. – Nathan Feb 13 '14 at 23:45

3 Answers3

10

I'm going to be contrary to the accepted answer, because the accepted answer is wrong.

While you can't explicitly define named functions, you can call any script that you can call with EVALSHA. More specifically, all of the Lua scripts that you have explicitly defined via SCRIPT LOAD or implicitly via EVAL are available in the global Lua namespace at f_<sha1 hash> (until/unless you call SCRIPT FLUSH), which you can call any time.

The problem that you run into is that the functions are defined as taking no arguments, and the KEYS and ARGV tables are actually globals. So if you want to be able to communicate between Lua scripts, you either need to mangle your KEYS and ARGV tables, or you need to use the standard Redis keyspace for communication between your functions.

127.0.0.1:6379> script load "return {KEYS[1], ARGV[1]}"
"d006f1a90249474274c76f5be725b8f5804a346b"
127.0.0.1:6379> eval "return f_d006f1a90249474274c76f5be725b8f5804a346b()" 1 "hello" "world"
1) "hello"
2) "world"
127.0.0.1:6379> eval "KEYS[1] = 'blah!'; return f_d006f1a90249474274c76f5be725b8f5804a346b()" 1 "hello" "world"
1) "blah!"
2) "world"
127.0.0.1:6379>

All of this said, this is in complete violation of spec, and is entirely possible to stop working in strange ways if you attempt to run this in a Redis cluster scenario.

Josiah
  • 727
  • 5
  • 15
  • Hi Josiah, excellent news. _complete violation of spec_ worries me a bit, but I'll give it some thought. It doesn't really surprise me that I didn't know about this, since it's completely undocumented. Well, except for the Redis source code itself ofcourse. – Tw Bert Mar 24 '14 at 02:29
  • Well, the trick is that the spec doesn't specify much about what actually goes on under the covers, just some things that you should and shouldn't do. It says nothing about the function being available, where the arguments are stored, etc., despite it being unchanged since the 2.4 branch with scripting from a couple years ago. – Josiah Mar 24 '14 at 17:26
2

Important Notice: See Josiah's answer below. My answer turns out to be wrong or at the least incomplete. Which makes me very happy ofcourse, it makes Redis all the more flexible.

My incorrect/incomplete answer:

I'm quite sure this is not possible. You are not allowed to use global variables (read the docs ), and the script itself gets a local and temporary scope by the Redis Lua engine.

Lua functions automatically set a 'writing' flag behind the scenes if they do any write action. This starts a transaction. If you cascade Lua calls, the bookkeeping in Redis would become very cumbersome, especially when the cascade is executed on a Redis slave. That's why EVAL and EVALSHA are intentionally not made available as valid Redis calls inside a Lua script. Same goes for calling an already 'loaded' Lua function which you are trying to do. What would happen if the slave is rebooted between the load of the first script and the exec of the second script?

What we do to overcome this limitation:

Don't use EVAL, only use SCRIPT LOAD and EVALSHA. Store the SHA1 inside a redis hash set.

We automated this in our versioning system, so a committed Lua script automatically gets it's SHA1 checksum stored in the Redis master, in a hash set, with a logical name. The clients can't use EVAL (on a slave; we disabled EVAL+LOAD in config). But the client can ask for the SHA1 for the next step. Almost all our Lua functions return a SHA1 for the next call.

Hope this helps, TW

Tw Bert
  • 3,659
  • 20
  • 28
  • I'm not really sure what you mean by "But the client can ask for the SHA1 for the next step" or "for the next call". So what I'm hearing is that you can't run scripts from within scripts but you can use EVALSHA outside of the scripts? How does this help? – Tim Fairbrother Feb 21 '14 at 02:10
  • It helps in running a centralized tested unit of code. Which might be a different thing from what you are after, I'm not sure. I do hope I made clear that the Lua script has to be functional 'on it's own'. That's by design (and quite beautiful in my opinion). Redis Lua is not meant to be a codebase. – Tw Bert Feb 21 '14 at 06:57
  • Ok thanks. I just had some methods that are used for formatting the response and wanted to reuse them in other scripts. Looks like I will need to duplicate the functions. – Tim Fairbrother Feb 22 '14 at 22:51
  • You're welcome, and that's correct. It might not seem "nice" from a programmer's point of view, but if you look at it from a distributed transactional NoSql point of view, it makes a lot of sense. And it's very powerful, you can 'slave' your logic all around the web. I can recommend source control automation for these kind of issues, if it suits the scope/size of your project. In generic NoSql terms: denormalisation is very common. Here you can see it applies to source code as well. – Tw Bert Feb 23 '14 at 01:10
0

Because I'm not one to leave well enough alone, I built a package that allows for simple internal calling semantics. The package (for Python) is available on GitHub.

Long story short, it uses ARGV as a call stack, translates KEYS/ARGV references to _KEYS and _ARGV, uses Redis as a name -> hash mapping internally, and translates CALL.<name>(<keys>, <argv>) to a table append + Redis lookup + Lua function call.

The METHOD.txt file describes what goes on, and all of the regular expressions I used to translate the Lua scripts are available in lua_call.py. Feel free to re-use my semantics.

The use of the function registry makes this very unlikely to work in Redis cluster or any other multi-shard setup, but for single-master applications, it should work for the foreseeable future.

Josiah
  • 727
  • 5
  • 15