Skip to content

Commit 37e3b6f

Browse files
committed
NCO-61: Update async analytics API to current spec
* Refactor async query types for spec compliance: * Replace QueryStatus and QueryHandleResults with QueryResultHandle * Remove handle serialization logic * Adopt fluent builder options (FetchResultHandleOptions, etc.) * Drop RequestTimeout, ResultTTL, and Deserializer from start options * Add Scope.StartQueryAsync() to support context-mapped async queries * Map 404s to QueryNotFoundException on fetches, and treat as success for cancel and discard operations * Update unit and func tests for options, 404s, and end-to-end flows
1 parent 1c88561 commit 37e3b6f

20 files changed

Lines changed: 915 additions & 597 deletions

src/Couchbase.Analytics/Async/QueryHandle.cs

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
using System.Text.Json;
2323
using Couchbase.AnalyticsClient.Internal;
24+
using Couchbase.AnalyticsClient.Options;
25+
using Couchbase.AnalyticsClient.Results;
2426

2527
namespace Couchbase.AnalyticsClient.Async;
2628

@@ -31,7 +33,6 @@ namespace Couchbase.AnalyticsClient.Async;
3133
public class QueryHandle
3234
{
3335
private readonly IAnalyticsService _analyticsService;
34-
private readonly TimeSpan? _requestTimeout;
3536

3637
/// <summary>
3738
/// The query handle string used to poll status and fetch results.
@@ -44,75 +45,53 @@ public class QueryHandle
4445
/// </summary>
4546
public string RequestId { get; }
4647

47-
internal QueryHandle(string handle, string requestId, IAnalyticsService analyticsService, TimeSpan? requestTimeout = null)
48+
internal QueryHandle(string handle, string requestId, IAnalyticsService analyticsService)
4849
{
4950
Handle = handle ?? throw new ArgumentNullException(nameof(handle));
5051
RequestId = requestId ?? throw new ArgumentNullException(nameof(requestId));
5152
_analyticsService = analyticsService ?? throw new ArgumentNullException(nameof(analyticsService));
52-
_requestTimeout = requestTimeout;
5353
}
5454

5555
/// <summary>
56-
/// Fetches the current status of the asynchronous query from the server.
56+
/// Fetches the result handle of the asynchronous query from the server.
5757
/// </summary>
58+
/// <param name="options">Options for fetching the result handle.</param>
5859
/// <param name="cancellationToken">A cancellation token.</param>
59-
/// <returns>A <see cref="QueryStatus"/> representing the current state of the query.</returns>
60-
public async Task<QueryStatus> FetchStatusAsync(CancellationToken cancellationToken = default)
60+
/// <returns>A <see cref="QueryResultHandle"/> if results are ready, otherwise null.</returns>
61+
public Task<QueryResultHandle?> FetchResultHandleAsync(FetchResultHandleOptions? options = null, CancellationToken cancellationToken = default)
6162
{
62-
return await _analyticsService.FetchStatusAsync(Handle, _requestTimeout, cancellationToken)
63-
.ConfigureAwait(false);
63+
options ??= new FetchResultHandleOptions();
64+
return _analyticsService.FetchResultHandleAsync(this, options, cancellationToken);
6465
}
6566

6667
/// <summary>
67-
/// Discards the query results on the server. After this call, the results can no longer be fetched.
68+
/// Fetches the result handle of the asynchronous query from the server.
6869
/// </summary>
69-
/// <param name="cancellationToken">A cancellation token.</param>
70-
public async Task DiscardResultsAsync(CancellationToken cancellationToken = default)
70+
public Task<QueryResultHandle?> FetchResultHandleAsync(Func<FetchResultHandleOptions, FetchResultHandleOptions> options, CancellationToken cancellationToken = default)
7171
{
72-
await _analyticsService.DiscardResultsAsync(Handle, _requestTimeout, cancellationToken)
73-
.ConfigureAwait(false);
72+
var fetchOptions = new FetchResultHandleOptions();
73+
fetchOptions = options.Invoke(fetchOptions);
74+
return FetchResultHandleAsync(fetchOptions, cancellationToken);
7475
}
7576

7677
/// <summary>
7778
/// Cancels the query on the server. If the query has already completed, this is a no-op.
7879
/// </summary>
80+
/// <param name="options">Options for cancellation.</param>
7981
/// <param name="cancellationToken">A cancellation token.</param>
80-
public async Task CancelAsync(CancellationToken cancellationToken = default)
81-
{
82-
await _analyticsService.CancelQueryAsync(RequestId, _requestTimeout, cancellationToken)
83-
.ConfigureAwait(false);
84-
}
85-
86-
/// <summary>
87-
/// Serializes this <see cref="QueryHandle"/> to a JSON string so it can be persisted and
88-
/// later reconstructed via <see cref="Cluster.QueryHandleFromSerialized"/>.
89-
/// This method does not perform any network operations.
90-
/// </summary>
91-
/// <returns>A JSON string containing the handle and request ID.</returns>
92-
public string Serialize()
82+
public Task CancelAsync(CancelOptions? options = null, CancellationToken cancellationToken = default)
9383
{
94-
var data = new SerializedQueryHandle(Handle, RequestId);
95-
return JsonSerializer.Serialize(data);
84+
options ??= new CancelOptions();
85+
return _analyticsService.CancelQueryAsync(RequestId, options, cancellationToken);
9686
}
9787

9888
/// <summary>
99-
/// Deserializes a <see cref="QueryHandle"/> from a JSON string previously produced by <see cref="Serialize"/>.
100-
/// This method does not perform any network operations.
89+
/// Cancels the query on the server. If the query has already completed, this is a no-op.
10190
/// </summary>
102-
internal static QueryHandle Deserialize(string serializedHandle, IAnalyticsService analyticsService, TimeSpan? requestTimeout = null)
91+
public Task CancelAsync(Func<CancelOptions, CancelOptions> options, CancellationToken cancellationToken = default)
10392
{
104-
ArgumentNullException.ThrowIfNull(serializedHandle);
105-
106-
var data = JsonSerializer.Deserialize<SerializedQueryHandle>(serializedHandle)
107-
?? throw new ArgumentException("Invalid serialized handle format.", nameof(serializedHandle));
108-
109-
if (string.IsNullOrWhiteSpace(data.Handle) || string.IsNullOrWhiteSpace(data.RequestId))
110-
{
111-
throw new ArgumentException("Serialized handle is missing required fields.", nameof(serializedHandle));
112-
}
113-
114-
return new QueryHandle(data.Handle, data.RequestId, analyticsService, requestTimeout);
93+
var cancelOptions = new CancelOptions();
94+
cancelOptions = options.Invoke(cancelOptions);
95+
return CancelAsync(cancelOptions, cancellationToken);
11596
}
116-
117-
private record SerializedQueryHandle(string Handle, string RequestId);
11897
}

