Skip to content

Commit

Permalink
Fix HDEL and HSET
Browse files Browse the repository at this point in the history
This commit also adds a bunch of documentation for hash commands
  • Loading branch information
jgaskins committed Nov 15, 2023
1 parent 4119837 commit 9d49409
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 30 deletions.
44 changes: 41 additions & 3 deletions spec/redis_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,49 @@ describe Redis::Client do
end

describe "hash" do
test "hset returns the number of new fields set on the given key" do
redis.hset(key, one: "", two: "").should eq 2

# Only "three" is added, the others already existed
redis.hset(key, {"one" => "", "two" => "", "three" => ""}).should eq 1

# "four" and "five" are both new
redis.hset(key, %w[one yes two yes three yes four yes five yes]).should eq 2
end

test "hmget returns the given fields for the given key" do
redis.hset key, one: "first", two: "second"

redis.hget(key, "one").should eq "first"
redis.hget(key, "nonexistent").should eq nil
redis.hmget(key, "one", "nonexistent").should eq ["first", nil]
redis.hmget(key, %w[one nonexistent]).should eq ["first", nil]
redis.hmget(key, "nope", "lol").should eq [nil, nil]
redis.hmget(key, %w[nope lol]).should eq [nil, nil]
end

test "hincrby increments the number stored at field in the hash" do
redis.hset(key, {"field" => "5"})
redis.hincrby(key, "field", 1).should eq(6)
redis.hincrby(key, "field", -1).should eq(5)
redis.hincrby(key, "field", -10).should eq(-5)
redis.hincrby(key, "field", 1).should eq 6
redis.hincrby(key, "field", -1).should eq 5
redis.hincrby(key, "field", -10).should eq -5
end

test "hdel deletes fields from hashes" do
redis.hset key,
name: "foo",
splat_arg: "yes",
array_arg: "also yes",
array_arg2: "still yes"

redis.hdel(key, "splat_arg", "nonexistent-field").should eq 1
redis.hdel(key, %w[array_arg array_arg2 nonexistent-field]).should eq 2
end

test "hsetnx sets fields on a key only if they do not exist" do
redis.hsetnx(key, "first", "lol").should eq 1
redis.hsetnx(key, "first", "omg").should eq 0
redis.hsetnx(key, "second", "lol").should eq 1
end
end

Expand Down
143 changes: 116 additions & 27 deletions src/commands/hash.cr
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
module Redis::Commands::Hash
# Delete one or more `fields` from the given `key`, returning the number of
# deleted fields.
#
# ```
# redis.hdel "my-hash",
# "pending",
# "nonexistent-field"
# # => 1
# ```
def hdel(key : String, *fields : String)
run({"hdel"} + fields)
run({"hdel", key} + fields)
end

# Delete one or more `fields` from the given `key`, returning the number of
# deleted fields.
#
# ```
# fields = %w[pending nonexistent-field]
# redis.hdel "my-hash", fields
# # => 1
# ```
def hdel(key : String, fields : Enumerable(String))
command = Array(String).new(initial_capacity: 2 + fields.size)
command << "hdel" << key
Expand All @@ -11,18 +28,62 @@ module Redis::Commands::Hash
run command
end

# Return the value of `field` for `key`, if both exist.
#
# ```
# redis.hset "person:jamie", email: "jamie@example.com"
# redis.hget "person:jamie", "email" # => "jamie@example.com"
# redis.hget "person:jamie", "password" # => nil
# ```
def hget(key : String, field : String)
run({"hget", key, field})
end

# Return the entire hash stored at `key` as an `Array`
#
# ```
# redis.hset "person:jamie", email: "jamie@example.com", name: "Jamie"
# redis.hgetall "person:jamie"
# # => ["email", "jamie@example.com", "name", "Jamie"]
# ```
def hgetall(key : String)
run({"hgetall", key})
end

# Return the values for `fields` in `key` as an `Array`
#
# ```
# redis.hset "person:jamie", email: "jamie@example.com", name: "Jamie"
# redis.hmget "person:jamie", "email", "name" # => ["jamie@example.com", "Jamie"]
# redis.hmget "person:jamie", "nonexistent", "fake-field" # => [nil, nil]
# ```
def hmget(key : String, *fields : String)
run({"hmget", key} + fields)
end

