diff --git a/sources/Valkey.Glide/BaseClient.GenericCommands.cs b/sources/Valkey.Glide/BaseClient.GenericCommands.cs index 185ab7f..d6fa755 100644 --- a/sources/Valkey.Glide/BaseClient.GenericCommands.cs +++ b/sources/Valkey.Glide/BaseClient.GenericCommands.cs @@ -158,6 +158,17 @@ public async Task KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationK return await Command(Request.KeyCopyAsync(sourceKey, destinationKey, replace)); } + public async Task KeyMoveAsync(ValkeyKey key, int database, CommandFlags flags = CommandFlags.None) + { + Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE"); + return await Command(Request.KeyMoveAsync(key, database)); + } + + public async Task KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false, CommandFlags flags = CommandFlags.None) + { + Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE"); + return await Command(Request.KeyCopyAsync(sourceKey, destinationKey, destinationDatabase, replace)); + } public async Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) { Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE"); diff --git a/sources/Valkey.Glide/Commands/IGenericBaseCommands.cs b/sources/Valkey.Glide/Commands/IGenericBaseCommands.cs index 485ed96..904d2b0 100644 --- a/sources/Valkey.Glide/Commands/IGenericBaseCommands.cs +++ b/sources/Valkey.Glide/Commands/IGenericBaseCommands.cs @@ -499,6 +499,46 @@ public interface IGenericBaseCommands /// Task KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, bool replace = false, CommandFlags flags = CommandFlags.None); + /// + /// Moves key from the currently selected database to the specified destination database. + /// When key already exists in the destination database, or it does not exist in the source database, it does nothing. + /// It is possible to use MOVE as a locking primitive because of this. + /// + /// + /// The key to move. + /// The database to move the key to. + /// The flags to use for this operation. Currently flags are ignored. + /// if key was moved. if key was not moved. + /// + /// + /// + /// bool result = await client.KeyMoveAsync(key, 2); + /// + /// + /// + Task KeyMoveAsync(ValkeyKey key, int database, CommandFlags flags = CommandFlags.None); + + /// + /// Copies the value stored at the source to the destination key in the specified database. When + /// replace is true, removes the destination key first if it already + /// exists, otherwise performs no action. + /// + /// + /// Since Valkey 6.2.0 and above. + /// The key to the source value. + /// The key where the value should be copied to. + /// The database ID to store destinationKey in. + /// Whether to overwrite an existing values at destinationKey. + /// The flags to use for this operation. Currently flags are ignored. + /// if sourceKey was copied. if sourceKey was not copied. + /// + /// + /// + /// bool result = await client.KeyCopyAsync(sourceKey, destKey, 1, replace: true); + /// + /// + /// + Task KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false, CommandFlags flags = CommandFlags.None); /// /// Returns a random key from the database. /// diff --git a/sources/Valkey.Glide/Commands/IGenericCommands.cs b/sources/Valkey.Glide/Commands/IGenericCommands.cs index f74adcd..b230c41 100644 --- a/sources/Valkey.Glide/Commands/IGenericCommands.cs +++ b/sources/Valkey.Glide/Commands/IGenericCommands.cs @@ -167,45 +167,7 @@ public interface IGenericCommands /// Task Exec(Batch batch, bool raiseOnError, BatchOptions options); - /// - /// Move key from the currently selected database to the database specified by database. - /// - /// - /// The key to move. - /// The index of the database to move key to. - /// The flags to use for this operation. Currently flags are ignored. - /// - /// if key was moved. if the key already exists in the destination - /// database or does not exist in the source database. - /// - /// - /// - /// - /// bool result = await client.KeyMoveAsync(key, 2); - /// - /// - /// - Task KeyMoveAsync(ValkeyKey key, int database, CommandFlags flags = CommandFlags.None); - /// - /// Copies the value stored at the source to the destination key. When - /// replace is true, removes the destination key first if it already - /// exists, otherwise performs no action. - /// - /// - /// Since Valkey 6.2.0 and above. - /// The key to the source value. - /// The key where the value should be copied to. - /// The database ID to store destinationKey in. - /// Whether to overwrite an existing values at destinationKey. - /// The flags to use for this operation. Currently flags are ignored. - /// if souceKey was copied. if sourceKey was not copied. - /// - /// - /// - /// bool result = await client.KeyCopyAsync(sourceKey, destKey, replace: true); - /// - /// - /// - Task KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false, CommandFlags flags = CommandFlags.None); + + } diff --git a/sources/Valkey.Glide/ConnectionConfiguration.cs b/sources/Valkey.Glide/ConnectionConfiguration.cs index 6646914..0d28623 100644 --- a/sources/Valkey.Glide/ConnectionConfiguration.cs +++ b/sources/Valkey.Glide/ConnectionConfiguration.cs @@ -187,7 +187,7 @@ internal StandaloneClientConfiguration() { } /// /// /// - /// + /// /// /// public StandaloneClientConfiguration( @@ -220,8 +220,7 @@ public StandaloneClientConfiguration( } /// - /// Configuration for a cluster client. Use or - /// to create an instance. + /// Configuration for a cluster client. Use to create an instance. /// public sealed class ClusterClientConfiguration : BaseClientConfiguration { @@ -237,6 +236,7 @@ internal ClusterClientConfiguration() { } /// /// /// + /// /// /// public ClusterClientConfiguration( @@ -248,6 +248,7 @@ public ClusterClientConfiguration( RetryStrategy? retryStrategy = null, string? username = null, string? password = null, + uint? databaseId = null, Protocol? protocol = null, string? clientName = null ) @@ -260,6 +261,7 @@ public ClusterClientConfiguration( _ = readFrom.HasValue ? builder.ReadFrom = readFrom.Value : new(); _ = retryStrategy.HasValue ? builder.ConnectionRetryStrategy = retryStrategy.Value : new(); _ = (username ?? password) is not null ? builder.Authentication = (username, password!) : new(); + _ = databaseId.HasValue ? builder.DataBaseId = databaseId.Value : new(); _ = protocol.HasValue ? builder.ProtocolVersion = protocol.Value : new(); _ = clientName is not null ? builder.ClientName = clientName : ""; Request = builder.Build().Request; @@ -515,6 +517,23 @@ public T WithConnectionRetryStrategy(RetryStrategy connectionRetryStrategy) public T WithConnectionRetryStrategy(uint numberOfRetries, uint factor, uint exponentBase, uint? jitterPercent = null) => WithConnectionRetryStrategy(new RetryStrategy(numberOfRetries, factor, exponentBase, jitterPercent)); #endregion + #region DataBase ID + /// + /// Index of the logical database to connect to. Must be non-negative and within the range + /// supported by the server configuration. If not specified, defaults to database 0. + /// For cluster mode, requires Valkey 9.0+ with cluster-databases configuration enabled. + /// + public uint DataBaseId + { + set => Config.DatabaseId = value; + } + /// + public T WithDataBaseId(uint dataBaseId) + { + DataBaseId = dataBaseId; + return (T)this; + } + #endregion internal ConnectionConfig Build() => Config; } @@ -530,22 +549,6 @@ public StandaloneClientConfigurationBuilder() : base(false) { } /// Complete the configuration with given settings. /// public new StandaloneClientConfiguration Build() => new() { Request = base.Build() }; - - #region DataBase ID - /// - /// Index of the logical database to connect to. - /// - public uint DataBaseId - { - set => Config.DatabaseId = value; - } - /// - public StandaloneClientConfigurationBuilder WithDataBaseId(uint dataBaseId) - { - DataBaseId = dataBaseId; - return this; - } - #endregion } /// diff --git a/sources/Valkey.Glide/GlideClient.cs b/sources/Valkey.Glide/GlideClient.cs index 4ea4ace..f374391 100644 --- a/sources/Valkey.Glide/GlideClient.cs +++ b/sources/Valkey.Glide/GlideClient.cs @@ -91,17 +91,9 @@ public async Task PingAsync(ValkeyValue message, CommandFlags flags = return await Command(Request.Ping(message)); } - public async Task KeyMoveAsync(ValkeyKey key, int database, CommandFlags flags = CommandFlags.None) - { - Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE"); - return await Command(Request.KeyMoveAsync(key, database)); - } - public async Task KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false, CommandFlags flags = CommandFlags.None) - { - Utils.Requires(flags == CommandFlags.None, "Command flags are not supported by GLIDE"); - return await Command(Request.KeyCopyAsync(sourceKey, destinationKey, destinationDatabase, replace)); - } + + public async Task[]> ConfigGetAsync(ValkeyValue pattern = default, CommandFlags flags = CommandFlags.None) { diff --git a/sources/Valkey.Glide/Pipeline/BaseBatch.GenericCommands.cs b/sources/Valkey.Glide/Pipeline/BaseBatch.GenericCommands.cs index bc65848..951e6f7 100644 --- a/sources/Valkey.Glide/Pipeline/BaseBatch.GenericCommands.cs +++ b/sources/Valkey.Glide/Pipeline/BaseBatch.GenericCommands.cs @@ -89,6 +89,11 @@ public abstract partial class BaseBatch /// public T KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, bool replace = false) => AddCmd(KeyCopyAsync(sourceKey, destinationKey, replace)); + /// + public T KeyMove(ValkeyKey key, int database) => AddCmd(KeyMoveAsync(key, database)); + + /// + public T KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false) => AddCmd(KeyCopyAsync(sourceKey, destinationKey, destinationDatabase, replace)); /// public T KeyRandom() => AddCmd(KeyRandomAsync()); @@ -125,6 +130,8 @@ public abstract partial class BaseBatch IBatch IBatchGenericCommands.KeyIdleTime(ValkeyKey key) => KeyIdleTime(key); IBatch IBatchGenericCommands.KeyRefCount(ValkeyKey key) => KeyRefCount(key); IBatch IBatchGenericCommands.KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, bool replace) => KeyCopy(sourceKey, destinationKey, replace); + IBatch IBatchGenericCommands.KeyMove(ValkeyKey key, int database) => KeyMove(key, database); + IBatch IBatchGenericCommands.KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace) => KeyCopy(sourceKey, destinationKey, destinationDatabase, replace); IBatch IBatchGenericCommands.KeyRandom() => KeyRandom(); IBatch IBatchGenericCommands.Sort(ValkeyKey key, long skip, long take, Order order, SortType sortType, ValkeyValue by, ValkeyValue[]? get) => Sort(key, skip, take, order, sortType, by, get); IBatch IBatchGenericCommands.Wait(long numreplicas, long timeout) => Wait(numreplicas, timeout); diff --git a/sources/Valkey.Glide/Pipeline/Batch.cs b/sources/Valkey.Glide/Pipeline/Batch.cs index 999e141..0dedee6 100644 --- a/sources/Valkey.Glide/Pipeline/Batch.cs +++ b/sources/Valkey.Glide/Pipeline/Batch.cs @@ -1,7 +1,5 @@ // Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 -using Valkey.Glide.Internals; - namespace Valkey.Glide.Pipeline; /// @@ -25,15 +23,6 @@ namespace Valkey.Glide.Pipeline; /// public sealed class Batch(bool isAtomic) : BaseBatch(isAtomic), IBatchStandalone { - // Standalone commands: select, move, copy, scan - - /// - public Batch KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false) => AddCmd(Request.KeyCopyAsync(sourceKey, destinationKey, destinationDatabase, replace)); - - /// - public Batch KeyMove(ValkeyKey key, int database) => AddCmd(Request.KeyMoveAsync(key, database)); - // Explicit interface implementations for IBatchStandalone - IBatchStandalone IBatchStandalone.KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace) => KeyCopy(sourceKey, destinationKey, destinationDatabase, replace); - IBatchStandalone IBatchStandalone.KeyMove(ValkeyKey key, int database) => KeyMove(key, database); + IBatch IBatchStandalone.SelectAsync(long index) => SelectAsync(index); } diff --git a/sources/Valkey.Glide/Pipeline/IBatchGenericCommands.cs b/sources/Valkey.Glide/Pipeline/IBatchGenericCommands.cs index f7d4b3c..0976574 100644 --- a/sources/Valkey.Glide/Pipeline/IBatchGenericCommands.cs +++ b/sources/Valkey.Glide/Pipeline/IBatchGenericCommands.cs @@ -111,6 +111,13 @@ internal interface IBatchGenericCommands /// Command Response - IBatch KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, bool replace = false); + /// + /// Command Response - + IBatch KeyMove(ValkeyKey key, int database); + + /// + /// Command Response - + IBatch KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false); /// /// Command Response - IBatch KeyRandom(); diff --git a/sources/Valkey.Glide/Pipeline/IBatchStandalone.cs b/sources/Valkey.Glide/Pipeline/IBatchStandalone.cs index 412c857..bffbeae 100644 --- a/sources/Valkey.Glide/Pipeline/IBatchStandalone.cs +++ b/sources/Valkey.Glide/Pipeline/IBatchStandalone.cs @@ -1,7 +1,5 @@ // Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 -using Valkey.Glide.Commands; - namespace Valkey.Glide.Pipeline; /// @@ -9,11 +7,7 @@ namespace Valkey.Glide.Pipeline; /// internal interface IBatchStandalone { - /// - /// Command Response - - IBatchStandalone KeyCopy(ValkeyKey sourceKey, ValkeyKey destinationKey, int destinationDatabase, bool replace = false); - - /// - /// Command Response - - IBatchStandalone KeyMove(ValkeyKey key, int database); + /// + /// Command Response - + IBatch SelectAsync(long index); } diff --git a/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs b/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs index c88b993..183fe51 100644 --- a/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs @@ -510,4 +510,79 @@ public async Task TestSelect(GlideClusterClient client) string result = await client.SelectAsync(0); Assert.Equal("OK", result); } + + [Fact] + public async Task TestClusterDatabaseId() + { + Assert.SkipWhen( + TestConfiguration.SERVER_VERSION < new Version("9.0.0"), + "Multi-database support for Cluster Client requires Valkey 9.0+" + ); + + var config = TestConfiguration.DefaultClusterClientConfig() + .WithDataBaseId(1) + .Build(); + + using var client = await GlideClusterClient.CreateClient(config); + + // Verify we can connect with database ID 1 + TimeSpan result = await client.PingAsync(); + Assert.True(result >= TimeSpan.Zero); + + // Verify database isolation by setting a key in database 1 + string testKey = Guid.NewGuid().ToString(); + string testValue = "test_value_db1"; + await client.StringSetAsync(testKey, testValue); + + // Verify the key exists in database 1 + ValkeyValue retrievedValue = await client.StringGetAsync(testKey); + Assert.Equal(testValue, retrievedValue.ToString()); + } + + [Theory(DisableDiscoveryEnumeration = true)] + [MemberData(nameof(Config.TestClusterClients), MemberType = typeof(TestConfiguration))] + public async Task TestKeyMoveAsync(GlideClusterClient client) + { + Assert.SkipWhen( + TestConfiguration.SERVER_VERSION < new Version("9.0.0"), + "MOVE command for Cluster Client requires Valkey 9.0+ with multi-database support" + ); + + string key = Guid.NewGuid().ToString(); + string value = "test_value"; + + // Set a key in the current database + await client.StringSetAsync(key, value); + + // Move the key to database 1 + bool moveResult = await client.KeyMoveAsync(key, 1); + Assert.True(moveResult); + + // Verify the key no longer exists in the current database + Assert.False(await client.KeyExistsAsync(key)); + } + + [Theory(DisableDiscoveryEnumeration = true)] + [MemberData(nameof(Config.TestClusterClients), MemberType = typeof(TestConfiguration))] + public async Task TestKeyCopyAsync(GlideClusterClient client) + { + Assert.SkipWhen( + TestConfiguration.SERVER_VERSION < new Version("9.0.0"), + "COPY command with database parameter for Cluster Client requires Valkey 9.0+ with multi-database support" + ); + + string sourceKey = Guid.NewGuid().ToString(); + string destKey = Guid.NewGuid().ToString(); + string value = "test_value"; + + // Set a key in the current database + await client.StringSetAsync(sourceKey, value); + + // Copy the key to database 1 + bool copyResult = await client.KeyCopyAsync(sourceKey, destKey, 1); + Assert.True(copyResult); + + // Verify the source key still exists in the current database + Assert.True(await client.KeyExistsAsync(sourceKey)); + } } diff --git a/tests/Valkey.Glide.IntegrationTests/StandaloneClientTests.cs b/tests/Valkey.Glide.IntegrationTests/StandaloneClientTests.cs index 72f875f..55b4f53 100644 --- a/tests/Valkey.Glide.IntegrationTests/StandaloneClientTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/StandaloneClientTests.cs @@ -269,7 +269,7 @@ public async Task BatchKeyCopyAndKeyMove(bool isAtomic) _ = batch.StringSet(sourceKey, value); _ = batch.StringSet(moveKey, value); - IBatchStandalone batch2 = new Batch(isAtomic); + Batch batch2 = new Batch(isAtomic); // Test KeyCopy with database parameter _ = batch2.KeyCopy(sourceKey, destKey, 1, false); @@ -278,7 +278,7 @@ public async Task BatchKeyCopyAndKeyMove(bool isAtomic) _ = batch2.KeyMove(moveKey, 2); object?[] results = (await client.Exec((Batch)batch, false))!; - object?[] results2 = (await client.Exec((Batch)batch2, false))!; + object?[] results2 = (await client.Exec(batch2, false))!; Assert.Multiple( () => Assert.True((bool)results[0]!), // Set sourceKey