/// <summary> /// Calls <c>PollQueryResult</c> in a loop until the query completes. Can be canceled. /// </summary> /// <remarks> /// Canceling via the <c>CancellationToken</c> cancels the returned <c>Task</c> and /// automatically cancels the query execution by calling <c>/api/queries/{query_id}/cancel</c>. /// </remarks> /// <param name="apiUrl">The Url of the Aircloak api.</param> /// <param name="queryId">The query Id obtained via a previous call to the /api/query endpoint.</param> /// <param name="query">An instance of the <see cref="DQuery{TRow}"/> interface.</param> /// <param name="rowParser">A delegate used for parsing a result row.</param> /// <param name="pollFrequency">How often to poll the api endpoint. </param> /// <param name="cancellationToken">A <c>CancellationToken</c> that cancels the returned <c>Task</c>.</param> /// <typeparam name="TRow">The type to use to deserialise each row returned in the query results.</typeparam> /// <returns>A QueryResult instance. If the query has finished executing, contains the query results, with each /// row seralised to type <c>TRow</c>.</returns> /// <exception cref="Exceptions.ApiException">The Aircloak Api returned a Http Error code.</exception> /// <exception cref="Exceptions.ResultException">The Aircloak Api result status is "Error".</exception> public async Task <QueryResult <TRow> > PollQueryUntilComplete <TRow>( Uri apiUrl, string queryId, string query, JsonRowParser <TRow> rowParser, TimeSpan pollFrequency, CancellationToken cancellationToken) { // Register the JsonArrayConverter so the TRow can be deserialized correctly var jsonDeserializeOptions = new JsonSerializerOptions { PropertyNamingPolicy = new SnakeCaseNamingPolicy(), Converters = { new JsonArrayConverter <TRow>(rowParser) }, }; var queryCompleted = false; var speed = 16; try { while (true) { cancellationToken.ThrowIfCancellationRequested(); var endPoint = new Uri(apiUrl, $"queries/{queryId}"); using var httpResponse = await ApiGetRequest(endPoint, cancellationToken); var queryResult = await ParseJson <QueryResult <TRow> >(httpResponse, jsonDeserializeOptions, cancellationToken); if (queryResult.Query.Completed) { queryCompleted = true; switch (queryResult.Query.QueryState) { case "completed": return(queryResult); case "error": throw new Exceptions.ResultException("Aircloak API query error.\n" + GetQueryResultDetails(query, queryResult)); case "cancelled": throw new OperationCanceledException("Aircloak API query canceled.\n" + GetQueryResultDetails(query, queryResult)); } } await Task.Delay(pollFrequency / speed, cancellationToken); speed = Math.Max(speed / 2, 1); } } catch (Exception) { if (!queryCompleted) { await CancelQuery(apiUrl, queryId); } throw; }
/// <summary> /// Posts a query to the Aircloak server, retrieves the query ID, and then polls for the result. /// </summary> /// <param name="apiUrl">The Url of the Aircloak api.</param> /// <param name="dataSource">The data source to run the query against.</param> /// <param name="query">The query statement as a string.</param> /// <param name="rowParser">A delegate used for parsing a result row.</param> /// <param name="pollFrequency">How often to poll the api endpoint. </param> /// <param name="cancellationToken">A <see cref="CancellationToken" /> object that can be used to cancel the operation.</param> /// <returns>A <see cref="QueryResult{TRow}"/> instance containing the success status and query Id.</returns> /// <typeparam name="TRow">The type that the query row will be deserialized to.</typeparam> /// <exception cref="Exceptions.ApiException">The Aircloak Api returned a Http Error code.</exception> /// <exception cref="Exceptions.QueryException">The Aircloak Api could not process the query.</exception> /// <exception cref="Exceptions.ResultException">The Aircloak Api result status is "Error".</exception> public async Task <QueryResult <TRow> > Query <TRow>( Uri apiUrl, string dataSource, string query, JsonRowParser <TRow> rowParser, TimeSpan pollFrequency, CancellationToken cancellationToken) { var queryResponse = await SubmitQuery(apiUrl, dataSource, query, cancellationToken); return(await PollQueryUntilComplete(apiUrl, queryResponse.QueryId, query, rowParser, pollFrequency, cancellationToken)); }
/// <summary> /// Executes the query. /// </summary> /// <param name="query">An object defining the query to be executed.</param> /// <param name="rowParser">A delegate used for parsing a result row.</param> /// <typeparam name="TRow">The type of the rows returned by the query.</typeparam> /// <returns>An object containing a collection with the rows returned by the query.</returns> public async Task <DResult <TRow> > Exec <TRow>(string query, JsonRowParser <TRow> rowParser) { await semaphore.WaitAsync(cancellationToken); try { return(await apiClient.Query( apiUrl, dataSourceName, query, rowParser, pollFrequency, cancellationToken)); } finally { semaphore.Release(); } }
public JsonArrayConverter(JsonRowParser <TRow> rowParser) { this.rowParser = rowParser; }