Skip to content

Commit

Permalink
Add Left/Right/Inner join (#516)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek authored Dec 5, 2023
1 parent 5d5637e commit abeff48
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 51 deletions.
4 changes: 2 additions & 2 deletions src/YesSql.Abstractions/ISqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public interface ISqlBuilder
string FormatColumn(string table, string column, string schema, bool isAlias = false);
string FormatTable(string table, string schema);
string GetSelector();
void InnerJoin(string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null);
bool HasJoin { get; }
bool HasOrder { get; }
void ClearGroupBy();
Expand Down Expand Up @@ -44,5 +43,6 @@ public interface ISqlBuilder
ISqlBuilder Clone();
IEnumerable<string> GetSelectors();
IEnumerable<string> GetOrders();
void Join(JoinType type, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null);
}
}
}
8 changes: 8 additions & 0 deletions src/YesSql.Abstractions/JoinType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace YesSql;

public enum JoinType
{
Inner = 0,
Left = 1,
Right = 2
}
13 changes: 13 additions & 0 deletions src/YesSql.Abstractions/SqlBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace YesSql;

public static class SqlBuilderExtensions
{
public static void InnerJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null)
=> builder.Join(JoinType.Inner, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias);

public static void LeftJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null)
=> builder.Join(JoinType.Left, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias);

public static void RightJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null)
=> builder.Join(JoinType.Right, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias);
}
99 changes: 50 additions & 49 deletions src/YesSql.Core/Sql/SqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,26 @@ public class SqlBuilder : ISqlBuilder
protected string _skip;
protected string _count;

protected List<string> SelectSegments => _select ??= new List<string>();
protected List<string> FromSegments => _from ??= new List<string>();
protected List<string> JoinSegments => _join ??= new List<string>();
protected List<List<string>> OrSegments => _or ??= new List<List<string>>();
protected List<string> WhereSegments => _where ??= new List<string>();
protected List<string> GroupSegments => _group ??= new List<string>();
protected List<string> HavingSegments => _having ??= new List<string>();
protected List<string> OrderSegments => _order ??= new List<string>();
protected List<string> TrailSegments => _trail ??= new List<string>();

public Dictionary<string, object> Parameters { get; protected set; } = new Dictionary<string, object>();
protected List<string> SelectSegments => _select ??= [];
protected List<string> FromSegments => _from ??= [];
protected List<string> JoinSegments => _join ??= [];
protected List<List<string>> OrSegments => _or ??= [];
protected List<string> WhereSegments => _where ??= [];
protected List<string> GroupSegments => _group ??= [];
protected List<string> HavingSegments => _having ??= [];
protected List<string> OrderSegments => _order ??= [];
protected List<string> TrailSegments => _trail ??= [];

public Dictionary<string, object> Parameters { get; protected set; } = [];

public SqlBuilder(string tablePrefix, ISqlDialect dialect)
{
_tablePrefix = tablePrefix;
_dialect = dialect;
}

public string Clause { get { return _clause; } }
public string Clause
=> _clause;

public void Table(string table, string alias, string schema)
{
Expand All @@ -59,23 +60,18 @@ public void Table(string table, string alias, string schema)
}

public void From(string from)
{
FromSegments.Add(from);
}
=> FromSegments.Add(from);

public bool HasPaging => _skip != null || _count != null;
public bool HasPaging
=> _skip != null || _count != null;

public void Skip(string skip)
{
_skip = skip;
}
=> _skip = skip;

public void Take(string take)
{
_count = take;
}
=> _count = take;

