fakeredis icon indicating copy to clipboard operation
fakeredis copied to clipboard

script command for Redis-cli v2.6.17

Open rbrenes opened this issue 11 years ago • 10 comments

ERR unknown command 'script' (Redis::CommandError) tryint to do a Redis.script( :load, @script_file_content )

rbrenes avatar Feb 05 '14 19:02 rbrenes

evalsha command missing too, for redis-cli v2.6.17

rbrenes avatar Feb 05 '14 19:02 rbrenes

Is anyone working on this ? @rbrenes What is the workaround you are using ?

bsubramaniam avatar Sep 06 '14 11:09 bsubramaniam

Bump. We're trying to use fakeredis in testing with the Kue package. fakeredis crashes and says the script command is not implemented and to tell you if we want it :+1:

hurricane766 avatar Jun 17 '16 16:06 hurricane766

Without adding a bunch (including Lua) into this gem, I started forking Redis when starting any tests and terminating on Kernel.at_exit.

carsonreinke avatar Jun 17 '16 18:06 carsonreinke

Ya, for now we've done something similar... just defeats the purpose of fakeredis, and the error said we should tell guilleiguaran if we want it.

hurricane766 avatar Jun 17 '16 18:06 hurricane766

One alternative would be to use camcorder to mock those missing Redis methods.

carsonreinke avatar Jun 17 '16 19:06 carsonreinke

I've been working around the absence of the script, eval, and evalsha commands by just implementing the specific functionality I need for testing. Remember, the primary reason for using LUA scripts with Redis is for the atomicity -- this is usually not needed in testing.

For example, we use the Redlock gem which currently makes use of script and evalsha (earlier versions used eval only).

# spec/support/redlock_helper.rb
# Monkey patching script and evalsha into Fakeredis for Redlock usage in M3LocationSyncWorkerSpec
class Redis
  module Connection
    class Memory
      UNLOCK_SCRIPT_SHA = 'abcd'
      LOCK_SCRIPT_SHA = 'bcde'
      EXTEND_LIFE_SCRIPT_SHA = 'cdef'

      def script(*args)
        case args[1]
        when Redlock::Client::RedisInstance::UNLOCK_SCRIPT
          return UNLOCK_SCRIPT_SHA
        when Redlock::Client::RedisInstance::LOCK_SCRIPT
          return LOCK_SCRIPT_SHA
        when Redlock::Client::RedisInstance::EXTEND_LIFE_SCRIPT
          return EXTEND_LIFE_SCRIPT_SHA
        else
          raise Redis::CommandError, "ERR unknown command 'script'"
        end
      end

      def evalsha(*args)
        case args[0]
        when UNLOCK_SCRIPT_SHA
          if get(args[2]) == args[3]
            del(args[2])
          else
            return 0
          end
        when LOCK_SCRIPT_SHA
          if !exists(args[2]) || get(args[2]) == args[3]
            set(args[2], args[3], 'PX', args[4])
          end
        when EXTEND_LIFE_SCRIPT_SHA
          if get(args[2]) == args[3]
            expire(args[2], args[4])
            return 0
          else
            return 1
          end
        else
          raise Redis::CommandError, "ERR unknown command 'evalsha'"
        end
      end
    end
  end
end

The script method above assumes you're calling :load (which is all Redlock does) and then just returns a hardcoded SHA for each script.

The evalsha method then implements the LUA found in the script in Ruby.

Both methods preserve the behavior of raising when an unknown script is eval'd or loaded.

hobodave avatar Oct 12 '16 19:10 hobodave

This is making the assumption that Redlock will not calculate the SHA independently of Redis.

carsonreinke avatar Oct 14 '16 12:10 carsonreinke

I had to update the stubs to support redlock 1.3 and fakeredis 0.9

# Based on https://github.com/guilleiguaran/fakeredis/issues/94
# Updated for redlock 1.3
# Monkey patching evalsha into Fakeredis for Redlock usage
class Redis
  module Connection
    class Memory
      def evalsha(*args)
        script_sha = args[0]
        mystery_arg = args[1]
        resource = args[2]
        argv = args[3..]
        case script_sha
        when Redlock::Scripts::UNLOCK_SCRIPT_SHA
          # Original Redlock unlock Lua script to be translated to Fakeredis
          # if redis.call("get",KEYS[1]) == ARGV[1] then
          #   return redis.call("del",KEYS[1])
          # else
          #   return 0
          # end
          if get(resource) == argv[0]
            del(resource)
          else
            return 0
          end
        when Redlock::Scripts::LOCK_SCRIPT_SHA
          # Original Redlock lock Lua script to be translated to Fakeredis
          # if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
          #   return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
          # end
          if !exists?(resource) || get(resource) == argv[0]
            set(resource, argv[0], 'PX', argv[1])
          end
        else
          raise Redis::CommandError, "ERR unknown command 'evalsha' for #{script_sha}"
        end
      end
    end
  end
end

polmuz avatar May 04 '23 17:05 polmuz

Thanks @polmuz . This is exactly what I needed. This allowed me to test without mocking Redlock itself, which is great!

BTW, Redlock 1.3.2 needed Redlock::Scripts::PTTL_SCRIPT_SHA as well, so I've added it below:

# Based on https://github.com/guilleiguaran/fakeredis/issues/94
# Updated for redlock 1.3
# Monkey patching evalsha into Fakeredis for Redlock usage
class Redis
  module Connection
    class Memory
      def evalsha(*args)
        script_sha = args[0]
        mystery_arg = args[1]
        resource = args[2]
        argv = args[3..]
        case script_sha
        when Redlock::Scripts::UNLOCK_SCRIPT_SHA
          # Original Redlock unlock Lua script to be translated to Fakeredis
          # if redis.call("get",KEYS[1]) == ARGV[1] then
          #   return redis.call("del",KEYS[1])
          # else
          #   return 0
          # end
          if get(resource) == argv[0]
            del(resource)
          else
            0
          end
        when Redlock::Scripts::LOCK_SCRIPT_SHA
          # Original Redlock lock Lua script to be translated to Fakeredis
          # if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
          #   return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
          # end
          if !exists?(resource) || get(resource) == argv[0]
            set(resource, argv[0], 'PX', argv[1])
          end
        when Redlock::Scripts::PTTL_SCRIPT_SHA
          # Original Redlock lock Lua script to be translated to Fakeredis
          # return { redis.call("get", KEYS[1]), redis.call("pttl", KEYS[1]) }
          value = get(resource)
          ttl = pttl(resource)
          [value, ttl]
        else
          raise Redis::CommandError, "ERR unknown command 'evalsha' for #{script_sha}"
        end
      end
    end
  end
end

timherby avatar Mar 13 '24 02:03 timherby