Skip to content

Commit

Permalink
Support plain request query parameters.
Browse files Browse the repository at this point in the history
  • Loading branch information
ibradwan committed Aug 3, 2021
1 parent e99d477 commit 79b3e06
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 11 deletions.
37 changes: 29 additions & 8 deletions ClickHouse.Client/ADO/ClickHouseCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Text;
Expand All @@ -14,6 +15,7 @@ public class ClickHouseCommand : DbCommand, IClickHouseCommand, IDisposable
{
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly ClickHouseParameterCollection commandParameters = new ClickHouseParameterCollection();
private readonly IDictionary<string, string> queryParameters = new Dictionary<string, string>();
private ClickHouseConnection connection;

public ClickHouseCommand()
Expand Down Expand Up @@ -61,7 +63,7 @@ public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellat
throw new InvalidOperationException("Connection is not set");

using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
using var response = await connection.PostSqlQueryAsync(CommandText, linkedCancellationTokenSource.Token, commandParameters).ConfigureAwait(false);
using var response = await connection.PostSqlQueryAsync(CommandText, linkedCancellationTokenSource.Token, commandParameters, this.queryParameters).ConfigureAwait(false);
try
{
using var reader = new ExtendedBinaryReader(await response.Content.ReadAsStreamAsync());
Expand All @@ -73,18 +75,14 @@ public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellat
}
}

/// <summary>
/// Allows to return raw result from a query (with custom FORMAT)
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>ClickHouseRawResult object containing response stream</returns>
/// <inheritdoc />
public async Task<ClickHouseRawResult> ExecuteRawResultAsync(CancellationToken cancellationToken)
{
if (connection == null)
throw new InvalidOperationException("Connection is not set");

using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
var response = await connection.PostSqlQueryAsync(CommandText, linkedCancellationTokenSource.Token, commandParameters).ConfigureAwait(false);
var response = await connection.PostSqlQueryAsync(CommandText, linkedCancellationTokenSource.Token, commandParameters, this.queryParameters).ConfigureAwait(false);
return new ClickHouseRawResult(response);
}

Expand All @@ -99,8 +97,31 @@ public override async Task<object> ExecuteScalarAsync(CancellationToken cancella

public override void Prepare() { /* ClickHouse has no notion of prepared statements */ }

/// <inheritdoc />
public new ClickHouseDbParameter CreateParameter() => new ClickHouseDbParameter();

/// <inheritdoc />
public void SetQueryParameter(string name, string value)
{
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value))
{
throw new ArgumentException($"The '{nameof(name)}' and '{nameof(value)}' parameters should not be null or consisting of white-spaces only.");
}

this.queryParameters[name] = value;
}

/// <inheritdoc />
public void RemoveQueryParameter(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException($"The '{nameof(name)}' parameter should not be null or consisting of white-spaces only.");
}

this.queryParameters.Remove(name);
}

protected override DbParameter CreateDbParameter() => CreateParameter();

protected override void Dispose(bool disposing)
Expand Down Expand Up @@ -132,7 +153,7 @@ protected override async Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBeha
default:
break;
}
var result = await connection.PostSqlQueryAsync(sqlBuilder.ToString(), linkedCancellationTokenSource.Token, commandParameters).ConfigureAwait(false);
var result = await connection.PostSqlQueryAsync(sqlBuilder.ToString(), linkedCancellationTokenSource.Token, commandParameters, this.queryParameters).ConfigureAwait(false);
return new ClickHouseDataReader(result);
}
}
Expand Down
11 changes: 9 additions & 2 deletions ClickHouse.Client/ADO/ClickHouseConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public async Task PostStreamAsync(string sql, Stream data, bool isCompressed, Ca
await HandleError(response, sql).ConfigureAwait(false);
}

internal async Task<HttpResponseMessage> PostSqlQueryAsync(string sqlQuery, CancellationToken token, ClickHouseParameterCollection parameters = null)
internal async Task<HttpResponseMessage> PostSqlQueryAsync(string sqlQuery, CancellationToken token, ClickHouseParameterCollection parameters = null, IDictionary<string, string> queryParamters = null)
{
var uriBuilder = CreateUriBuilder();
if (parameters != null)
Expand All @@ -166,7 +166,7 @@ internal async Task<HttpResponseMessage> PostSqlQueryAsync(string sqlQuery, Canc
if (SupportedFeatures.HasFlag(FeatureFlags.SupportsHttpParameters))
{
foreach (ClickHouseDbParameter parameter in parameters)
uriBuilder.AddQueryParameter(parameter.ParameterName, HttpParameterFormatter.Format(parameter));
uriBuilder.AddCommandParameter(parameter.ParameterName, HttpParameterFormatter.Format(parameter));
}
else
{
Expand All @@ -176,6 +176,13 @@ internal async Task<HttpResponseMessage> PostSqlQueryAsync(string sqlQuery, Canc
sqlQuery = SubstituteParameters(sqlQuery, formattedParameters);
}
}

if (queryParamters != null)
{
foreach (var parameter in queryParamters)
uriBuilder.AddQueryParameter(parameter.Key, parameter.Value);
}

string uri = uriBuilder.ToString();

using var postMessage = new HttpRequestMessage(HttpMethod.Post, uri);
Expand Down
9 changes: 8 additions & 1 deletion ClickHouse.Client/ClickHouseUriBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal class ClickHouseUriBuilder
{
private readonly IDictionary<string, string> queryParameters = new Dictionary<string, string>();

private readonly IDictionary<string, string> commandParameters = new Dictionary<string, string>();

public ClickHouseUriBuilder(Uri baseUri) => BaseUri = baseUri;

public Uri BaseUri { get; }
Expand All @@ -28,6 +30,8 @@ internal class ClickHouseUriBuilder

public bool AddQueryParameter(string name, string value) => DictionaryExtensions.TryAdd(queryParameters, name, value);

public bool AddCommandParameter(string name, string value) => DictionaryExtensions.TryAdd(commandParameters, name, value);

public override string ToString()
{
var parameters = HttpUtility.ParseQueryString(string.Empty); // NameValueCollection but a special one
Expand All @@ -37,9 +41,12 @@ public override string ToString()
parameters.SetOrRemove("session_id", SessionId);
parameters.SetOrRemove("query", Sql);

foreach (var parameter in queryParameters)
foreach (var parameter in commandParameters)
parameters.Set("param_" + parameter.Key, parameter.Value);

foreach (var parameter in queryParameters)
parameters.Set(parameter.Key, parameter.Value);

if (CustomParameters != null)
{
foreach (var parameter in CustomParameters)
Expand Down
28 changes: 28 additions & 0 deletions ClickHouse.Client/IClickHouseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,36 @@ namespace ClickHouse.Client
{
public interface IClickHouseCommand : IDbCommand
{
/// <summary>
/// Creates an instance of <see cref="ClickHouseDbParameter"/>.
/// </summary>
/// <returns>A <see cref="ClickHouseDbParameter"> object.</returns>
new ClickHouseDbParameter CreateParameter();

/// <summary>
/// Sets a query parameter that will be added to the query request.
/// </summary>
/// <param name="name">The name of the query parameter.</param>
/// <param name="value">The value of the query parameter.</param>
/// <remarks>
/// <list type="bullet">
/// <item>All query parameters are scoped to each <seealso cref="IClickHouseCommand"/> only.</item>
/// <item>Adding new value for the same parameter name overrides the old value.</item>
/// </list>
/// </remarks>
public void SetQueryParameter(string name, string value);

/// <summary>
/// Removes the specified query parameter. The removed parameters do not get included in the query request.
/// </summary>
/// <param name="name">The name of the query parameter that should be removed.</param>
public void RemoveQueryParameter(string name);

/// <summary>
/// Allows to return raw result from a query (with custom FORMAT)
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>ClickHouseRawResult object containing response stream</returns>
Task<ClickHouseRawResult> ExecuteRawResultAsync(CancellationToken cancellationToken);
}
}

0 comments on commit 79b3e06

Please sign in to comment.