public virtual void InnerJoin(string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null)
public virtual void Join(JoinType type, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null)
{
// Don't prefix if alias is used
if (alias != onTable)
Expand All @@ -101,7 +97,19 @@ public virtual void InnerJoin(string table, string onTable, string onColumn, str
toTable = _dialect.QuoteForAliasName(toAlias);
}

JoinSegments.Add(" INNER JOIN ");
if (type == JoinType.Left)
{
JoinSegments.Add(" LEFT JOIN ");
}
else if (type == JoinType.Right)
{
JoinSegments.Add(" RIGHT JOIN ");
}
else
{
JoinSegments.Add(" INNER JOIN ");
}

JoinSegments.Add(FormatTable(table, schema));

if (!string.IsNullOrEmpty(alias))
Expand All @@ -117,13 +125,13 @@ public virtual void InnerJoin(string table, string onTable, string onColumn, str
}

public void Select()
{
_clause = "SELECT";
}
=> _clause = "SELECT";

public IEnumerable<string> GetSelectors() => SelectSegments;
public IEnumerable<string> GetSelectors()
=> SelectSegments;

public IEnumerable<string> GetOrders() => OrderSegments;
public IEnumerable<string> GetOrders()
=> OrderSegments;

public void Selector(string selector)
{
Expand All @@ -132,36 +140,29 @@ public void Selector(string selector)
}

public void Selector(string table, string column, string schema)
{
Selector(FormatColumn(table, column, schema));
}
=> Selector(FormatColumn(table, column, schema));

public void AddSelector(string select)
{
Select();
SelectSegments.Add(select);
}

public void InsertSelector(string select)
{
SelectSegments.Insert(0, select);
}
=> SelectSegments.Insert(0, select);

public string GetSelector()
{
if (SelectSegments.Count == 1)
{
return SelectSegments[0];
}
else
{
return string.Join("", SelectSegments);
}

return string.Join(string.Empty, SelectSegments);
}

public void Distinct()
{
_distinct = true;
}
=> _distinct = true;

public virtual string FormatColumn(string table, string column, string schema, bool isAlias = false)
{
Expand Down Expand Up @@ -231,14 +232,14 @@ public virtual void WhereAnd(string where)
WhereSegments.Add(where);
}

public bool HasJoin => _join != null && _join.Count > 0;
public bool HasJoin
=> _join?.Count > 0;

public bool HasOrder => _order != null && _order.Count > 0;
public bool HasOrder
=> _order?.Count > 0;

public void ClearOrder()
{
_order = null;
}
=> _order = null;

public void ClearGroupBy()
{
Expand Down Expand Up @@ -267,11 +268,11 @@ public virtual void OrderByRandom()

public virtual void ThenOrderBy(string orderBy)
{
if (HasOrder)
if (HasOrder)
{
OrderSegments.Add(", ");
}

OrderSegments.Add(orderBy);
}

Expand Down
127 changes: 127 additions & 0 deletions test/YesSql.Tests/CoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6167,6 +6167,133 @@ public async Task SameQueryShouldBeReusable()
}
}

[Fact]
public async Task CanRunLeftJoin()
{
_store.RegisterIndexes<ArticleBydPublishedDateProvider>();

var now = DateTime.UtcNow;

await using (var session = _store.CreateSession())
{
for (var i = 0; i < 10; i++)
{
await session.SaveAsync(new Article
{
PublishedUtc = now.AddDays(i)
});
}

await session.SaveChangesAsync();
}

int result;

await using (var connection = _store.Configuration.ConnectionFactory.CreateConnection())
{
await connection.OpenAsync();

var dialect = _store.Configuration.SqlDialect;

var sqlBuilder = new SqlBuilder(TablePrefix, dialect);

sqlBuilder.AddSelector("count(1)");
sqlBuilder.Table("document", "d", _store.Configuration.Schema);
sqlBuilder.LeftJoin(nameof(ArticleByPublishedDate), "a", "DocumentId", "document", "Id", _store.Configuration.Schema, "a", "d");

var query = sqlBuilder.ToSqlString();

result = await connection.QueryFirstOrDefaultAsync<int>(query);
}

Assert.Equal(10, result);
}

[Fact]
public async Task CanRunRightJoin()
{
_store.RegisterIndexes<ArticleBydPublishedDateProvider>();

var now = DateTime.UtcNow;

await using (var session = _store.CreateSession())
{
for (var i = 0; i < 10; i++)
{
await session.SaveAsync(new Article
{
PublishedUtc = now.AddDays(i)
});
}

await session.SaveChangesAsync();
}

int result;

await using (var connection = _store.Configuration.ConnectionFactory.CreateConnection())
{
await connection.OpenAsync();

var dialect = _store.Configuration.SqlDialect;

var sqlBuilder = new SqlBuilder(TablePrefix, dialect);

sqlBuilder.AddSelector("count(1)");
sqlBuilder.Table("document", "d", _store.Configuration.Schema);
sqlBuilder.RightJoin(nameof(ArticleByPublishedDate), "a", "DocumentId", "document", "Id", _store.Configuration.Schema, "a", "d");

var query = sqlBuilder.ToSqlString();

result = await connection.QueryFirstOrDefaultAsync<int>(query);
}

Assert.Equal(10, result);
}


[Fact]
public async Task CanRunInnerJoin()
{
_store.RegisterIndexes<ArticleBydPublishedDateProvider>();

var now = DateTime.UtcNow;

await using (var session = _store.CreateSession())
{
for (var i = 0; i < 10; i++)
{
await session.SaveAsync(new Article
{
PublishedUtc = now.AddDays(i)
});
}

await session.SaveChangesAsync();
}

int result;

await using (var connection = _store.Configuration.ConnectionFactory.CreateConnection())
{
await connection.OpenAsync();

var dialect = _store.Configuration.SqlDialect;

var sqlBuilder = new SqlBuilder(TablePrefix, dialect);

sqlBuilder.AddSelector("count(1)");
sqlBuilder.Table("document", "d", _store.Configuration.Schema);
sqlBuilder.InnerJoin(nameof(ArticleByPublishedDate), "a", "DocumentId", "document", "Id", _store.Configuration.Schema, "a", "d");

var query = sqlBuilder.ToSqlString();

result = await connection.QueryFirstOrDefaultAsync<int>(query);
}

Assert.Equal(10, result);
}

#region FilterTests

[Fact]
Expand Down

0 comments on commit abeff48

Please sign in to comment.