# Return the values for `fields` in `key` as an `Array`
#
# ```
# redis.hset "person:jamie", email: "jamie@example.com", name: "Jamie"
# redis.hmget "person:jamie", %w[email name] # => ["jamie@example.com", "Jamie"]
# redis.hmget "person:jamie", %w[nonexistent fake-field] # => [nil, nil]
# ```
def hmget(key : String, fields : Enumerable(String))
command = Array(String).new(initial_capacity: 2 + fields.size)
command << "hmget" << key
fields.each { |field| command << field }

run command
end

# Set the values for `fields` in the hash stored in `key`, returning the
# number of fields created (not updated)
#
# ```
# redis.hset "person:jamie", email: "jamie@example.com", name: "Jamie" # => 2
# redis.hset "person:jamie", email: "jamie@example.dev", admin: "true" # => 1
# redis.hset "person:jamie", admin: "false" # => 0
# ```
def hset(key : String, **fields : String)
hash = ::Hash(String, String).new(initial_capacity: fields.size)
fields.each do |key, value|
Expand All @@ -32,51 +93,79 @@ module Redis::Commands::Hash
hset key, hash
end

# Set the values for `fields` in the hash stored in `key`, returning the
# number of fields created (not updated).
#
# NOTE: You _MUST_ pass an even number of arguments to `fields`
#
# ```
# redis.hset "person:jamie", "email", "jamie@example.com", "name", "Jamie" # => 2
# redis.hset "person:jamie", "email", "jamie@example.dev", "admin", "true" # => 1
# redis.hset "person:jamie", "admin", "false" # => 0
# ```
def hset(key : String, *fields : String)
run({"hset", key} + fields)
end

def hset(key : String, data : ::Hash(String, String))
command = Array(String).new(initial_capacity: 2 + data.size)
# Set the values for `fields` in the hash stored in `key`, returning the
# number of fields created (not updated)
#
# NOTE: `fields` _MUST_ contain an even number of elements
#
# ```
# redis.hset "person:jamie", %w[email jamie@example.com name Jamie] # => 2
# redis.hset "person:jamie", %w[email jamie@example.dev admin true] # => 1
# redis.hset "person:jamie", %w[admin false] # => 0
# ```
def hset(key : String, fields : Enumerable(String))
command = Array(String).new(initial_capacity: 2 + fields.size)

command << "hset" << key
data.each do |key, value|
command << key << value
end
fields.each { |field| command << field }

run command
end

def hsetnx(key : String, **fields : String)
# Set the values for `fields` in the hash stored in `key`, returning the
# number of fields created (not updated)
#
# ```
# redis.hset "person:jamie", {"email" => "jamie@example.com", "name" => "Jamie"} # => 2
# redis.hset "person:jamie", {"email" => "jamie@example.dev", "admin" => "true"} # => 1
# redis.hset "person:jamie", {"admin" => "false"} # => 0
# ```
def hset(key : String, fields : ::Hash(String, String))
command = Array(String).new(initial_capacity: 2 + 2 * fields.size)
command << "hsetnx" << key
fields.each do |key, value|
command << key.to_s << value
end

run command
end

def hsetnx(key : String, fields : Hash(String, String))
command = Array(String).new(initial_capacity: 2 + 2 * fields.size)
command << "hsetnx" << key
command << "hset" << key
fields.each { |key, value| command << key << value }

run command
end

def hsetnx(key : String, fields : Enumerable(String))
if fields.size.even?
raise ArgumentError.new("fields must have an even number of elements")
end

command = Array(String).new(initial_capacity: 2 + fields.size)
command << "hsetnx" << key
fields.each { |value| command << key }

run command
# Set `field` in the hash stored in `key` to `value` if and only if it does not exist. Returns `1` if the field was set, `0` if it was not.
#
# ```
# id = 1234
#
# redis.hsetnx "job:#{id}", "locked_at", Time.utc.to_rfc3339 # => 1
# # Returned 1, lock succeeds
#
# redis.hsetnx "job:#{id}", "locked_at", Time.utc.to_rfc3339 # => 0
# # Returned 0, lock did not succeed, so the job is already being processed
# ```
def hsetnx(key : String, field : String, value : String)
run({"hsetnx", key, field, value})
end

# Increment the numeric value for `field` in the hash stored in `key` by
# `increment`, returning the new value.
#
# ```
# id = 1234
# redis.hincrby "posts:#{id}", "likes", 1 # => 1
# redis.hincrby "posts:#{id}", "likes", 1 # => 2
# ```
def hincrby(key : String, field : String, increment : Int | String)
run({"hincrby", key, field, increment.to_s})
end
Expand Down

0 comments on commit 9d49409

Please sign in to comment.