Skip to content

Commit

Permalink
bug fixes and test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
elexisvenator committed Jan 23, 2025
1 parent e3b600b commit 61f4fa0
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 7 deletions.
2 changes: 1 addition & 1 deletion docs/documents/querying/linq/sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task query_with_matches_sql()
user.Id.ShouldBe(u.Id);
}
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/DocumentDbTests/Reading/query_by_sql.cs#L267-L282' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_with_matches_sql' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/DocumentDbTests/Reading/query_by_sql.cs#L336-L351' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_with_matches_sql' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

**But**, if you want to take advantage of the more recent and very powerful JSONPath style querying, you will find that using `?` as a placeholder is not suitable, as that character is widely used in JSONPath expressions. If you encounter this issue or write another query where the `?` character is not suitable, you can change the placeholder by providing an alternative. Pass this in before the sql argument.
Expand Down
4 changes: 2 additions & 2 deletions docs/documents/querying/sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ a document body, but in that case you will need to supply the full SQL statement
var sumResults = await session
.QueryAsync<int>("select count(*) from mt_doc_target");
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/DocumentDbTests/Reading/query_by_sql.cs#L376-L381' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_by_full_sql' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/DocumentDbTests/Reading/query_by_sql.cs#L458-L463' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_by_full_sql' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

When querying single JSONB properties into a primitive/value type, you'll need to cast the value to the respective postgres type:
Expand All @@ -65,7 +65,7 @@ When querying single JSONB properties into a primitive/value type, you'll need t
var times = await session.QueryAsync<DateTimeOffset>(
"SELECT (data ->> 'ModifiedAt')::timestamptz from mt_doc_user");
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/DocumentDbTests/Reading/query_by_sql.cs#L330-L335' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using-queryasync-casting' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/DocumentDbTests/Reading/query_by_sql.cs#L412-L417' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using-queryasync-casting' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The basic rules for how Marten handles user-supplied queries are:
Expand Down
84 changes: 83 additions & 1 deletion src/DocumentDbTests/Reading/query_by_sql.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -70,6 +70,31 @@ public async Task stream_query_by_one_parameter()
firstnames[2].ShouldBe("Max");
}

[Fact]
public async Task stream_query_by_one_parameter_custom_placeholder()
{
await using var session = theStore.LightweightSession();
session.Store(new User { FirstName = "Jeremy", LastName = "Miller" });
session.Store(new User { FirstName = "Lindsey", LastName = "Miller" });
session.Store(new User { FirstName = "Max", LastName = "Miller" });
session.Store(new User { FirstName = "Frank", LastName = "Zombo" });
await session.SaveChangesAsync();

var stream = new MemoryStream();
await session.StreamJson<User>(stream, '$', "where data ->> 'LastName' = $", "Miller");

stream.Position = 0;
var results = theStore.Options.Serializer().FromJson<User[]>(stream);
var firstnames = results
.OrderBy(x => x.FirstName)
.Select(x => x.FirstName).ToArray();

firstnames.Length.ShouldBe(3);
firstnames[0].ShouldBe("Jeremy");
firstnames[1].ShouldBe("Lindsey");
firstnames[2].ShouldBe("Max");
}

[Fact]
public async Task query_by_one_parameter()
{
Expand All @@ -90,6 +115,50 @@ public async Task query_by_one_parameter()
firstnames[2].ShouldBe("Max");
}

[Fact]
public async Task query_by_one_parameter_async()
{
await using var session = theStore.LightweightSession();
session.Store(new User { FirstName = "Jeremy", LastName = "Miller" });
session.Store(new User { FirstName = "Lindsey", LastName = "Miller" });
session.Store(new User { FirstName = "Max", LastName = "Miller" });
session.Store(new User { FirstName = "Frank", LastName = "Zombo" });
await session.SaveChangesAsync();

var firstnames =
(await session.QueryAsync<User>("where data ->> 'LastName' = ?", "Miller"))
.OrderBy(x => x.FirstName)
.Select(x => x.FirstName)
.ToArray();

firstnames.Length.ShouldBe(3);
firstnames[0].ShouldBe("Jeremy");
firstnames[1].ShouldBe("Lindsey");
firstnames[2].ShouldBe("Max");
}

[Fact]
public async Task query_by_one_parameter_async_custom_placeholder()
{
await using var session = theStore.LightweightSession();
session.Store(new User { FirstName = "Jeremy", LastName = "Miller" });
session.Store(new User { FirstName = "Lindsey", LastName = "Miller" });
session.Store(new User { FirstName = "Max", LastName = "Miller" });
session.Store(new User { FirstName = "Frank", LastName = "Zombo" });
await session.SaveChangesAsync();

var firstnames =
(await session.QueryAsync<User>('$', "where data ->> 'LastName' = $", "Miller"))
.OrderBy(x => x.FirstName)
.Select(x => x.FirstName)
.ToArray();

firstnames.Length.ShouldBe(3);
firstnames[0].ShouldBe("Jeremy");
firstnames[1].ShouldBe("Lindsey");
firstnames[2].ShouldBe("Max");
}

[Fact]
public async Task query_ignores_case_of_where_keyword()
{
Expand Down Expand Up @@ -280,6 +349,19 @@ public async Task query_with_matches_sql()
}

#endregion

[Fact]
public async Task query_with_matches_sql_custom_placeholder()
{
await using var session = theStore.LightweightSession();
var u = new User { FirstName = "Eric", LastName = "Smith" };
session.Store(u);
await session.SaveChangesAsync();

var user = await session.Query<User>().Where(x => x.MatchesSql('$', "data->> 'FirstName' = $", "Eric")).SingleAsync();
user.LastName.ShouldBe("Smith");
user.Id.ShouldBe(u.Id);
}

[Fact]
public async Task query_with_select_in_query()
Expand Down
6 changes: 3 additions & 3 deletions src/Marten/Linq/MatchesSql/MatchesSqlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class MatchesSqlParser: IMethodCallParser

public bool Matches(MethodCallExpression expression)
{
return Equals(expression.Method, _sqlMethod) || Equals(expression.Method, _fragmentMethod);
return Equals(expression.Method, _sqlMethod) || Equals(expression.Method, _sqlMethodWithPlaceholder) || Equals(expression.Method, _fragmentMethod);
}

public ISqlFragment? Parse(IQueryableMemberCollection memberCollection, IReadOnlyStoreOptions options,
Expand All @@ -40,8 +40,8 @@ public bool Matches(MethodCallExpression expression)

if (expression.Method.Equals(_sqlMethodWithPlaceholder))
{
return new CustomizableWhereFragment(expression.Arguments[1].Value().As<string>(),
expression.Arguments[2].Value().As<char>().ToString(),
return new CustomizableWhereFragment(expression.Arguments[2].Value().As<string>(),
expression.Arguments[1].Value().As<char>().ToString(),
expression.Arguments[3].Value().As<object[]>());
}

Expand Down

0 comments on commit 61f4fa0

Please sign in to comment.