diff --git a/AerospikeClient/Async/AsyncClient.cs b/AerospikeClient/Async/AsyncClient.cs
index 48f3469a..e1647a5b 100644
--- a/AerospikeClient/Async/AsyncClient.cs
+++ b/AerospikeClient/Async/AsyncClient.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 Aerospike, Inc.
+ * Copyright 2012-2024 Aerospike, Inc.
*
* Portions may be licensed to Aerospike, Inc. under one or more contributor
* license agreements.
@@ -563,7 +563,7 @@ public void Delete(BatchPolicy batchPolicy, BatchDeletePolicy deletePolicy, Batc
AsyncBatchOperateRecordSequenceExecutor executor = new(cluster, batchPolicy, listener, keys, null, attr);
AsyncTxnMonitor.ExecuteBatch(batchPolicy, executor, keys);
}
-
+
//-------------------------------------------------------
// Touch Operations
//-------------------------------------------------------
@@ -571,7 +571,7 @@ public void Delete(BatchPolicy batchPolicy, BatchDeletePolicy deletePolicy, Batc
///
/// Asynchronously reset record's time to expiration using the policy's expiration.
/// Create listener, call asynchronous touch and return task monitor.
- /// Fail if the record does not exist.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
///
/// write configuration parameters, pass in null for defaults
/// cancellation token
@@ -588,7 +588,7 @@ public Task Touch(WritePolicy policy, CancellationToken token, Key key)
/// Asynchronously reset record's time to expiration using the policy's expiration.
/// Schedule the touch command with a channel selector and return.
/// Another thread will process the command and send the results to the listener.
- /// Fail if the record does not exist.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
///
/// write configuration parameters, pass in null for defaults
/// where to send results, pass in null for fire and forget
@@ -604,6 +604,46 @@ public void Touch(WritePolicy policy, WriteListener listener, Key key)
AsyncTxnMonitor.Execute(cluster, policy, async);
}
+ ///
+ /// Asynchronously reset record's time to expiration using the policy's expiration.
+ /// Create listener, call asynchronous touched and return task monitor.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ ///
+ /// write configuration parameters, pass in null for defaults
+ /// cancellation token
+ /// unique record identifier
+ /// if queue is full
+ public Task Touched(WritePolicy policy, CancellationToken token, Key key)
+ {
+ ExistsListenerAdapter listener = new(token);
+ Touched(policy, listener, key);
+ return listener.Task;
+ }
+
+ ///
+ /// Asynchronously reset record's time to expiration using the policy's expiration.
+ /// Schedule the touched command with a channel selector and return.
+ /// Another thread will process the command and send the results to the listener.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ ///
+ /// If the record does not exist, send a value of false to
+ ///
+ ///
+ ///
+ /// write configuration parameters, pass in null for defaults
+ /// where to send results, pass in null for fire and forget
+ /// unique record identifier
+ /// if queue is full
+ public void Touched(WritePolicy policy, ExistsListener listener, Key key)
+ {
+ if (policy == null)
+ {
+ policy = writePolicyDefault;
+ }
+ AsyncTouch async = new(cluster, policy, listener, key);
+ AsyncTxnMonitor.Execute(cluster, policy, async);
+ }
+
//-------------------------------------------------------
// Existence-Check Operations
//-------------------------------------------------------
diff --git a/AerospikeClient/Async/AsyncTouch.cs b/AerospikeClient/Async/AsyncTouch.cs
index 9e8ed5bf..0d0a64b8 100644
--- a/AerospikeClient/Async/AsyncTouch.cs
+++ b/AerospikeClient/Async/AsyncTouch.cs
@@ -20,17 +20,28 @@ namespace Aerospike.Client
public sealed class AsyncTouch : AsyncWriteBase
{
private readonly WriteListener listener;
+ private readonly ExistsListener existsListener;
+ private bool touched;
public AsyncTouch(AsyncCluster cluster, WritePolicy writePolicy, WriteListener listener, Key key)
: base(cluster, writePolicy, key)
{
this.listener = listener;
+ this.existsListener = null;
+ }
+
+ public AsyncTouch(AsyncCluster cluster, WritePolicy writePolicy, ExistsListener listener, Key key)
+ : base(cluster, writePolicy, key)
+ {
+ this.listener = null;
+ this.existsListener = listener;
}
public AsyncTouch(AsyncTouch other)
: base(other)
{
this.listener = other.listener;
+ this.existsListener = other.existsListener;
}
protected internal override AsyncCommand CloneCommand()
@@ -50,6 +61,17 @@ protected internal override bool ParseResult()
if (resultCode == ResultCode.OK)
{
+ touched = true;
+ return true;
+ }
+
+ touched = false;
+ if (resultCode == ResultCode.KEY_NOT_FOUND_ERROR)
+ {
+ if (existsListener == null)
+ {
+ throw new AerospikeException(resultCode);
+ }
return true;
}
@@ -71,6 +93,10 @@ protected internal override void OnSuccess()
{
listener.OnSuccess(Key);
}
+ else if (existsListener != null)
+ {
+ existsListener.OnSuccess(Key, touched);
+ }
}
protected internal override void OnFailure(AerospikeException e)
@@ -79,6 +105,10 @@ protected internal override void OnFailure(AerospikeException e)
{
listener.OnFailure(e);
}
+ else if (existsListener != null)
+ {
+ existsListener.OnFailure(e);
+ }
}
}
}
diff --git a/AerospikeClient/Async/IAsyncClient.cs b/AerospikeClient/Async/IAsyncClient.cs
index ccbbb017..0cfd5b5d 100644
--- a/AerospikeClient/Async/IAsyncClient.cs
+++ b/AerospikeClient/Async/IAsyncClient.cs
@@ -311,7 +311,7 @@ public interface IAsyncClient : IAerospikeClient
///
/// Asynchronously reset record's time to expiration using the policy's expiration.
/// Create listener, call asynchronous touch and return task monitor.
- /// Fail if the record does not exist.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
///
/// write configuration parameters, pass in null for defaults
/// cancellation token
@@ -323,7 +323,7 @@ public interface IAsyncClient : IAerospikeClient
/// Asynchronously reset record's time to expiration using the policy's expiration.
/// Schedule the touch command with a channel selector and return.
/// Another thread will process the command and send the results to the listener.
- /// Fail if the record does not exist.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
///
/// write configuration parameters, pass in null for defaults
/// where to send results, pass in null for fire and forget
@@ -331,6 +331,33 @@ public interface IAsyncClient : IAerospikeClient
/// if queue is full
void Touch(WritePolicy policy, WriteListener listener, Key key);
+ ///
+ /// Asynchronously reset record's time to expiration using the policy's expiration.
+ /// Create listener, call asynchronous touched and return task monitor.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ ///
+ /// write configuration parameters, pass in null for defaults
+ /// cancellation token
+ /// unique record identifier
+ /// if queue is full
+ Task Touched(WritePolicy policy, CancellationToken token, Key key);
+
+ ///
+ /// Asynchronously reset record's time to expiration using the policy's expiration.
+ /// Schedule the touched command with a channel selector and return.
+ /// Another thread will process the command and send the results to the listener.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ ///
+ /// If the record does not exist, send a value of false to
+ ///
+ ///
+ ///
+ /// write configuration parameters, pass in null for defaults
+ /// where to send results, pass in null for fire and forget
+ /// unique record identifier
+ /// if queue is full
+ void Touched(WritePolicy policy, ExistsListener listener, Key key);
+
//-------------------------------------------------------
// Existence-Check Operations
//-------------------------------------------------------
diff --git a/AerospikeClient/Command/TouchCommand.cs b/AerospikeClient/Command/TouchCommand.cs
index 10b6b84c..1d1913bf 100644
--- a/AerospikeClient/Command/TouchCommand.cs
+++ b/AerospikeClient/Command/TouchCommand.cs
@@ -19,9 +19,18 @@ namespace Aerospike.Client
{
public sealed class TouchCommand : SyncWriteCommand
{
+ private readonly bool failOnNotFound;
+ internal bool Touched { get; private set; }
public TouchCommand(Cluster cluster, WritePolicy writePolicy, Key key)
: base(cluster, writePolicy, key)
{
+ this.failOnNotFound = true;
+ }
+
+ public TouchCommand(Cluster cluster, WritePolicy writePolicy, Key key, bool failOnNotFound)
+ : base(cluster, writePolicy, key)
+ {
+ this.failOnNotFound = failOnNotFound;
}
protected internal override void WriteBuffer()
@@ -36,6 +45,17 @@ protected internal override void ParseResult(Connection conn)
if (resultCode == ResultCode.OK)
{
+ Touched = true;
+ return;
+ }
+
+ Touched = false;
+ if (resultCode == ResultCode.KEY_NOT_FOUND_ERROR)
+ {
+ if (failOnNotFound)
+ {
+ throw new AerospikeException(resultCode);
+ }
return;
}
diff --git a/AerospikeClient/Main/AerospikeClient.cs b/AerospikeClient/Main/AerospikeClient.cs
index cbf897da..a91de549 100644
--- a/AerospikeClient/Main/AerospikeClient.cs
+++ b/AerospikeClient/Main/AerospikeClient.cs
@@ -821,7 +821,8 @@ public void Truncate(InfoPolicy policy, string ns, string set, DateTime? beforeL
///
/// Reset record's time to expiration using the policy's expiration.
- /// Fail if the record does not exist.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ /// Throw an exception if the record does not exist.
///
/// write configuration parameters, pass in null for defaults
/// unique record identifier
@@ -842,6 +843,32 @@ public void Touch(WritePolicy policy, Key key)
command.Execute();
}
+ ///
+ /// Reset record's time to expiration using the policy's expiration.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ /// Return true if the record exists and is touched.Return false if the record does not exist.
+ ///
+ /// write configuration parameters, pass in null for defaults
+ /// unique record identifier
+ /// true if record was touched, false otherwise
+ /// if touch fails
+ public bool Touched(WritePolicy policy, Key key)
+ {
+ if (policy == null)
+ {
+ policy = writePolicyDefault;
+ }
+ if (policy.Txn != null)
+ {
+ TxnMonitor.AddKey(cluster, policy, key);
+ }
+
+ TouchCommand command = new(cluster, policy, key, false);
+ command.Execute();
+
+ return command.Touched;
+ }
+
//-------------------------------------------------------
// Existence-Check Operations
//-------------------------------------------------------
diff --git a/AerospikeClient/Main/IAerospikeClient.cs b/AerospikeClient/Main/IAerospikeClient.cs
index 210dab9d..2dff73b1 100644
--- a/AerospikeClient/Main/IAerospikeClient.cs
+++ b/AerospikeClient/Main/IAerospikeClient.cs
@@ -291,15 +291,30 @@ public interface IAerospikeClient
// Touch Operations
//-------------------------------------------------------
+
///
/// Reset record's time to expiration using the policy's expiration.
- /// Fail if the record does not exist.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ /// Throw an exception if the record does not exist.
///
/// write configuration parameters, pass in null for defaults
/// unique record identifier
/// if touch fails
void Touch(WritePolicy policy, Key key);
+
+
+ ///
+ /// Reset record's time to expiration using the policy's expiration.
+ /// If the record does not exist, it can't be created because the server deletes empty records.
+ /// Return true if the record exists and is touched.Return false if the record does not exist.
+ ///
+ /// write configuration parameters, pass in null for defaults
+ /// unique record identifier
+ /// true if record was touched, false otherwise
+ /// if touch fails
+ bool Touched(WritePolicy policy, Key key);
+
//-------------------------------------------------------
// Existence-Check Operations
//-------------------------------------------------------
diff --git a/AerospikeTest/Async/TestAsyncTouch.cs b/AerospikeTest/Async/TestAsyncTouch.cs
new file mode 100644
index 00000000..a058da48
--- /dev/null
+++ b/AerospikeTest/Async/TestAsyncTouch.cs
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2024 Aerospike, Inc.
+ *
+ * Portions may be licensed to Aerospike, Inc. under one or more contributor
+ * license agreements.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+using Aerospike.Client;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Aerospike.Test
+{
+ [TestClass]
+ public class TestAsyncTouch : TestAsync
+ {
+ [TestMethod]
+ public void AsyncTouched()
+ {
+ Key key = new(args.ns, args.set, "doesNotExistAsyncTouch");
+
+ client.Touched(null, new TouchListener(this), key);
+ WaitTillComplete();
+ }
+
+ private class TouchListener : ExistsListener
+ {
+ private readonly TestAsyncTouch parent;
+
+ public TouchListener(TestAsyncTouch parent)
+ {
+ this.parent = parent;
+ }
+
+ public void OnSuccess(Key key, bool exists)
+ {
+ Assert.IsFalse(exists);
+ parent.NotifyCompleted();
+ }
+
+ public void OnFailure(AerospikeException e)
+ {
+ parent.SetError(e);
+ parent.NotifyCompleted();
+ }
+ }
+ }
+}
diff --git a/AerospikeTest/Sync/Basic/TestTouch.cs b/AerospikeTest/Sync/Basic/TestTouch.cs
index 54fe497e..2d873080 100644
--- a/AerospikeTest/Sync/Basic/TestTouch.cs
+++ b/AerospikeTest/Sync/Basic/TestTouch.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 Aerospike, Inc.
+ * Copyright 2012-2024 Aerospike, Inc.
*
* Portions may be licensed to Aerospike, Inc. under one or more contributor
* license agreements.
@@ -23,31 +23,77 @@ namespace Aerospike.Test
public class TestTouch : TestSync
{
[TestMethod]
- public void Touch()
+ public void TouchOperate()
{
- Key key = new Key(args.ns, args.set, "touchkey");
+ Key key = new Key(args.ns, args.set, "TouchOperate");
Bin bin = new Bin(args.GetBinName("touchbin"), "touchvalue");
WritePolicy writePolicy = new WritePolicy();
- writePolicy.expiration = 2;
+ writePolicy.expiration = 1;
client.Put(writePolicy, key, bin);
- writePolicy.expiration = 5;
+ writePolicy.expiration = 2;
Record record = client.Operate(writePolicy, key, Operation.Touch(), Operation.GetHeader());
AssertRecordFound(key, record);
Assert.AreNotEqual(0, record.expiration);
- Util.Sleep(3000);
+ Util.Sleep(1000);
record = client.Get(null, key, bin.name);
AssertRecordFound(key, record);
- Util.Sleep(4000);
+ Util.Sleep(3000);
record = client.Get(null, key, bin.name);
Assert.IsNull(record);
}
+
+ [TestMethod]
+ public void Touch()
+ {
+ Key key = new Key(args.ns, args.set, "touch");
+ Bin bin = new Bin(args.GetBinName("touchbin"), "touchvalue");
+
+ WritePolicy writePolicy = new WritePolicy();
+ writePolicy.expiration = 1;
+
+ client.Put(writePolicy, key, bin);
+
+ writePolicy.expiration = 2;
+ client.Touch(writePolicy, key);
+
+ Util.Sleep(1000);
+
+ var record = client.GetHeader(writePolicy, key);
+ AssertRecordFound(key, record);
+ Assert.AreNotEqual(0, record.expiration);
+
+ Util.Sleep(3000);
+
+ record = client.GetHeader(null, key);
+ Assert.IsNull(record);
+ }
+
+ [TestMethod]
+ public void Touched()
+ {
+ Key key = new(args.ns, args.set, "touched");
+
+ client.Delete(null, key);
+
+ WritePolicy writePolicy = new();
+ writePolicy.expiration = 10;
+
+ bool touched = client.Touched(writePolicy, key);
+ Assert.IsFalse(touched);
+
+ Bin bin = new("touchBin", "touchValue");
+ client.Put(writePolicy, key, bin);
+
+ touched = client.Touched(writePolicy, key);
+ Assert.IsTrue(touched);
+ }
}
}