diff options
-rw-r--r-- | redis/client.py | 11 | ||||
-rw-r--r-- | tests/test_scripting.py | 81 |
2 files changed, 91 insertions, 1 deletions
diff --git a/redis/client.py b/redis/client.py index 78b3dbe..27162f6 100644 --- a/redis/client.py +++ b/redis/client.py @@ -2383,11 +2383,14 @@ class BasePipeline(object): scripts = list(self.scripts) immediate = self.immediate_execute_command shas = [s.sha for s in scripts] + # we can't use the normal script_* methods because they would just + # get buffered in the pipeline. exists = immediate('SCRIPT', 'EXISTS', *shas, **{'parse': 'EXISTS'}) if not all(exists): for s, exist in izip(scripts, exists): if not exist: - immediate('SCRIPT', 'LOAD', s.script, **{'parse': 'LOAD'}) + s.sha = immediate('SCRIPT', 'LOAD', s.script, + **{'parse': 'LOAD'}) def execute(self, raise_on_error=True): "Execute all the commands in the current pipeline" @@ -2439,6 +2442,12 @@ class BasePipeline(object): def script_load_for_pipeline(self, script): "Make sure scripts are loaded prior to pipeline execution" + # we need the sha now so that Script.__call__ can use it to run + # evalsha. + if not script.sha: + script.sha = self.immediate_execute_command('SCRIPT', 'LOAD', + script.script, + **{'parse': 'LOAD'}) self.scripts.add(script) diff --git a/tests/test_scripting.py b/tests/test_scripting.py new file mode 100644 index 0000000..48f6138 --- /dev/null +++ b/tests/test_scripting.py @@ -0,0 +1,81 @@ +from __future__ import with_statement +import pytest + +from redis import exceptions + + +multiply_script = """ +local value = redis.call('GET', KEYS[1]) +value = tonumber(value) +return value * ARGV[1]""" + + +class TestScripting(object): + @pytest.fixture(autouse=True) + def reset_scripts(self, r): + r.script_flush() + + def test_eval(self, r): + r.set('a', 2) + # 2 * 3 == 6 + assert r.eval(multiply_script, 1, 'a', 3) == 6 + + def test_evalsha(self, r): + r.set('a', 2) + sha = r.script_load(multiply_script) + # 2 * 3 == 6 + assert r.evalsha(sha, 1, 'a', 3) == 6 + + def test_evalsha_script_not_loaded(self, r): + r.set('a', 2) + sha = r.script_load(multiply_script) + # remove the script from Redis's cache + r.script_flush() + with pytest.raises(exceptions.NoScriptError): + r.evalsha(sha, 1, 'a', 3) + + def test_script_loading(self, r): + # get the sha, then clear the cache + sha = r.script_load(multiply_script) + r.script_flush() + assert r.script_exists(sha) == [False] + r.script_load(multiply_script) + assert r.script_exists(sha) == [True] + + def test_script_object(self, r): + r.set('a', 2) + multiply = r.register_script(multiply_script) + assert not multiply.sha + # test evalsha fail -> script load + retry + assert multiply(keys=['a'], args=[3]) == 6 + assert multiply.sha + assert r.script_exists(multiply.sha) == [True] + # test first evalsha + assert multiply(keys=['a'], args=[3]) == 6 + + def test_script_object_in_pipeline(self, r): + multiply = r.register_script(multiply_script) + assert not multiply.sha + pipe = r.pipeline() + pipe.set('a', 2) + pipe.get('a') + multiply(keys=['a'], args=[3], client=pipe) + # even though the pipeline wasn't executed yet, we made sure the + # script was loaded and got a valid sha + assert multiply.sha + assert r.script_exists(multiply.sha) == [True] + # [SET worked, GET 'a', result of multiple script] + assert pipe.execute() == [True, '2', 6] + + # purge the script from redis's cache and re-run the pipeline + # the multiply script object knows it's sha, so it shouldn't get + # reloaded until pipe.execute() + r.script_flush() + pipe = r.pipeline() + pipe.set('a', 2) + pipe.get('a') + assert multiply.sha + multiply(keys=['a'], args=[3], client=pipe) + assert r.script_exists(multiply.sha) == [False] + # [SET worked, GET 'a', result of multiple script] + assert pipe.execute() == [True, '2', 6] |