Skip to content

Commit

Permalink
Merge pull request #396 from mk3008/303-proposal-make-it-easier-to-wr…
Browse files Browse the repository at this point in the history
…ite-typesafe-descriptions

Experimental: Allowed selection queries to have types.
  • Loading branch information
mk3008 authored Apr 21, 2024
2 parents 9003b03 + fcf04a8 commit ecef31f
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 1 deletion.
8 changes: 8 additions & 0 deletions src/Carbunql.Postgres/FromnExtension.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Carbunql.Building;
using Carbunql.Clauses;
using Carbunql.Postgres.Linq;

namespace Carbunql.Postgres;

Expand All @@ -24,4 +25,11 @@ public static (FromClause, T) As<T>(this FromClause source, string alias)
var r = (T)Activator.CreateInstance(typeof(T))!;
return (source, r);
}

public static (FromClause, T) FromAs<T>(this SelectQuery source, SelectQuery<T> query, string alias)
{
var r = (T)Activator.CreateInstance(typeof(T))!;
var (from, _) = source.From(query).As(alias);
return (from, r);
}
}
12 changes: 12 additions & 0 deletions src/Carbunql.Postgres/SelectQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Carbunql.Postgres;

public class SelectQuery<T> : SelectQuery
{
public SelectQuery() : base()
{
}

public SelectQuery(string query) : base(query)
{
}
}
2 changes: 1 addition & 1 deletion test/Carbunql.Postgres.Test/Carbunql.Postgres.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand Down
31 changes: 31 additions & 0 deletions test/Carbunql.Postgres.Test/SampleQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Carbunql.Postgres.Test;

public static class SampleQuery
{
public static string CommandText =>
"""
SELECT
a.a_id,
a.text,
b.value
FROM
table_a AS a
INNER JOIN table_b AS b ON a.a_id = b.a_id
""";

public static SelectQuery<Row> Query => GetQuery();

private static SelectQuery<Row>? QueryCache;

private static SelectQuery<Row> GetQuery()
{
if (QueryCache == null) QueryCache = new SelectQuery<Row>(CommandText);
return QueryCache;
}

public readonly record struct Row(
int a_id,
string text,
int value
);
}
200 changes: 200 additions & 0 deletions test/Carbunql.Postgres.Test/SubQueryText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
using Carbunql.Building;
using Carbunql.Postgres;
using Xunit.Abstractions;

namespace Carbunql.Postgres.Test;

public class SubQueryText
{
private readonly QueryCommandMonitor Monitor;

public SubQueryText(ITestOutputHelper output)
{
Monitor = new QueryCommandMonitor(output);
Output = output;
}

private ITestOutputHelper Output { get; set; }

[Fact]
public void FromAs()
{
var sq = new SelectQuery();
var (from, a) = sq.FromAs<RecordA>("a");

sq.Select("a", "a_id");
sq.Select(nameof(a), "a_id");
sq.Select(() => a.a_id);
sq.Select(() => a.a_id).As("id");

Monitor.Log(sq);

var sql = @"SELECT
a.a_id,
a.a_id,
a.a_id,
a.a_id AS id
FROM
RecordA AS a";

Assert.Equal(22, sq.GetTokens().ToList().Count);
Assert.Equal(sql.ToValidateText(), sq.ToText().ToValidateText());
}

[Fact]
public void AliasAs()
{
var sq = new SelectQuery();
var (from, a) = sq.From("table_a").As<RecordA>("a"); ;

sq.Select("a", "a_id");
sq.Select(nameof(a), "a_id");
sq.Select(() => a.a_id);
sq.Select(() => a.a_id).As("id");

Monitor.Log(sq);

Assert.Equal(22, sq.GetTokens().ToList().Count);

var sql = @"SELECT
a.a_id,
a.a_id,
a.a_id,
a.a_id AS id
FROM
table_a AS a";
Assert.Equal(sql, sq.ToText(), true, true, true);

}
[Fact]
public void SubQueryTest()
{
var sq = new SelectQuery();
var (from, a) = sq.From(new SelectQuery("select * from table_a")).As<RecordA>("a"); ;

sq.Select("a", "a_id");
sq.Select(nameof(a), "a_id");
sq.Select(() => a.a_id);
sq.Select(() => a.a_id).As("id");

Monitor.Log(sq);

Assert.Equal(27, sq.GetTokens().ToList().Count);

var sql = @"SELECT
a.a_id,
a.a_id,
a.a_id,
a.a_id AS id
FROM
(
SELECT
*
FROM
table_a
) AS a";
Assert.Equal(sql, sq.ToText(), true, true, true);
}

[Fact]
public void SubroutineTest()
{
var sq = new SelectQuery();
var (from, a) = sq.FromAs(GetSelectQuery(), "a");

sq.Select("a", "a_id");
sq.Select(nameof(a), "a_id");
sq.Select(() => a.a_id);
sq.Select(() => a.a_id).As("id");

Monitor.Log(sq);

Assert.Equal(59, sq.GetTokens().ToList().Count);

var sql = @"SELECT
a.a_id,
a.a_id,
a.a_id,
a.a_id AS id
FROM
(
SELECT
a.a_id,
a.text,
a.value
FROM
RecordA AS a
INNER JOIN RecordB AS b ON (a.a_id = b.a_id AND b.text = 'test')
) AS a";

Assert.Equal(sql, sq.ToText(), true, true, true);
}

public SelectQuery<SubQueryRow> GetSelectQuery()
{
var sq = new SelectQuery<SubQueryRow>();
var (from, a) = sq.FromAs<RecordA>("a");
var b = from.InnerJoinAs<RecordB>(b => a.a_id == b.a_id && b.text == "test");

sq.Select(() => a.a_id);
sq.Select(() => a.text);
sq.Select(() => a.value);

/*
SELECT
a.a_id,
a.text,
a.value
FROM
RecordA AS a
INNER JOIN RecordB AS b ON (a.a_id = b.a_id AND b.text = 'test')
*/

return sq;
}


[Fact]
public void QueryClassTest()
{
var sq = new SelectQuery();
var (from, a) = sq.FromAs(SampleQuery.Query, "a");

sq.Select(() => a.a_id);
sq.Select(() => a.value * 2).As("value");

Monitor.Log(sq);

Assert.Equal(45, sq.GetTokens().ToList().Count);

var sql = @"SELECT
a.a_id,
a.value * 2 AS value
FROM
(
SELECT
a.a_id,
a.text,
b.value
FROM
table_a AS a
INNER JOIN table_b AS b ON a.a_id = b.a_id
) AS a";

Assert.Equal(sql, sq.ToText(), true, true, true);
}

public record struct RecordA(int a_id, string text, int value, bool is_enabled, double rate, DateTime timestamp, Gender gender);

public record struct RecordB(int a_id, int b_id, string text, int value);

public record struct SubQueryRow(int a_id, string text, int value);

public enum Gender
{
Male,
Female,
Other,
Unknown
}
}

0 comments on commit ecef31f

Please sign in to comment.