diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs
index 4a90825..16c35e6 100644
--- a/AmalgamationTool/DynamORM.Amalgamation.cs
+++ b/AmalgamationTool/DynamORM.Amalgamation.cs
@@ -6674,6 +6674,47 @@ namespace DynamORM
#endregion GroupBy
+ #region Having
+
+ ///
+ /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Having( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder Having(Func func);
+
+ /// Add Having condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(DynamicColumn column);
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value);
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(string column, object value);
+
+ /// Add Having condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(object conditions, bool schema = false);
+
+ #endregion Having
+
#region OrderBy
///
@@ -6857,6 +6898,211 @@ namespace DynamORM
namespace Extensions
{
+ internal static class DynamicHavingQueryExtensions
+ {
+ #region Where
+
+ internal static T InternalHaving(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ return builder.InternalHaving(false, false, func);
+ }
+
+ internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ if (func == null) throw new ArgumentNullException("Array of functions cannot be null.");
+
+ using (DynamicParser parser = DynamicParser.Parse(func))
+ {
+ string condition = null;
+ bool and = true;
+
+ object result = parser.Result;
+ if (result is string)
+ {
+ condition = (string)result;
+
+ if (condition.ToUpper().IndexOf("OR") == 0)
+ {
+ and = false;
+ condition = condition.Substring(3);
+ }
+ else if (condition.ToUpper().IndexOf("AND") == 0)
+ condition = condition.Substring(4);
+ }
+ else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
+ return builder.InternalHaving(result);
+ else
+ {
+ // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods...
+ if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument)
+ {
+ DynamicParser.Node.Method node = (DynamicParser.Node.Method)result;
+ string name = node.Name.ToUpper();
+ if (name == "AND" || name == "OR")
+ {
+ object[] args = ((DynamicParser.Node.Method)node).Arguments;
+ if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name));
+ if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch()));
+
+ and = name == "AND" ? true : false;
+ result = args[0];
+ }
+ }
+
+ // Just parsing the contents now...
+ condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
+ }
+
+ if (addBeginBrace) builder.HavingOpenBracketsCount++;
+ if (addEndBrace) builder.HavingOpenBracketsCount--;
+
+ if (builder.HavingCondition == null)
+ builder.HavingCondition = string.Format("{0}{1}{2}",
+ addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
+ else
+ builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, and ? "AND" : "OR",
+ addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
+ }
+
+ return builder;
+ }
+
+ internal static T InternalHaving(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ bool virt = builder.VirtualMode;
+ if (column.VirtualColumn.HasValue)
+ builder.VirtualMode = column.VirtualColumn.Value;
+
+ Action modParam = (p) =>
+ {
+ if (column.Schema.HasValue)
+ p.Schema = column.Schema;
+
+ if (!p.Schema.HasValue)
+ p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName);
+ };
+
+ builder.CreateTemporaryParameterAction(modParam);
+
+ // It's kind of uglu, but... well it works.
+ if (column.Or)
+ switch (column.Operator)
+ {
+ default:
+ case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break;
+ case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break;
+ case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break;
+ case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break;
+ case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break;
+ case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break;
+ case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break;
+ case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break;
+ case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break;
+ case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break;
+ }
+ else
+ switch (column.Operator)
+ {
+ default:
+ case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break;
+ case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break;
+ case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break;
+ case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break;
+ case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break;
+ case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break;
+ case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break;
+ case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break;
+ case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break;
+ case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break;
+ }
+
+ builder.OnCreateTemporaryParameter.Remove(modParam);
+ builder.VirtualMode = virt;
+
+ return builder;
+ }
+
+ internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ if (value is DynamicColumn)
+ {
+ DynamicColumn v = (DynamicColumn)value;
+
+ if (string.IsNullOrEmpty(v.ColumnName))
+ v.ColumnName = column;
+
+ return builder.InternalHaving(v);
+ }
+ else if (value is IEnumerable)
+ {
+ foreach (DynamicColumn v in (IEnumerable)value)
+ builder.InternalHaving(v);
+
+ return builder;
+ }
+
+ return builder.InternalHaving(new DynamicColumn
+ {
+ ColumnName = column,
+ Operator = op,
+ Value = value
+ });
+ }
+
+ internal static T InternalHaving(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value);
+ }
+
+ internal static T InternalHaving(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ if (conditions is DynamicColumn)
+ return builder.InternalHaving((DynamicColumn)conditions);
+ else if (conditions is IEnumerable)
+ {
+ foreach (DynamicColumn v in (IEnumerable)conditions)
+ builder.InternalHaving(v);
+
+ return builder;
+ }
+
+ IDictionary dict = conditions.ToDictionary();
+ DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType());
+ string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty);
+
+ foreach (KeyValuePair condition in dict)
+ {
+ if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table")
+ continue;
+
+ string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key;
+
+ DynamicSchemaColumn? col = null;
+
+ // This should be used on typed queries or update/delete steatements, which usualy operate on a single table.
+ if (schema)
+ {
+ col = builder.GetColumnFromSchema(colName, mapper, table);
+
+ if ((!col.HasValue || !col.Value.IsKey) &&
+ (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true)))
+ continue;
+
+ colName = col.HasValue ? col.Value.Name : colName;
+ }
+
+ if (!string.IsNullOrEmpty(table))
+ builder.InternalHaving(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value);
+ else
+ builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value);
+ }
+
+ return builder;
+ }
+
+ #endregion Where
+ }
+
internal static class DynamicModifyBuilderExtensions
{
internal static T Table(this T builder, Func func) where T : DynamicModifyBuilder
@@ -7033,8 +7279,8 @@ namespace DynamORM
condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
}
- if (addBeginBrace) builder.OpenBracketsCount++;
- if (addEndBrace) builder.OpenBracketsCount--;
+ if (addBeginBrace) builder.WhereOpenBracketsCount++;
+ if (addEndBrace) builder.WhereOpenBracketsCount--;
if (builder.WhereCondition == null)
builder.WhereCondition = string.Format("{0}{1}{2}",
@@ -7512,7 +7758,17 @@ namespace DynamORM
string WhereCondition { get; set; }
/// Gets or sets the amount of not closed brackets in where statement.
- int OpenBracketsCount { get; set; }
+ int WhereOpenBracketsCount { get; set; }
+ }
+
+ /// Empty interface to allow having query builder implementation use universal approach.
+ internal interface IQueryWithHaving
+ {
+ /// Gets or sets the having condition.
+ string HavingCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in having statement.
+ int HavingOpenBracketsCount { get; set; }
}
private DynamicQueryBuilder _parent = null;
@@ -7679,7 +7935,7 @@ namespace DynamORM
OnCreateParameter = new List>();
WhereCondition = null;
- OpenBracketsCount = 0;
+ WhereOpenBracketsCount = 0;
Database = db;
if (Database != null)
@@ -7705,7 +7961,7 @@ namespace DynamORM
public string WhereCondition { get; set; }
/// Gets or sets the amount of not closed brackets in where statement.
- public int OpenBracketsCount { get; set; }
+ public int WhereOpenBracketsCount { get; set; }
#endregion IQueryWithWhere
@@ -7749,10 +8005,22 @@ namespace DynamORM
// End not ended where statement
if (this is IQueryWithWhere)
{
- while (OpenBracketsCount > 0)
+ while (WhereOpenBracketsCount > 0)
{
WhereCondition += ")";
- OpenBracketsCount--;
+ WhereOpenBracketsCount--;
+ }
+ }
+
+ // End not ended having statement
+ if (this is IQueryWithHaving)
+ {
+ IQueryWithHaving h = this as IQueryWithHaving;
+
+ while (h.HavingOpenBracketsCount > 0)
+ {
+ h.HavingCondition += ")";
+ h.HavingOpenBracketsCount--;
}
}
@@ -8170,6 +8438,12 @@ namespace DynamORM
return "COUNT(*)";
return string.Format("COUNT({0})", Parse(node.Arguments[0], ref columnSchema, pars: Parameters, nulls: true));
+
+ case "COUNT0":
+ if (node.Arguments != null && node.Arguments.Length > 0)
+ throw new ArgumentException("COUNT0 method doesn't expect arguments");
+
+ return "COUNT(0)";
}
}
@@ -8407,7 +8681,7 @@ namespace DynamORM
}
/// Implementation of dynamic select query builder.
- internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving
{
private int? _limit = null;
private int? _offset = null;
@@ -8419,6 +8693,16 @@ namespace DynamORM
private string _groupby;
private string _orderby;
+ #region IQueryWithHaving
+
+ /// Gets or sets the having condition.
+ public string HavingCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in having statement.
+ public int HavingOpenBracketsCount { get; set; }
+
+ #endregion IQueryWithHaving
+
///
/// Gets a value indicating whether this instance has select columns.
///
@@ -8478,6 +8762,7 @@ namespace DynamORM
if (_join != null) sb.AppendFormat(" {0}", _join);
if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition);
if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby);
+ if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition);
if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby);
if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset)
sb.AppendFormat(" LIMIT {0}", _limit);
@@ -9377,6 +9662,62 @@ namespace DynamORM
#endregion GroupBy
+ #region Having
+
+ ///
+ /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Having( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicSelectQueryBuilder Having(Func func)
+ {
+ return this.InternalHaving(func);
+ }
+
+ /// Add Having condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column)
+ {
+ return this.InternalHaving(column);
+ }
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value)
+ {
+ return this.InternalHaving(column, op, value);
+ }
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(string column, object value)
+ {
+ return this.InternalHaving(column, value);
+ }
+
+ /// Add Having condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(object conditions, bool schema = false)
+ {
+ return this.InternalHaving(conditions, schema);
+ }
+
+ #endregion Having
+
#region OrderBy
///
diff --git a/DynamORM.Tests/Select/ParserTests.cs b/DynamORM.Tests/Select/ParserTests.cs
index 198010c..e353b2c 100644
--- a/DynamORM.Tests/Select/ParserTests.cs
+++ b/DynamORM.Tests/Select/ParserTests.cs
@@ -26,10 +26,10 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
+using System.Linq;
using DynamORM.Builders;
using DynamORM.Builders.Implementation;
using NUnit.Framework;
-using System.Linq;
namespace DynamORM.Tests.Select
{
@@ -225,6 +225,20 @@ namespace DynamORM.Tests.Select
Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c WHERE (c.\"UserName\" = [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText());
}
+ ///
+ /// Tests where method with alias.
+ ///
+ [Test]
+ public void TestHavingAlias()
+ {
+ IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database);
+
+ cmd.From(u => u.dbo.Users.As(u.c))
+ .Having(u => u.Sum(u.c.ClientsCount) > 10);
+
+ Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS c HAVING (Sum(c.\"ClientsCount\") > [${0}])", cmd.Parameters.Keys.First()), cmd.CommandText());
+ }
+
///
/// Tests complex where method with alias.
///
@@ -735,6 +749,25 @@ namespace DynamORM.Tests.Select
cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText());
}
+ ///
+ /// Tests select escaped case.
+ ///
+ [Test]
+ public void TestCoalesceCalculatedArgs()
+ {
+ IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database);
+
+ cmd.From(u => u.dbo.Users.As(u.c))
+ .Select(u => u.Coalesce(u.c.Test1 + "_", u.c.Test2 + "_", u.c.Test3 + "_").As(u.Hash));
+
+ Assert.AreEqual(string.Format("SELECT Coalesce((c.\"Test1\" + [${0}]), (c.\"Test2\" + [${1}]), (c.\"Test3\" + [${2}])) AS \"Hash\" FROM \"dbo\".\"Users\" AS c",
+ cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText());
+
+ //var c = Database.Open().CreateCommand();
+ //cmd.FillCommand(c);
+ //c.Dispose();
+ }
+
///
/// Tests select escaped case.
///
diff --git a/DynamORM/Builders/Extensions/DynamicHavingQueryExtensions.cs b/DynamORM/Builders/Extensions/DynamicHavingQueryExtensions.cs
new file mode 100644
index 0000000..3193bd5
--- /dev/null
+++ b/DynamORM/Builders/Extensions/DynamicHavingQueryExtensions.cs
@@ -0,0 +1,245 @@
+/*
+ * DynamORM - Dynamic Object-Relational Mapping library.
+ * Copyright (c) 2012-2015, Grzegorz Russek (grzegorz.russek@gmail.com)
+ * All rights reserved.
+ *
+ * Some of methods in this code file is based on Kerosene ORM solution
+ * for parsing dynamic lambda expressions by Moisés Barba Cebeira
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using DynamORM.Builders.Implementation;
+using DynamORM.Helpers;
+using DynamORM.Helpers.Dynamics;
+using DynamORM.Mapper;
+
+namespace DynamORM.Builders.Extensions
+{
+ internal static class DynamicHavingQueryExtensions
+ {
+ #region Where
+
+ internal static T InternalHaving(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ return builder.InternalHaving(false, false, func);
+ }
+
+ internal static T InternalHaving(this T builder, bool addBeginBrace, bool addEndBrace, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ if (func == null) throw new ArgumentNullException("Array of functions cannot be null.");
+
+ using (DynamicParser parser = DynamicParser.Parse(func))
+ {
+ string condition = null;
+ bool and = true;
+
+ object result = parser.Result;
+ if (result is string)
+ {
+ condition = (string)result;
+
+ if (condition.ToUpper().IndexOf("OR") == 0)
+ {
+ and = false;
+ condition = condition.Substring(3);
+ }
+ else if (condition.ToUpper().IndexOf("AND") == 0)
+ condition = condition.Substring(4);
+ }
+ else if (!(result is DynamicParser.Node) && !result.GetType().IsValueType)
+ return builder.InternalHaving(result);
+ else
+ {
+ // Intercepting the 'x => x.And()' and 'x => x.Or()' virtual methods...
+ if (result is DynamicParser.Node.Method && ((DynamicParser.Node.Method)result).Host is DynamicParser.Node.Argument)
+ {
+ DynamicParser.Node.Method node = (DynamicParser.Node.Method)result;
+ string name = node.Name.ToUpper();
+ if (name == "AND" || name == "OR")
+ {
+ object[] args = ((DynamicParser.Node.Method)node).Arguments;
+ if (args == null) throw new ArgumentNullException("arg", string.Format("{0} is not a parameterless method.", name));
+ if (args.Length != 1) throw new ArgumentException(string.Format("{0} requires one and only one parameter: {1}.", name, args.Sketch()));
+
+ and = name == "AND" ? true : false;
+ result = args[0];
+ }
+ }
+
+ // Just parsing the contents now...
+ condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
+ }
+
+ if (addBeginBrace) builder.HavingOpenBracketsCount++;
+ if (addEndBrace) builder.HavingOpenBracketsCount--;
+
+ if (builder.HavingCondition == null)
+ builder.HavingCondition = string.Format("{0}{1}{2}",
+ addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
+ else
+ builder.HavingCondition = string.Format("{0} {1} {2}{3}{4}", builder.HavingCondition, and ? "AND" : "OR",
+ addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
+ }
+
+ return builder;
+ }
+
+ internal static T InternalHaving(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ bool virt = builder.VirtualMode;
+ if (column.VirtualColumn.HasValue)
+ builder.VirtualMode = column.VirtualColumn.Value;
+
+ Action modParam = (p) =>
+ {
+ if (column.Schema.HasValue)
+ p.Schema = column.Schema;
+
+ if (!p.Schema.HasValue)
+ p.Schema = column.Schema ?? builder.GetColumnFromSchema(column.ColumnName);
+ };
+
+ builder.CreateTemporaryParameterAction(modParam);
+
+ // It's kind of uglu, but... well it works.
+ if (column.Or)
+ switch (column.Operator)
+ {
+ default:
+ case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break;
+ case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break;
+ case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break;
+ case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break;
+ case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break;
+ case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break;
+ case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break;
+ case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break;
+ case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break;
+ case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break;
+ }
+ else
+ switch (column.Operator)
+ {
+ default:
+ case DynamicColumn.CompareOperator.Eq: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break;
+ case DynamicColumn.CompareOperator.Not: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break;
+ case DynamicColumn.CompareOperator.Like: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break;
+ case DynamicColumn.CompareOperator.NotLike: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break;
+ case DynamicColumn.CompareOperator.In: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break;
+ case DynamicColumn.CompareOperator.Lt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break;
+ case DynamicColumn.CompareOperator.Lte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break;
+ case DynamicColumn.CompareOperator.Gt: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break;
+ case DynamicColumn.CompareOperator.Gte: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break;
+ case DynamicColumn.CompareOperator.Between: builder.InternalHaving(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break;
+ }
+
+ builder.OnCreateTemporaryParameter.Remove(modParam);
+ builder.VirtualMode = virt;
+
+ return builder;
+ }
+
+ internal static T InternalHaving(this T builder, string column, DynamicColumn.CompareOperator op, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ if (value is DynamicColumn)
+ {
+ DynamicColumn v = (DynamicColumn)value;
+
+ if (string.IsNullOrEmpty(v.ColumnName))
+ v.ColumnName = column;
+
+ return builder.InternalHaving(v);
+ }
+ else if (value is IEnumerable)
+ {
+ foreach (DynamicColumn v in (IEnumerable)value)
+ builder.InternalHaving(v);
+
+ return builder;
+ }
+
+ return builder.InternalHaving(new DynamicColumn
+ {
+ ColumnName = column,
+ Operator = op,
+ Value = value
+ });
+ }
+
+ internal static T InternalHaving(this T builder, string column, object value) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ return builder.InternalHaving(column, DynamicColumn.CompareOperator.Eq, value);
+ }
+
+ internal static T InternalHaving(this T builder, object conditions, bool schema = false) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithHaving
+ {
+ if (conditions is DynamicColumn)
+ return builder.InternalHaving((DynamicColumn)conditions);
+ else if (conditions is IEnumerable)
+ {
+ foreach (DynamicColumn v in (IEnumerable)conditions)
+ builder.InternalHaving(v);
+
+ return builder;
+ }
+
+ IDictionary dict = conditions.ToDictionary();
+ DynamicTypeMap mapper = DynamicMapperCache.GetMapper(conditions.GetType());
+ string table = dict.TryGetValue("_table").NullOr(x => x.ToString(), string.Empty);
+
+ foreach (KeyValuePair condition in dict)
+ {
+ if (mapper.Ignored.Contains(condition.Key) || condition.Key == "_table")
+ continue;
+
+ string colName = mapper != null ? mapper.PropertyMap.TryGetValue(condition.Key) ?? condition.Key : condition.Key;
+
+ DynamicSchemaColumn? col = null;
+
+ // This should be used on typed queries or update/delete steatements, which usualy operate on a single table.
+ if (schema)
+ {
+ col = builder.GetColumnFromSchema(colName, mapper, table);
+
+ if ((!col.HasValue || !col.Value.IsKey) &&
+ (mapper == null || mapper.ColumnsMap.TryGetValue(colName).NullOr(m => m.Ignore || m.Column.NullOr(c => !c.IsKey, true), true)))
+ continue;
+
+ colName = col.HasValue ? col.Value.Name : colName;
+ }
+
+ if (!string.IsNullOrEmpty(table))
+ builder.InternalHaving(x => x(builder.FixObjectName(string.Format("{0}.{1}", table, colName))) == condition.Value);
+ else
+ builder.InternalHaving(x => x(builder.FixObjectName(colName)) == condition.Value);
+ }
+
+ return builder;
+ }
+
+ #endregion Where
+ }
+}
\ No newline at end of file
diff --git a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs
index 5dbbed9..96e76f9 100644
--- a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs
+++ b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs
@@ -93,8 +93,8 @@ namespace DynamORM.Builders.Extensions
condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
}
- if (addBeginBrace) builder.OpenBracketsCount++;
- if (addEndBrace) builder.OpenBracketsCount--;
+ if (addBeginBrace) builder.WhereOpenBracketsCount++;
+ if (addEndBrace) builder.WhereOpenBracketsCount--;
if (builder.WhereCondition == null)
builder.WhereCondition = string.Format("{0}{1}{2}",
diff --git a/DynamORM/Builders/IDynamicSelectQueryBuilder.cs b/DynamORM/Builders/IDynamicSelectQueryBuilder.cs
index 9e97307..faabac2 100644
--- a/DynamORM/Builders/IDynamicSelectQueryBuilder.cs
+++ b/DynamORM/Builders/IDynamicSelectQueryBuilder.cs
@@ -193,6 +193,47 @@ namespace DynamORM.Builders
#endregion GroupBy
+ #region Having
+
+ ///
+ /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Having( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ IDynamicSelectQueryBuilder Having(Func func);
+
+ /// Add Having condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(DynamicColumn column);
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value);
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(string column, object value);
+
+ /// Add Having condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ IDynamicSelectQueryBuilder Having(object conditions, bool schema = false);
+
+ #endregion Having
+
#region OrderBy
///
diff --git a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs
index 28258ff..9372834 100644
--- a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs
@@ -51,7 +51,17 @@ namespace DynamORM.Builders.Implementation
string WhereCondition { get; set; }
/// Gets or sets the amount of not closed brackets in where statement.
- int OpenBracketsCount { get; set; }
+ int WhereOpenBracketsCount { get; set; }
+ }
+
+ /// Empty interface to allow having query builder implementation use universal approach.
+ internal interface IQueryWithHaving
+ {
+ /// Gets or sets the having condition.
+ string HavingCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in having statement.
+ int HavingOpenBracketsCount { get; set; }
}
private DynamicQueryBuilder _parent = null;
@@ -218,7 +228,7 @@ namespace DynamORM.Builders.Implementation
OnCreateParameter = new List>();
WhereCondition = null;
- OpenBracketsCount = 0;
+ WhereOpenBracketsCount = 0;
Database = db;
if (Database != null)
@@ -244,7 +254,7 @@ namespace DynamORM.Builders.Implementation
public string WhereCondition { get; set; }
/// Gets or sets the amount of not closed brackets in where statement.
- public int OpenBracketsCount { get; set; }
+ public int WhereOpenBracketsCount { get; set; }
#endregion IQueryWithWhere
@@ -288,10 +298,22 @@ namespace DynamORM.Builders.Implementation
// End not ended where statement
if (this is IQueryWithWhere)
{
- while (OpenBracketsCount > 0)
+ while (WhereOpenBracketsCount > 0)
{
WhereCondition += ")";
- OpenBracketsCount--;
+ WhereOpenBracketsCount--;
+ }
+ }
+
+ // End not ended having statement
+ if (this is IQueryWithHaving)
+ {
+ IQueryWithHaving h = this as IQueryWithHaving;
+
+ while (h.HavingOpenBracketsCount > 0)
+ {
+ h.HavingCondition += ")";
+ h.HavingOpenBracketsCount--;
}
}
@@ -639,7 +661,7 @@ namespace DynamORM.Builders.Implementation
return string.Format("{0} IN({1})", parent, sbin.ToString());
}
-
+
case "NOTIN":
{
if (node.Arguments == null || node.Arguments.Length == 0)
@@ -709,6 +731,12 @@ namespace DynamORM.Builders.Implementation
return "COUNT(*)";
return string.Format("COUNT({0})", Parse(node.Arguments[0], ref columnSchema, pars: Parameters, nulls: true));
+
+ case "COUNT0":
+ if (node.Arguments != null && node.Arguments.Length > 0)
+ throw new ArgumentException("COUNT0 method doesn't expect arguments");
+
+ return "COUNT(0)";
}
}
diff --git a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs
index 28af8ba..8b7072f 100644
--- a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs
@@ -42,7 +42,7 @@ using DynamORM.Mapper;
namespace DynamORM.Builders.Implementation
{
/// Implementation of dynamic select query builder.
- internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ internal class DynamicSelectQueryBuilder : DynamicQueryBuilder, IDynamicSelectQueryBuilder, DynamicQueryBuilder.IQueryWithWhere, DynamicQueryBuilder.IQueryWithHaving
{
private int? _limit = null;
private int? _offset = null;
@@ -54,6 +54,16 @@ namespace DynamORM.Builders.Implementation
private string _groupby;
private string _orderby;
+ #region IQueryWithHaving
+
+ /// Gets or sets the having condition.
+ public string HavingCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in having statement.
+ public int HavingOpenBracketsCount { get; set; }
+
+ #endregion IQueryWithHaving
+
///
/// Gets a value indicating whether this instance has select columns.
///
@@ -113,6 +123,7 @@ namespace DynamORM.Builders.Implementation
if (_join != null) sb.AppendFormat(" {0}", _join);
if (WhereCondition != null) sb.AppendFormat(" WHERE {0}", WhereCondition);
if (_groupby != null) sb.AppendFormat(" GROUP BY {0}", _groupby);
+ if (HavingCondition != null) sb.AppendFormat(" HAVING {0}", HavingCondition);
if (_orderby != null) sb.AppendFormat(" ORDER BY {0}", _orderby);
if (_limit.HasValue && !lused && (Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset)
sb.AppendFormat(" LIMIT {0}", _limit);
@@ -1012,6 +1023,62 @@ namespace DynamORM.Builders.Implementation
#endregion GroupBy
+ #region Having
+
+ ///
+ /// Adds to the 'Having' clause the contents obtained from parsing the dynamic lambda expression given. The condition
+ /// is parsed to the appropriate syntax, Having the specific customs virtual methods supported by the parser are used
+ /// as needed.
+ /// - If several Having() methods are chained their contents are, by default, concatenated with an 'AND' operator.
+ /// - The 'And()' and 'Or()' virtual method can be used to concatenate with an 'OR' or an 'AND' operator, as in:
+ /// 'Having( x => x.Or( condition ) )'.
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicSelectQueryBuilder Having(Func func)
+ {
+ return this.InternalHaving(func);
+ }
+
+ /// Add Having condition.
+ /// Condition column with operator and value.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(DynamicColumn column)
+ {
+ return this.InternalHaving(column);
+ }
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition operator.
+ /// Condition value.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(string column, DynamicColumn.CompareOperator op, object value)
+ {
+ return this.InternalHaving(column, op, value);
+ }
+
+ /// Add Having condition.
+ /// Condition column.
+ /// Condition value.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(string column, object value)
+ {
+ return this.InternalHaving(column, value);
+ }
+
+ /// Add Having condition.
+ /// Set conditions as properties and values of an object.
+ /// If true use schema to determine key columns and ignore those which
+ /// aren't keys.
+ /// Builder instance.
+ public virtual IDynamicSelectQueryBuilder Having(object conditions, bool schema = false)
+ {
+ return this.InternalHaving(conditions, schema);
+ }
+
+ #endregion Having
+
#region OrderBy
///
diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj
index c91f9d0..cf107bc 100644
--- a/DynamORM/DynamORM.csproj
+++ b/DynamORM/DynamORM.csproj
@@ -64,6 +64,7 @@
+
diff --git a/DynamORM/DynamicCachedReader.cs b/DynamORM/DynamicCachedReader.cs
new file mode 100644
index 0000000..0fc6644
--- /dev/null
+++ b/DynamORM/DynamicCachedReader.cs
@@ -0,0 +1,496 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Dynamic;
+using System.IO;
+using DynamORM.Helpers;
+using DynamORM.Mapper;
+
+namespace DynamORM
+{
+ /// Cache data reader in memory.
+ internal class DynamicCachedReader : DynamicObject, IDataReader
+ {
+ #region Constructor and Data
+
+ private DataTable _schema;
+ private int _fields;
+ private int _rows;
+ private int _position;
+ private int _cachePos;
+
+ private IList _names;
+ private IDictionary _ordinals;
+ private IList _types;
+ private IList