Skip to content

Commit

Permalink
Add overload that allows a custom parameter placeholder.
Browse files Browse the repository at this point in the history
The `char placeholder` param is always before the `string sql` param. This is to ensure that the overload is backwards compatible.,

Updated all methods that accept parameters except for:
- Sync methods that are marked obsolete
- `MatchesJsonPath` - This should probably be marked as obsolete as the placeholder overload does the same thing but better.

Todo:
- Tests.
  • Loading branch information
elexisvenator committed Jan 10, 2025
1 parent 98540de commit 4e1c354
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 32 deletions.
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);

/// <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

0 comments on commit 4e1c354

Please sign in to comment.