Commit 94213e9a authored by Marc Gravell's avatar Marc Gravell

Merge branch 'wjdavis5-AddGeoAdd'

parents bed3c2bc b2622101
...@@ -134,6 +134,7 @@ public void CheckFailureRecovered() ...@@ -134,6 +134,7 @@ public void CheckFailureRecovered()
ClearAmbientFailures(); ClearAmbientFailures();
} }
} }
#endif #endif
[Test] [Test]
public void TryGetAzureRoleInstanceIdNoThrow() public void TryGetAzureRoleInstanceIdNoThrow()
......
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
namespace StackExchange.Redis.Tests
{
public class AzureTestAttribute : TestAttribute
{
}
[TestFixture]
public class GeoTests : TestBase
{
private ConnectionMultiplexer Create()
{
string name, password;
GetAzureCredentials(out name, out password);
var options = new ConfigurationOptions();
options.EndPoints.Add(name + ".redis.cache.windows.net");
options.Ssl = true;
options.ConnectTimeout = 5000;
options.Password = password;
options.TieBreaker = "";
var log = new StringWriter();
var conn = ConnectionMultiplexer.Connect(options, log);
var s = log.ToString();
Console.WriteLine(s);
return conn;
}
public const int Db = 0;
public static GeoEntry
palermo = new GeoEntry(13.361389, 38.115556, "Palermo"),
catania = new GeoEntry(15.087269, 37.502669, "Catania"),
agrigento = new GeoEntry(13.5765, 37.311, "Agrigento"),
cefal = new GeoEntry(14.0188, 38.0084, "Cefal");
public static GeoEntry[] all = { palermo, catania, agrigento , cefal };
[AzureTest]
public void GeoAdd()
{
using (var conn = Create())
{
var db = conn.GetDatabase(Db);
RedisKey key = Me();
db.KeyDelete(key);
// add while not there
Assert.IsTrue(db.GeoAdd(key, cefal.Longitude, cefal.Latitude, cefal.Member));
Assert.AreEqual(2, db.GeoAdd(key, new GeoEntry[] { palermo, catania }));
Assert.IsTrue(db.GeoAdd(key, agrigento));
// now add again
Assert.IsFalse(db.GeoAdd(key, cefal.Longitude, cefal.Latitude, cefal.Member));
Assert.AreEqual(0, db.GeoAdd(key, new GeoEntry[] { palermo, catania }));
Assert.IsFalse(db.GeoAdd(key, agrigento));
}
}
[AzureTest]
public void GetDistance()
{
using (var conn = Create())
{
var db = conn.GetDatabase(Db);
RedisKey key = Me();
db.KeyDelete(key);
db.GeoAdd(key, all);
var val = db.GeoDistance(key, "Palermo", "Catania", GeoUnit.Meters);
Assert.IsTrue(val.HasValue);
var rounded = Math.Round(val.Value, 10);
Assert.AreEqual(166274.1516, val);
val = db.GeoDistance(key, "Palermo", "Nowhere", GeoUnit.Meters);
Assert.IsFalse(val.HasValue);
}
}
[AzureTest]
public void GeoHash()
{
using (var conn = Create())
{
var db = conn.GetDatabase(Db);
RedisKey key = Me();
db.KeyDelete(key);
db.GeoAdd(key, all);
var hashes = db.GeoHash(key, new RedisValue[] { palermo.Member, "Nowhere", agrigento.Member });
Assert.AreEqual(3, hashes.Length);
Assert.AreEqual("sqc8b49rny0", hashes[0]);
Assert.IsNull(hashes[1]);
Assert.AreEqual("sq9skbq0760", hashes[2]);
var hash = db.GeoHash(key, "Palermo");
Assert.AreEqual("sqc8b49rny0", hash);
hash = db.GeoHash(key, "Nowhere");
Assert.IsNull(hash);
}
}
[AzureTest]
public void GeoGetPosition()
{
using (var conn = Create())
{
var db = conn.GetDatabase(Db);
RedisKey key = Me();
db.KeyDelete(key);
db.GeoAdd(key, all);
var pos = db.GeoPosition(key, palermo.Member);
Assert.IsTrue(pos.HasValue);
Assert.AreEqual(Math.Round(palermo.Longitude, 6), Math.Round(pos.Value.Longitude, 6));
Assert.AreEqual(Math.Round(palermo.Latitude, 6), Math.Round(pos.Value.Latitude, 6));
pos = db.GeoPosition(key, "Nowhere");
Assert.IsFalse(pos.HasValue);
}
}
[AzureTest]
public void GeoRemove()
{
using (var conn = Create())
{
var db = conn.GetDatabase(Db);
RedisKey key = Me();
db.KeyDelete(key);
db.GeoAdd(key, all);
var pos = db.GeoPosition(key, "Palermo");
Assert.IsTrue(pos.HasValue);
Assert.IsFalse(db.GeoRemove(key, "Nowhere"));
Assert.IsTrue(db.GeoRemove(key, "Palermo"));
Assert.IsFalse(db.GeoRemove(key, "Palermo"));
pos = db.GeoPosition(key, "Palermo");
Assert.IsFalse(pos.HasValue);
}
}
[AzureTest]
public void GeoRadius()
{
using (var conn = Create())
{
var db = conn.GetDatabase(Db);
RedisKey key = Me();
db.KeyDelete(key);
db.GeoAdd(key, all);
var results = db.GeoRadius(key, cefal.Member, 60, GeoUnit.Miles, 2, Order.Ascending);
Assert.AreEqual(2, results.Length);
Assert.AreEqual(results[0].Member, cefal.Member);
Assert.AreEqual(0, results[0].Distance.Value);
Assert.AreEqual(Math.Round(results[0].Position.Value.Longitude, 5), Math.Round(cefal.Position.Longitude, 5));
Assert.AreEqual(Math.Round(results[0].Position.Value.Latitude, 5), Math.Round(cefal.Position.Latitude, 5));
Assert.IsFalse(results[0].Hash.HasValue);
Assert.AreEqual(results[1].Member, palermo.Member);
Assert.AreEqual(Math.Round(36.5319, 6), Math.Round(results[1].Distance.Value, 6));
Assert.AreEqual(Math.Round(results[1].Position.Value.Longitude, 5), Math.Round(palermo.Position.Longitude, 5));
Assert.AreEqual(Math.Round(results[1].Position.Value.Latitude, 5), Math.Round(palermo.Position.Latitude, 5));
Assert.IsFalse(results[1].Hash.HasValue);
results = db.GeoRadius(key, cefal.Member, 60, GeoUnit.Miles, 2, Order.Ascending, GeoRadiusOptions.None);
Assert.AreEqual(2, results.Length);
Assert.AreEqual(results[0].Member, cefal.Member);
Assert.IsFalse(results[0].Position.HasValue);
Assert.IsFalse(results[0].Distance.HasValue);
Assert.IsFalse(results[0].Hash.HasValue);
Assert.AreEqual(results[1].Member, palermo.Member);
Assert.IsFalse(results[1].Position.HasValue);
Assert.IsFalse(results[1].Distance.HasValue);
Assert.IsFalse(results[1].Hash.HasValue);
}
}
}
}
using NUnit.Framework; using NUnit.Framework;
using System.IO;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
......
using System;
namespace StackExchange.Redis
{
/// <summary>
/// GeoRadius command options.
/// </summary>
[Flags]
public enum GeoRadiusOptions
{
/// <summary>
/// No Options
/// </summary>
None = 0,
/// <summary>
/// Redis will return the coordinates of any results.
/// </summary>
WithCoordinates = 1,
/// <summary>
/// Redis will return the distance from center for all results.
/// </summary>
WithDistance = 2,
/// <summary>
/// Redis will return the geo hash value as an integer. (This is the score in the sorted set)
/// </summary>
WithGeoHash = 4,
/// <summary>
/// Populates the commonly used values from the entry (the integer hash is not returned as it is not commonly useful)
/// </summary>
Default = WithCoordinates | GeoRadiusOptions.WithDistance
}
/// <summary>
/// The result of a GeoRadius command.
/// </summary>
public struct GeoRadiusResult
{
/// <summary>
/// Indicate the member being represented
/// </summary>
public override string ToString() => Member.ToString();
/// <summary>
/// The matched member.
/// </summary>
public RedisValue Member { get; }
/// <summary>
/// The distance of the matched member from the center of the geo radius command.
/// </summary>
public double? Distance { get; }
/// <summary>
/// The hash value of the matched member as an integer. (The key in the sorted set)
/// </summary>
/// <remarks>Note that this is not the same as the hash returned from GeoHash</remarks>
public long? Hash { get; }
/// <summary>
/// The coordinates of the matched member.
/// </summary>
public GeoPosition? Position { get; }
/// <summary>
/// Returns a new GeoRadiusResult
/// </summary>
internal GeoRadiusResult(RedisValue member, double? distance, long? hash, GeoPosition? position)
{
Member = member;
Distance = distance;
Hash = hash;
Position = position;
}
}
/// <summary>
/// Describes the longitude and latitude of a GeoEntry
/// </summary>
public struct GeoPosition : IEquatable<GeoPosition>
{
internal static string GetRedisUnit(GeoUnit unit)
{
switch (unit)
{
case GeoUnit.Meters: return "m";
case GeoUnit.Kilometers: return "km";
case GeoUnit.Miles: return "mi";
case GeoUnit.Feet: return "ft";
default:
throw new ArgumentOutOfRangeException(nameof(unit));
}
}
/// <summary>
/// The Latitude of the GeoPosition
/// </summary>
public double Latitude { get; }
/// <summary>
/// The Logitude of the GeoPosition
/// </summary>
public double Longitude { get; }
/// <summary>
/// Creates a new GeoPosition
/// </summary>
/// <param name="longitude"></param>
/// <param name="latitude"></param>
public GeoPosition(double longitude, double latitude)
{
Longitude = longitude;
Latitude = latitude;
}
/// <summary>
/// See Object.ToString()
/// </summary>
public override string ToString()
{
return string.Format("{0} {1}", Longitude, Latitude);
}
/// <summary>
/// See Object.GetHashCode()
/// Diagonals not an issue in the case of lat/long
/// </summary>
public override int GetHashCode()
{
// diagonals not an issue in the case of lat/long
return Longitude.GetHashCode() ^ Latitude.GetHashCode();
}
/// <summary>
/// Compares two values for equality
/// </summary>
public override bool Equals(object obj)
{
return obj is GeoPosition && Equals((GeoPosition)obj);
}
/// <summary>
/// Compares two values for equality
/// </summary>
public bool Equals(GeoPosition value)
{
return this == value;
}
/// <summary>
/// Compares two values for equality
/// </summary>
public static bool operator ==(GeoPosition x, GeoPosition y)
{
return x.Longitude == y.Longitude && x.Latitude == y.Latitude;
}
/// <summary>
/// Compares two values for non-equality
/// </summary>
public static bool operator !=(GeoPosition x, GeoPosition y)
{
return x.Longitude != y.Longitude || x.Latitude != y.Latitude;
}
}
/// <summary>
/// Describes a GeoEntry element with the corresponding value
/// GeoEntries are stored in redis as SortedSetEntries
/// </summary>
public struct GeoEntry : IEquatable<GeoEntry>
{
/// <summary>
/// The name of the geo entry
/// </summary>
public RedisValue Member { get; }
/// <summary>
/// Describes the longitude and latitude of a GeoEntry
/// </summary>
public GeoPosition Position { get; }
/// <summary>
/// Initializes a GeoEntry value
/// </summary>
public GeoEntry(double longitude, double latitude, RedisValue member)
{
Member = member;
Position = new GeoPosition(longitude, latitude);
}
/// <summary>
/// The longitude of the geo entry
/// </summary>
public double Longitude => Position.Longitude;
/// <summary>
/// The latitude of the geo entry
/// </summary>
public double Latitude => Position.Latitude;
/// <summary>
/// See Object.ToString()
/// </summary>
public override string ToString()
{
return $"({Longitude},{Latitude})={Member}";
}
/// <summary>
/// See Object.GetHashCode()
/// </summary>
public override int GetHashCode()
{
return Position.GetHashCode() ^ Member.GetHashCode();
}
/// <summary>
/// Compares two values for equality
/// </summary>
public override bool Equals(object obj)
{
return obj is GeoEntry && Equals((GeoEntry)obj);
}
/// <summary>
/// Compares two values for equality
/// </summary>
public bool Equals(GeoEntry value)
{
return this == value;
}
/// <summary>
/// Compares two values for equality
/// </summary>
public static bool operator ==(GeoEntry x, GeoEntry y)
{
return x.Position == y.Position && x.Member == y.Member;
}
/// <summary>
/// Compares two values for non-equality
/// </summary>
public static bool operator !=(GeoEntry x, GeoEntry y)
{
return x.Position != y.Position || x.Member != y.Member;
}
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
namespace StackExchange.Redis
{
/// <summary>
/// Units associated with Geo Commands
/// </summary>
public enum GeoUnit
{
/// <summary>
/// Meters
/// </summary>
Meters,
/// <summary>
/// Kilometers
/// </summary>
Kilometers,
/// <summary>
/// Miles
/// </summary>
Miles,
/// <summary>
/// Feet
/// </summary>
Feet
}
}
\ No newline at end of file
...@@ -44,6 +44,86 @@ public interface IDatabase : IRedis, IDatabaseAsync ...@@ -44,6 +44,86 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks>http://redis.io/commands/debug-object</remarks> /// <remarks>http://redis.io/commands/debug-object</remarks>
RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None); RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members.
/// </summary>
/// <returns>True if the specified member was not already present in the set, else False</returns>
/// <remarks>http://redis.io/commands/geoadd</remarks>
bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members.
/// </summary>
/// <returns>True if the specified member was not already present in the set, else False</returns>
/// <remarks>http://redis.io/commands/geoadd</remarks>
bool GeoAdd(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members.
/// </summary>
/// <returns>the number of elements that were added to the set, not including all the elements already present into the set.</returns>
/// <remarks>http://redis.io/commands/geoadd</remarks>
long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Removes the specified member from the geo sorted set stored at key. Non existing members are ignored.
/// </summary>
/// <returns>True if the member existed in the sorted set and was removed; False otherwise.</returns>
/// <remarks>http://redis.io/commands/zrem</remarks>
bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the distance between two members in the geospatial index represented by the sorted set.
/// </summary>
/// <returns>The command returns the distance as a double (represented as a string) in the specified unit, or NULL if one or both the elements are missing.</returns>
/// <remarks>http://redis.io/commands/geodist</remarks>
double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD).
/// </summary>
/// <returns>The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command.</returns>
/// <remarks>http://redis.io/commands/geohash</remarks>
string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD).
/// </summary>
/// <returns>The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command.</returns>
/// <remarks>http://redis.io/commands/geohash</remarks>
string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key.
/// </summary>
/// <returns>The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array.</returns>
/// <remarks>http://redis.io/commands/geopos</remarks>
GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key.
/// </summary>
/// <returns>The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array.</returns>
/// <remarks>http://redis.io/commands/geopos</remarks>
GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius).
/// </summary>
/// <returns>GeoRadiusResult[]</returns>
/// <remarks>http://redis.io/commands/georadius</remarks>
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);
/// <summary>
/// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius).
/// </summary>
/// <returns>GeoRadiusResult[]</returns>
/// <remarks>http://redis.io/commands/georadius</remarks>
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);
/// <summary> /// <summary>
/// Decrements the number stored at field in the hash stored at key by decrement. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. /// Decrements the number stored at field in the hash stored at key by decrement. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed.
/// </summary> /// </summary>
......
...@@ -16,6 +16,87 @@ public interface IDatabaseAsync : IRedisAsync ...@@ -16,6 +16,87 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks>http://redis.io/commands/debug-object</remarks> /// <remarks>http://redis.io/commands/debug-object</remarks>
Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None); Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members.
/// </summary>
/// <returns>True if the specified member was not already present in the set, else False</returns>
/// <remarks>http://redis.io/commands/geoadd</remarks>
Task<bool> GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Add the specified member to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members.
/// </summary>
/// <returns>True if the specified member was not already present in the set, else False</returns>
/// <remarks>http://redis.io/commands/geoadd</remarks>
Task<bool> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Add the specified members to the set stored at key. Specified members that are already a member of this set are ignored. If key does not exist, a new set is created before adding the specified members.
/// </summary>
/// <returns>the number of elements that were added to the set, not including all the elements already present into the set.</returns>
/// <remarks>http://redis.io/commands/geoadd</remarks>
Task<long> GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Removes the specified member from the geo sorted set stored at key. Non existing members are ignored.
/// </summary>
/// <returns>True if the member existed in the sorted set and was removed; False otherwise.</returns>
/// <remarks>http://redis.io/commands/zrem</remarks>
Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the distance between two members in the geospatial index represented by the sorted set.
/// </summary>
/// <returns>The command returns the distance as a double (represented as a string) in the specified unit, or NULL if one or both the elements are missing.</returns>
/// <remarks>http://redis.io/commands/geodist</remarks>
Task<double?> GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD).
/// </summary>
/// <returns>The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command.</returns>
/// <remarks>http://redis.io/commands/geohash</remarks>
Task<string[]> GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index (where elements were added using GEOADD).
/// </summary>
/// <returns>The command returns an array where each element is the Geohash corresponding to each member name passed as argument to the command.</returns>
/// <remarks>http://redis.io/commands/geohash</remarks>
Task<string> GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key.
/// </summary>
/// <returns>The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array.</returns>
/// <remarks>http://redis.io/commands/geopos</remarks>
Task<GeoPosition?[]> GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the positions (longitude,latitude) of all the specified members of the geospatial index represented by the sorted set at key.
/// </summary>
/// <returns>The command returns an array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command.Non existing elements are reported as NULL elements of the array.</returns>
/// <remarks>http://redis.io/commands/geopos</remarks>
Task<GeoPosition?> GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius).
/// </summary>
/// <returns>GeoRadiusResult[]</returns>
/// <remarks>http://redis.io/commands/georadius</remarks>
Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None);
/// <summary>
/// Return the members of a sorted set populated with geospatial information using GEOADD, which are within the borders of the area specified with the center location and the maximum distance from the center (the radius).
/// </summary>
/// <returns>GeoRadiusResult[]</returns>
/// <remarks>http://redis.io/commands/georadius</remarks>
Task<GeoRadiusResult[]> GeoRadiusAsync(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);
/// <summary> /// <summary>
/// Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed. /// Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. If field does not exist or holds a string that cannot be interpreted as integer, the value is set to 0 before the operation is performed.
/// </summary> /// </summary>
......
...@@ -27,6 +27,59 @@ public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.No ...@@ -27,6 +27,59 @@ public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.No
return Inner.DebugObject(ToInner(key), flags); return Inner.DebugObject(ToInner(key), flags);
} }
public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoAdd(ToInner(key), longitude, latitude, member, flags);
}
public long GeoAdd(RedisKey key, GeoEntry[] geoEntries, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoAdd(ToInner(key), geoEntries, flags);
}
public bool GeoAdd(RedisKey key, GeoEntry geoEntry, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoAdd(ToInner(key), geoEntry, flags);
}
public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoRemove(ToInner(key), member, flags);
}
public double? GeoDistance(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None)
{
return Inner.GeoDistance(ToInner(key), value0, value1, unit, flags);
}
public string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoHash(ToInner(key), members, flags);
}
public string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoHash(ToInner(key), member, flags);
}
public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoPosition(ToInner(key), members, flags);
}
public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return 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)
{
return 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)
{
return Inner.GeoRadius(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags);
}
public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDecrement(ToInner(key), hashField, value, flags); return Inner.HashDecrement(ToInner(key), hashField, value, flags);
......
...@@ -24,6 +24,41 @@ public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = Comm ...@@ -24,6 +24,41 @@ public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = Comm
return Inner.DebugObjectAsync(ToInner(key), flags); return Inner.DebugObjectAsync(ToInner(key), flags);
} }
public Task<bool> GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoAddAsync(ToInner(key), longitude, latitude, member, flags);
public Task<bool> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry value, CommandFlags flags = CommandFlags.None)
=> Inner.GeoAddAsync(ToInner(key), value, flags);
public Task<long> GeoAddAsync(RedisKey key, StackExchange.Redis.GeoEntry[] values, CommandFlags flags = CommandFlags.None)
=> Inner.GeoAddAsync(ToInner(key), values, flags);
public Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoRemoveAsync(ToInner(key), member, flags);
public Task<double?> GeoDistanceAsync(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None)
=> Inner.GeoDistanceAsync(ToInner(key), member1, member2, unit, flags);
public Task<string[]> GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
=> Inner.GeoHashAsync(ToInner(key), members, flags);
public Task<string> GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoHashAsync(ToInner(key), member, flags);
public Task<GeoPosition?[]> GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
=> Inner.GeoPositionAsync(ToInner(key), members, flags);
public Task<GeoPosition?> GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
=> Inner.GeoPositionAsync(ToInner(key), member, flags);
public Task<GeoRadiusResult[]> GeoRadiusAsync(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.GeoRadiusAsync(ToInner(key), member, radius, unit, count, order, options, flags);
public Task<GeoRadiusResult[]> GeoRadiusAsync(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)
=> Inner.GeoRadiusAsync(ToInner(key), longitude, latitude, radius, unit, count, order, options, flags);
public Task<double> HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) public Task<double> HashDecrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{ {
return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags); return Inner.HashDecrementAsync(ToInner(key), hashField, value, flags);
......
...@@ -350,6 +350,29 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R ...@@ -350,6 +350,29 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, R
return new CommandKeyValueValueValueMessage(db, flags, command, key, value0, value1, value2); return new CommandKeyValueValueValueMessage(db, flags, command, key, value0, value1, value2);
} }
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, GeoEntry[] values)
{
if (values == null) throw new ArgumentNullException(nameof(values));
if (values.Length == 0)
{
throw new ArgumentOutOfRangeException(nameof(values));
}
if (values.Length == 1)
{
var value = values[0];
return Message.Create(db, flags, command, key, value.Longitude, value.Latitude, value.Member);
}
var arr = new RedisValue[3 * values.Length];
int index = 0;
foreach (var value in values)
{
arr[index++] = value.Longitude;
arr[index++] = value.Latitude;
arr[index++] = value.Member;
}
return new CommandKeyValuesMessage(db, flags, command, key, arr);
}
public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1, RedisValue value2, RedisValue value3) public static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey key, RedisValue value0, RedisValue value1, RedisValue value2, RedisValue value3)
{ {
return new CommandKeyValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3); return new CommandKeyValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3);
...@@ -907,7 +930,7 @@ public CommandKeysMessage(int db, CommandFlags flags, RedisCommand command, Redi ...@@ -907,7 +930,7 @@ public CommandKeysMessage(int db, CommandFlags flags, RedisCommand command, Redi
public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy) public override int GetHashSlot(ServerSelectionStrategy serverSelectionStrategy)
{ {
int slot = ServerSelectionStrategy.NoSlot; int slot = ServerSelectionStrategy.NoSlot;
for(int i = 0; i < keys.Length; i++) for (int i = 0; i < keys.Length; i++)
{ {
slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); slot = serverSelectionStrategy.CombineSlot(slot, keys[i]);
} }
......
...@@ -230,6 +230,78 @@ internal RedisValue[] GetItemsAsValues() ...@@ -230,6 +230,78 @@ internal RedisValue[] GetItemsAsValues()
return arr; return arr;
} }
} }
static readonly string[] NilStrings = new string[0];
internal string[] GetItemsAsStrings()
{
RawResult[] items = GetItems();
if (items == null)
{
return null;
}
else if (items.Length == 0)
{
return NilStrings;
}
else
{
var arr = new string[items.Length];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = (string)(items[i].AsRedisValue());
}
return arr;
}
}
internal GeoPosition? GetItemsAsGeoPosition()
{
RawResult[] items = GetItems();
if (items == null || items.Length == 0)
{
return null;
}
var coords = items[0].GetArrayOfRawResults();
if (coords == null)
{
return null;
}
return new GeoPosition((double)coords[0].AsRedisValue(), (double)coords[1].AsRedisValue());
}
internal GeoPosition?[] GetItemsAsGeoPositionArray()
{
RawResult[] items = GetItems();
if (items == null)
{
return null;
}
else if (items.Length == 0)
{
return new GeoPosition?[0];
}
else
{
var arr = new GeoPosition?[items.Length];
for (int i = 0; i < arr.Length; i++)
{
RawResult[] item = items[i].GetArrayOfRawResults();
if (item == null)
{
arr[i] = null;
}
else
{
arr[i] = new GeoPosition((double)item[0].AsRedisValue(), (double)item[1].AsRedisValue());
}
}
return arr;
}
}
internal RawResult[] GetItemsAsRawResults()
{
return GetItems();
}
// returns an array of RawResults // returns an array of RawResults
internal RawResult[] GetArrayOfRawResults() internal RawResult[] GetArrayOfRawResults()
......
...@@ -38,6 +38,13 @@ enum RedisCommand ...@@ -38,6 +38,13 @@ enum RedisCommand
FLUSHALL, FLUSHALL,
FLUSHDB, FLUSHDB,
GEOADD,
GEODIST,
GEOHASH,
GEOPOS,
GEORADIUS,
GEORADIUSBYMEMBER,
GET, GET,
GETBIT, GETBIT,
GETRANGE, GETRANGE,
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace StackExchange.Redis namespace StackExchange.Redis
...@@ -44,13 +46,179 @@ public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.No ...@@ -44,13 +46,179 @@ public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.No
var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key);
return ExecuteSync(msg, ResultProcessor.RedisValue); return ExecuteSync(msg, ResultProcessor.RedisValue);
} }
public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{ {
var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key);
return ExecuteAsync(msg, ResultProcessor.RedisValue); return ExecuteAsync(msg, ResultProcessor.RedisValue);
} }
public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return GeoAdd(key, new GeoEntry(longitude, latitude, member), flags);
}
public Task<bool> GeoAddAsync(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return GeoAddAsync(key, new GeoEntry(longitude, latitude, member), flags);
}
public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member);
return ExecuteSync(msg, ResultProcessor.Boolean);
}
public Task<bool> GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member);
return ExecuteAsync(msg, ResultProcessor.Boolean);
}
public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public Task<long> GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return SortedSetRemove(key, member, flags);
}
public Task<bool> GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return SortedSetRemoveAsync(key, member, flags);
}
public double? GeoDistance(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, StackExchange.Redis.GeoPosition.GetRedisUnit(unit));
return ExecuteSync(msg, ResultProcessor.NullableDouble);
}
public Task<double?> GeoDistanceAsync(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, StackExchange.Redis.GeoPosition.GetRedisUnit(unit));
return ExecuteAsync(msg, ResultProcessor.NullableDouble);
}
public string[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{
if (members == null) throw new ArgumentNullException(nameof(members));
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues);
return ExecuteSync(msg, ResultProcessor.StringArray);
}
public Task<string[]> GeoHashAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{
if (members == null) throw new ArgumentNullException(nameof(members));
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues);
return ExecuteAsync(msg, ResultProcessor.StringArray);
}
public string GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member);
return ExecuteSync(msg, ResultProcessor.String);
}
public Task<string> GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member);
return ExecuteAsync(msg, ResultProcessor.String);
}
public GeoPosition?[] GeoPosition(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{
if (members == null) throw new ArgumentNullException(nameof(members));
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues);
return ExecuteSync(msg, ResultProcessor.RedisGeoPositionArray);
}
public Task<GeoPosition?[]> GeoPositionAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None)
{
if (members == null) throw new ArgumentNullException(nameof(members));
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues);
return ExecuteAsync(msg, ResultProcessor.RedisGeoPositionArray);
}
public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member);
return ExecuteSync(msg, ResultProcessor.RedisGeoPosition);
}
public Task<GeoPosition?> GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member);
return ExecuteAsync(msg, ResultProcessor.RedisGeoPosition);
}
static readonly RedisValue
WITHCOORD = Encoding.ASCII.GetBytes("WITHCOORD"),
WITHDIST = Encoding.ASCII.GetBytes("WITHDIST"),
WITHHASH = Encoding.ASCII.GetBytes("WITHHASH"),
COUNT = Encoding.ASCII.GetBytes("COUNT"),
ASC = Encoding.ASCII.GetBytes("ASC"),
DESC = Encoding.ASCII.GetBytes("DESC");
private Message GetGeoRadiusMessage(RedisKey key, RedisValue? member, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags)
{
var redisValues = new List<RedisValue>();
RedisCommand command;
if (member == null)
{
redisValues.Add(longitude);
redisValues.Add(latitude);
command = RedisCommand.GEORADIUS;
}
else
{
redisValues.Add(member.Value);
command = RedisCommand.GEORADIUSBYMEMBER;
}
redisValues.Add(radius);
redisValues.Add(StackExchange.Redis.GeoPosition.GetRedisUnit(unit));
if ((options & GeoRadiusOptions.WithCoordinates) != 0) redisValues.Add(WITHCOORD);
if ((options & GeoRadiusOptions.WithDistance) != 0) redisValues.Add(WITHDIST);
if ((options & GeoRadiusOptions.WithGeoHash) != 0) redisValues.Add(WITHHASH);
if (count > 0)
{
redisValues.Add(COUNT);
redisValues.Add(count);
}
if (order != null)
{
switch (order.Value)
{
case Order.Ascending: redisValues.Add(ASC); break;
case Order.Descending: redisValues.Add(DESC); break;
default: throw new ArgumentOutOfRangeException(nameof(order));
}
}
return Message.Create(Database, flags, command, key, redisValues.ToArray());
}
public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags)
{
return ExecuteSync(GetGeoRadiusMessage(key, member, double.NaN, double.NaN, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options));
}
public Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags)
{
return ExecuteAsync(GetGeoRadiusMessage(key, member, double.NaN, double.NaN, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options));
}
public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags)
{
return ExecuteSync(GetGeoRadiusMessage(key, null, longitude, latitude, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options));
}
public Task<GeoRadiusResult[]> GeoRadiusAsync(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags)
{
return ExecuteAsync(GetGeoRadiusMessage(key, null, longitude, latitude, radius, unit, count, order, options, flags), ResultProcessor.GeoRadiusArray(options));
}
public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) public long HashDecrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None)
{ {
return HashIncrement(key, hashField, -value, flags); return HashIncrement(key, hashField, -value, flags);
...@@ -926,7 +1094,6 @@ public Task<RedisResult> ScriptEvaluateAsync(LoadedLuaScript script, object para ...@@ -926,7 +1094,6 @@ public Task<RedisResult> ScriptEvaluateAsync(LoadedLuaScript script, object para
{ {
return script.EvaluateAsync(this, parameters, null, flags); return script.EvaluateAsync(this, parameters, null, flags);
} }
public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{ {
var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value);
...@@ -1685,7 +1852,8 @@ private Message GetHashSetMessage(RedisKey key, HashEntry[] hashFields, CommandF ...@@ -1685,7 +1852,8 @@ private Message GetHashSetMessage(RedisKey key, HashEntry[] hashFields, CommandF
switch (hashFields.Length) switch (hashFields.Length)
{ {
case 0: return null; case 0: return null;
case 1: return Message.Create(Database, flags, RedisCommand.HMSET, key, case 1:
return Message.Create(Database, flags, RedisCommand.HMSET, key,
hashFields[0].name, hashFields[0].value); hashFields[0].name, hashFields[0].value);
case 2: case 2:
return Message.Create(Database, flags, RedisCommand.HMSET, key, return Message.Create(Database, flags, RedisCommand.HMSET, key,
......
...@@ -27,7 +27,8 @@ public struct RedisFeatures ...@@ -27,7 +27,8 @@ public struct RedisFeatures
v2_8_12 = new Version(2, 8, 12), v2_8_12 = new Version(2, 8, 12),
v2_8_18 = new Version(2, 8, 18), v2_8_18 = new Version(2, 8, 18),
v2_9_5 = new Version(2, 9, 5), v2_9_5 = new Version(2, 9, 5),
v3_0_0 = new Version(3, 0, 0); v3_0_0 = new Version(3, 0, 0),
v3_2_0 = new Version(3, 2, 0);
private readonly Version version; private readonly Version version;
/// <summary> /// <summary>
...@@ -144,6 +145,11 @@ public RedisFeatures(Version version) ...@@ -144,6 +145,11 @@ public RedisFeatures(Version version)
/// </summary> /// </summary>
public bool HyperLogLogCountSlaveSafe => Version >= v2_8_18; public bool HyperLogLogCountSlaveSafe => Version >= v2_8_18;
/// <summary>
/// Are the GEO commands available?
/// </summary>
public bool Geo => Version >= v3_2_0;
/// <summary> /// <summary>
/// The Redis version of the server /// The Redis version of the server
/// </summary> /// </summary>
......
...@@ -66,6 +66,15 @@ abstract class ResultProcessor ...@@ -66,6 +66,15 @@ abstract class ResultProcessor
public static readonly ResultProcessor<RedisValue[]> public static readonly ResultProcessor<RedisValue[]>
RedisValueArray = new RedisValueArrayProcessor(); RedisValueArray = new RedisValueArrayProcessor();
public static readonly ResultProcessor<string[]>
StringArray = new StringArrayProcessor();
public static readonly ResultProcessor<GeoPosition?[]>
RedisGeoPositionArray = new RedisValueGeoPositionArrayProcessor();
public static readonly ResultProcessor<GeoPosition?>
RedisGeoPosition = new RedisValueGeoPositionProcessor();
public static readonly ResultProcessor<TimeSpan> public static readonly ResultProcessor<TimeSpan>
ResponseTimer = new TimingProcessor(); ResponseTimer = new TimingProcessor();
...@@ -75,6 +84,8 @@ abstract class ResultProcessor ...@@ -75,6 +84,8 @@ abstract class ResultProcessor
public static readonly SortedSetEntryArrayProcessor public static readonly SortedSetEntryArrayProcessor
SortedSetWithScores = new SortedSetEntryArrayProcessor(); SortedSetWithScores = new SortedSetEntryArrayProcessor();
public static ResultProcessor<GeoRadiusResult[]> GeoRadiusArray(GeoRadiusOptions options) => GeoRadiusResultArrayProcessor.Get(options);
public static readonly ResultProcessor<string> public static readonly ResultProcessor<string>
String = new StringProcessor(), String = new StringProcessor(),
ClusterNodesRaw = new ClusterNodesRawProcessor(); ClusterNodesRaw = new ClusterNodesRawProcessor();
...@@ -204,7 +215,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, Ra ...@@ -204,7 +215,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, Ra
private void UnexpectedResponse(Message message, RawResult result) private void UnexpectedResponse(Message message, RawResult result)
{ {
ConnectionMultiplexer.TraceWithoutContext("From " + GetType().Name, "Unexpected Response"); ConnectionMultiplexer.TraceWithoutContext("From " + GetType().Name, "Unexpected Response");
ConnectionFail(message, ConnectionFailureType.ProtocolFailure, "Unexpected response to " + (message?.Command.ToString() ?? "n/a") +": " + result.ToString()); ConnectionFail(message, ConnectionFailureType.ProtocolFailure, "Unexpected response to " + (message?.Command.ToString() ?? "n/a") + ": " + result.ToString());
} }
public sealed class TimeSpanProcessor : ResultProcessor<TimeSpan?> public sealed class TimeSpanProcessor : ResultProcessor<TimeSpan?>
...@@ -314,7 +325,7 @@ public sealed class TrackSubscriptionsProcessor : ResultProcessor<bool> ...@@ -314,7 +325,7 @@ public sealed class TrackSubscriptionsProcessor : ResultProcessor<bool>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
if(result.Type == ResultType.MultiBulk) if (result.Type == ResultType.MultiBulk)
{ {
var items = result.GetItems(); var items = result.GetItems();
long count; long count;
...@@ -503,7 +514,7 @@ sealed class AutoConfigureProcessor : ResultProcessor<bool> ...@@ -503,7 +514,7 @@ sealed class AutoConfigureProcessor : ResultProcessor<bool>
static readonly byte[] READONLY = Encoding.UTF8.GetBytes("READONLY "); static readonly byte[] READONLY = Encoding.UTF8.GetBytes("READONLY ");
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result) public override bool SetResult(PhysicalConnection connection, Message message, RawResult result)
{ {
if(result.IsError && result.AssertStarts(READONLY)) if (result.IsError && result.AssertStarts(READONLY))
{ {
var server = connection.Bridge.ServerEndPoint; var server = connection.Bridge.ServerEndPoint;
server.Multiplexer.Trace("Auto-configured role: slave"); server.Multiplexer.Trace("Auto-configured role: slave");
...@@ -584,7 +595,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -584,7 +595,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
break; break;
} }
} }
else if((val = Extract(line, "run_id:")) != null) else if ((val = Extract(line, "run_id:")) != null)
{ {
server.RunId = val; server.RunId = val;
} }
...@@ -927,11 +938,11 @@ class PubSubNumSubProcessor : Int64Processor ...@@ -927,11 +938,11 @@ class PubSubNumSubProcessor : Int64Processor
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
if(result.Type == ResultType.MultiBulk) if (result.Type == ResultType.MultiBulk)
{ {
var arr = result.GetItems(); var arr = result.GetItems();
long val; long val;
if(arr != null && arr.Length == 2 && arr[1].TryGetInt64(out val)) if (arr != null && arr.Length == 2 && arr[1].TryGetInt64(out val))
{ {
SetResult(message, val); SetResult(message, val);
return true; return true;
...@@ -950,7 +961,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -950,7 +961,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.Integer: case ResultType.Integer:
case ResultType.SimpleString: case ResultType.SimpleString:
case ResultType.BulkString: case ResultType.BulkString:
if(result.IsNull) if (result.IsNull)
{ {
SetResult(message, null); SetResult(message, null);
return true; return true;
...@@ -975,7 +986,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -975,7 +986,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.Integer: case ResultType.Integer:
case ResultType.SimpleString: case ResultType.SimpleString:
case ResultType.BulkString: case ResultType.BulkString:
if(result.IsNull) if (result.IsNull)
{ {
SetResult(message, null); SetResult(message, null);
return true; return true;
...@@ -1091,11 +1102,140 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1091,11 +1102,140 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
} }
sealed class StringArrayProcessor : ResultProcessor<string[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItemsAsStrings();
SetResult(message, arr);
return true;
}
return false;
}
}
sealed class RedisValueGeoPositionProcessor : ResultProcessor<GeoPosition?>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var pos = result.GetItemsAsGeoPosition();
SetResult(message, pos);
return true;
}
return false;
}
}
sealed class RedisValueGeoPositionArrayProcessor : ResultProcessor<GeoPosition?[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItemsAsGeoPositionArray();
SetResult(message, arr);
return true;
}
return false;
}
}
sealed class GeoRadiusResultArrayProcessor : ResultProcessor<GeoRadiusResult[]>
{
private static readonly GeoRadiusResultArrayProcessor[] instances;
private readonly GeoRadiusOptions options;
static GeoRadiusResultArrayProcessor()
{
instances = new GeoRadiusResultArrayProcessor[8];
for (int i = 0; i < 8; i++) instances[i] = new GeoRadiusResultArrayProcessor((GeoRadiusOptions)i);
}
public static GeoRadiusResultArrayProcessor Get(GeoRadiusOptions options)
{
int i = (int)options;
if (i < 0 || i >= instances.Length) throw new ArgumentOutOfRangeException(nameof(options));
return instances[i];
}
private GeoRadiusResultArrayProcessor(GeoRadiusOptions options)
{
this.options = options;
}
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItemsAsRawResults();
GeoRadiusResult[] typed;
if (arr == null)
{
typed = null;
}
else
{
var options = this.options;
typed = new GeoRadiusResult[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
typed[i] = Parse(options, arr[i]);
}
}
SetResult(message, typed);
return true;
}
return false;
}
private static GeoRadiusResult Parse(GeoRadiusOptions options, RawResult item)
{
if (options == GeoRadiusOptions.None)
{
// Without any WITH option specified, the command just returns a linear array like ["New York","Milan","Paris"].
return new GeoRadiusResult(item.AsRedisValue(), null, null, null);
}
// If WITHCOORD, WITHDIST or WITHHASH options are specified, the command returns an array of arrays, where each sub-array represents a single item.
var arr = item.GetArrayOfRawResults();
int index = 0;
// the first item in the sub-array is always the name of the returned item.
var member = arr[index++].AsRedisValue();
/* The other information is returned in the following order as successive elements of the sub-array.
The distance from the center as a floating point number, in the same unit specified in the radius.
The geohash integer.
The coordinates as a two items x,y array (longitude,latitude).
*/
double? distance = null;
GeoPosition? position = null;
long? hash = null;
if ((options & GeoRadiusOptions.WithDistance) != 0) { distance = (double?)arr[index++].AsRedisValue(); }
if ((options & GeoRadiusOptions.WithGeoHash) != 0) { hash = (long?)arr[index++].AsRedisValue(); }
if ((options & GeoRadiusOptions.WithCoordinates) != 0)
{
var coords = arr[index++].GetArrayOfRawResults();
double longitude = (double)coords[0].AsRedisValue(), latitude = (double)coords[1].AsRedisValue();
position = new GeoPosition(longitude, latitude);
}
return new GeoRadiusResult(member, distance, hash, position);
}
}
sealed class RedisValueProcessor : ResultProcessor<RedisValue> sealed class RedisValueProcessor : ResultProcessor<RedisValue>
{ {
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
switch(result.Type) switch (result.Type)
{ {
case ResultType.Integer: case ResultType.Integer:
case ResultType.SimpleString: case ResultType.SimpleString:
...@@ -1153,6 +1293,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1153,6 +1293,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.BulkString: case ResultType.BulkString:
SetResult(message, result.GetString()); SetResult(message, result.GetString());
return true; return true;
case ResultType.MultiBulk:
var arr = result.GetItems();
if(arr.Length == 1)
{
SetResult(message, arr[0].GetString());
return true;
}
break;
} }
return false; return false;
} }
...@@ -1194,7 +1342,7 @@ public override bool SetResult(PhysicalConnection connection, Message message, R ...@@ -1194,7 +1342,7 @@ public override bool SetResult(PhysicalConnection connection, Message message, R
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{ {
bool happy; bool happy;
switch(message.Command) switch (message.Command)
{ {
case RedisCommand.ECHO: case RedisCommand.ECHO:
happy = result.Type == ResultType.BulkString && (!establishConnection || result.IsEqual(connection.Multiplexer.UniqueId)); happy = result.Type == ResultType.BulkString && (!establishConnection || result.IsEqual(connection.Multiplexer.UniqueId));
...@@ -1212,9 +1360,9 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1212,9 +1360,9 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
happy = true; happy = true;
break; break;
} }
if(happy) if (happy)
{ {
if(establishConnection) connection.Bridge.OnFullyEstablished(connection); if (establishConnection) connection.Bridge.OnFullyEstablished(connection);
SetResult(message, happy); SetResult(message, happy);
return true; return true;
} }
......
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
/// <summary> /// <summary>
/// Describes a sorted-set element with the corresponding value /// Describes a sorted-set element with the corresponding value
/// </summary> /// </summary>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment