Skip to content

Add overload that allows a custom parameter placeholder. #3622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/Marten/IAdvancedSql.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Marten.Internal.Sessions;

namespace Marten;

Expand All @@ -20,6 +22,21 @@ public interface IAdvancedSql
/// <returns></returns>
Task<IReadOnlyList<T>> QueryAsync<T>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameter can be a document class, a scalar or any JSON-serializable class.
/// If the result is a document, the SQL must contain a select with the required fields in the correct order,
/// depending on the session type and the metadata the document might use, at least id and data must be
/// selected.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<IReadOnlyList<T>> QueryAsync<T>(char placeholder, string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
Expand All @@ -35,6 +52,23 @@ public interface IAdvancedSql
/// <returns></returns>
Task<IReadOnlyList<(T1, T2)>> QueryAsync<T1, T2>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
/// For each result type parameter, the SQL SELECT statement must contain a ROW.
/// For document types, the row must contain the required fields in the correct order,
/// depending on the session type and the metadata the document might use, at least id and data must be
/// provided.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<IReadOnlyList<(T1, T2)>> QueryAsync<T1, T2>(char placeholder, string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
Expand All @@ -51,6 +85,24 @@ public interface IAdvancedSql
/// <returns></returns>
Task<IReadOnlyList<(T1, T2, T3)>> QueryAsync<T1, T2, T3>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
/// For each result type parameter, the SQL SELECT statement must contain a ROW.
/// For document types, the row must contain the required fields in the correct order,
/// depending on the session type and the metadata the document might use, at least id and data must be
/// provided.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<IReadOnlyList<(T1, T2, T3)>> QueryAsync<T1, T2, T3>(char placeholder, string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameter can be a document class, a scalar or any JSON-serializable class.
Expand All @@ -62,6 +114,7 @@ public interface IAdvancedSql
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
[Obsolete(QuerySession.SynchronousRemoval)]
IReadOnlyList<T> Query<T>(string sql, params object[] parameters);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of spots where the obsolete warning for synchronous db methods was missing.


/// <summary>
Expand All @@ -77,6 +130,7 @@ public interface IAdvancedSql
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
[Obsolete(QuerySession.SynchronousRemoval)]
IReadOnlyList<(T1, T2)> Query<T1, T2>(string sql, params object[] parameters);

/// <summary>
Expand All @@ -93,6 +147,7 @@ public interface IAdvancedSql
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
[Obsolete(QuerySession.SynchronousRemoval)]
IReadOnlyList<(T1, T2, T3)> Query<T1, T2, T3>(string sql, params object[] parameters);

/// <summary>
Expand All @@ -109,6 +164,22 @@ public interface IAdvancedSql
/// <returns>An async enumerable iterating over the results</returns>
IAsyncEnumerable<T> StreamAsync<T>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
/// For each result type parameter, the SQL SELECT statement must contain a ROW.
/// For document types, the row must contain the required fields in the correct order,
/// depending on the session type and the metadata the document might use, at least id and data must be
/// provided.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns>An async enumerable iterating over the results</returns>
IAsyncEnumerable<T> StreamAsync<T>(char placeholder, string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
Expand All @@ -124,6 +195,23 @@ public interface IAdvancedSql
/// <returns>An async enumerable iterating over the list of result tuples</returns>
IAsyncEnumerable<(T1, T2)> StreamAsync<T1, T2>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
/// For each result type parameter, the SQL SELECT statement must contain a ROW.
/// For document types, the row must contain the required fields in the correct order,
/// depending on the session type and the metadata the document might use, at least id and data must be
/// provided.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns>An async enumerable iterating over the list of result tuples</returns>
IAsyncEnumerable<(T1, T2)> StreamAsync<T1, T2>(char placeholder, string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
Expand All @@ -139,4 +227,22 @@ public interface IAdvancedSql
/// <param name="parameters"></param>
/// <returns>An async enumerable iterating over the list of result tuples</returns>
IAsyncEnumerable<(T1, T2, T3)> StreamAsync<T1, T2, T3>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameters can be any document class, scalar or JSON-serializable class.
/// For each result type parameter, the SQL SELECT statement must contain a ROW.
/// For document types, the row must contain the required fields in the correct order,
/// depending on the session type and the metadata the document might use, at least id and data must be
/// provided.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns>An async enumerable iterating over the list of result tuples</returns>
IAsyncEnumerable<(T1, T2, T3)> StreamAsync<T1, T2, T3>(char placeholder, string sql, CancellationToken token, params object[] parameters);
}
9 changes: 9 additions & 0 deletions src/Marten/IDocumentOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,15 @@ public interface IDocumentOperations: IQuerySession
/// <param name="parameterValues"></param>
void QueueSqlCommand(string sql, params object[] parameterValues);

/// <summary>
/// Registers a SQL command to be executed with the underlying unit of work as part of the batched command.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameterValues"></param>
void QueueSqlCommand(char placeholder, string sql, params object[] parameterValues);

/// <summary>
/// In the case of a lightweight session, this will direct Marten to opt into identity map mechanics
/// for only the document type T. This is a micro-optimization added for the event sourcing + projections
Expand Down
51 changes: 51 additions & 0 deletions src/Marten/IQuerySession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public interface IQuerySession: IDisposable, IAsyncDisposable
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
[Obsolete(QuerySession.SynchronousRemoval)]
IReadOnlyList<T> Query<T>(string sql, params object[] parameters);

/// <summary>
Expand All @@ -197,6 +198,19 @@ public interface IQuerySession: IDisposable, IAsyncDisposable
/// <returns></returns>
Task<int> StreamJson<T>(Stream destination, CancellationToken token, string sql, params object[] parameters);

/// <summary>
/// Stream the results of a user-supplied query directly to a stream as a JSON array.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <param name="destination"></param>
/// <param name="token"></param>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<int> StreamJson<T>(Stream destination, CancellationToken token, char placeholder, string sql, params object[] parameters);

/// <summary>
/// Stream the results of a user-supplied query directly to a stream as a JSON array
/// </summary>
Expand All @@ -207,6 +221,18 @@ public interface IQuerySession: IDisposable, IAsyncDisposable
/// <returns></returns>
Task<int> StreamJson<T>(Stream destination, string sql, params object[] parameters);

/// <summary>
/// Stream the results of a user-supplied query directly to a stream as a JSON array.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <param name="destination"></param>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<int> StreamJson<T>(Stream destination, char placeholder, string sql, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage table for the document type T by supplied SQL. See
/// https://martendb.io/documents/querying/sql.html for more information on usage.
Expand All @@ -218,6 +244,19 @@ public interface IQuerySession: IDisposable, IAsyncDisposable
/// <returns></returns>
Task<IReadOnlyList<T>> QueryAsync<T>(string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage table for the document type T by supplied SQL. See
/// https://martendb.io/documents/querying/sql.html for more information on usage.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="token"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<IReadOnlyList<T>> QueryAsync<T>(char placeholder, string sql, CancellationToken token, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage table for the document type T by supplied SQL. See
/// https://martendb.io/documents/querying/sql.html for more information on usage.
Expand All @@ -228,6 +267,18 @@ public interface IQuerySession: IDisposable, IAsyncDisposable
/// <returns></returns>
Task<IReadOnlyList<T>> QueryAsync<T>(string sql, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage table for the document type T by supplied SQL. See
/// https://martendb.io/documents/querying/sql.html for more information on usage.
/// Use <paramref name="placeholder"/> to specify a character that will be replaced by positional parameters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="placeholder"></param>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
Task<IReadOnlyList<T>> QueryAsync<T>(char placeholder, string sql, params object[] parameters);

/// <summary>
/// Asynchronously queries the document storage with the supplied SQL.
/// The type parameter can be a document class, a scalar or any JSON-serializable class.
Expand Down
6 changes: 4 additions & 2 deletions src/Marten/Internal/Operations/ExecuteSqlStorageOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ namespace Marten.Internal.Operations;
internal class ExecuteSqlStorageOperation: IStorageOperation, NoDataReturnedCall
{
private readonly string _commandText;
private readonly char _placeholder;
private readonly object[] _parameterValues;

public ExecuteSqlStorageOperation(string commandText, params object[] parameterValues)
public ExecuteSqlStorageOperation(char placeholder, string commandText, params object[] parameterValues)
{
_commandText = commandText.TrimEnd(';');
_placeholder = placeholder;
_parameterValues = parameterValues;
}

public void ConfigureCommand(ICommandBuilder builder, IMartenSession session)
{
var parameters = builder.AppendWithParameters(_commandText);
var parameters = builder.AppendWithParameters(_commandText, _placeholder);
if (parameters.Length != _parameterValues.Length)
{
throw new InvalidOperationException(
Expand Down
7 changes: 6 additions & 1 deletion src/Marten/Internal/Sessions/DocumentSessionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,18 @@ public void InsertObjects(IEnumerable<object> documents)
}

public void QueueSqlCommand(string sql, params object[] parameterValues)
{
QueueSqlCommand(DefaultParameterPlaceholder, sql, parameterValues: parameterValues);
}

public void QueueSqlCommand(char placeholder, string sql, params object[] parameterValues)
{
sql = sql.TrimEnd(';');
if (sql.Contains(';'))
throw new ArgumentOutOfRangeException(nameof(sql),
"You must specify one SQL command at a time because of Marten's usage of command batching. ';' cannot be used as a command separator here.");

var operation = new ExecuteSqlStorageOperation(sql, parameterValues);
var operation = new ExecuteSqlStorageOperation(placeholder, sql, parameterValues);
QueueOperation(operation);
}

Expand Down
Loading
Loading