Skip to content

Commit f203f8c

Browse files
iss-507: add the SelectAggregate method to QueryBuilder
1 parent 6337a57 commit f203f8c

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

QueryBuilder/Clauses/ColumnClause.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,33 @@ public override AbstractClause Clone()
7777
};
7878
}
7979
}
80+
81+
/// <summary>
82+
/// Represents an aggregated column clause with an optional filter
83+
/// </summary>
84+
/// <seealso cref="AbstractColumn" />
85+
public class AggregatedColumn : AbstractColumn
86+
{
87+
/// <summary>
88+
/// Gets or sets the a query that used to filter the data,
89+
/// the compiler will consider only the `Where` clause.
90+
/// </summary>
91+
/// <value>
92+
/// The filter query.
93+
/// </value>
94+
public Query Filter { get; set; } = null;
95+
public string Aggregate { get; set; }
96+
public AbstractColumn Column { get; set; }
97+
public override AbstractClause Clone()
98+
{
99+
return new AggregatedColumn
100+
{
101+
Engine = Engine,
102+
Filter = Filter?.Clone(),
103+
Column = Column.Clone() as AbstractColumn,
104+
Aggregate = Aggregate,
105+
Component = Component,
106+
};
107+
}
108+
}
80109
}

QueryBuilder/Compilers/Compiler.cs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ protected Compiler()
2424

2525
public virtual string EngineCode { get; }
2626

27+
/// <summary>
28+
/// Whether the compiler supports the `SELECT ... FILTER` syntax
29+
/// </summary>
30+
/// <value></value>
31+
public virtual bool SupportsFilterClause { get; set; } = false;
32+
2733
protected virtual string SingleRowDummyTableName { get => null; }
2834

2935
/// <summary>
@@ -512,10 +518,44 @@ public virtual string CompileColumn(SqlResult ctx, AbstractColumn column)
512518
return "(" + subCtx.RawSql + $"){alias}";
513519
}
514520

521+
if (column is AggregatedColumn aggregatedColumn)
522+
{
523+
string agg = aggregatedColumn.Aggregate.ToUpperInvariant();
524+
525+
var (col, alias) = SplitAlias(CompileColumn(ctx, aggregatedColumn.Column));
526+
527+
alias = string.IsNullOrEmpty(alias) ? "" : $" {ColumnAsKeyword}{alias}";
528+
529+
string filterCondition = CompileFilterConditions(ctx, aggregatedColumn);
530+
531+
if (string.IsNullOrEmpty(filterCondition))
532+
{
533+
return $"{agg}({col}){alias}";
534+
}
535+
536+
if (SupportsFilterClause)
537+
{
538+
return $"{agg}({col}) FILTER (WHERE {filterCondition}){alias}";
539+
}
540+
541+
return $"{agg}(CASE WHEN {filterCondition} THEN {col} END){alias}";
542+
}
543+
515544
return Wrap((column as Column).Name);
516545

517546
}
518547

548+
protected virtual string CompileFilterConditions(SqlResult ctx, AggregatedColumn aggregatedColumn)
549+
{
550+
if (aggregatedColumn.Filter == null)
551+
{
552+
return null;
553+
}
554+
555+
var wheres = aggregatedColumn.Filter.GetComponents<AbstractCondition>("where");
556+
557+
return CompileConditions(ctx, wheres);
558+
}
519559

520560
public virtual SqlResult CompileCte(AbstractFrom cte)
521561
{
@@ -872,9 +912,7 @@ public virtual string Wrap(string value)
872912

873913
if (value.ToLowerInvariant().Contains(" as "))
874914
{
875-
var index = value.ToLowerInvariant().IndexOf(" as ");
876-
var before = value.Substring(0, index);
877-
var after = value.Substring(index + 4);
915+
var (before, after) = SplitAlias(value);
878916

879917
return Wrap(before) + $" {ColumnAsKeyword}" + WrapValue(after);
880918
}
@@ -892,6 +930,20 @@ public virtual string Wrap(string value)
892930
return WrapValue(value);
893931
}
894932

933+
public virtual (string, string) SplitAlias(string value)
934+
{
935+
var index = value.LastIndexOf(" as ", StringComparison.OrdinalIgnoreCase);
936+
937+
if (index > 0)
938+
{
939+
var before = value.Substring(0, index);
940+
var after = value.Substring(index + 4);
941+
return (before, after);
942+
}
943+
944+
return (value, null);
945+
}
946+
895947
/// <summary>
896948
/// Wrap a single string in keyword identifiers.
897949
/// </summary>

QueryBuilder/Query.Select.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,54 @@ public Query Select(Func<Query, Query> callback, string alias)
6868
{
6969
return Select(callback.Invoke(NewChild()), alias);
7070
}
71+
72+
public Query SelectAggregate(string aggregate, string column, Query filter = null)
73+
{
74+
Method = "select";
75+
76+
AddComponent("select", new AggregatedColumn
77+
{
78+
Column = new Column { Name = column },
79+
Aggregate = aggregate,
80+
Filter = filter,
81+
});
82+
83+
return this;
84+
}
85+
86+
public Query SelectAggregate(string aggregate, string column, Func<Query, Query> filter)
87+
{
88+
if (filter == null)
89+
{
90+
return SelectAggregate(aggregate, column);
91+
}
92+
93+
return SelectAggregate(aggregate, column, filter.Invoke(NewChild()));
94+
}
95+
96+
public Query SelectSum(string column, Func<Query, Query> filter = null)
97+
{
98+
return SelectAggregate("sum", column, filter);
99+
}
100+
101+
public Query SelectCount(string column, Func<Query, Query> filter = null)
102+
{
103+
return SelectAggregate("count", column, filter);
104+
}
105+
106+
public Query SelectAvg(string column, Func<Query, Query> filter = null)
107+
{
108+
return SelectAggregate("avg", column, filter);
109+
}
110+
111+
public Query SelectMin(string column, Func<Query, Query> filter = null)
112+
{
113+
return SelectAggregate("min", column, filter);
114+
}
115+
116+
public Query SelectMax(string column, Func<Query, Query> filter = null)
117+
{
118+
return SelectAggregate("max", column, filter);
119+
}
71120
}
72121
}

0 commit comments

Comments
 (0)