From 1ae4d4a0eed3a308fa96fa6b0c953f19930e4f1f Mon Sep 17 00:00:00 2001 From: atakavci Date: Fri, 17 May 2024 16:37:03 +0300 Subject: [PATCH] support for hash field expiration HPEXPIRETIME, HPTTL, HPERSIST, HGETF, HSETF --- .../Enums/HashFieldFlags.cs | 63 +++ src/StackExchange.Redis/Enums/RedisCommand.cs | 12 + .../Interfaces/IDatabase.cs | 194 ++++++++- .../Interfaces/IDatabaseAsync.cs | 192 +++++++++ .../KeyspaceIsolation/KeyPrefixed.cs | 36 ++ .../KeyspaceIsolation/KeyPrefixedDatabase.cs | 47 ++- .../PublicAPI/PublicAPI.Shipped.txt | 31 ++ src/StackExchange.Redis/RedisDatabase.cs | 209 ++++++++++ src/StackExchange.Redis/RedisLiterals.cs | 1 + src/StackExchange.Redis/ResultProcessor.cs | 71 +++- .../HashFieldTests.cs | 393 ++++++++++++++++++ 11 files changed, 1236 insertions(+), 13 deletions(-) create mode 100644 src/StackExchange.Redis/Enums/HashFieldFlags.cs diff --git a/src/StackExchange.Redis/Enums/HashFieldFlags.cs b/src/StackExchange.Redis/Enums/HashFieldFlags.cs new file mode 100644 index 000000000..0320ff31b --- /dev/null +++ b/src/StackExchange.Redis/Enums/HashFieldFlags.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StackExchange.Redis; + +/// +/// Specifies the options to the HSETF command when to create hash/fields and the return data +/// +[Flags] +public enum HashFieldFlags +{ + + /// + /// No options specified. + /// + None = 0, + /// + /// When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// + DC = 1, + /// + /// When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// + DCF = 2, + /// + /// When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + DOF = 4, + /// + /// When GETNEW is specified: returns the new value of given fields + /// + GETNEW = 8, + /// + /// When GETOLD is specified: returns the old value of given fields + /// + GETOLD = 16, +} + +internal static class HashFieldFlagsExtensions +{ + internal static bool isNone(this HashFieldFlags flags) => + flags == HashFieldFlags.None; + internal static bool isDC(this HashFieldFlags flags) => + flags.HasFlag(HashFieldFlags.DC); + + internal static bool isDCF(this HashFieldFlags flags) => + flags.HasFlag(HashFieldFlags.DCF); + + internal static bool isDOF(this HashFieldFlags flags) => + flags.HasFlag(HashFieldFlags.DOF); + + internal static bool isGETNEW(this HashFieldFlags flags) => + flags.HasFlag(HashFieldFlags.GETNEW); + + internal static bool isGETOLD(this HashFieldFlags flags) => + flags.HasFlag(HashFieldFlags.GETOLD); + + internal static List ToRedisValueList(this HashFieldFlags flags) => + flags.isNone() ? new List() : flags.ToString().Split(',').Select(v => (RedisValue)v).ToList(); + +} + diff --git a/src/StackExchange.Redis/Enums/RedisCommand.cs b/src/StackExchange.Redis/Enums/RedisCommand.cs index a910c7e49..1d228e36c 100644 --- a/src/StackExchange.Redis/Enums/RedisCommand.cs +++ b/src/StackExchange.Redis/Enums/RedisCommand.cs @@ -68,19 +68,25 @@ internal enum RedisCommand HEXISTS, HEXPIRE, HEXPIREAT, + HEXPIRETIME, HGET, HGETALL, + HGETF, HINCRBY, HINCRBYFLOAT, HKEYS, HLEN, HMGET, HMSET, + HPERSIST, HPEXPIRE, HPEXPIREAT, + HPEXPIRETIME, + HPTTL, HRANDFIELD, HSCAN, HSET, + HSETF, HSETNX, HSTRLEN, HVALS, @@ -285,12 +291,15 @@ internal static bool IsPrimaryOnly(this RedisCommand command) case RedisCommand.HDEL: case RedisCommand.HEXPIRE: case RedisCommand.HEXPIREAT: + case RedisCommand.HGETF: case RedisCommand.HINCRBY: case RedisCommand.HINCRBYFLOAT: case RedisCommand.HMSET: + case RedisCommand.HPERSIST: case RedisCommand.HPEXPIRE: case RedisCommand.HPEXPIREAT: case RedisCommand.HSET: + case RedisCommand.HSETF: case RedisCommand.HSETNX: case RedisCommand.INCR: case RedisCommand.INCRBY: @@ -386,11 +395,14 @@ internal static bool IsPrimaryOnly(this RedisCommand command) case RedisCommand.GETRANGE: case RedisCommand.HELLO: case RedisCommand.HEXISTS: + case RedisCommand.HEXPIRETIME: case RedisCommand.HGET: case RedisCommand.HGETALL: case RedisCommand.HKEYS: case RedisCommand.HLEN: case RedisCommand.HMGET: + case RedisCommand.HPEXPIRETIME: + case RedisCommand.HPTTL: case RedisCommand.HRANDFIELD: case RedisCommand.HSCAN: case RedisCommand.HSTRLEN: diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs index f5db64022..b5baf75a1 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabase.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs @@ -394,6 +394,84 @@ public interface IDatabase : IRedis, IDatabaseAsync /// ExpireResult[]? HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + /// + /// For specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch) + /// + /// The key of the hash. + /// The field in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given field + /// expiration time: as a UNIX timestamp in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + long? HashFieldExpireTime(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch) + /// + /// The key of the hash. + /// The fields in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// expiration time: as a UNIX timestamp in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + long[]? HashFieldExpireTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For specified field, it removes the expiration time + /// + /// The key of the hash. + /// The field in the hash to remove expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given field + /// 1: if the expiration time was removed + /// -1: if field has no associated expiration time + /// -2: no such field + /// + long? HashFieldPersist(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it removes the expiration time + /// + /// The key of the hash. + /// The fields in the hash to remove expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// 1: if the expiration time was removed + /// -1: if field has no associated expiration time + /// -2: no such field + /// + long[]? HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For specified field, it gets the remaining time to live in milliseconds + /// + /// The key of the hash. + /// The field in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given field + /// time to live: in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + long? HashFieldTimeToLive(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the remaining time to live in milliseconds + /// + /// The key of the hash. + /// The fields in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// time to live: in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + long[]? HashFieldTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + /// /// Returns the value associated with field in the hash stored at key. /// @@ -426,6 +504,120 @@ public interface IDatabase : IRedis, IDatabaseAsync /// RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + /// + /// For each specified field, it gets the value and sets the field's remaining time to live + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The time out to set in milliseconds + /// under which condition the expiration will be set using . + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// value: value of field + /// nil: if no such field exists + /// + RedisValue[]? HashGet(RedisKey key, RedisValue[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the value and sets the field's expiration timestamp + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The exact date to expiry to set. + /// under which condition the expiration will be set using . + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// value: value of field + /// nil: if no such field exists + /// + RedisValue[]? HashGet(RedisKey key, RedisValue[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the value and removes the field's expiration + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// value: value of field + /// nil: if no such field exists + /// + RedisValue[]? HashGetPersistFields(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it sets the value and optionally sets the fields expiration + /// Depending on HashFieldFlags it creates the hash key or its fields in case they dont exist + /// - When DC is not specified: if key does not exist: create key + /// - When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// - When neither DCF nor DOF are specified: for each specified field: if such field does not exist: create field; set all fields' value and expiration time + /// - When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// - When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// Whether to maintain the existing expiration. + /// + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// return value depends on the HashFieldFlags (GETNEW/GETOLD) + /// if GETNEW is specified: the value of field after command execution + /// if GETOLD is specified: the value of field before command execution + /// if GETNEW or GETOLD is not specified: a + b where + /// a: 1 if the field's value was set or 0 if not (DCF/DOF met) + /// b: 2 if the field's expiration time was set/discarded or 0 if not (DCF/DOF met, NX/XX/GT/LT not met) + /// + RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, bool keepExpiry, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it sets the value and optionally sets the fields expiration + /// Depending on HashFieldFlags it creates the hash key or its fields in case they dont exist + /// - When DC is not specified: if key does not exist: create key + /// - When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// - When neither DCF nor DOF are specified: for each specified field: if such field does not exist: create field; set all fields' value and expiration time + /// - When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// - When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The time out to set in milliseconds + /// under which condition the expiration will be set using . + /// + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// return value depends on the HashFieldFlags (GETNEW/GETOLD) + /// if GETNEW is specified: the value of field after command execution + /// if GETOLD is specified: the value of field before command execution + /// if GETNEW or GETOLD is not specified: a + b where + /// a: 1 if the field's value was set or 0 if not (DCF/DOF met) + /// b: 2 if the field's expiration time was set/discarded or 0 if not (DCF/DOF met, NX/XX/GT/LT not met) + /// + RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it sets the value and optionally sets the fields expiration + /// Depending on HashFieldFlags it creates the hash key or its fields in case they dont exist + /// - When DC is not specified: if key does not exist: create key + /// - When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// - When neither DCF nor DOF are specified: for each specified field: if such field does not exist: create field; set all fields' value and expiration time + /// - When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// - When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The exact date to expiry to set. + /// under which condition the expiration will be set using . + /// + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// return value depends on the HashFieldFlags (GETNEW/GETOLD) + /// if GETNEW is specified: the value of field after command execution + /// if GETOLD is specified: the value of field before command execution + /// if GETNEW or GETOLD is not specified: a + b where + /// a: 1 if the field's value was set or 0 if not (DCF/DOF met) + /// b: 2 if the field's expiration time was set/discarded or 0 if not (DCF/DOF met, NX/XX/GT/LT not met) + /// + RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None); + /// /// Returns all fields and values of the hash stored at key. /// @@ -1697,7 +1889,7 @@ public interface IDatabase : IRedis, IDatabaseAsync /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] - bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when, CommandFlags flags= CommandFlags.None); + bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when, CommandFlags flags = CommandFlags.None); /// /// Adds the specified member with the specified score to the sorted set stored at key. diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs index e49fba6f9..58291e0f7 100644 --- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs +++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs @@ -380,6 +380,198 @@ public interface IDatabaseAsync : IRedisAsync /// Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + /// + /// For specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch) + /// + /// The key of the hash. + /// The field in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given field + /// expiration time: as a UNIX timestamp in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + Task HashFieldExpireTimeAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the expiration time as a Unix timestamp in milliseconds (milliseconds since the Unix epoch) + /// + /// The key of the hash. + /// The fields in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// expiration time: as a UNIX timestamp in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + Task HashFieldExpireTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For specified field, it removes the expiration time + /// + /// The key of the hash. + /// The field in the hash to remove expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given field + /// 1: if the expiration time was removed + /// -1: if field has no associated expiration time + /// -2: no such field + /// + Task HashFieldPersistAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it removes the expiration time + /// + /// The key of the hash. + /// The fields in the hash to remove expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// 1: if the expiration time was removed + /// -1: if field has no associated expiration time + /// -2: no such field + /// + Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For specified field, it gets the remaining time to live in milliseconds + /// + /// The key of the hash. + /// The field in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given field + /// time to live: in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + Task HashFieldTimeToLiveAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the remaining time to live in milliseconds + /// + /// The key of the hash. + /// The fields in the hash to get expire time. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// time to live: in milliseconds + /// -1: if field has no associated expiration time + /// -2: no such field + /// + Task HashFieldTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the value and sets the field's remaining time to live + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The time out to set in milliseconds + /// under which condition the expiration will be set using . + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// value: value of field + /// nil: if no such field exists + /// + Task HashGetAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the value and sets the field's expiration timestamp + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The exact date to expiry to set. + /// under which condition the expiration will be set using . + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// value: value of field + /// nil: if no such field exists + /// + Task HashGetAsync(RedisKey key, RedisValue[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it gets the value and removes the field's expiration + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// value: value of field + /// nil: if no such field exists + /// + Task HashGetPersistFieldsAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it sets the value and optionally sets the fields expiration + /// Depending on HashFieldFlags it creates the hash key or its fields in case they dont exist + /// - When DC is not specified: if key does not exist: create key + /// - When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// - When neither DCF nor DOF are specified: for each specified field: if such field does not exist: create field; set all fields' value and expiration time + /// - When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// - When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// Whether to maintain the existing expiration. + /// + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// return value depends on the HashFieldFlags (GETNEW/GETOLD) + /// if GETNEW is specified: the value of field after command execution + /// if GETOLD is specified: the value of field before command execution + /// if GETNEW or GETOLD is not specified: a + b where + /// a: 1 if the field's value was set or 0 if not (DCF/DOF met) + /// b: 2 if the field's expiration time was set/discarded or 0 if not (DCF/DOF met, NX/XX/GT/LT not met) + /// + Task HashSetAsync(RedisKey key, HashEntry[] hashFields, bool keepExpiry, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it sets the value and optionally sets the fields expiration + /// Depending on HashFieldFlags it creates the hash key or its fields in case they dont exist + /// - When DC is not specified: if key does not exist: create key + /// - When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// - When neither DCF nor DOF are specified: for each specified field: if such field does not exist: create field; set all fields' value and expiration time + /// - When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// - When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The time out to set in milliseconds + /// under which condition the expiration will be set using . + /// + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// return value depends on the HashFieldFlags (GETNEW/GETOLD) + /// if GETNEW is specified: the value of field after command execution + /// if GETOLD is specified: the value of field before command execution + /// if GETNEW or GETOLD is not specified: a + b where + /// a: 1 if the field's value was set or 0 if not (DCF/DOF met) + /// b: 2 if the field's expiration time was set/discarded or 0 if not (DCF/DOF met, NX/XX/GT/LT not met) + /// + Task HashSetAsync(RedisKey key, HashEntry[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None); + + /// + /// For each specified field, it sets the value and optionally sets the fields expiration + /// Depending on HashFieldFlags it creates the hash key or its fields in case they dont exist + /// - When DC is not specified: if key does not exist: create key + /// - When DC (“Don’t Create”) is specified: if key does not exist: do nothing (don’t create key) + /// - When neither DCF nor DOF are specified: for each specified field: if such field does not exist: create field; set all fields' value and expiration time + /// - When DCF (“Don’t Create Fields”) is specified: for each specified field: if the field already exists: set the field's value and expiration time; ignore fields that do not exist + /// - When DOF (“Don’t Overwrite Fields”) is specified: for each specified field: if such field does not exist: create field and set its value and expiration time; ignore fields that already exists + /// + /// The key of the hash. + /// The fields in the hash for this operation. + /// The exact date to expiry to set. + /// under which condition the expiration will be set using . + /// + /// The flags to use for this operation. + /// null if the key does not exist. Otherwise returns the result of operation for given fields + /// return value depends on the HashFieldFlags (GETNEW/GETOLD) + /// if GETNEW is specified: the value of field after command execution + /// if GETOLD is specified: the value of field before command execution + /// if GETNEW or GETOLD is not specified: a + b where + /// a: 1 if the field's value was set or 0 if not (DCF/DOF met) + /// b: 2 if the field's expiration time was set/discarded or 0 if not (DCF/DOF met, NX/XX/GT/LT not met) + /// + Task HashSetAsync(RedisKey key, HashEntry[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None); + /// /// Returns the value associated with field in the hash stored at key. /// diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs index ec13180ac..6f59a408a 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs @@ -96,6 +96,42 @@ public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFla public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpireAsync(ToInner(key), hashFields, expiry, when, flags); + public Task HashFieldExpireTimeAsync(RedisKey key, RedisValue hashField, CommandFlags flags) => + Inner.HashFieldExpireTimeAsync(ToInner(key), hashField, flags); + + public Task HashFieldExpireTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldExpireTimeAsync(ToInner(key), hashFields, flags); + + public Task HashFieldPersistAsync(RedisKey key, RedisValue hashField, CommandFlags flags) => + Inner.HashFieldPersistAsync(ToInner(key), hashField, flags); + + public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldPersistAsync(ToInner(key), hashFields, flags); + + public Task HashFieldTimeToLiveAsync(RedisKey key, RedisValue hashField, CommandFlags flags) => + Inner.HashFieldTimeToLiveAsync(ToInner(key), hashField, flags); + + public Task HashFieldTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldTimeToLiveAsync(ToInner(key), hashFields, flags); + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expireDuration, ExpireWhen when, CommandFlags flags) => + Inner.HashGetAsync(ToInner(key), hashFields, expireDuration, when, flags); + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, DateTime expireTime, ExpireWhen when, CommandFlags flags) => + Inner.HashGetAsync(ToInner(key), hashFields, expireTime, when, flags); + + public Task HashGetPersistFieldsAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashGetPersistFieldsAsync(ToInner(key), hashFields, flags); + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, bool keepExpiry, HashFieldFlags fieldFlags, CommandFlags flags) => + Inner.HashSetAsync(ToInner(key), hashFields, keepExpiry, fieldFlags, flags); + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, TimeSpan expireDuration, ExpireWhen when, HashFieldFlags fieldFlags, CommandFlags flags) => + Inner.HashSetAsync(ToInner(key), hashFields, expireDuration, when, fieldFlags, flags); + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, DateTime expireTime, ExpireWhen when, HashFieldFlags fieldFlags, CommandFlags flags) => + Inner.HashSetAsync(ToInner(key), hashFields, expireTime, when, fieldFlags, flags); + public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.HashGetAllAsync(ToInner(key), flags); diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs index 05a4c6463..bf5583c37 100644 --- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs +++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs @@ -33,7 +33,7 @@ public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFla public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => Inner.GeoRemove(ToInner(key), member, flags); - public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None) => + public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) => Inner.GeoDistance(ToInner(key), member1, member2, unit, flags); public string?[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => @@ -48,7 +48,7 @@ public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = Comm public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) => Inner.GeoPosition(ToInner(key), member, flags); - public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null,GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => + public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => Inner.GeoRadius(ToInner(key), member, radius, unit, count, order, options, flags); public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) => @@ -93,6 +93,24 @@ public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = public ExpireResult[]? HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.HashFieldExpire(ToInner(key), hashFields, expiry, when, flags); + public long? HashFieldExpireTime(RedisKey key, RedisValue hashField, CommandFlags flags) => + Inner.HashFieldExpireTime(ToInner(key), hashField, flags); + + public long[]? HashFieldExpireTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldExpireTime(ToInner(key), hashFields, flags); + + public long? HashFieldPersist(RedisKey key, RedisValue hashField, CommandFlags flags) => + Inner.HashFieldPersist(ToInner(key), hashField, flags); + + public long[]? HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldPersist(ToInner(key), hashFields, flags); + + public long? HashFieldTimeToLive(RedisKey key, RedisValue hashField, CommandFlags flags) => + Inner.HashFieldTimeToLive(ToInner(key), hashField, flags); + + public long[]? HashFieldTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashFieldTimeToLive(ToInner(key), hashFields, flags); + public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) => Inner.HashGetAll(ToInner(key), flags); @@ -102,6 +120,24 @@ public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => Inner.HashGet(ToInner(key), hashField, flags); + public RedisValue[]? HashGet(RedisKey key, RedisValue[] hashFields, TimeSpan expireDuration, ExpireWhen when, CommandFlags flags) => + Inner.HashGet(ToInner(key), hashFields, expireDuration, when, flags); + + public RedisValue[]? HashGet(RedisKey key, RedisValue[] hashFields, DateTime expireTime, ExpireWhen when, CommandFlags flags) => + Inner.HashGet(ToInner(key), hashFields, expireTime, when, flags); + + public RedisValue[]? HashGetPersistFields(RedisKey key, RedisValue[] hashFields, CommandFlags flags) => + Inner.HashGetPersistFields(ToInner(key), hashFields, flags); + + public RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, bool keepExpiry, HashFieldFlags fieldFlags, CommandFlags flags) => + Inner.HashSet(ToInner(key), hashFields, keepExpiry, fieldFlags, flags); + + public RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, TimeSpan expireDuration, ExpireWhen when, HashFieldFlags fieldFlags, CommandFlags flags) => + Inner.HashSet(ToInner(key), hashFields, expireDuration, when, fieldFlags, flags); + + public RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, DateTime expireTime, ExpireWhen when, HashFieldFlags fieldFlags, CommandFlags flags) => + Inner.HashSet(ToInner(key), hashFields, expireTime, when, fieldFlags, flags); + public Lease? HashGetLease(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) => Inner.HashGetLease(ToInner(key), hashField, flags); @@ -419,7 +455,7 @@ public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags fla public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) => Inner.SortedSetAdd(ToInner(key), values, when, flags); - public long SortedSetAdd(RedisKey key, SortedSetEntry[] values,SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.SortedSetAdd(ToInner(key), values, when, flags); public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) => @@ -522,7 +558,7 @@ public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) => Inner.SortedSetScores(ToInner(key), members, flags); - public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values,SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => + public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => Inner.SortedSetUpdate(ToInner(key), values, when, flags); public bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) => @@ -719,7 +755,7 @@ IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int => Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags) - => Inner.SetScan(ToInner(key), pattern, pageSize, flags); + => Inner.SetScan(ToInner(key), pattern, pageSize, flags); IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) => Inner.SetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags); @@ -735,5 +771,6 @@ public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None) => public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None) => Inner.KeyTouch(ToInner(keys), flags); + } } diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt index 51bbb22df..27ba9517c 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt @@ -462,6 +462,13 @@ StackExchange.Redis.HashEntry.HashEntry(StackExchange.Redis.RedisValue name, Sta StackExchange.Redis.HashEntry.Key.get -> StackExchange.Redis.RedisValue StackExchange.Redis.HashEntry.Name.get -> StackExchange.Redis.RedisValue StackExchange.Redis.HashEntry.Value.get -> StackExchange.Redis.RedisValue +StackExchange.Redis.HashFieldFlags +StackExchange.Redis.HashFieldFlags.DC = 1 -> StackExchange.Redis.HashFieldFlags +StackExchange.Redis.HashFieldFlags.DCF = 2 -> StackExchange.Redis.HashFieldFlags +StackExchange.Redis.HashFieldFlags.DOF = 4 -> StackExchange.Redis.HashFieldFlags +StackExchange.Redis.HashFieldFlags.GETNEW = 8 -> StackExchange.Redis.HashFieldFlags +StackExchange.Redis.HashFieldFlags.GETOLD = 16 -> StackExchange.Redis.HashFieldFlags +StackExchange.Redis.HashFieldFlags.None = 0 -> StackExchange.Redis.HashFieldFlags StackExchange.Redis.HashSlotMovedEventArgs StackExchange.Redis.HashSlotMovedEventArgs.HashSlot.get -> int StackExchange.Redis.HashSlotMovedEventArgs.HashSlotMovedEventArgs(object! sender, int hashSlot, System.Net.EndPoint! old, System.Net.EndPoint! new) -> void @@ -548,8 +555,17 @@ StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult? StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult[]? StackExchange.Redis.IDatabase.HashFieldExpire(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ExpireResult[]? +StackExchange.Redis.IDatabase.HashFieldExpireTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? +StackExchange.Redis.IDatabase.HashFieldExpireTime(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]? +StackExchange.Redis.IDatabase.HashFieldPersist(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? +StackExchange.Redis.IDatabase.HashFieldPersist(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]? +StackExchange.Redis.IDatabase.HashFieldTimeToLive(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long? +StackExchange.Redis.IDatabase.HashFieldTimeToLive(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]? StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! +StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expireTime, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]? +StackExchange.Redis.IDatabase.HashGet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expireDuration, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]? +StackExchange.Redis.IDatabase.HashGetPersistFields(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]? StackExchange.Redis.IDatabase.HashGetAll(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]! StackExchange.Redis.IDatabase.HashGetLease(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.Lease? StackExchange.Redis.IDatabase.HashIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> double @@ -563,6 +579,9 @@ StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackEx StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable! StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool +StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, bool keepExpiry, StackExchange.Redis.HashFieldFlags fieldFlags = StackExchange.Redis.HashFieldFlags.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]? +StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expireTime, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.HashFieldFlags fieldFlags = StackExchange.Redis.HashFieldFlags.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]? +StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan expireDuration, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.HashFieldFlags fieldFlags = StackExchange.Redis.HashFieldFlags.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]? StackExchange.Redis.IDatabase.HashStringLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long StackExchange.Redis.IDatabase.HashValues(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]! StackExchange.Redis.IDatabase.HyperLogLogAdd(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool @@ -783,10 +802,19 @@ StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.Redi StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashFieldExpireAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expiry, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldExpireTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldExpireTimeAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldPersistAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldPersistAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldTimeToLiveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashFieldTimeToLiveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashGetAllAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.DateTime expireTime, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashGetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, System.TimeSpan expireDuration, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashGetLeaseAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task?>! +StackExchange.Redis.IDatabaseAsync.HashGetPersistFieldsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, double value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashKeysAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! @@ -797,6 +825,9 @@ StackExchange.Redis.IDatabaseAsync.HashRandomFieldsWithValuesAsync(StackExchange StackExchange.Redis.IDatabaseAsync.HashScanAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable! StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, bool keepExpiry, StackExchange.Redis.HashFieldFlags fieldFlags = StackExchange.Redis.HashFieldFlags.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.DateTime expireTime, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.HashFieldFlags fieldFlags = StackExchange.Redis.HashFieldFlags.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! +StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, System.TimeSpan expireDuration, StackExchange.Redis.ExpireWhen when = StackExchange.Redis.ExpireWhen.Always, StackExchange.Redis.HashFieldFlags fieldFlags = StackExchange.Redis.HashFieldFlags.None, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashStringLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HashValuesAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! StackExchange.Redis.IDatabaseAsync.HyperLogLogAddAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task! diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 29135fb2a..c0d4d072c 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Threading.Tasks; using Pipelines.Sockets.Unofficial.Arenas; @@ -468,8 +469,87 @@ private T HashFieldExpireExecute(RedisKey key, long milliseconds, ExpireWhen private Task AsyncExpireResultArrayExecutor(Message msg, ServerEndPoint? server) => ExecuteAsync(msg, ResultProcessor.ExpireResultArray, server); + private T HashFieldExecute(RedisCommand cmd, RedisKey key, CustomExecutor executor, P processor, CommandFlags flags = CommandFlags.None, params RedisValue[] hashFields) + { + var values = new List { RedisLiterals.FIELDS, hashFields.Length }; + values.AddRange(hashFields); + RedisFeatures features = GetFeatures(key, flags, cmd, out ServerEndPoint? server); + var msg = Message.Create(Database, flags, cmd, key, values.ToArray()); + return executor(msg, processor, server); + } + + private delegate T CustomExecutor(Message msg, P processor, ServerEndPoint? server); + + + private T? SyncCustomExecutor(Message msg, P processor, ServerEndPoint? server) where P : ResultProcessor { return ExecuteSync(msg, processor, server); } + + private T[]? SyncCustomArrExecutor(Message msg, P processor, ServerEndPoint? server) where P : ResultProcessor { return ExecuteSync(msg, processor, server); } + + private Task AsyncCustomExecutor(Message msg, P processor, ServerEndPoint? server) where P : ResultProcessor { return ExecuteAsync(msg, processor, server); } + + private Task AsyncCustomArrExecutor(Message msg, P processor, ServerEndPoint? server) where P : ResultProcessor { return ExecuteAsync(msg, processor, server); } + #endregion helper stuff for HFE + public long? HashFieldExpireTime(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPEXPIRETIME, key, SyncCustomExecutor>, ResultProcessor.NullableInt64, flags, hashField); + } + + public long[]? HashFieldExpireTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPEXPIRETIME, key, SyncCustomArrExecutor>, ResultProcessor.Int64NullableArray, flags, hashFields); + } + + public Task HashFieldExpireTimeAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPEXPIRETIME, key, AsyncCustomExecutor>, ResultProcessor.NullableInt64, flags, hashField); + } + + public Task HashFieldExpireTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPEXPIRETIME, key, AsyncCustomArrExecutor>, ResultProcessor.Int64NullableArray, flags, hashFields); + } + + public long? HashFieldPersist(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPERSIST, key, SyncCustomExecutor>, ResultProcessor.NullableInt64, flags, hashField); + } + + public long[]? HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPERSIST, key, SyncCustomArrExecutor>, ResultProcessor.Int64NullableArray, flags, hashFields); + } + + public Task HashFieldPersistAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPERSIST, key, AsyncCustomExecutor>, ResultProcessor.NullableInt64, flags, hashField); + } + + public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPERSIST, key, AsyncCustomArrExecutor>, ResultProcessor.Int64NullableArray, flags, hashFields); + } + + public long? HashFieldTimeToLive(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPTTL, key, SyncCustomExecutor>, ResultProcessor.NullableInt64, flags, hashField); + } + + public long[]? HashFieldTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPTTL, key, SyncCustomArrExecutor>, ResultProcessor.Int64NullableArray, flags, hashFields); + } + + public Task HashFieldTimeToLiveAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPTTL, key, AsyncCustomExecutor>, ResultProcessor.NullableInt64, flags, hashField); + } + + public Task HashFieldTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + return HashFieldExecute(RedisCommand.HPTTL, key, AsyncCustomArrExecutor>, ResultProcessor.Int64NullableArray, flags, hashFields); + } public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { @@ -491,6 +571,135 @@ public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } + public RedisValue[]? HashGet(RedisKey key, RedisValue[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + List args = new() { RedisLiterals.PX, expireDuration.Ticks / TimeSpan.TicksPerMillisecond }; + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + return HashGetFields(key, args, hashFields, flags); + } + + public RedisValue[]? HashGet(RedisKey key, RedisValue[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + List args = new() { RedisLiterals.PXAT, GetMillisecondsUntil(expireTime) }; + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + return HashGetFields(key, args, hashFields, flags); + } + + public RedisValue[]? HashGetPersistFields(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + List args = new() { RedisLiterals.PERSIST, }; + return HashGetFields(key, args, hashFields, flags); + } + + private RedisValue[]? HashGetFields(RedisKey key, List args, RedisValue[] hashFields, CommandFlags flags) + { + if (hashFields == null || hashFields.Length == 0) throw new ArgumentNullException(nameof(hashFields)); + args.Add(RedisLiterals.FIELDS); + args.Add(hashFields.Length); + args.AddRange(hashFields); + var msg = Message.Create(Database, flags, RedisCommand.HGETF, key, args.ToArray()); + return ExecuteSync(msg, ResultProcessor.NullableRedisValueArray, defaultValue: null); + } + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + List args = new() { RedisLiterals.PX, expireDuration.Ticks / TimeSpan.TicksPerMillisecond }; + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + return HashGetFieldsAsync(key, args, hashFields, flags); + } + + public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + { + List args = new() { RedisLiterals.PXAT, GetMillisecondsUntil(expireTime) }; + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + return HashGetFieldsAsync(key, args, hashFields, flags); + } + + public Task HashGetPersistFieldsAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + { + List args = new() { RedisLiterals.PERSIST, }; + return HashGetFieldsAsync(key, args, hashFields, flags); + } + + private Task HashGetFieldsAsync(RedisKey key, List args, RedisValue[] hashFields, CommandFlags flags) + { + if (hashFields == null || hashFields.Length == 0) throw new ArgumentNullException(nameof(hashFields)); + args.Add(RedisLiterals.FIELDS); + args.Add(hashFields.Length); + args.AddRange(hashFields); + var msg = Message.Create(Database, flags, RedisCommand.HGETF, key, args.ToArray()); + return ExecuteAsync(msg, ResultProcessor.NullableRedisValueArray, defaultValue: null); + } + + public RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, bool keepExpiry, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None) + { + var args = fieldFlags.ToRedisValueList(); + if (keepExpiry) args.Add(RedisLiterals.KEEPTTL); + return HashSetFields(key, args, hashFields, flags); + } + + public RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None) + { + var args = fieldFlags.ToRedisValueList(); + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + args.Add(RedisLiterals.PX); + args.Add(expireDuration.Ticks / TimeSpan.TicksPerMillisecond); + return HashSetFields(key, args, hashFields, flags); + } + + public RedisValue[]? HashSet(RedisKey key, HashEntry[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None) + { + var args = fieldFlags.ToRedisValueList(); + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + args.Add(RedisLiterals.PXAT); + args.Add(GetMillisecondsUntil(expireTime)); + return HashSetFields(key, args, hashFields, flags); + } + + private RedisValue[]? HashSetFields(RedisKey key, List args, HashEntry[] hashFields, CommandFlags flags) + { + if (hashFields == null || hashFields.Length == 0) throw new ArgumentNullException(nameof(hashFields)); + args.Add(RedisLiterals.FVS); + args.Add(hashFields.Length); + args.AddRange(hashFields.SelectMany(e => new[] { e.Name, e.Value }).ToList()); + var msg = Message.Create(Database, flags, RedisCommand.HSETF, key, args.ToArray()); + return ExecuteSync(msg, ResultProcessor.NullableRedisValueArray, defaultValue: null); + } + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, bool keepExpiry, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None) + { + var args = fieldFlags.ToRedisValueList(); + if (keepExpiry) args.Add(RedisLiterals.KEEPTTL); + return HashSetFieldsAsync(key, args, hashFields, flags); + } + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, TimeSpan expireDuration, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None) + { + var args = fieldFlags.ToRedisValueList(); + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + args.Add(RedisLiterals.PX); + args.Add(expireDuration.Ticks / TimeSpan.TicksPerMillisecond); + return HashSetFieldsAsync(key, args, hashFields, flags); + } + + public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, DateTime expireTime, ExpireWhen when = ExpireWhen.Always, HashFieldFlags fieldFlags = HashFieldFlags.None, CommandFlags flags = CommandFlags.None) + { + var args = fieldFlags.ToRedisValueList(); + if (when != ExpireWhen.Always) args.Add(when.ToLiteral()); + args.Add(RedisLiterals.PXAT); + args.Add(GetMillisecondsUntil(expireTime)); + return HashSetFieldsAsync(key, args, hashFields, flags); + } + + private Task HashSetFieldsAsync(RedisKey key, List args, HashEntry[] hashFields, CommandFlags flags) + { + if (hashFields == null || hashFields.Length == 0) throw new ArgumentNullException(nameof(hashFields)); + args.Add(RedisLiterals.FVS); + args.Add(hashFields.Length); + args.AddRange(hashFields.SelectMany(e => new[] { e.Name, e.Value }).ToList()); + var msg = Message.Create(Database, flags, RedisCommand.HSETF, key, args.ToArray()); + return ExecuteAsync(msg, ResultProcessor.NullableRedisValueArray, defaultValue: null); + } public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) { var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs index 737effc1d..2f54a8fff 100644 --- a/src/StackExchange.Redis/RedisLiterals.cs +++ b/src/StackExchange.Redis/RedisLiterals.cs @@ -80,6 +80,7 @@ public static readonly RedisValue FILTERBY = "FILTERBY", FLUSH = "FLUSH", FREQ = "FREQ", + FVS = "FVS", GET = "GET", GETKEYS = "GETKEYS", GETNAME = "GETNAME", diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs index 0a84bdcb8..297adf974 100644 --- a/src/StackExchange.Redis/ResultProcessor.cs +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -93,9 +93,16 @@ public static readonly ResultProcessor> public static readonly ResultProcessor RedisValueArray = new RedisValueArrayProcessor(); + + public static readonly ResultProcessor + NullableRedisValueArray = new NullableRedisValueArrayProcessor(); + public static readonly ResultProcessor Int64Array = new Int64ArrayProcessor(); + public static readonly ResultProcessor + Int64NullableArray = new Int64NullableArrayProcessor(); + public static readonly ResultProcessor NullableStringArray = new NullableStringArrayProcessor(); @@ -1279,7 +1286,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes SetResult(message, true); return true; } - if(message.Command == RedisCommand.AUTH) connection?.BridgeCouldBeNull?.Multiplexer?.SetAuthSuspect(new RedisException("Unknown AUTH exception")); + if (message.Command == RedisCommand.AUTH) connection?.BridgeCouldBeNull?.Multiplexer?.SetAuthSuspect(new RedisException("Unknown AUTH exception")); return false; } } @@ -1444,6 +1451,17 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes return true; } break; + case ResultType.Array: + var items = result.GetItems(); + if (items.Length == 1) + { // treat an array of 1 like a single reply + if (items[0].TryGetInt64(out long value)) + { + SetResult(message, value); + return true; + } + } + break; } return false; } @@ -1602,6 +1620,29 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } } + private sealed class NullableRedisValueArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + switch (result.Resp2TypeBulkString) + { + // allow a single item to pass explicitly pretending to be an array + case ResultType.BulkString: + // If the result is nil, the result should be an empty array + var arr = result.IsNull + ? null + : new[] { result.AsRedisValue() }; + SetResult(message, arr); + return true; + case ResultType.Array: + arr = result.GetItemsAsValues()!; + SetResult(message, arr); + return true; + } + return false; + } + } + private sealed class Int64ArrayProcessor : ResultProcessor { protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) @@ -1617,6 +1658,21 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes } } + private sealed class Int64NullableArrayProcessor : ResultProcessor + { + protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) + { + if (result.Resp2TypeArray == ResultType.Array || result.IsNull) + { + var arr = result.ToArray((in RawResult x) => (long)x.AsRedisValue())!; + SetResult(message, arr); + return true; + } + + return false; + } + } + private sealed class NullableStringArrayProcessor : ResultProcessor { protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) @@ -2285,7 +2341,8 @@ internal static bool TryRead(Sequence pairs, in CommandBytes key, ref internal static bool TryRead(Sequence pairs, in CommandBytes key, ref int value) { long tmp = default; - if(TryRead(pairs, key, ref tmp)) { + if(TryRead(pairs, key, ref tmp)) + { value = checked((int)tmp); return true; } @@ -2411,13 +2468,13 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes var lastGeneratedId = Redis.RedisValue.Null; StreamEntry firstEntry = StreamEntry.Null, lastEntry = StreamEntry.Null; var iter = arr.GetEnumerator(); - for(int i = 0; i < max; i++) + for (int i = 0; i < max; i++) { ref RawResult key = ref iter.GetNext(), value = ref iter.GetNext(); if (key.Payload.Length > CommandBytes.MaxLength) continue; var keyBytes = new CommandBytes(key.Payload); - if(keyBytes.Equals(CommonReplies.length)) + if (keyBytes.Equals(CommonReplies.length)) { if (!value.TryGetInt64(out length)) return false; } @@ -2507,8 +2564,8 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes lowestId: arr[1].AsRedisValue(), highestId: arr[2].AsRedisValue(), consumers: consumers ?? Array.Empty()); - // ^^^^^ - // Should we bother allocating an empty array only to prevent the need for a null check? + // ^^^^^ + // Should we bother allocating an empty array only to prevent the need for a null check? SetResult(message, pendingInfo); return true; @@ -2895,7 +2952,7 @@ internal abstract class ArrayResultProcessor : ResultProcessor { protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) { - switch(result.Resp2TypeArray) + switch (result.Resp2TypeArray) { case ResultType.Array: var items = result.GetItems(); diff --git a/tests/StackExchange.Redis.Tests/HashFieldTests.cs b/tests/StackExchange.Redis.Tests/HashFieldTests.cs index 8869c3bb7..f9b9c5b80 100644 --- a/tests/StackExchange.Redis.Tests/HashFieldTests.cs +++ b/tests/StackExchange.Redis.Tests/HashFieldTests.cs @@ -175,4 +175,397 @@ public void HashFieldExpireConditionsNotSatisfied() result = db.HashFieldExpire(hashKey, "f4", oneYearInMs, ExpireWhen.GreaterThanCurrentExpiry); Assert.Equal(ExpireResult.ConditionNotMet, result); } + + [Fact] + public void HashFieldExpireTime() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, nextCentury); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + + var result = db.HashFieldExpireTime(hashKey, "f1"); + Assert.Equal(ms, result); + + var fieldsResult = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new[] { ms, ms }, fieldsResult); + } + + [Fact] + public void HashFieldExpireFieldNoExpireTime() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var result = db.HashFieldExpireTime(hashKey, "f1"); + Assert.Equal(-1, result); + + var fieldsResult = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new long[] { -1, -1, }, fieldsResult); + } + + [Fact] + public void HashFieldExpireTimeNoKey() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + + var result = db.HashFieldExpireTime(hashKey, "f1"); + Assert.Null(result); + + var fieldsResult = db.HashFieldExpireTime(hashKey, fields); + Assert.Null(fieldsResult); + } + + [Fact] + public void HashFieldExpireTimeNoField() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var result = db.HashFieldExpireTime(hashKey, "notExistingField1"); + Assert.Equal(-2, result); + + var fieldsResult = db.HashFieldExpireTime(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" }); + Assert.Equal(new long[] { -2, -2, }, fieldsResult); + } + + [Fact] + public void HashFieldTimeToLive() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + + var result = db.HashFieldTimeToLive(hashKey, "f1"); + Assert.NotNull(result); + Assert.True(result > 0); + + var fieldsResult = db.HashFieldTimeToLive(hashKey, fields); + Assert.NotNull(fieldsResult); + Assert.True(fieldsResult.Length > 0); + Assert.True(fieldsResult.All(x => x > 0)); + } + + [Fact] + public void HashFieldTimeToLiveNoExpireTime() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var result = db.HashFieldTimeToLive(hashKey, "f1"); + Assert.Equal(-1, result); + + var fieldsResult = db.HashFieldTimeToLive(hashKey, fields); + Assert.Equal(new long[] { -1, -1, }, fieldsResult); + } + + [Fact] + public void HashFieldTimeToLiveNoKey() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + + var result = db.HashFieldTimeToLive(hashKey, "f1"); + Assert.Null(result); + + var fieldsResult = db.HashFieldTimeToLive(hashKey, fields); + Assert.Null(fieldsResult); + } + + [Fact] + public void HashFieldTimeToLiveNoField() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var result = db.HashFieldTimeToLive(hashKey, "notExistingField1"); + Assert.Equal(-2, result); + + var fieldsResult = db.HashFieldTimeToLive(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" }); + Assert.Equal(new long[] { -2, -2, }, fieldsResult); + } + + [Fact] + public void HashFieldPersist() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + + var result = db.HashFieldPersist(hashKey, "f1"); + Assert.Equal(1, result); + + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashFieldPersist(hashKey, fields); + Assert.Equal(new long[] { 1, 1, }, fieldsResult); + } + + [Fact] + public void HashFieldPersistNoExpireTime() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var result = db.HashFieldPersist(hashKey, "f1"); + Assert.Equal(-1, result); + + var fieldsResult = db.HashFieldPersist(hashKey, fields); + Assert.Equal(new long[] { -1, -1, }, fieldsResult); + } + + [Fact] + public void HashFieldPersistNoKey() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + + var result = db.HashFieldPersist(hashKey, "f1"); + Assert.Null(result); + + var fieldsResult = db.HashFieldPersist(hashKey, fields); + Assert.Null(fieldsResult); + } + + [Fact] + public void HashFieldPersistNoField() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var result = db.HashFieldPersist(hashKey, "notExistingField1"); + Assert.Equal(-2, result); + + var fieldsResult = db.HashFieldPersist(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" }); + Assert.Equal(new long[] { -2, -2, }, fieldsResult); + } + + [Fact] + public void HashFieldGet() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = db.HashGet(hashKey, fields, oneYearInMs); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var ttlResults = db.HashFieldTimeToLive(hashKey, fields); + Assert.NotNull(ttlResults); + Assert.True(ttlResults.Length > 0); + Assert.True(ttlResults.All(x => x > 0)); + + fieldsResult = db.HashGet(hashKey, fields, nextCentury); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var expireDates = db.HashFieldExpireTime(hashKey, fields); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + Assert.Equal(new[] { ms, ms }, expireDates); + + + fieldsResult = db.HashGetPersistFields(hashKey, fields); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var fieldsNoExpireDates = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldsNoExpireDates); + } + + [Fact] + public void HashFieldGetWithExpireConditions() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = db.HashGet(hashKey, fields, oneYearInMs, ExpireWhen.HasNoExpiry); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var ttlResults = db.HashFieldTimeToLive(hashKey, fields); + Assert.NotNull(ttlResults); + Assert.True(ttlResults.Length > 0); + Assert.True(ttlResults.All(x => x > 0)); + + fieldsResult = db.HashGet(hashKey, fields, nextCentury, ExpireWhen.HasNoExpiry); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var expireDates = db.HashFieldExpireTime(hashKey, fields); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + Assert.NotEqual(new[] { ms, ms }, expireDates); + + + fieldsResult = db.HashGetPersistFields(hashKey, fields); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var fieldsNoExpireDates = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldsNoExpireDates); + } + + [Fact] + public void HashFieldGetNoKey() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = db.HashGet(hashKey, fields, oneYearInMs); + Assert.Null(fieldsResult); + + fieldsResult = db.HashGet(hashKey, fields, nextCentury); + Assert.Null(fieldsResult); + } + + [Fact] + public void HashFieldGetNoField() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashGet(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" }, oneYearInMs); + Assert.NotNull(fieldsResult); + Assert.Equal(new RedisValue[] { RedisValue.Null, RedisValue.Null }, fieldsResult); + + fieldsResult = db.HashGet(hashKey, new RedisValue[] { "notExistingField1", "notExistingField2" }, nextCentury); + Assert.NotNull(fieldsResult); + Assert.Equal(new RedisValue[] { RedisValue.Null, RedisValue.Null }, fieldsResult); + } + + [Fact] + public void HashFieldSet() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + + var fieldsResult = db.HashSet(hashKey, new[] { new HashEntry("f1", 1), new HashEntry("f2", 2) }, oneYearInMs); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + var ttlResults = db.HashFieldTimeToLive(hashKey, fields); + Assert.NotNull(ttlResults); + Assert.True(ttlResults.Length > 0); + Assert.True(ttlResults.All(x => x > 0)); + + fieldsResult = db.HashSet(hashKey, entries, nextCentury); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + var expireDates = db.HashFieldExpireTime(hashKey, fields); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + Assert.Equal(new[] { ms, ms }, expireDates); + + fieldsResult = db.HashSet(hashKey, entries, false); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + var fieldsNoExpireDates = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldsNoExpireDates); + } + + [Fact] + public void HashFieldSetNoKey() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + + var fieldsResult = db.HashSet(hashKey, entries, oneYearInMs); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + fieldsResult = db.HashSet(hashKey, entries, nextCentury); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + } + + [Fact] + public void HashFieldSetNoField() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.HashSet(hashKey, entries); + db.HashFieldExpire(hashKey, fields, oneYearInMs); + + var fieldsResult = db.HashSet(hashKey, new[] { new HashEntry("notExistingField1", 1), new HashEntry("notExistingField2", 2) }, oneYearInMs); + Assert.NotNull(fieldsResult); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + fieldsResult = db.HashSet(hashKey, new[] { new HashEntry("notExistingField1", 1), new HashEntry("notExistingField2", 2) }, nextCentury); + Assert.NotNull(fieldsResult); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + } + + [Fact] + public void HashFieldSetWithExpireConditions() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.KeyDelete(hashKey); + + var fieldsResult = db.HashSet(hashKey, entries, oneYearInMs, ExpireWhen.HasNoExpiry); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + var ttlResults = db.HashFieldTimeToLive(hashKey, fields); + Assert.NotNull(ttlResults); + Assert.True(ttlResults.Length > 0); + Assert.True(ttlResults.All(x => x > 0)); + + fieldsResult = db.HashSet(hashKey, entries, nextCentury, ExpireWhen.HasNoExpiry); + Assert.Equal(new RedisValue[] { 1, 1, }, fieldsResult); + + var expireDates = db.HashFieldExpireTime(hashKey, fields); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + Assert.NotEqual(new[] { ms, ms }, expireDates); + + fieldsResult = db.HashSet(hashKey, entries, false); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + var fieldsNoExpireDates = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldsNoExpireDates); + } + + [Fact] + public void HashFieldSetWithFlags() + { + var db = Create(require: RedisFeatures.v7_2_0_rc1).GetDatabase(); + var hashKey = Me(); + db.KeyDelete(hashKey); + + var fieldsResult = db.HashSet(hashKey, entries, oneYearInMs, ExpireWhen.HasNoExpiry, HashFieldFlags.DC); + Assert.Null(fieldsResult); + + fieldsResult = db.HashSet(hashKey, entries, oneYearInMs, ExpireWhen.HasNoExpiry, HashFieldFlags.DCF); + Assert.Null(fieldsResult); + + fieldsResult = db.HashSet(hashKey, entries, oneYearInMs, ExpireWhen.HasNoExpiry, HashFieldFlags.DOF); + Assert.Equal(new RedisValue[] { 3, 3 }, fieldsResult); + + var ttlResults = db.HashFieldTimeToLive(hashKey, fields); + Assert.NotNull(ttlResults); + Assert.True(ttlResults.Length > 0); + Assert.True(ttlResults.All(x => x > 0)); + + fieldsResult = db.HashSet(hashKey, new HashEntry[] { new("f1", "a"), new("f2", "b") }, nextCentury, ExpireWhen.HasNoExpiry, HashFieldFlags.GETOLD); + Assert.Equal(entries.Select(i => i.Value).ToArray(), fieldsResult); + + var expireDates = db.HashFieldExpireTime(hashKey, fields); + long ms = new DateTimeOffset(nextCentury).ToUnixTimeMilliseconds(); + Assert.NotEqual(new[] { ms, ms }, expireDates); + + + fieldsResult = db.HashSet(hashKey, new HashEntry[] { new("f1", "x"), new("f2", "y") }, false, HashFieldFlags.GETNEW); + Assert.Equal(new RedisValue[] { "x", "y" }, fieldsResult); + + var fieldsNoExpireDates = db.HashFieldExpireTime(hashKey, fields); + Assert.Equal(new long[] { -1, -1 }, fieldsNoExpireDates); + } }