Skip to content

Commit

Permalink
Permit querying any npgsql mapped type with custom SQL
Browse files Browse the repository at this point in the history
  • Loading branch information
Hawxy committed Jul 11, 2023
1 parent 999d371 commit 8c9ee19
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 10 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,5 +18,5 @@ public void 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#L259-L274' 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#L260-L275' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_with_matches_sql' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
16 changes: 13 additions & 3 deletions docs/documents/querying/sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,25 @@ 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#L343-L348' 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#L358-L363' 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:

<!-- snippet: sample_using-queryasync-casting -->
<a id='snippet-sample_using-queryasync-casting'></a>
```cs
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#L324-L328' 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:

* The `T` argument to `Query<T>()/QueryAsync<T>()` denotes the return value of each item
* If the `T` is a simple, scalar value like a .Net `int`, the data is handled by reading the first
* If the `T` is a [npgsql mapped type](https://www.npgsql.org/doc/types/basic.html) like a .Net `int` or `Guid`, the data is handled by reading the first
field of the returned data
* If the `T` is not a simple type, Marten will try to read the first field with the JSON serializer
* If the `T` is not a mapped type, Marten will try to read the first field with the JSON serializer
for the current `DocumentStore`
* If the SQL starts with the `SELECT` keyword (and it's not case sensitive), the SQL supplied is used verbatim
* If the supplied SQL does not start with a `SELECT` keyword, Marten assumes that the `T` is a document
Expand Down
19 changes: 17 additions & 2 deletions src/DocumentDbTests/Reading/query_by_sql.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -297,7 +298,8 @@ public void query_with_select_in_query()
public async Task query_with_select_in_query_async()
{
await using var session = theStore.LightweightSession();
var u = new User {FirstName = "Jeremy", LastName = "Miller"};
var time = DateTimeOffset.UtcNow;
var u = new User {FirstName = "Jeremy", LastName = "Miller", ModifiedAt = time};
session.Store(u);
await session.SaveChangesAsync();

Expand All @@ -313,6 +315,19 @@ public async Task query_with_select_in_query_async()

user.LastName.ShouldBe("Miller");
user.Id.ShouldBe(u.Id);


var ids = await session.QueryAsync<Guid>("SELECT id from mt_doc_user");
var id = ids.Single();
id.ShouldBe(u.Id);

#region sample_using-queryasync-casting

var times = await session.QueryAsync<DateTimeOffset>("SELECT (data ->> 'ModifiedAt')::timestamptz from mt_doc_user");

#endregion
var fetchedTime = times.Single();
fetchedTime.Second.ShouldBe(time.Second);
}

[Fact]
Expand Down
2 changes: 2 additions & 0 deletions src/Marten.Testing/Documents/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public User()

public int Age { get; set; }

public DateTimeOffset ModifiedAt { get; set; }

public string ToJson()
{
return
Expand Down
9 changes: 5 additions & 4 deletions src/Marten/Linq/QueryHandlers/UserSuppliedQueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,18 @@ private ISelectClause GetSelectClause(IMartenSession session)
{
if (typeof(T) == typeof(string))
{
return new ScalarStringSelectClause("", "");
return new ScalarStringSelectClause(string.Empty, string.Empty);
}

if (typeof(T).IsSimple())
if (PostgresqlProvider.Instance.HasTypeMapping(typeof(T)))
{
return typeof(ScalarSelectClause<>).CloseAndBuildAs<ISelectClause>("", "", typeof(T));
return typeof(ScalarSelectClause<>).CloseAndBuildAs<ISelectClause>(string.Empty, string.Empty, typeof(T));
}


if (SqlContainsCustomSelect)
{
return new DataSelectClause<T>("", "");
return new DataSelectClause<T>(string.Empty, string.Empty);
}

return session.StorageFor(typeof(T));
Expand Down

0 comments on commit 8c9ee19

Please sign in to comment.