src/Couchbase.Analytics/Async/QueryHandleResults.cs

Lines changed: 0 additions & 58 deletions
This file was deleted.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#region License
2+
/* ************************************************************
3+
*
4+
* @author Couchbase <info@couchbase.com>
5+
* @copyright 2025 Couchbase, Inc.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
* ************************************************************/
20+
#endregion
21+
22+
using Couchbase.AnalyticsClient.Internal;
23+
using Couchbase.AnalyticsClient.Options;
24+
using Couchbase.AnalyticsClient.Results;
25+
26+
namespace Couchbase.AnalyticsClient.Async;
27+
28+
/// <summary>
29+
/// Provides access to the results of a completed asynchronous query.
30+
/// </summary>
31+
public class QueryResultHandle
32+
{
33+
private readonly string _handlePath;
34+
private readonly IAnalyticsService _analyticsService;
35+
36+
/// <summary>
37+
/// The request ID assigned by the server when the query was submitted.
38+
/// </summary>
39+
public string RequestId { get; }
40+
41+
internal QueryResultHandle(string handlePath, string requestId, IAnalyticsService analyticsService)
42+
{
43+
_handlePath = handlePath ?? throw new ArgumentNullException(nameof(handlePath));
44+
RequestId = requestId ?? throw new ArgumentNullException(nameof(requestId));
45+
_analyticsService = analyticsService ?? throw new ArgumentNullException(nameof(analyticsService));
46+
}
47+
48+
/// <summary>
49+
/// Fetches the results of the query from the server.
50+
/// </summary>
51+
/// <param name="options">Options for fetching the results.</param>
52+
/// <param name="cancellationToken">A cancellation token.</param>
53+
/// <returns>An <see cref="IQueryResult"/> that can be used to enumerate the result rows.</returns>
54+
public Task<IQueryResult> FetchResultsAsync(FetchResultsOptions? options = null, CancellationToken cancellationToken = default)
55+
{
56+
options ??= new FetchResultsOptions();
57+
return _analyticsService.FetchResultsAsync(RequestId, _handlePath, options, cancellationToken);
58+
}
59+
60+
/// <summary>
61+
/// Fetches the results of the query from the server.
62+
/// </summary>
63+
public Task<IQueryResult> FetchResultsAsync(Func<FetchResultsOptions, FetchResultsOptions> options, CancellationToken cancellationToken = default)
64+
{
65+
var fetchOptions = new FetchResultsOptions();
66+
fetchOptions = options.Invoke(fetchOptions);
67+
return FetchResultsAsync(fetchOptions, cancellationToken);
68+
}
69+
70+
/// <summary>
71+
/// Discards the query results on the server. After this call, the results can no longer be fetched.
72+
/// </summary>
73+
/// <param name="options">Options for discarding results.</param>
74+
/// <param name="cancellationToken">A cancellation token.</param>
75+
public Task DiscardResultsAsync(DiscardResultsOptions? options = null, CancellationToken cancellationToken = default)
76+
{
77+
options ??= new DiscardResultsOptions();
78+
return _analyticsService.DiscardResultsAsync(RequestId, _handlePath, options, cancellationToken);
79+
}
80+
81+
/// <summary>
82+
/// Discards the query results on the server. After this call, the results can no longer be fetched.
83+
/// </summary>
84+
public Task DiscardResultsAsync(Func<DiscardResultsOptions, DiscardResultsOptions> options, CancellationToken cancellationToken = default)
85+
{
86+
var discardOptions = new DiscardResultsOptions();
87+
discardOptions = options.Invoke(discardOptions);
88+
return DiscardResultsAsync(discardOptions, cancellationToken);
89+
}
90+
}

0 commit comments

Comments
 (0)