Mercurial > pub > bltoolkit
diff Source/Data/Sql/SqlQuery.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Data/Sql/SqlQuery.cs Thu Mar 27 21:46:09 2014 +0400 @@ -0,0 +1,4850 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +using JetBrains.Annotations; + +namespace BLToolkit.Data.Sql +{ + using Reflection; + + using FJoin = SqlQuery.FromClause.Join; + + [DebuggerDisplay("SQL = {SqlText}")] + public class SqlQuery : ISqlTableSource + { + #region Init + + static readonly Dictionary<string,object> _reservedWords = new Dictionary<string,object>(); + + static SqlQuery() + { + using (var stream = typeof(SqlQuery).Assembly.GetManifestResourceStream(typeof(SqlQuery), "ReservedWords.txt")) + using (var reader = new StreamReader(stream)) + { + /* + var words = reader.ReadToEnd().Replace(' ', '\n').Replace('\t', '\n').Split('\n'); + var q = from w in words where w.Length > 0 orderby w select w; + + var text = string.Join("\n", q.Distinct().ToArray()); + */ + + string s; + while ((s = reader.ReadLine()) != null) + _reservedWords.Add(s, s); + } + } + + public SqlQuery() + { + SourceID = Interlocked.Increment(ref SourceIDCounter); + + _select = new SelectClause (this); + _from = new FromClause (this); + _where = new WhereClause (this); + _groupBy = new GroupByClause(this); + _having = new WhereClause (this); + _orderBy = new OrderByClause(this); + } + + internal SqlQuery(int id) + { + SourceID = id; + } + + internal void Init( + InsertClause insert, + UpdateClause update, + DeleteClause delete, + SelectClause select, + FromClause from, + WhereClause where, + GroupByClause groupBy, + WhereClause having, + OrderByClause orderBy, + List<Union> unions, + SqlQuery parentSql, + bool parameterDependent, + List<SqlParameter> parameters) + { + _insert = insert; + _update = update; + _delete = delete; + _select = select; + _from = from; + _where = where; + _groupBy = groupBy; + _having = having; + _orderBy = orderBy; + _unions = unions; + ParentSql = parentSql; + IsParameterDependent = parameterDependent; + _parameters.AddRange(parameters); + + foreach (var col in select.Columns) + col.Parent = this; + + _select. SetSqlQuery(this); + _from. SetSqlQuery(this); + _where. SetSqlQuery(this); + _groupBy.SetSqlQuery(this); + _having. SetSqlQuery(this); + _orderBy.SetSqlQuery(this); + } + + readonly List<SqlParameter> _parameters = new List<SqlParameter>(); + public List<SqlParameter> Parameters + { + get { return _parameters; } + } + + private List<object> _properties; + public List<object> Properties + { + get { return _properties ?? (_properties = new List<object>()); } + } + + public bool IsParameterDependent { get; set; } + public SqlQuery ParentSql { get; set; } + + public bool IsSimple + { + get { return !Select.HasModifier && Where.IsEmpty && GroupBy.IsEmpty && Having.IsEmpty && OrderBy.IsEmpty; } + } + + private QueryType _queryType = QueryType.Select; + public QueryType QueryType + { + get { return _queryType; } + set { _queryType = value; } + } + + public bool IsSelect { get { return _queryType == QueryType.Select; } } + public bool IsDelete { get { return _queryType == QueryType.Delete; } } + public bool IsInsertOrUpdate { get { return _queryType == QueryType.InsertOrUpdate; } } + public bool IsInsert { get { return _queryType == QueryType.Insert || _queryType == QueryType.InsertOrUpdate; } } + public bool IsUpdate { get { return _queryType == QueryType.Update || _queryType == QueryType.InsertOrUpdate; } } + + #endregion + + #region Column + + public class Column : IEquatable<Column>, ISqlExpression, IChild<SqlQuery> + { + public Column(SqlQuery parent, ISqlExpression expression, string alias) + { + if (expression == null) throw new ArgumentNullException("expression"); + + Parent = parent; + Expression = expression; + _alias = alias; + +#if DEBUG + _columnNumber = ++_columnCounter; +#endif + } + + public Column(SqlQuery builder, ISqlExpression expression) + : this(builder, expression, null) + { + } + +#if DEBUG + readonly int _columnNumber; + static int _columnCounter; +#endif + + public ISqlExpression Expression { get; set; } + + internal string _alias; + public string Alias + { + get + { + if (_alias == null) + { + if (Expression is SqlField) + { + var field = (SqlField)Expression; + return field.Alias ?? field.PhysicalName; + } + + if (Expression is Column) + { + var col = (Column)Expression; + return col.Alias; + } + } + + return _alias; + } + set { _alias = value; } + } + + public bool Equals(Column other) + { + return Expression.Equals(other.Expression); + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region ISqlExpression Members + + public bool CanBeNull() + { + return Expression.CanBeNull(); + } + + public bool Equals(ISqlExpression other, Func<ISqlExpression,ISqlExpression,bool> comparer) + { + if (this == other) + return true; + + return + other is Column && + Expression.Equals(((Column)other).Expression, comparer) && + comparer(this, other); + } + + public int Precedence + { + get { return Sql.Precedence.Primary; } + } + + public Type SystemType + { + get { return Expression.SystemType; } + } + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + var parent = (SqlQuery)Parent.Clone(objectTree, doClone); + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new Column( + parent, + (ISqlExpression)Expression.Clone(objectTree, doClone), + _alias)); + + return clone; + } + + #endregion + + #region IEquatable<ISqlExpression> Members + + bool IEquatable<ISqlExpression>.Equals(ISqlExpression other) + { + if (this == other) + return true; + + return other is Column && Equals((Column)other); + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + public ISqlExpression Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + if (!(skipColumns && Expression is Column)) + Expression = Expression.Walk(skipColumns, func); + + return func(this); + } + + #endregion + + #region IChild<ISqlTableSource> Members + + string IChild<SqlQuery>.Name + { + get { return Alias; } + } + + public SqlQuery Parent { get; set; } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.Column; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + sb + .Append('t') + .Append(Parent.SourceID) + .Append("."); + +#if DEBUG + sb.Append('[').Append(_columnNumber).Append(']'); +#endif + + if (Expression is SqlQuery) + { + sb + .Append("(\n\t\t"); + var len = sb.Length; + Expression.ToString(sb, dic).Replace("\n", "\n\t\t", len, sb.Length - len); + sb.Append("\n\t)"); + } + /*else if (Expression is Column) + { + var col = (Column)Expression; + sb + .Append("t") + .Append(col.Parent.SourceID) + .Append(".") + .Append(col.Alias ?? "c" + (col.Parent.Select.Columns.IndexOf(col) + 1)); + }*/ + else + { + Expression.ToString(sb, dic); + } + + dic.Remove(this); + + return sb; + } + + #endregion + } + + #endregion + + #region TableSource + + public class TableSource : ISqlTableSource + { + public TableSource(ISqlTableSource source, string alias) + : this(source, alias, null) + { + } + + public TableSource(ISqlTableSource source, string alias, params JoinedTable[] joins) + { + if (source == null) throw new ArgumentNullException("source"); + + Source = source; + _alias = alias; + + if (joins != null) + _joins.AddRange(joins); + } + + public TableSource(ISqlTableSource source, string alias, IEnumerable<JoinedTable> joins) + { + if (source == null) throw new ArgumentNullException("source"); + + Source = source; + _alias = alias; + + if (joins != null) + _joins.AddRange(joins); + } + + public ISqlTableSource Source { get; set; } + public SqlTableType SqlTableType { get { return Source.SqlTableType; } } + + internal string _alias; + public string Alias + { + get + { + if (string.IsNullOrEmpty(_alias)) + { + if (Source is TableSource) + return (Source as TableSource).Alias; + + if (Source is SqlTable) + return ((SqlTable)Source).Alias; + } + + return _alias; + } + set { _alias = value; } + } + + public TableSource this[ISqlTableSource table] + { + get { return this[table, null]; } + } + + public TableSource this[ISqlTableSource table, string alias] + { + get + { + foreach (var tj in Joins) + { + var t = CheckTableSource(tj.Table, table, alias); + + if (t != null) + return t; + } + + return null; + } + } + + readonly List<JoinedTable> _joins = new List<JoinedTable>(); + public List<JoinedTable> Joins + { + get { return _joins; } + } + + public void ForEach(Action<TableSource> action, HashSet<SqlQuery> visitedQueries) + { + action(this); + foreach (var join in Joins) + join.Table.ForEach(action, visitedQueries); + + if (Source is SqlQuery && visitedQueries.Contains((SqlQuery)Source)) + ((SqlQuery)Source).ForEachTable(action, visitedQueries); + } + + public IEnumerable<ISqlTableSource> GetTables() + { + yield return Source; + + foreach (var join in Joins) + foreach (var table in join.Table.GetTables()) + yield return table; + } + + public int GetJoinNumber() + { + var n = Joins.Count; + + foreach (var join in Joins) + n += join.Table.GetJoinNumber(); + + return n; + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region IEquatable<ISqlExpression> Members + + bool IEquatable<ISqlExpression>.Equals(ISqlExpression other) + { + return this == other; + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + public ISqlExpression Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + Source = (ISqlTableSource)Source.Walk(skipColumns, func); + + foreach (var t in Joins) + ((ISqlExpressionWalkable)t).Walk(skipColumns, func); + + return this; + } + + #endregion + + #region ISqlTableSource Members + + public int SourceID { get { return Source.SourceID; } } + public SqlField All { get { return Source.All; } } + + IList<ISqlExpression> ISqlTableSource.GetKeys(bool allIfEmpty) + { + return Source.GetKeys(allIfEmpty); + } + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + { + var ts = new TableSource((ISqlTableSource)Source.Clone(objectTree, doClone), _alias); + + objectTree.Add(this, clone = ts); + + ts._joins.AddRange(_joins.ConvertAll(jt => (JoinedTable)jt.Clone(objectTree, doClone))); + } + + return clone; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.TableSource; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (sb.Length > 500) + return sb; + + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + if (Source is SqlQuery) + { + sb.Append("(\n\t"); + var len = sb.Length; + Source.ToString(sb, dic).Replace("\n", "\n\t", len, sb.Length - len); + sb.Append("\n)"); + } + else + Source.ToString(sb, dic); + + sb + .Append(" as t") + .Append(SourceID); + + foreach (IQueryElement join in Joins) + { + sb.AppendLine().Append('\t'); + var len = sb.Length; + join.ToString(sb, dic).Replace("\n", "\n\t", len, sb.Length - len); + } + + dic.Remove(this); + + return sb; + } + + #endregion + + #region ISqlExpression Members + + public bool CanBeNull() + { + return Source.CanBeNull(); + } + + public bool Equals(ISqlExpression other, Func<ISqlExpression,ISqlExpression,bool> comparer) + { + return this == other; + } + + public int Precedence { get { return Source.Precedence; } } + public Type SystemType { get { return Source.SystemType; } } + + #endregion + } + + #endregion + + #region TableJoin + + public enum JoinType + { + Auto, + Inner, + Left, + CrossApply, + OuterApply + } + + public class JoinedTable : IQueryElement, ISqlExpressionWalkable, ICloneableElement + { + public JoinedTable(JoinType joinType, TableSource table, bool isWeak, SearchCondition searchCondition) + { + JoinType = joinType; + Table = table; + IsWeak = isWeak; + Condition = searchCondition; + CanConvertApply = true; + } + + public JoinedTable(JoinType joinType, TableSource table, bool isWeak) + : this(joinType, table, isWeak, new SearchCondition()) + { + } + + public JoinedTable(JoinType joinType, ISqlTableSource table, string alias, bool isWeak) + : this(joinType, new TableSource(table, alias), isWeak) + { + } + + public JoinType JoinType { get; set; } + public TableSource Table { get; set; } + public SearchCondition Condition { get; private set; } + public bool IsWeak { get; set; } + public bool CanConvertApply { get; set; } + + public ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new JoinedTable( + JoinType, + (TableSource)Table.Clone(objectTree, doClone), + IsWeak, + (SearchCondition)Condition.Clone(objectTree, doClone))); + + return clone; + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region ISqlExpressionWalkable Members + + [Obsolete] + public ISqlExpression Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> action) + { + Condition = (SearchCondition)((ISqlExpressionWalkable)Condition).Walk(skipColumns, action); + +#pragma warning disable 0618 + Table.Walk(skipColumns, action); +#pragma warning restore 0618 + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.JoinedTable; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + switch (JoinType) + { + case JoinType.Inner : sb.Append("INNER JOIN "); break; + case JoinType.Left : sb.Append("LEFT JOIN "); break; + case JoinType.CrossApply : sb.Append("CROSS APPLY "); break; + case JoinType.OuterApply : sb.Append("OUTER APPLY "); break; + default : sb.Append("SOME JOIN "); break; + } + + ((IQueryElement)Table).ToString(sb, dic); + sb.Append(" ON "); + ((IQueryElement)Condition).ToString(sb, dic); + + dic.Remove(this); + + return sb; + } + + #endregion + } + + #endregion + + #region Predicate + + public abstract class Predicate : ISqlPredicate + { + public enum Operator + { + Equal, // = Is the operator used to test the equality between two expressions. + NotEqual, // <> != Is the operator used to test the condition of two expressions not being equal to each other. + Greater, // > Is the operator used to test the condition of one expression being greater than the other. + GreaterOrEqual, // >= Is the operator used to test the condition of one expression being greater than or equal to the other expression. + NotGreater, // !> Is the operator used to test the condition of one expression not being greater than the other expression. + Less, // < Is the operator used to test the condition of one expression being less than the other. + LessOrEqual, // <= Is the operator used to test the condition of one expression being less than or equal to the other expression. + NotLess // !< Is the operator used to test the condition of one expression not being less than the other expression. + } + + public class Expr : Predicate + { + public Expr([NotNull] ISqlExpression exp1, int precedence) + : base(precedence) + { + if (exp1 == null) throw new ArgumentNullException("exp1"); + + Expr1 = exp1; + } + + public Expr([NotNull] ISqlExpression exp1) + : base(exp1.Precedence) + { + if (exp1 == null) throw new ArgumentNullException("exp1"); + + Expr1 = exp1; + } + + public ISqlExpression Expr1 { get; set; } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + Expr1 = Expr1.Walk(skipColumns, func); + + if (Expr1 == null) + throw new InvalidOperationException(); + } + + public override bool CanBeNull() + { + return Expr1.CanBeNull(); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new Expr((ISqlExpression)Expr1.Clone(objectTree, doClone), Precedence)); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.ExprPredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + } + } + + public class NotExpr : Expr + { + public NotExpr(ISqlExpression exp1, bool isNot, int precedence) + : base(exp1, precedence) + { + IsNot = isNot; + } + + public bool IsNot { get; set; } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new NotExpr((ISqlExpression)Expr1.Clone(objectTree, doClone), IsNot, Precedence)); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.NotExprPredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + if (IsNot) sb.Append("NOT ("); + base.ToString(sb, dic); + if (IsNot) sb.Append(")"); + } + } + + // { expression { = | <> | != | > | >= | ! > | < | <= | !< } expression + // + public class ExprExpr : Expr + { + public ExprExpr(ISqlExpression exp1, Operator op, ISqlExpression exp2) + : base(exp1, Sql.Precedence.Comparison) + { + this.Operator = op; + Expr2 = exp2; + } + + public new Operator Operator { get; private set; } + public ISqlExpression Expr2 { get; internal set; } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { +#pragma warning disable 0618 + base.Walk(skipColumns, func); +#pragma warning restore 0618 + Expr2 = Expr2.Walk(skipColumns, func); + } + + public override bool CanBeNull() + { + return base.CanBeNull() || Expr2.CanBeNull(); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new ExprExpr( + (ISqlExpression)Expr1.Clone(objectTree, doClone), this.Operator, (ISqlExpression)Expr2.Clone(objectTree, doClone))); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.ExprExprPredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + + string op; + + switch (this.Operator) + { + case Operator.Equal : op = "="; break; + case Operator.NotEqual : op = "<>"; break; + case Operator.Greater : op = ">"; break; + case Operator.GreaterOrEqual: op = ">="; break; + case Operator.NotGreater : op = "!>"; break; + case Operator.Less : op = "<"; break; + case Operator.LessOrEqual : op = "<="; break; + case Operator.NotLess : op = "!<"; break; + default: throw new InvalidOperationException(); + } + + sb.Append(" ").Append(op).Append(" "); + + Expr2.ToString(sb, dic); + } + } + + // string_expression [ NOT ] LIKE string_expression [ ESCAPE 'escape_character' ] + // + public class Like : NotExpr + { + public Like(ISqlExpression exp1, bool isNot, ISqlExpression exp2, ISqlExpression escape) + : base(exp1, isNot, Sql.Precedence.Comparison) + { + Expr2 = exp2; + Escape = escape; + } + + public ISqlExpression Expr2 { get; internal set; } + public ISqlExpression Escape { get; internal set; } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { +#pragma warning disable 0618 + base.Walk(skipColumns, func); +#pragma warning restore 0618 + Expr2 = Expr2.Walk(skipColumns, func); + + if (Escape != null) + Escape = Escape.Walk(skipColumns, func); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new Like( + (ISqlExpression)Expr1.Clone(objectTree, doClone), IsNot, (ISqlExpression)Expr2.Clone(objectTree, doClone), Escape)); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.LikePredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + + if (IsNot) sb.Append(" NOT"); + sb.Append(" LIKE "); + + Expr2.ToString(sb, dic); + + if (Escape != null) + { + sb.Append(" ESCAPE "); + Escape.ToString(sb, dic); + } + } + } + + // expression [ NOT ] BETWEEN expression AND expression + // + public class Between : NotExpr + { + public Between(ISqlExpression exp1, bool isNot, ISqlExpression exp2, ISqlExpression exp3) + : base(exp1, isNot, Sql.Precedence.Comparison) + { + Expr2 = exp2; + Expr3 = exp3; + } + + public ISqlExpression Expr2 { get; internal set; } + public ISqlExpression Expr3 { get; internal set; } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { +#pragma warning disable 0618 + base.Walk(skipColumns, func); +#pragma warning restore 0618 + Expr2 = Expr2.Walk(skipColumns, func); + Expr3 = Expr3.Walk(skipColumns, func); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new Between( + (ISqlExpression)Expr1.Clone(objectTree, doClone), + IsNot, + (ISqlExpression)Expr2.Clone(objectTree, doClone), + (ISqlExpression)Expr3.Clone(objectTree, doClone))); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.BetweenPredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + + if (IsNot) sb.Append(" NOT"); + sb.Append(" BETWEEN "); + + Expr2.ToString(sb, dic); + sb.Append(" AND "); + Expr3.ToString(sb, dic); + } + } + + // expression IS [ NOT ] NULL + // + public class IsNull : NotExpr + { + public IsNull(ISqlExpression exp1, bool isNot) + : base(exp1, isNot, Sql.Precedence.Comparison) + { + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new IsNull((ISqlExpression)Expr1.Clone(objectTree, doClone), IsNot)); + + return clone; + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + sb + .Append(" IS ") + .Append(IsNot ? "NOT " : "") + .Append("NULL"); + } + + public override QueryElementType ElementType + { + get { return QueryElementType.IsNullPredicate; } + } + } + + // expression [ NOT ] IN ( subquery | expression [ ,...n ] ) + // + public class InSubQuery : NotExpr + { + public InSubQuery(ISqlExpression exp1, bool isNot, SqlQuery subQuery) + : base(exp1, isNot, Sql.Precedence.Comparison) + { + SubQuery = subQuery; + } + + public SqlQuery SubQuery { get; private set; } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { +#pragma warning disable 0618 + base.Walk(skipColumns, func); +#pragma warning restore 0618 + SubQuery = (SqlQuery)((ISqlExpression)SubQuery).Walk(skipColumns, func); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new InSubQuery( + (ISqlExpression)Expr1.Clone(objectTree, doClone), + IsNot, + (SqlQuery)SubQuery.Clone(objectTree, doClone))); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.InSubQueryPredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + + if (IsNot) sb.Append(" NOT"); + sb.Append(" IN ("); + + ((IQueryElement)SubQuery).ToString(sb, dic); + sb.Append(")"); + } + } + + public class InList : NotExpr + { + public InList(ISqlExpression exp1, bool isNot, params ISqlExpression[] values) + : base(exp1, isNot, Sql.Precedence.Comparison) + { + if (values != null && values.Length > 0) + _values.AddRange(values); + } + + public InList(ISqlExpression exp1, bool isNot, IEnumerable<ISqlExpression> values) + : base(exp1, isNot, Sql.Precedence.Comparison) + { + if (values != null) + _values.AddRange(values); + } + + readonly List<ISqlExpression> _values = new List<ISqlExpression>(); + public List<ISqlExpression> Values { get { return _values; } } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> action) + { +#pragma warning disable 0618 + base.Walk(skipColumns, action); +#pragma warning restore 0618 + for (var i = 0; i < _values.Count; i++) + _values[i] = _values[i].Walk(skipColumns, action); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + { + objectTree.Add(this, clone = new InList( + (ISqlExpression)Expr1.Clone(objectTree, doClone), + IsNot, + _values.ConvertAll(e => (ISqlExpression)e.Clone(objectTree, doClone)).ToArray())); + } + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.InListPredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + Expr1.ToString(sb, dic); + + if (IsNot) sb.Append(" NOT"); + sb.Append(" IN ("); + + foreach (var value in Values) + { + value.ToString(sb, dic); + sb.Append(','); + } + + if (Values.Count > 0) + sb.Length--; + + sb.Append(")"); + } + } + + // CONTAINS ( { column | * } , '< contains_search_condition >' ) + // FREETEXT ( { column | * } , 'freetext_string' ) + // expression { = | <> | != | > | >= | !> | < | <= | !< } { ALL | SOME | ANY } ( subquery ) + // EXISTS ( subquery ) + + public class FuncLike : Predicate + { + public FuncLike(SqlFunction func) + : base(func.Precedence) + { + Function = func; + } + + public SqlFunction Function { get; private set; } + + [Obsolete] + protected override void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + Function = (SqlFunction)((ISqlExpression)Function).Walk(skipColumns, func); + } + + public override bool CanBeNull() + { + return Function.CanBeNull(); + } + + protected override ICloneableElement Clone(Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new FuncLike((SqlFunction)Function.Clone(objectTree, doClone))); + + return clone; + } + + public override QueryElementType ElementType + { + get { return QueryElementType.FuncLikePredicate; } + } + + protected override void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic) + { + ((IQueryElement)Function).ToString(sb, dic); + } + } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + protected Predicate(int precedence) + { + Precedence = precedence; + } + + #region IPredicate Members + + public int Precedence { get; private set; } + + public abstract bool CanBeNull(); + protected abstract ICloneableElement Clone (Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone); + [Obsolete] + protected abstract void Walk (bool skipColumns, Func<ISqlExpression,ISqlExpression> action); + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + Walk(skipColumns, func); + return null; + } + + ICloneableElement ICloneableElement.Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + return Clone(objectTree, doClone); + } + + #endregion + + #region IQueryElement Members + + public abstract QueryElementType ElementType { get; } + + protected abstract void ToString(StringBuilder sb, Dictionary<IQueryElement, IQueryElement> dic); + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + ToString(sb, dic); + dic.Remove(this); + + return sb; + } + + #endregion + } + + #endregion + + #region Condition + + public class Condition : IQueryElement, ICloneableElement + { + public Condition(bool isNot, ISqlPredicate predicate) + { + IsNot = isNot; + Predicate = predicate; + } + + public Condition(bool isNot, ISqlPredicate predicate, bool isOr) + { + IsNot = isNot; + Predicate = predicate; + IsOr = isOr; + } + + public bool IsNot { get; set; } + public ISqlPredicate Predicate { get; set; } + public bool IsOr { get; set; } + + public int Precedence + { + get + { + return + IsNot ? Sql.Precedence.LogicalNegation : + IsOr ? Sql.Precedence.LogicalDisjunction : + Sql.Precedence.LogicalConjunction; + } + } + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new Condition(IsNot, (ISqlPredicate)Predicate.Clone(objectTree, doClone), IsOr)); + + return clone; + } + + public bool CanBeNull() + { + return Predicate.CanBeNull(); + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.Condition; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + sb.Append('('); + + if (IsNot) sb.Append("NOT "); + + Predicate.ToString(sb, dic); + sb.Append(')').Append(IsOr ? " OR " : " AND "); + + dic.Remove(this); + + return sb; + } + + #endregion + } + + #endregion + + #region SearchCondition + + public class SearchCondition : ConditionBase<SearchCondition, SearchCondition.Next>, ISqlPredicate, ISqlExpression + { + public SearchCondition() + { + } + + public SearchCondition(IEnumerable<Condition> list) + { + _conditions.AddRange(list); + } + + public SearchCondition(params Condition[] list) + { + _conditions.AddRange(list); + } + + public class Next + { + internal Next(SearchCondition parent) + { + _parent = parent; + } + + readonly SearchCondition _parent; + + public SearchCondition Or { get { return _parent.SetOr(true); } } + public SearchCondition And { get { return _parent.SetOr(false); } } + + public ISqlExpression ToExpr() { return _parent; } + } + + readonly List<Condition> _conditions = new List<Condition>(); + public List<Condition> Conditions + { + get { return _conditions; } + } + + protected override SearchCondition Search + { + get { return this; } + } + + protected override Next GetNext() + { + return new Next(this); + } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region IPredicate Members + + public int Precedence + { + get + { + if (_conditions.Count == 0) return Sql.Precedence.Unknown; + if (_conditions.Count == 1) return _conditions[0].Precedence; + + return _conditions.Select(_ => + _.IsNot ? Sql.Precedence.LogicalNegation : + _.IsOr ? Sql.Precedence.LogicalDisjunction : + Sql.Precedence.LogicalConjunction).Min(); + } + } + + public Type SystemType + { + get { return typeof(bool); } + } + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + foreach (var condition in Conditions) + condition.Predicate.Walk(skipColumns, func); + + return func(this); + } + + #endregion + + #region IEquatable<ISqlExpression> Members + + bool IEquatable<ISqlExpression>.Equals(ISqlExpression other) + { + return this == other; + } + + #endregion + + #region ISqlExpression Members + + public bool CanBeNull() + { + foreach (var c in Conditions) + if (c.CanBeNull()) + return true; + + return false; + } + + public bool Equals(ISqlExpression other, Func<ISqlExpression,ISqlExpression,bool> comparer) + { + return this == other; + } + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + { + var sc = new SearchCondition(); + + objectTree.Add(this, clone = sc); + + sc._conditions.AddRange(_conditions.ConvertAll(c => (Condition)c.Clone(objectTree, doClone))); + } + + return clone; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.SearchCondition; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + foreach (IQueryElement c in Conditions) + c.ToString(sb, dic); + + if (Conditions.Count > 0) + sb.Length -= 4; + + dic.Remove(this); + + return sb; + } + + #endregion + } + + #endregion + + #region ConditionBase + + interface IConditionExpr<T> + { + T Expr (ISqlExpression expr); + T Field (SqlField field); + T SubQuery(SqlQuery sqlQuery); + T Value (object value); + } + + public abstract class ConditionBase<T1,T2> : IConditionExpr<ConditionBase<T1,T2>.Expr_> + where T1 : ConditionBase<T1,T2> + { + public class Expr_ + { + internal Expr_(ConditionBase<T1,T2> condition, bool isNot, ISqlExpression expr) + { + _condition = condition; + _isNot = isNot; + _expr = expr; + } + + readonly ConditionBase<T1,T2> _condition; + readonly bool _isNot; + readonly ISqlExpression _expr; + + T2 Add(ISqlPredicate predicate) + { + _condition.Search.Conditions.Add(new Condition(_isNot, predicate)); + return _condition.GetNext(); + } + + #region Predicate.ExprExpr + + public class Op_ : IConditionExpr<T2> + { + internal Op_(Expr_ expr, Predicate.Operator op) + { + _expr = expr; + _op = op; + } + + readonly Expr_ _expr; + readonly Predicate.Operator _op; + + public T2 Expr (ISqlExpression expr) { return _expr.Add(new Predicate.ExprExpr(_expr._expr, _op, expr)); } + public T2 Field (SqlField field) { return Expr(field); } + public T2 SubQuery(SqlQuery subQuery) { return Expr(subQuery); } + public T2 Value (object value) { return Expr(new SqlValue(value)); } + + public T2 All (SqlQuery subQuery) { return Expr(SqlFunction.CreateAll (subQuery)); } + public T2 Some (SqlQuery subQuery) { return Expr(SqlFunction.CreateSome(subQuery)); } + public T2 Any (SqlQuery subQuery) { return Expr(SqlFunction.CreateAny (subQuery)); } + } + + public Op_ Equal { get { return new Op_(this, Predicate.Operator.Equal); } } + public Op_ NotEqual { get { return new Op_(this, Predicate.Operator.NotEqual); } } + public Op_ Greater { get { return new Op_(this, Predicate.Operator.Greater); } } + public Op_ GreaterOrEqual { get { return new Op_(this, Predicate.Operator.GreaterOrEqual); } } + public Op_ NotGreater { get { return new Op_(this, Predicate.Operator.NotGreater); } } + public Op_ Less { get { return new Op_(this, Predicate.Operator.Less); } } + public Op_ LessOrEqual { get { return new Op_(this, Predicate.Operator.LessOrEqual); } } + public Op_ NotLess { get { return new Op_(this, Predicate.Operator.NotLess); } } + + #endregion + + #region Predicate.Like + + public T2 Like(ISqlExpression expression, SqlValue escape) { return Add(new Predicate.Like(_expr, false, expression, escape)); } + public T2 Like(ISqlExpression expression) { return Like(expression, null); } + public T2 Like(string expression, SqlValue escape) { return Like(new SqlValue(expression), escape); } + public T2 Like(string expression) { return Like(new SqlValue(expression), null); } + + #endregion + + #region Predicate.Between + + public T2 Between (ISqlExpression expr1, ISqlExpression expr2) { return Add(new Predicate.Between(_expr, false, expr1, expr2)); } + public T2 NotBetween(ISqlExpression expr1, ISqlExpression expr2) { return Add(new Predicate.Between(_expr, true, expr1, expr2)); } + + #endregion + + #region Predicate.IsNull + + public T2 IsNull { get { return Add(new Predicate.IsNull(_expr, false)); } } + public T2 IsNotNull { get { return Add(new Predicate.IsNull(_expr, true)); } } + + #endregion + + #region Predicate.In + + public T2 In (SqlQuery subQuery) { return Add(new Predicate.InSubQuery(_expr, false, subQuery)); } + public T2 NotIn(SqlQuery subQuery) { return Add(new Predicate.InSubQuery(_expr, true, subQuery)); } + + Predicate.InList CreateInList(bool isNot, object[] exprs) + { + var list = new Predicate.InList(_expr, isNot, null); + + if (exprs != null && exprs.Length > 0) + { + foreach (var item in exprs) + { + if (item == null || item is SqlValue && ((SqlValue)item).Value == null) + continue; + + if (item is ISqlExpression) + list.Values.Add((ISqlExpression)item); + else + list.Values.Add(new SqlValue(item)); + } + } + + return list; + } + + public T2 In (params object[] exprs) { return Add(CreateInList(false, exprs)); } + public T2 NotIn(params object[] exprs) { return Add(CreateInList(true, exprs)); } + + #endregion + } + + public class Not_ : IConditionExpr<Expr_> + { + internal Not_(ConditionBase<T1,T2> condition) + { + _condition = condition; + } + + readonly ConditionBase<T1,T2> _condition; + + public Expr_ Expr (ISqlExpression expr) { return new Expr_(_condition, true, expr); } + public Expr_ Field (SqlField field) { return Expr(field); } + public Expr_ SubQuery(SqlQuery subQuery) { return Expr(subQuery); } + public Expr_ Value (object value) { return Expr(new SqlValue(value)); } + + public T2 Exists(SqlQuery subQuery) + { + _condition.Search.Conditions.Add(new Condition(true, new Predicate.FuncLike(SqlFunction.CreateExists(subQuery)))); + return _condition.GetNext(); + } + } + + protected abstract SearchCondition Search { get; } + protected abstract T2 GetNext(); + + protected T1 SetOr(bool value) + { + Search.Conditions[Search.Conditions.Count - 1].IsOr = value; + return (T1)this; + } + + public Not_ Not { get { return new Not_(this); } } + + public Expr_ Expr (ISqlExpression expr) { return new Expr_(this, false, expr); } + public Expr_ Field (SqlField field) { return Expr(field); } + public Expr_ SubQuery(SqlQuery subQuery) { return Expr(subQuery); } + public Expr_ Value (object value) { return Expr(new SqlValue(value)); } + + public T2 Exists(SqlQuery subQuery) + { + Search.Conditions.Add(new Condition(false, new Predicate.FuncLike(SqlFunction.CreateExists(subQuery)))); + return GetNext(); + } + } + + #endregion + + #region OrderByItem + + public class OrderByItem : IQueryElement, ICloneableElement + { + public OrderByItem(ISqlExpression expression, bool isDescending) + { + Expression = expression; + IsDescending = isDescending; + } + + public ISqlExpression Expression { get; internal set; } + public bool IsDescending { get; private set; } + + [Obsolete] + internal void Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + Expression = Expression.Walk(skipColumns, func); + } + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + objectTree.Add(this, clone = new OrderByItem((ISqlExpression)Expression.Clone(objectTree, doClone), IsDescending)); + + return clone; + } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType + { + get { return QueryElementType.OrderByItem; } + } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + Expression.ToString(sb, dic); + + if (IsDescending) + sb.Append(" DESC"); + + return sb; + } + + #endregion + } + + #endregion + + #region ClauseBase + + public abstract class ClauseBase + { + protected ClauseBase(SqlQuery sqlQuery) + { + SqlQuery = sqlQuery; + } + + public SelectClause Select { get { return SqlQuery.Select; } } + public FromClause From { get { return SqlQuery.From; } } + public WhereClause Where { get { return SqlQuery.Where; } } + public GroupByClause GroupBy { get { return SqlQuery.GroupBy; } } + public WhereClause Having { get { return SqlQuery.Having; } } + public OrderByClause OrderBy { get { return SqlQuery.OrderBy; } } + public SqlQuery End() { return SqlQuery; } + + protected internal SqlQuery SqlQuery { get; private set; } + + internal void SetSqlQuery(SqlQuery sqlQuery) + { + SqlQuery = sqlQuery; + } + } + + public abstract class ClauseBase<T1, T2> : ConditionBase<T1, T2> + where T1 : ClauseBase<T1, T2> + { + protected ClauseBase(SqlQuery sqlQuery) + { + SqlQuery = sqlQuery; + } + + public SelectClause Select { get { return SqlQuery.Select; } } + public FromClause From { get { return SqlQuery.From; } } + public GroupByClause GroupBy { get { return SqlQuery.GroupBy; } } + public WhereClause Having { get { return SqlQuery.Having; } } + public OrderByClause OrderBy { get { return SqlQuery.OrderBy; } } + public SqlQuery End() { return SqlQuery; } + + protected internal SqlQuery SqlQuery { get; private set; } + + internal void SetSqlQuery(SqlQuery sqlQuery) + { + SqlQuery = sqlQuery; + } + } + + #endregion + + #region SelectClause + + public class SelectClause : ClauseBase, IQueryElement, ISqlExpressionWalkable + { + #region Init + + internal SelectClause(SqlQuery sqlQuery) : base(sqlQuery) + { + } + + internal SelectClause( + SqlQuery sqlQuery, + SelectClause clone, + Dictionary<ICloneableElement,ICloneableElement> objectTree, + Predicate<ICloneableElement> doClone) + : base(sqlQuery) + { + _columns.AddRange(clone._columns.ConvertAll(c => (Column)c.Clone(objectTree, doClone))); + + IsDistinct = clone.IsDistinct; + TakeValue = clone.TakeValue == null ? null : (ISqlExpression)clone.TakeValue.Clone(objectTree, doClone); + SkipValue = clone.SkipValue == null ? null : (ISqlExpression)clone.SkipValue.Clone(objectTree, doClone); + } + + internal SelectClause(bool isDistinct, ISqlExpression takeValue, ISqlExpression skipValue, IEnumerable<Column> columns) + : base(null) + { + IsDistinct = isDistinct; + TakeValue = takeValue; + SkipValue = skipValue; + + _columns.AddRange(columns); + } + + #endregion + + #region Columns + + public SelectClause Field(SqlField field) + { + AddOrGetColumn(new Column(SqlQuery, field)); + return this; + } + + public SelectClause Field(SqlField field, string alias) + { + AddOrGetColumn(new Column(SqlQuery, field, alias)); + return this; + } + + public SelectClause SubQuery(SqlQuery subQuery) + { + if (subQuery.ParentSql != null && subQuery.ParentSql != SqlQuery) + throw new ArgumentException("SqlQuery already used as subquery"); + + subQuery.ParentSql = SqlQuery; + + AddOrGetColumn(new Column(SqlQuery, subQuery)); + return this; + } + + public SelectClause SubQuery(SqlQuery sqlQuery, string alias) + { + if (sqlQuery.ParentSql != null && sqlQuery.ParentSql != SqlQuery) + throw new ArgumentException("SqlQuery already used as subquery"); + + sqlQuery.ParentSql = SqlQuery; + + AddOrGetColumn(new Column(SqlQuery, sqlQuery, alias)); + return this; + } + + public SelectClause Expr(ISqlExpression expr) + { + AddOrGetColumn(new Column(SqlQuery, expr)); + return this; + } + + public SelectClause Expr(ISqlExpression expr, string alias) + { + AddOrGetColumn(new Column(SqlQuery, expr, alias)); + return this; + } + + public SelectClause Expr(string expr, params ISqlExpression[] values) + { + AddOrGetColumn(new Column(SqlQuery, new SqlExpression(null, expr, values))); + return this; + } + + public SelectClause Expr(Type systemType, string expr, params ISqlExpression[] values) + { + AddOrGetColumn(new Column(SqlQuery, new SqlExpression(systemType, expr, values))); + return this; + } + + public SelectClause Expr(string expr, int priority, params ISqlExpression[] values) + { + AddOrGetColumn(new Column(SqlQuery, new SqlExpression(null, expr, priority, values))); + return this; + } + + public SelectClause Expr(Type systemType, string expr, int priority, params ISqlExpression[] values) + { + AddOrGetColumn(new Column(SqlQuery, new SqlExpression(systemType, expr, priority, values))); + return this; + } + + public SelectClause Expr(string alias, string expr, int priority, params ISqlExpression[] values) + { + AddOrGetColumn(new Column(SqlQuery, new SqlExpression(null, expr, priority, values))); + return this; + } + + public SelectClause Expr(Type systemType, string alias, string expr, int priority, params ISqlExpression[] values) + { + AddOrGetColumn(new Column(SqlQuery, new SqlExpression(systemType, expr, priority, values))); + return this; + } + + public SelectClause Expr<T>(ISqlExpression expr1, string operation, ISqlExpression expr2) + { + AddOrGetColumn(new Column(SqlQuery, new SqlBinaryExpression(typeof(T), expr1, operation, expr2))); + return this; + } + + public SelectClause Expr<T>(ISqlExpression expr1, string operation, ISqlExpression expr2, int priority) + { + AddOrGetColumn(new Column(SqlQuery, new SqlBinaryExpression(typeof(T), expr1, operation, expr2, priority))); + return this; + } + + public SelectClause Expr<T>(string alias, ISqlExpression expr1, string operation, ISqlExpression expr2, int priority) + { + AddOrGetColumn(new Column(SqlQuery, new SqlBinaryExpression(typeof(T), expr1, operation, expr2, priority), alias)); + return this; + } + + public int Add(ISqlExpression expr) + { + if (expr is Column && ((Column)expr).Parent == SqlQuery) + throw new InvalidOperationException(); + + return Columns.IndexOf(AddOrGetColumn(new Column(SqlQuery, expr))); + } + + public int Add(ISqlExpression expr, string alias) + { + return Columns.IndexOf(AddOrGetColumn(new Column(SqlQuery, expr, alias))); + } + + Column AddOrGetColumn(Column col) + { + foreach (var c in Columns) + if (c.Equals(col)) + return col; + +#if DEBUG + + switch (col.Expression.ElementType) + { + case QueryElementType.SqlField : + { + var table = ((SqlField)col.Expression).Table; + + //if (SqlQuery.From.GetFromTables().Any(_ => _ == table)) + // throw new InvalidOperationException("Wrong field usage."); + + break; + } + + case QueryElementType.Column : + { + var query = ((Column)col.Expression).Parent; + + //if (!SqlQuery.From.GetFromQueries().Any(_ => _ == query)) + // throw new InvalidOperationException("Wrong column usage."); + + break; + } + + case QueryElementType.SqlQuery : + { + if (col.Expression == SqlQuery) + throw new InvalidOperationException("Wrong query usage."); + break; + } + } + +#endif + + Columns.Add(col); + + return col; + } + + readonly List<Column> _columns = new List<Column>(); + public List<Column> Columns + { + get { return _columns; } + } + + #endregion + + #region HasModifier + + public bool HasModifier + { + get { return IsDistinct || SkipValue != null || TakeValue != null; } + } + + #endregion + + #region Distinct + + public SelectClause Distinct + { + get { IsDistinct = true; return this; } + } + + public bool IsDistinct { get; set; } + + #endregion + + #region Take + + public SelectClause Take(int value) + { + TakeValue = new SqlValue(value); + return this; + } + + public SelectClause Take(ISqlExpression value) + { + TakeValue = value; + return this; + } + + public ISqlExpression TakeValue { get; set; } + + #endregion + + #region Skip + + public SelectClause Skip(int value) + { + SkipValue = new SqlValue(value); + return this; + } + + public SelectClause Skip(ISqlExpression value) + { + SkipValue = value; + return this; + } + + public ISqlExpression SkipValue { get; set; } + + #endregion + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + for (var i = 0; i < Columns.Count; i++) + { + var col = Columns[i]; +#pragma warning disable 0618 + var expr = col.Walk(skipColumns, func); +#pragma warning restore 0618 + + if (expr is Column) + Columns[i] = (Column)expr; + else + Columns[i] = new Column(col.Parent, expr, col.Alias); + } + + if (TakeValue != null) TakeValue = TakeValue.Walk(skipColumns, func); + if (SkipValue != null) SkipValue = SkipValue.Walk(skipColumns, func); + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.SelectClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + sb.Append("SELECT "); + + if (IsDistinct) sb.Append("DISTINCT "); + + if (SkipValue != null) + { + sb.Append("SKIP "); + SkipValue.ToString(sb, dic); + sb.Append(" "); + } + + if (TakeValue != null) + { + sb.Append("TAKE "); + TakeValue.ToString(sb, dic); + sb.Append(" "); + } + + sb.AppendLine(); + + if (Columns.Count == 0) + sb.Append("\t*, \n"); + else + foreach (var c in Columns) + { + sb.Append("\t"); + ((IQueryElement)c).ToString(sb, dic); + sb + .Append(" as ") + .Append(c.Alias ?? "c" + (Columns.IndexOf(c) + 1)) + .Append(", \n"); + } + + sb.Length -= 3; + + dic.Remove(this); + + return sb; + } + + #endregion + } + + private SelectClause _select; + public SelectClause Select + { + get { return _select; } + } + + #endregion + + #region InsertClause + + public class SetExpression : IQueryElement, ISqlExpressionWalkable, ICloneableElement + { + public SetExpression(ISqlExpression column, ISqlExpression expression) + { + Column = column; + Expression = expression; + + if (expression is SqlParameter) + { + var p = (SqlParameter)expression; + + //if (type.IsEnum) + // p.SetEnumConverter(type, mappingSchema); + + if (column is SqlField) + { + var field = (SqlField)column; + + if (field.MemberMapper != null) + { + if (field.MemberMapper.MapMemberInfo.IsDbTypeSet) + p.DbType = field.MemberMapper.MapMemberInfo.DbType; + + if (field.MemberMapper.MapMemberInfo.IsDbSizeSet) + p.DbSize = field.MemberMapper.MapMemberInfo.DbSize; + } + } + } + } + + public ISqlExpression Column { get; set; } + public ISqlExpression Expression { get; set; } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + { + objectTree.Add(this, clone = new SetExpression( + (ISqlExpression)Column. Clone(objectTree, doClone), + (ISqlExpression)Expression.Clone(objectTree, doClone))); + } + + return clone; + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + Column = Column. Walk(skipColumns, func); + Expression = Expression.Walk(skipColumns, func); + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.SetExpression; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + Column.ToString(sb, dic); + sb.Append(" = "); + Expression.ToString(sb, dic); + + return sb; + } + + #endregion + } + + public class InsertClause : IQueryElement, ISqlExpressionWalkable, ICloneableElement + { + public InsertClause() + { + Items = new List<SetExpression>(); + } + + public List<SetExpression> Items { get; private set; } + public SqlTable Into { get; set; } + public bool WithIdentity { get; set; } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + var clone = new InsertClause { WithIdentity = WithIdentity }; + + if (Into != null) + clone.Into = (SqlTable)Into.Clone(objectTree, doClone); + + foreach (var item in Items) + clone.Items.Add((SetExpression)item.Clone(objectTree, doClone)); + + objectTree.Add(this, clone); + + return clone; + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + if (Into != null) + ((ISqlExpressionWalkable)Into).Walk(skipColumns, func); + + foreach (var t in Items) + ((ISqlExpressionWalkable)t).Walk(skipColumns, func); + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.InsertClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + sb.Append("VALUES "); + + if (Into != null) + ((IQueryElement)Into).ToString(sb, dic); + + sb.AppendLine(); + + foreach (var e in Items) + { + sb.Append("\t"); + ((IQueryElement)e).ToString(sb, dic); + sb.AppendLine(); + } + + return sb; + } + + #endregion + } + + private InsertClause _insert; + public InsertClause Insert + { + get { return _insert ?? (_insert = new InsertClause()); } + } + + public void ClearInsert() + { + _insert = null; + } + + #endregion + + #region UpdateClause + + public class UpdateClause : IQueryElement, ISqlExpressionWalkable, ICloneableElement + { + public UpdateClause() + { + Items = new List<SetExpression>(); + Keys = new List<SetExpression>(); + } + + public List<SetExpression> Items { get; private set; } + public List<SetExpression> Keys { get; private set; } + public SqlTable Table { get; set; } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + var clone = new UpdateClause(); + + if (Table != null) + clone.Table = (SqlTable)Table.Clone(objectTree, doClone); + + foreach (var item in Items) + clone.Items.Add((SetExpression)item.Clone(objectTree, doClone)); + + foreach (var item in Keys) + clone.Keys.Add((SetExpression)item.Clone(objectTree, doClone)); + + objectTree.Add(this, clone); + + return clone; + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + if (Table != null) + ((ISqlExpressionWalkable)Table).Walk(skipColumns, func); + + foreach (var t in Items) + ((ISqlExpressionWalkable)t).Walk(skipColumns, func); + + foreach (var t in Keys) + ((ISqlExpressionWalkable)t).Walk(skipColumns, func); + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.UpdateClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + sb.Append("SET "); + + if (Table != null) + ((IQueryElement)Table).ToString(sb, dic); + + sb.AppendLine(); + + foreach (var e in Items) + { + sb.Append("\t"); + ((IQueryElement)e).ToString(sb, dic); + sb.AppendLine(); + } + + return sb; + } + + #endregion + } + + private UpdateClause _update; + public UpdateClause Update + { + get { return _update ?? (_update = new UpdateClause()); } + } + + public void ClearUpdate() + { + _update = null; + } + + #endregion + + #region DeleteClause + + public class DeleteClause : IQueryElement, ISqlExpressionWalkable, ICloneableElement + { + public SqlTable Table { get; set; } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + var clone = new DeleteClause(); + + if (Table != null) + clone.Table = (SqlTable)Table.Clone(objectTree, doClone); + + objectTree.Add(this, clone); + + return clone; + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + if (Table != null) + ((ISqlExpressionWalkable)Table).Walk(skipColumns, func); + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.DeleteClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + sb.Append("DELETE FROM "); + + if (Table != null) + ((IQueryElement)Table).ToString(sb, dic); + + sb.AppendLine(); + + return sb; + } + + #endregion + } + + private DeleteClause _delete; + public DeleteClause Delete + { + get { return _delete ?? (_delete = new DeleteClause()); } + } + + public void ClearDelete() + { + _delete = null; + } + + #endregion + + #region FromClause + + public class FromClause : ClauseBase, IQueryElement, ISqlExpressionWalkable + { + #region Join + + public class Join : ConditionBase<Join,Join.Next> + { + public class Next + { + internal Next(Join parent) + { + _parent = parent; + } + + readonly Join _parent; + + public Join Or { get { return _parent.SetOr(true); } } + public Join And { get { return _parent.SetOr(false); } } + + public static implicit operator Join(Next next) + { + return next._parent; + } + } + + protected override SearchCondition Search + { + get { return JoinedTable.Condition; } + } + + protected override Next GetNext() + { + return new Next(this); + } + + internal Join(JoinType joinType, ISqlTableSource table, string alias, bool isWeak, ICollection<Join> joins) + { + JoinedTable = new JoinedTable(joinType, table, alias, isWeak); + + if (joins != null && joins.Count > 0) + foreach (var join in joins) + JoinedTable.Table.Joins.Add(join.JoinedTable); + } + + public JoinedTable JoinedTable { get; private set; } + } + + #endregion + + internal FromClause(SqlQuery sqlQuery) : base(sqlQuery) + { + } + + internal FromClause( + SqlQuery sqlQuery, + FromClause clone, + Dictionary<ICloneableElement,ICloneableElement> objectTree, + Predicate<ICloneableElement> doClone) + : base(sqlQuery) + { + _tables.AddRange(clone._tables.ConvertAll(ts => (TableSource)ts.Clone(objectTree, doClone))); + } + + internal FromClause(IEnumerable<TableSource> tables) + : base(null) + { + _tables.AddRange(tables); + } + + public FromClause Table(ISqlTableSource table, params FJoin[] joins) + { + return Table(table, null, joins); + } + + public FromClause Table(ISqlTableSource table, string alias, params FJoin[] joins) + { + var ts = AddOrGetTable(table, alias); + + if (joins != null && joins.Length > 0) + foreach (var join in joins) + ts.Joins.Add(join.JoinedTable); + + return this; + } + + TableSource GetTable(ISqlTableSource table, string alias) + { + foreach (var ts in Tables) + if (ts.Source == table) + if (alias == null || ts.Alias == alias) + return ts; + else + throw new ArgumentException("alias"); + + return null; + } + + TableSource AddOrGetTable(ISqlTableSource table, string alias) + { + var ts = GetTable(table, alias); + + if (ts != null) + return ts; + + var t = new TableSource(table, alias); + + Tables.Add(t); + + return t; + } + + public TableSource this[ISqlTableSource table] + { + get { return this[table, null]; } + } + + public TableSource this[ISqlTableSource table, string alias] + { + get + { + foreach (var ts in Tables) + { + var t = CheckTableSource(ts, table, alias); + + if (t != null) + return t; + } + + return null; + } + } + + public bool IsChild(ISqlTableSource table) + { + foreach (var ts in Tables) + if (ts.Source == table || CheckChild(ts.Joins, table)) + return true; + return false; + } + + static bool CheckChild(IEnumerable<JoinedTable> joins, ISqlTableSource table) + { + foreach (var j in joins) + if (j.Table.Source == table || CheckChild(j.Table.Joins, table)) + return true; + return false; + } + + readonly List<TableSource> _tables = new List<TableSource>(); + public List<TableSource> Tables + { + get { return _tables; } + } + + static IEnumerable<ISqlTableSource> GetJoinTables(TableSource source, QueryElementType elementType) + { + if (source.Source.ElementType == elementType) + yield return source.Source; + + foreach (var join in source.Joins) + foreach (var table in GetJoinTables(join.Table, elementType)) + yield return table; + } + + internal IEnumerable<ISqlTableSource> GetFromTables() + { + return Tables.SelectMany(_ => GetJoinTables(_, QueryElementType.SqlTable)); + } + + internal IEnumerable<ISqlTableSource> GetFromQueries() + { + return Tables.SelectMany(_ => GetJoinTables(_, QueryElementType.SqlQuery)); + } + + static TableSource FindTableSource(TableSource source, SqlTable table) + { + if (source.Source == table) + return source; + + foreach (var join in source.Joins) + { + var ts = FindTableSource(join.Table, table); + if (ts != null) + return ts; + } + + return null; + } + + public ISqlTableSource FindTableSource(SqlTable table) + { + foreach (var source in Tables) + { + var ts = FindTableSource(source, table); + if (ts != null) + return ts; + } + + return null; + } + + #region Overrides + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + for (var i = 0; i < Tables.Count; i++) + ((ISqlExpressionWalkable)Tables[i]).Walk(skipColumns, func); + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.FromClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + sb.Append(" \nFROM \n"); + + if (Tables.Count > 0) + { + foreach (IQueryElement ts in Tables) + { + sb.Append('\t'); + var len = sb.Length; + ts.ToString(sb, dic).Replace("\n", "\n\t", len, sb.Length - len); + sb.Append(", "); + } + + sb.Length -= 2; + } + + return sb; + } + + #endregion + } + + public static FJoin InnerJoin (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.Inner, table, null, false, joins); } + public static FJoin InnerJoin (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.Inner, table, alias, false, joins); } + public static FJoin LeftJoin (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.Left, table, null, false, joins); } + public static FJoin LeftJoin (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.Left, table, alias, false, joins); } + public static FJoin Join (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.Auto, table, null, false, joins); } + public static FJoin Join (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.Auto, table, alias, false, joins); } + public static FJoin CrossApply (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.CrossApply, table, null, false, joins); } + public static FJoin CrossApply (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.CrossApply, table, alias, false, joins); } + public static FJoin OuterApply (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.OuterApply, table, null, false, joins); } + public static FJoin OuterApply (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.OuterApply, table, alias, false, joins); } + + public static FJoin WeakInnerJoin(ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.Inner, table, null, true, joins); } + public static FJoin WeakInnerJoin(ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.Inner, table, alias, true, joins); } + public static FJoin WeakLeftJoin (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.Left, table, null, true, joins); } + public static FJoin WeakLeftJoin (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.Left, table, alias, true, joins); } + public static FJoin WeakJoin (ISqlTableSource table, params FJoin[] joins) { return new FJoin(JoinType.Auto, table, null, true, joins); } + public static FJoin WeakJoin (ISqlTableSource table, string alias, params FJoin[] joins) { return new FJoin(JoinType.Auto, table, alias, true, joins); } + + private FromClause _from; + public FromClause From + { + get { return _from; } + } + + #endregion + + #region WhereClause + + public class WhereClause : ClauseBase<WhereClause,WhereClause.Next>, IQueryElement, ISqlExpressionWalkable + { + public class Next : ClauseBase + { + internal Next(WhereClause parent) : base(parent.SqlQuery) + { + _parent = parent; + } + + readonly WhereClause _parent; + + public WhereClause Or { get { return _parent.SetOr(true); } } + public WhereClause And { get { return _parent.SetOr(false); } } + } + + internal WhereClause(SqlQuery sqlQuery) : base(sqlQuery) + { + SearchCondition = new SearchCondition(); + } + + internal WhereClause( + SqlQuery sqlQuery, + WhereClause clone, + Dictionary<ICloneableElement,ICloneableElement> objectTree, + Predicate<ICloneableElement> doClone) + : base(sqlQuery) + { + SearchCondition = (SearchCondition)clone.SearchCondition.Clone(objectTree, doClone); + } + + internal WhereClause(SearchCondition searchCondition) : base(null) + { + SearchCondition = searchCondition; + } + + public SearchCondition SearchCondition { get; private set; } + + public bool IsEmpty + { + get { return SearchCondition.Conditions.Count == 0; } + } + + protected override SearchCondition Search + { + get { return SearchCondition; } + } + + protected override Next GetNext() + { + return new Next(this); + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> action) + { + SearchCondition = (SearchCondition)((ISqlExpressionWalkable)SearchCondition).Walk(skipColumns, action); + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType + { + get { return QueryElementType.WhereClause; } + } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (Search.Conditions.Count == 0) + return sb; + + sb.Append("\nWHERE\n\t"); + return ((IQueryElement)Search).ToString(sb, dic); + } + + #endregion + } + + private WhereClause _where; + public WhereClause Where + { + get { return _where; } + } + + #endregion + + #region GroupByClause + + public class GroupByClause : ClauseBase, IQueryElement, ISqlExpressionWalkable + { + internal GroupByClause(SqlQuery sqlQuery) : base(sqlQuery) + { + } + + internal GroupByClause( + SqlQuery sqlQuery, + GroupByClause clone, + Dictionary<ICloneableElement,ICloneableElement> objectTree, + Predicate<ICloneableElement> doClone) + : base(sqlQuery) + { + _items.AddRange(clone._items.ConvertAll(e => (ISqlExpression)e.Clone(objectTree, doClone))); + } + + internal GroupByClause(IEnumerable<ISqlExpression> items) : base(null) + { + _items.AddRange(items); + } + + public GroupByClause Expr(ISqlExpression expr) + { + Add(expr); + return this; + } + + public GroupByClause Field(SqlField field) + { + return Expr(field); + } + + void Add(ISqlExpression expr) + { + foreach (var e in Items) + if (e.Equals(expr)) + return; + + Items.Add(expr); + } + + readonly List<ISqlExpression> _items = new List<ISqlExpression>(); + public List<ISqlExpression> Items + { + get { return _items; } + } + + public bool IsEmpty + { + get { return Items.Count == 0; } + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + for (var i = 0; i < Items.Count; i++) + Items[i] = Items[i].Walk(skipColumns, func); + + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.GroupByClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (Items.Count == 0) + return sb; + + sb.Append(" \nGROUP BY \n"); + + foreach (var item in Items) + { + sb.Append('\t'); + item.ToString(sb, dic); + sb.Append(","); + } + + sb.Length--; + + return sb; + } + + #endregion + } + + private GroupByClause _groupBy; + public GroupByClause GroupBy + { + get { return _groupBy; } + } + + #endregion + + #region HavingClause + + private WhereClause _having; + public WhereClause Having + { + get { return _having; } + } + + #endregion + + #region OrderByClause + + public class OrderByClause : ClauseBase, IQueryElement, ISqlExpressionWalkable + { + internal OrderByClause(SqlQuery sqlQuery) : base(sqlQuery) + { + } + + internal OrderByClause( + SqlQuery sqlQuery, + OrderByClause clone, + Dictionary<ICloneableElement,ICloneableElement> objectTree, + Predicate<ICloneableElement> doClone) + : base(sqlQuery) + { + _items.AddRange(clone._items.ConvertAll(item => (OrderByItem)item.Clone(objectTree, doClone))); + } + + internal OrderByClause(IEnumerable<OrderByItem> items) : base(null) + { + _items.AddRange(items); + } + + public OrderByClause Expr(ISqlExpression expr, bool isDescending) + { + Add(expr, isDescending); + return this; + } + + public OrderByClause Expr (ISqlExpression expr) { return Expr(expr, false); } + public OrderByClause ExprAsc (ISqlExpression expr) { return Expr(expr, false); } + public OrderByClause ExprDesc (ISqlExpression expr) { return Expr(expr, true); } + public OrderByClause Field (SqlField field, bool isDescending) { return Expr(field, isDescending); } + public OrderByClause Field (SqlField field) { return Expr(field, false); } + public OrderByClause FieldAsc (SqlField field) { return Expr(field, false); } + public OrderByClause FieldDesc(SqlField field) { return Expr(field, true); } + + void Add(ISqlExpression expr, bool isDescending) + { + foreach (var item in Items) + if (item.Expression.Equals(expr, (x, y) => + { + var col = x as Column; + return col == null || !col.Parent.HasUnion || x == y; + })) + return; + + Items.Add(new OrderByItem(expr, isDescending)); + } + + readonly List<OrderByItem> _items = new List<OrderByItem>(); + public List<OrderByItem> Items + { + get { return _items; } + } + + public bool IsEmpty + { + get { return Items.Count == 0; } + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { +#pragma warning disable 0618 + foreach (var t in Items) + t.Walk(skipColumns, func); +#pragma warning restore 0618 + return null; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.OrderByClause; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (Items.Count == 0) + return sb; + + sb.Append(" \nORDER BY \n"); + + foreach (IQueryElement item in Items) + { + sb.Append('\t'); + item.ToString(sb, dic); + sb.Append(", "); + } + + sb.Length -= 2; + + return sb; + } + + #endregion + } + + private OrderByClause _orderBy; + public OrderByClause OrderBy + { + get { return _orderBy; } + } + + #endregion + + #region Union + + public class Union : IQueryElement + { + public Union() + { + } + + public Union(SqlQuery sqlQuery, bool isAll) + { + SqlQuery = sqlQuery; + IsAll = isAll; + } + + public SqlQuery SqlQuery { get; private set; } + public bool IsAll { get; private set; } + + public QueryElementType ElementType + { + get { return QueryElementType.Union; } + } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + sb.Append(" \nUNION").Append(IsAll ? " ALL" : "").Append(" \n"); + return ((IQueryElement)SqlQuery).ToString(sb, dic); + } + } + + private List<Union> _unions; + public List<Union> Unions + { + get { return _unions ?? (_unions = new List<Union>()); } + } + + public bool HasUnion { get { return _unions != null && _unions.Count > 0; } } + + public void AddUnion(SqlQuery union, bool isAll) + { + Unions.Add(new Union(union, isAll)); + } + + #endregion + + #region FinalizeAndValidate + + public void FinalizeAndValidate(bool isApplySupported, bool optimizeColumns) + { +#if DEBUG + var sqlText = SqlText; + + var dic = new Dictionary<SqlQuery,SqlQuery>(); + + new QueryVisitor().VisitAll(this, e => + { + var sql = e as SqlQuery; + + if (sql != null) + { + if (dic.ContainsKey(sql)) + throw new InvalidOperationException("SqlQuery circle reference detected."); + + dic.Add(sql, sql); + } + }); +#endif + + OptimizeUnions(); + FinalizeAndValidateInternal(isApplySupported, optimizeColumns, true, new List<ISqlTableSource>()); + ResolveFields(); + SetAliases(); + +#if DEBUG + sqlText = SqlText; +#endif + } + + class QueryData + { + public SqlQuery Query; + public List<ISqlExpression> Fields = new List<ISqlExpression>(); + public List<QueryData> Queries = new List<QueryData>(); + } + + void ResolveFields() + { + var root = GetQueryData(); + + ResolveFields(root); + } + + QueryData GetQueryData() + { + var data = new QueryData { Query = this }; + + new QueryVisitor().VisitParentFirst(this, e => + { + switch (e.ElementType) + { + case QueryElementType.SqlField : + { + var field = (SqlField)e; + + if (field.Name.Length != 1 || field.Name[0] != '*') + data.Fields.Add(field); + + break; + } + + case QueryElementType.SqlQuery : + { + if (e != this) + { + data.Queries.Add(((SqlQuery)e).GetQueryData()); + return false; + } + + break; + } + + case QueryElementType.Column : + return ((Column)e).Parent == this; + + case QueryElementType.SqlTable : + return false; + } + + return true; + }); + + return data; + } + + static TableSource FindField(SqlField field, TableSource table) + { + if (field.Table == table.Source) + return table; + + foreach (var @join in table.Joins) + { + var t = FindField(field, @join.Table); + + if (t != null) + return @join.Table; + } + + return null; + } + + static ISqlExpression GetColumn(QueryData data, SqlField field) + { + foreach (var query in data.Queries) + { + var q = query.Query; + + foreach (var table in q.From.Tables) + { + var t = FindField(field, table); + + if (t != null) + { + var n = q.Select.Columns.Count; + var idx = q.Select.Add(field); + + if (n != q.Select.Columns.Count) + if (!q.GroupBy.IsEmpty || q.Select.Columns.Exists(c => IsAggregationFunction(c.Expression))) + q.GroupBy.Items.Add(field); + + return q.Select.Columns[idx]; + } + } + } + + return null; + } + + static void ResolveFields(QueryData data) + { + if (data.Queries.Count == 0) + return; + + var dic = new Dictionary<ISqlExpression,ISqlExpression>(); + + foreach (SqlField field in data.Fields) + { + if (dic.ContainsKey(field)) + continue; + + var found = false; + + foreach (var table in data.Query.From.Tables) + { + found = FindField(field, table) != null; + + if (found) + break; + } + + if (!found) + { + var expr = GetColumn(data, field); + + if (expr != null) + dic.Add(field, expr); + } + } + + if (dic.Count > 0) + new QueryVisitor().VisitParentFirst(data.Query, e => + { + ISqlExpression ex; + + switch (e.ElementType) + { + case QueryElementType.SqlQuery : + return e == data.Query; + + case QueryElementType.SqlFunction : + { + var parms = ((SqlFunction)e).Parameters; + + for (var i = 0; i < parms.Length; i++) + if (dic.TryGetValue(parms[i], out ex)) + parms[i] = ex; + + break; + } + + case QueryElementType.SqlExpression : + { + var parms = ((SqlExpression)e).Parameters; + + for (var i = 0; i < parms.Length; i++) + if (dic.TryGetValue(parms[i], out ex)) + parms[i] = ex; + + break; + } + + case QueryElementType.SqlBinaryExpression : + { + var expr = (SqlBinaryExpression)e; + if (dic.TryGetValue(expr.Expr1, out ex)) expr.Expr1 = ex; + if (dic.TryGetValue(expr.Expr2, out ex)) expr.Expr2 = ex; + break; + } + + case QueryElementType.ExprPredicate : + case QueryElementType.NotExprPredicate : + case QueryElementType.IsNullPredicate : + case QueryElementType.InSubQueryPredicate : + { + var expr = (Predicate.Expr)e; + if (dic.TryGetValue(expr.Expr1, out ex)) expr.Expr1 = ex; + break; + } + + case QueryElementType.ExprExprPredicate : + { + var expr = (Predicate.ExprExpr)e; + if (dic.TryGetValue(expr.Expr1, out ex)) expr.Expr1 = ex; + if (dic.TryGetValue(expr.Expr2, out ex)) expr.Expr2 = ex; + break; + } + + case QueryElementType.LikePredicate : + { + var expr = (Predicate.Like)e; + if (dic.TryGetValue(expr.Expr1, out ex)) expr.Expr1 = ex; + if (dic.TryGetValue(expr.Expr2, out ex)) expr.Expr2 = ex; + if (dic.TryGetValue(expr.Escape, out ex)) expr.Escape = ex; + break; + } + + case QueryElementType.BetweenPredicate : + { + var expr = (Predicate.Between)e; + if (dic.TryGetValue(expr.Expr1, out ex)) expr.Expr1 = ex; + if (dic.TryGetValue(expr.Expr2, out ex)) expr.Expr2 = ex; + if (dic.TryGetValue(expr.Expr3, out ex)) expr.Expr3 = ex; + break; + } + + case QueryElementType.InListPredicate : + { + var expr = (Predicate.InList)e; + + if (dic.TryGetValue(expr.Expr1, out ex)) expr.Expr1 = ex; + + for (var i = 0; i < expr.Values.Count; i++) + if (dic.TryGetValue(expr.Values[i], out ex)) + expr.Values[i] = ex; + + break; + } + + case QueryElementType.Column : + { + var expr = (Column)e; + + if (expr.Parent != data.Query) + return false; + + if (dic.TryGetValue(expr.Expression, out ex)) expr.Expression = ex; + + break; + } + + case QueryElementType.SetExpression : + { + var expr = (SetExpression)e; + if (dic.TryGetValue(expr.Expression, out ex)) expr.Expression = ex; + break; + } + + case QueryElementType.GroupByClause : + { + var expr = (GroupByClause)e; + + for (var i = 0; i < expr.Items.Count; i++) + if (dic.TryGetValue(expr.Items[i], out ex)) + expr.Items[i] = ex; + + break; + } + + case QueryElementType.OrderByItem : + { + var expr = (OrderByItem)e; + if (dic.TryGetValue(expr.Expression, out ex)) expr.Expression = ex; + break; + } + } + + return true; + }); + + foreach (var query in data.Queries) + if (query.Queries.Count > 0) + ResolveFields(query); + } + + void OptimizeUnions() + { + var exprs = new Dictionary<ISqlExpression,ISqlExpression>(); + + new QueryVisitor().Visit(this, e => + { + var sql = e as SqlQuery; + + if (sql == null || sql.From.Tables.Count != 1 || !sql.IsSimple || sql._insert != null || sql._update != null || sql._delete != null) + return; + + var table = sql.From.Tables[0]; + + if (table.Joins.Count != 0 || !(table.Source is SqlQuery)) + return; + + var union = (SqlQuery)table.Source; + + if (!union.HasUnion) + return; + + for (var i = 0; i < sql.Select.Columns.Count; i++) + { + var scol = sql. Select.Columns[i]; + var ucol = union.Select.Columns[i]; + + if (scol.Expression != ucol) + return; + } + + exprs.Add(union, sql); + + for (var i = 0; i < sql.Select.Columns.Count; i++) + { + var scol = sql. Select.Columns[i]; + var ucol = union.Select.Columns[i]; + + scol.Expression = ucol.Expression; + scol._alias = ucol._alias; + + exprs.Add(ucol, scol); + } + + for (var i = sql.Select.Columns.Count; i < union.Select.Columns.Count; i++) + sql.Select.Expr(union.Select.Columns[i].Expression); + + sql.From.Tables.Clear(); + sql.From.Tables.AddRange(union.From.Tables); + + sql.Where. SearchCondition.Conditions.AddRange(union.Where. SearchCondition.Conditions); + sql.Having. SearchCondition.Conditions.AddRange(union.Having.SearchCondition.Conditions); + sql.GroupBy.Items. AddRange(union.GroupBy.Items); + sql.OrderBy.Items. AddRange(union.OrderBy.Items); + sql.Unions.InsertRange(0, union.Unions); + }); + + ((ISqlExpressionWalkable)this).Walk(false, expr => + { + ISqlExpression e; + + if (exprs.TryGetValue(expr, out e)) + return e; + + return expr; + }); + } + + void FinalizeAndValidateInternal(bool isApplySupported, bool optimizeColumns, bool optimizeSearchCondition, List<ISqlTableSource> tables) + { + OptimizeSearchCondition(Where. SearchCondition); + OptimizeSearchCondition(Having.SearchCondition); + + if (optimizeSearchCondition) + { + ForEachTable(table => + { + foreach (var join in table.Joins) + OptimizeSearchCondition(join.Condition); + }, new HashSet<SqlQuery>()); + } + + new QueryVisitor().Visit(this, e => + { + var sql = e as SqlQuery; + + if (sql != null && sql != this) + { + sql.ParentSql = this; + sql.FinalizeAndValidateInternal(isApplySupported, optimizeColumns, false, tables); + + if (sql.IsParameterDependent) + IsParameterDependent = true; + } + }); + + ResolveWeakJoins(tables); + OptimizeColumns(); + OptimizeApplies (isApplySupported, optimizeColumns); + OptimizeSubQueries(isApplySupported, optimizeColumns); + OptimizeApplies (isApplySupported, optimizeColumns); + + new QueryVisitor().Visit(this, e => + { + var sql = e as SqlQuery; + + if (sql != null && sql != this) + sql.RemoveOrderBy(); + }); + } + + internal static void OptimizeSearchCondition(SearchCondition searchCondition) + { + // This 'if' could be replaced by one simple match: + // + // match (searchCondition.Conditions) + // { + // | [SearchCondition(true, _) sc] => + // searchCondition.Conditions = sc.Conditions; + // OptimizeSearchCondition(searchCodition) + // + // | [SearchCondition(false, [SearchCondition(true, [ExprExpr]) sc])] => ... + // + // | [Expr(true, SqlValue(true))] + // | [Expr(false, SqlValue(false))] + // searchCondition.Conditions = [] + // } + // + // One day I am going to rewrite all this crap in Nemerle. + // + if (searchCondition.Conditions.Count == 1) + { + var cond = searchCondition.Conditions[0]; + + if (cond.Predicate is SearchCondition) + { + var sc = (SearchCondition)cond.Predicate; + + if (!cond.IsNot) + { + searchCondition.Conditions.Clear(); + searchCondition.Conditions.AddRange(sc.Conditions); + + OptimizeSearchCondition(searchCondition); + return; + } + + if (sc.Conditions.Count == 1) + { + var c1 = sc.Conditions[0]; + + if (!c1.IsNot && c1.Predicate is Predicate.ExprExpr) + { + var ee = (Predicate.ExprExpr)c1.Predicate; + Predicate.Operator op; + + switch (ee.Operator) + { + case Predicate.Operator.Equal : op = Predicate.Operator.NotEqual; break; + case Predicate.Operator.NotEqual : op = Predicate.Operator.Equal; break; + case Predicate.Operator.Greater : op = Predicate.Operator.LessOrEqual; break; + case Predicate.Operator.NotLess : + case Predicate.Operator.GreaterOrEqual : op = Predicate.Operator.Less; break; + case Predicate.Operator.Less : op = Predicate.Operator.GreaterOrEqual; break; + case Predicate.Operator.NotGreater : + case Predicate.Operator.LessOrEqual : op = Predicate.Operator.Greater; break; + default: throw new InvalidOperationException(); + } + + c1.Predicate = new Predicate.ExprExpr(ee.Expr1, op, ee.Expr2); + + searchCondition.Conditions.Clear(); + searchCondition.Conditions.AddRange(sc.Conditions); + + OptimizeSearchCondition(searchCondition); + return; + } + } + } + + if (cond.Predicate.ElementType == QueryElementType.ExprPredicate) + { + var expr = (Predicate.Expr)cond.Predicate; + + if (expr.Expr1 is SqlValue) + { + var value = (SqlValue)expr.Expr1; + + if (value.Value is bool) + if (cond.IsNot ? !(bool)value.Value : (bool)value.Value) + searchCondition.Conditions.Clear(); + } + } + } + + for (var i = 0; i < searchCondition.Conditions.Count; i++) + { + var cond = searchCondition.Conditions[i]; + + if (cond.Predicate is Predicate.Expr) + { + var expr = (Predicate.Expr)cond.Predicate; + + if (expr.Expr1 is SqlValue) + { + var value = (SqlValue)expr.Expr1; + + if (value.Value is bool) + { + if (cond.IsNot ? !(bool)value.Value : (bool)value.Value) + { + if (i > 0) + { + if (searchCondition.Conditions[i-1].IsOr) + { + searchCondition.Conditions.RemoveRange(0, i); + OptimizeSearchCondition(searchCondition); + + break; + } + } + } + } + } + } + else if (cond.Predicate is SearchCondition) + { + var sc = (SearchCondition)cond.Predicate; + OptimizeSearchCondition(sc); + } + } + } + + void ForEachTable(Action<TableSource> action, HashSet<SqlQuery> visitedQueries) + { + if (!visitedQueries.Add(this)) + return; + + foreach (var table in From.Tables) + table.ForEach(action, visitedQueries); + + new QueryVisitor().Visit(this, e => + { + if (e is SqlQuery && e != this) + ((SqlQuery)e).ForEachTable(action, visitedQueries); + }); + } + + void RemoveOrderBy() + { + if (OrderBy.Items.Count > 0 && Select.SkipValue == null && Select.TakeValue == null) + OrderBy.Items.Clear(); + } + + internal void ResolveWeakJoins(List<ISqlTableSource> tables) + { + Func<TableSource,bool> findTable = null; findTable = table => + { + if (tables.Contains(table.Source)) + return true; + + foreach (var join in table.Joins) + { + if (findTable(join.Table)) + { + join.IsWeak = false; + return true; + } + } + + if (table.Source is SqlQuery) + foreach (var t in ((SqlQuery)table.Source).From.Tables) + if (findTable(t)) + return true; + + return false; + }; + + var areTablesCollected = false; + + ForEachTable(table => + { + for (var i = 0; i < table.Joins.Count; i++) + { + var join = table.Joins[i]; + + if (join.IsWeak) + { + if (!areTablesCollected) + { + areTablesCollected = true; + + Action<IQueryElement> tableCollector = expr => + { + var field = expr as SqlField; + + if (field != null && !tables.Contains(field.Table)) + tables.Add(field.Table); + }; + + var visitor = new QueryVisitor(); + + visitor.VisitAll(Select, tableCollector); + visitor.VisitAll(Where, tableCollector); + visitor.VisitAll(GroupBy, tableCollector); + visitor.VisitAll(Having, tableCollector); + visitor.VisitAll(OrderBy, tableCollector); + + if (_insert != null) + visitor.VisitAll(Insert, tableCollector); + + if (_update != null) + visitor.VisitAll(Update, tableCollector); + + if (_delete != null) + visitor.VisitAll(Delete, tableCollector); + + visitor.VisitAll(From, expr => + { + var tbl = expr as SqlTable; + + if (tbl != null && tbl.TableArguments != null) + { + var v = new QueryVisitor(); + + foreach (var arg in tbl.TableArguments) + v.VisitAll(arg, tableCollector); + } + }); + } + + if (findTable(join.Table)) + { + join.IsWeak = false; + } + else + { + table.Joins.RemoveAt(i); + i--; + } + } + } + }, new HashSet<SqlQuery>()); + } + + TableSource OptimizeSubQuery( + TableSource source, + bool optimizeWhere, + bool allColumns, + bool isApplySupported, + bool optimizeValues, + bool optimizeColumns) + { + foreach (var jt in source.Joins) + { + var table = OptimizeSubQuery( + jt.Table, + jt.JoinType == JoinType.Inner || jt.JoinType == JoinType.CrossApply, + false, + isApplySupported, + jt.JoinType == JoinType.Inner || jt.JoinType == JoinType.CrossApply, + optimizeColumns); + + if (table != jt.Table) + { + var sql = jt.Table.Source as SqlQuery; + + if (sql != null && sql.OrderBy.Items.Count > 0) + foreach (var item in sql.OrderBy.Items) + OrderBy.Expr(item.Expression, item.IsDescending); + + jt.Table = table; + } + } + + return source.Source is SqlQuery ? + RemoveSubQuery(source, optimizeWhere, allColumns && !isApplySupported, optimizeValues, optimizeColumns) : + source; + } + + static bool CheckColumn(Column column, ISqlExpression expr, SqlQuery query, bool optimizeValues, bool optimizeColumns) + { + if (expr is SqlField || expr is Column) + return false; + + if (expr is SqlValue) + return !optimizeValues && 1.Equals(((SqlValue)expr).Value); + + if (expr is SqlBinaryExpression) + { + var e = (SqlBinaryExpression)expr; + + if (e.Operation == "*" && e.Expr1 is SqlValue) + { + var value = (SqlValue)e.Expr1; + + if (value.Value is int && (int)value.Value == -1) + return CheckColumn(column, e.Expr2, query, optimizeValues, optimizeColumns); + } + } + + var visitor = new QueryVisitor(); + + if (optimizeColumns && + visitor.Find(expr, e => e is SqlQuery || IsAggregationFunction(e)) == null) + { + var n = 0; + var q = query.ParentSql ?? query; + + visitor.VisitAll(q, e => { if (e == column) n++; }); + + return n > 2; + } + + return true; + } + + TableSource RemoveSubQuery( + TableSource childSource, + bool concatWhere, + bool allColumns, + bool optimizeValues, + bool optimizeColumns) + { + var query = (SqlQuery)childSource.Source; + + var isQueryOK = query.From.Tables.Count == 1; + + isQueryOK = isQueryOK && (concatWhere || query.Where.IsEmpty && query.Having.IsEmpty); + isQueryOK = isQueryOK && !query.HasUnion && query.GroupBy.IsEmpty && !query.Select.HasModifier; + + if (!isQueryOK) + return childSource; + + var isColumnsOK = + (allColumns && !query.Select.Columns.Exists(c => IsAggregationFunction(c.Expression))) || + !query.Select.Columns.Exists(c => CheckColumn(c, c.Expression, query, optimizeValues, optimizeColumns)); + + if (!isColumnsOK) + return childSource; + + var map = new Dictionary<ISqlExpression,ISqlExpression>(query.Select.Columns.Count); + + foreach (var c in query.Select.Columns) + map.Add(c, c.Expression); + + var top = this; + + while (top.ParentSql != null) + top = top.ParentSql; + + ((ISqlExpressionWalkable)top).Walk(false, expr => + { + ISqlExpression fld; + return map.TryGetValue(expr, out fld) ? fld : expr; + }); + + new QueryVisitor().Visit(top, expr => + { + if (expr.ElementType == QueryElementType.InListPredicate) + { + var p = (Predicate.InList)expr; + + if (p.Expr1 == query) + p.Expr1 = query.From.Tables[0]; + } + }); + + query.From.Tables[0].Joins.AddRange(childSource.Joins); + + if (query.From.Tables[0].Alias == null) + query.From.Tables[0].Alias = childSource.Alias; + + if (!query.Where. IsEmpty) ConcatSearchCondition(Where, query.Where); + if (!query.Having.IsEmpty) ConcatSearchCondition(Having, query.Having); + + ((ISqlExpressionWalkable)top).Walk(false, expr => + { + if (expr is SqlQuery) + { + var sql = (SqlQuery)expr; + + if (sql.ParentSql == query) + sql.ParentSql = query.ParentSql ?? this; + } + + return expr; + }); + + return query.From.Tables[0]; + } + + static bool IsAggregationFunction(IQueryElement expr) + { + if (expr is SqlFunction) + switch (((SqlFunction)expr).Name) + { + case "Count" : + case "Average" : + case "Min" : + case "Max" : + case "Sum" : return true; + } + + return false; + } + + void OptimizeApply(TableSource tableSource, JoinedTable joinTable, bool isApplySupported, bool optimizeColumns) + { + var joinSource = joinTable.Table; + + foreach (var join in joinSource.Joins) + if (join.JoinType == JoinType.CrossApply || join.JoinType == JoinType.OuterApply) + OptimizeApply(joinSource, join, isApplySupported, optimizeColumns); + + if (isApplySupported && !joinTable.CanConvertApply) + return; + + if (joinSource.Source.ElementType == QueryElementType.SqlQuery) + { + var sql = (SqlQuery)joinSource.Source; + var isAgg = sql.Select.Columns.Exists(c => IsAggregationFunction(c.Expression)); + + if (isApplySupported && (isAgg || sql.Select.TakeValue != null || sql.Select.SkipValue != null)) + return; + + var searchCondition = new List<Condition>(sql.Where.SearchCondition.Conditions); + + sql.Where.SearchCondition.Conditions.Clear(); + + if (!ContainsTable(tableSource.Source, sql)) + { + joinTable.JoinType = joinTable.JoinType == JoinType.CrossApply ? JoinType.Inner : JoinType.Left; + joinTable.Condition.Conditions.AddRange(searchCondition); + } + else + { + sql.Where.SearchCondition.Conditions.AddRange(searchCondition); + + var table = OptimizeSubQuery( + joinTable.Table, + joinTable.JoinType == JoinType.Inner || joinTable.JoinType == JoinType.CrossApply, + joinTable.JoinType == JoinType.CrossApply, + isApplySupported, + joinTable.JoinType == JoinType.Inner || joinTable.JoinType == JoinType.CrossApply, + optimizeColumns); + + if (table != joinTable.Table) + { + var q = joinTable.Table.Source as SqlQuery; + + if (q != null && q.OrderBy.Items.Count > 0) + foreach (var item in q.OrderBy.Items) + OrderBy.Expr(item.Expression, item.IsDescending); + + joinTable.Table = table; + + OptimizeApply(tableSource, joinTable, isApplySupported, optimizeColumns); + } + } + } + else + { + if (!ContainsTable(tableSource.Source, joinSource.Source)) + joinTable.JoinType = joinTable.JoinType == JoinType.CrossApply ? JoinType.Inner : JoinType.Left; + } + } + + static bool ContainsTable(ISqlTableSource table, IQueryElement sql) + { + return null != new QueryVisitor().Find(sql, e => + e == table || + e.ElementType == QueryElementType.SqlField && table == ((SqlField)e).Table || + e.ElementType == QueryElementType.Column && table == ((Column) e).Parent); + } + + static void ConcatSearchCondition(WhereClause where1, WhereClause where2) + { + if (where1.IsEmpty) + { + where1.SearchCondition.Conditions.AddRange(where2.SearchCondition.Conditions); + } + else + { + if (where1.SearchCondition.Precedence < Sql.Precedence.LogicalConjunction) + { + var sc1 = new SearchCondition(); + + sc1.Conditions.AddRange(where1.SearchCondition.Conditions); + + where1.SearchCondition.Conditions.Clear(); + where1.SearchCondition.Conditions.Add(new Condition(false, sc1)); + } + + if (where2.SearchCondition.Precedence < Sql.Precedence.LogicalConjunction) + { + var sc2 = new SearchCondition(); + + sc2.Conditions.AddRange(where2.SearchCondition.Conditions); + + where1.SearchCondition.Conditions.Add(new Condition(false, sc2)); + } + else + where1.SearchCondition.Conditions.AddRange(where2.SearchCondition.Conditions); + } + } + + void OptimizeSubQueries(bool isApplySupported, bool optimizeColumns) + { + for (var i = 0; i < From.Tables.Count; i++) + { + var table = OptimizeSubQuery(From.Tables[i], true, false, isApplySupported, true, optimizeColumns); + + if (table != From.Tables[i]) + { + var sql = From.Tables[i].Source as SqlQuery; + + if (!Select.Columns.All(c => IsAggregationFunction(c.Expression))) + if (sql != null && sql.OrderBy.Items.Count > 0) + foreach (var item in sql.OrderBy.Items) + OrderBy.Expr(item.Expression, item.IsDescending); + + From.Tables[i] = table; + } + } + } + + void OptimizeApplies(bool isApplySupported, bool optimizeColumns) + { + foreach (var table in From.Tables) + foreach (var join in table.Joins) + if (join.JoinType == JoinType.CrossApply || join.JoinType == JoinType.OuterApply) + OptimizeApply(table, join, isApplySupported, optimizeColumns); + } + + void OptimizeColumns() + { + ((ISqlExpressionWalkable)Select).Walk(false, expr => + { + var query = expr as SqlQuery; + + if (query != null && query.From.Tables.Count == 0 && query.Select.Columns.Count == 1) + { + new QueryVisitor().Visit(query.Select.Columns[0].Expression, e => + { + if (e.ElementType == QueryElementType.SqlQuery) + { + var q = (SqlQuery)e; + + if (q.ParentSql == query) + q.ParentSql = query.ParentSql; + } + }); + + return query.Select.Columns[0].Expression; + } + + return expr; + }); + } + + IDictionary<string,object> _aliases; + + public void RemoveAlias(string alias) + { + if (_aliases != null) + { + alias = alias.ToUpper(); + if (_aliases.ContainsKey(alias)) + _aliases.Remove(alias); + } + } + + public string GetAlias(string desiredAlias, string defaultAlias) + { + if (_aliases == null) + _aliases = new Dictionary<string,object>(); + + var alias = desiredAlias; + + if (string.IsNullOrEmpty(desiredAlias) || desiredAlias.Length > 30) + { + desiredAlias = defaultAlias; + alias = defaultAlias + "1"; + } + + for (var i = 1; ; i++) + { + var s = alias.ToUpper(); + + if (!_aliases.ContainsKey(s) && !_reservedWords.ContainsKey(s)) + { + _aliases.Add(s, s); + break; + } + + alias = desiredAlias + i; + } + + return alias; + } + + public string[] GetTempAliases(int n, string defaultAlias) + { + var aliases = new string[n]; + + for (var i = 0; i < aliases.Length; i++) + aliases[i] = GetAlias(defaultAlias, defaultAlias); + + foreach (var t in aliases) + RemoveAlias(t); + + return aliases; + } + + void SetAliases() + { + _aliases = null; + + var objs = new Dictionary<object,object>(); + + Parameters.Clear(); + + new QueryVisitor().VisitAll(this, expr => + { + switch (expr.ElementType) + { + case QueryElementType.SqlParameter: + { + var p = (SqlParameter)expr; + + if (p.IsQueryParameter) + { + if (!objs.ContainsKey(expr)) + { + objs.Add(expr, expr); + p.Name = GetAlias(p.Name, "p"); + } + + Parameters.Add(p); + } + else + IsParameterDependent = true; + } + + break; + + case QueryElementType.Column: + { + if (!objs.ContainsKey(expr)) + { + objs.Add(expr, expr); + + var c = (Column)expr; + + if (c.Alias != "*") + c.Alias = GetAlias(c.Alias, "c"); + } + } + + break; + + case QueryElementType.TableSource: + { + var table = (TableSource)expr; + + if (!objs.ContainsKey(table)) + { + objs.Add(table, table); + table.Alias = GetAlias(table.Alias, "t"); + } + } + + break; + + case QueryElementType.SqlQuery: + { + var sql = (SqlQuery)expr; + + if (sql.HasUnion) + { + for (var i = 0; i < sql.Select.Columns.Count; i++) + { + var col = sql.Select.Columns[i]; + + foreach (var t in sql.Unions) + { + var union = t.SqlQuery.Select; + + objs.Remove(union.Columns[i].Alias); + + union.Columns[i].Alias = col.Alias; + } + } + } + } + + break; + } + }); + } + + #endregion + + #region ProcessParameters + + public SqlQuery ProcessParameters() + { + if (IsParameterDependent) + { + var query = new QueryVisitor().Convert(this, e => + { + switch (e.ElementType) + { + case QueryElementType.SqlParameter : + { + var p = (SqlParameter)e; + + if (p.Value == null) + return new SqlValue(null); + } + + break; + + case QueryElementType.ExprExprPredicate : + { + var ee = (Predicate.ExprExpr)e; + + if (ee.Operator == Predicate.Operator.Equal || ee.Operator == Predicate.Operator.NotEqual) + { + object value1; + object value2; + + if (ee.Expr1 is SqlValue) + value1 = ((SqlValue)ee.Expr1).Value; + else if (ee.Expr1 is SqlParameter) + value1 = ((SqlParameter)ee.Expr1).Value; + else + break; + + if (ee.Expr2 is SqlValue) + value2 = ((SqlValue)ee.Expr2).Value; + else if (ee.Expr2 is SqlParameter) + value2 = ((SqlParameter)ee.Expr2).Value; + else + break; + + var value = Equals(value1, value2); + + if (ee.Operator == Predicate.Operator.NotEqual) + value = !value; + + return new Predicate.Expr(new SqlValue(value), Sql.Precedence.Comparison); + } + } + + break; + + case QueryElementType.InListPredicate : + return ConvertInListPredicate((Predicate.InList)e); + } + + return null; + }); + + if (query != this) + { + query.Parameters.Clear(); + + new QueryVisitor().VisitAll(query, expr => + { + if (expr.ElementType == QueryElementType.SqlParameter) + { + var p = (SqlParameter)expr; + if (p.IsQueryParameter) + query.Parameters.Add(p); + } + }); + } + + return query; + } + + return this; + } + + static Predicate ConvertInListPredicate(Predicate.InList p) + { + if (p.Values == null || p.Values.Count == 0) + return new Predicate.Expr(new SqlValue(p.IsNot)); + + if (p.Values.Count == 1 && p.Values[0] is SqlParameter) + { + var pr = (SqlParameter)p.Values[0]; + + if (pr.Value == null) + return new Predicate.Expr(new SqlValue(p.IsNot)); + + if (pr.Value is IEnumerable) + { + if (p.Expr1 is ISqlTableSource) + { + var items = (IEnumerable)pr.Value; + var table = (ISqlTableSource)p.Expr1; + var keys = table.GetKeys(true); + + if (keys == null || keys.Count == 0) + throw new SqlException("Cant create IN expression."); + + if (keys.Count == 1) + { + var values = new List<ISqlExpression>(); + var field = GetUnderlayingField(keys[0]); + + foreach (var item in items) + { + var value = field.MemberMapper.GetValue(item); + values.Add(new SqlValue(value)); + } + + if (values.Count == 0) + return new Predicate.Expr(new SqlValue(p.IsNot)); + + return new Predicate.InList(keys[0], p.IsNot, values); + } + + { + var sc = new SearchCondition(); + + foreach (var item in items) + { + var itemCond = new SearchCondition(); + + foreach (var key in keys) + { + var field = GetUnderlayingField(key); + var value = field.MemberMapper.GetValue(item); + var cond = value == null ? + new Condition(false, new Predicate.IsNull (field, false)) : + new Condition(false, new Predicate.ExprExpr(field, Predicate.Operator.Equal, new SqlValue(value))); + + itemCond.Conditions.Add(cond); + } + + sc.Conditions.Add(new Condition(false, new Predicate.Expr(itemCond), true)); + } + + if (sc.Conditions.Count == 0) + return new Predicate.Expr(new SqlValue(p.IsNot)); + + if (p.IsNot) + return new Predicate.NotExpr(sc, true, Sql.Precedence.LogicalNegation); + + return new Predicate.Expr(sc, Sql.Precedence.LogicalDisjunction); + } + } + + if (p.Expr1 is SqlExpression) + { + var expr = (SqlExpression)p.Expr1; + + if (expr.Expr.Length > 1 && expr.Expr[0] == '\x1') + { + var type = TypeHelper.GetListItemType(pr.Value); + var ta = TypeAccessor.GetAccessor(type); + var items = (IEnumerable)pr.Value; + var names = expr.Expr.Substring(1).Split(','); + + if (expr.Parameters.Length == 1) + { + var values = new List<ISqlExpression>(); + + foreach (var item in items) + { + var value = ta[names[0]].GetValue(item); + values.Add(new SqlValue(value)); + } + + if (values.Count == 0) + return new Predicate.Expr(new SqlValue(p.IsNot)); + + return new Predicate.InList(expr.Parameters[0], p.IsNot, values); + } + + { + var sc = new SearchCondition(); + + foreach (var item in items) + { + var itemCond = new SearchCondition(); + + for (var i = 0; i < expr.Parameters.Length; i++) + { + var sql = expr.Parameters[i]; + var value = ta[names[i]].GetValue(item); + var cond = value == null ? + new Condition(false, new Predicate.IsNull (sql, false)) : + new Condition(false, new Predicate.ExprExpr(sql, Predicate.Operator.Equal, new SqlValue(value))); + + itemCond.Conditions.Add(cond); + } + + sc.Conditions.Add(new Condition(false, new Predicate.Expr(itemCond), true)); + } + + if (sc.Conditions.Count == 0) + return new Predicate.Expr(new SqlValue(p.IsNot)); + + if (p.IsNot) + return new Predicate.NotExpr(sc, true, Sql.Precedence.LogicalNegation); + + return new Predicate.Expr(sc, Sql.Precedence.LogicalDisjunction); + } + } + } + + /* + var itemType = items.GetType().GetItemType(); + + if (itemType == typeof(DateTime) || itemType == typeof(DateTimeOffset) || + itemType == typeof(DateTime?) || itemType == typeof(DateTimeOffset?)) + { + var list = new List<SqlParameter>(); + + foreach (var item in items) + list.Add(new SqlParameter(itemType, "p", item, (MappingSchema)null)); + + return new Predicate.InList(p.Expr1, p.IsNot, list); + } + */ + } + } + + return null; + } + + static SqlField GetUnderlayingField(ISqlExpression expr) + { + switch (expr.ElementType) + { + case QueryElementType.SqlField: return (SqlField)expr; + case QueryElementType.Column : return GetUnderlayingField(((Column)expr).Expression); + } + + throw new InvalidOperationException(); + } + + #endregion + + #region Clone + + SqlQuery(SqlQuery clone, Dictionary<ICloneableElement,ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + objectTree.Add(clone, this); + objectTree.Add(clone.All, All); + + SourceID = Interlocked.Increment(ref SourceIDCounter); + + _queryType = clone._queryType; + + if (IsInsert) _insert = (InsertClause)clone._insert.Clone(objectTree, doClone); + if (IsUpdate) _update = (UpdateClause)clone._update.Clone(objectTree, doClone); + if (IsDelete) _delete = (DeleteClause)clone._delete.Clone(objectTree, doClone); + + _select = new SelectClause (this, clone._select, objectTree, doClone); + _from = new FromClause (this, clone._from, objectTree, doClone); + _where = new WhereClause (this, clone._where, objectTree, doClone); + _groupBy = new GroupByClause(this, clone._groupBy, objectTree, doClone); + _having = new WhereClause (this, clone._having, objectTree, doClone); + _orderBy = new OrderByClause(this, clone._orderBy, objectTree, doClone); + + _parameters.AddRange(clone._parameters.ConvertAll(p => (SqlParameter)p.Clone(objectTree, doClone))); + IsParameterDependent = clone.IsParameterDependent; + + new QueryVisitor().Visit(this, expr => + { + var sb = expr as SqlQuery; + + if (sb != null && sb.ParentSql == clone) + sb.ParentSql = this; + }); + } + + public SqlQuery Clone() + { + return (SqlQuery)Clone(new Dictionary<ICloneableElement,ICloneableElement>(), _ => true); + } + + public SqlQuery Clone(Predicate<ICloneableElement> doClone) + { + return (SqlQuery)Clone(new Dictionary<ICloneableElement,ICloneableElement>(), doClone); + } + + #endregion + + #region Helpers + + public TableSource GetTableSource(ISqlTableSource table) + { + var ts = From[table]; + return ts == null && ParentSql != null? ParentSql.GetTableSource(table) : ts; + } + + static TableSource CheckTableSource(TableSource ts, ISqlTableSource table, string alias) + { + if (ts.Source == table && (alias == null || ts.Alias == alias)) + return ts; + + var jt = ts[table, alias]; + + if (jt != null) + return jt; + + if (ts.Source is SqlQuery) + { + var s = ((SqlQuery)ts.Source).From[table, alias]; + + if (s != null) + return s; + } + + return null; + } + + #endregion + + #region Overrides + + public string SqlText { get { return ToString(); } } + +#if OVERRIDETOSTRING + + public override string ToString() + { + return ((IQueryElement)this).ToString(new StringBuilder(), new Dictionary<IQueryElement,IQueryElement>()).ToString(); + } + +#endif + + #endregion + + #region ISqlExpression Members + + public bool CanBeNull() + { + return true; + } + + public bool Equals(ISqlExpression other, Func<ISqlExpression, ISqlExpression, bool> comparer) + { + return this == other; + } + + public int Precedence + { + get { return Sql.Precedence.Unknown; } + } + + public Type SystemType + { + get + { + if (Select.Columns.Count == 1) + return Select.Columns[0].SystemType; + + if (From.Tables.Count == 1 && From.Tables[0].Joins.Count == 0) + return From.Tables[0].SystemType; + + return null; + } + } + + #endregion + + #region ICloneableElement Members + + public ICloneableElement Clone(Dictionary<ICloneableElement, ICloneableElement> objectTree, Predicate<ICloneableElement> doClone) + { + if (!doClone(this)) + return this; + + ICloneableElement clone; + + if (!objectTree.TryGetValue(this, out clone)) + clone = new SqlQuery(this, objectTree, doClone); + + return clone; + } + + #endregion + + #region ISqlExpressionWalkable Members + + [Obsolete] + ISqlExpression ISqlExpressionWalkable.Walk(bool skipColumns, Func<ISqlExpression,ISqlExpression> func) + { + if (_insert != null) ((ISqlExpressionWalkable)_insert).Walk(skipColumns, func); + if (_update != null) ((ISqlExpressionWalkable)_update).Walk(skipColumns, func); + if (_delete != null) ((ISqlExpressionWalkable)_delete).Walk(skipColumns, func); + + ((ISqlExpressionWalkable)Select) .Walk(skipColumns, func); + ((ISqlExpressionWalkable)From) .Walk(skipColumns, func); + ((ISqlExpressionWalkable)Where) .Walk(skipColumns, func); + ((ISqlExpressionWalkable)GroupBy).Walk(skipColumns, func); + ((ISqlExpressionWalkable)Having) .Walk(skipColumns, func); + ((ISqlExpressionWalkable)OrderBy).Walk(skipColumns, func); + + if (HasUnion) + foreach (var union in Unions) + ((ISqlExpressionWalkable)union.SqlQuery).Walk(skipColumns, func); + + return func(this); + } + + #endregion + + #region IEquatable<ISqlExpression> Members + + bool IEquatable<ISqlExpression>.Equals(ISqlExpression other) + { + return this == other; + } + + #endregion + + #region ISqlTableSource Members + + public static int SourceIDCounter; + + public int SourceID { get; private set; } + public SqlTableType SqlTableType { get { return SqlTableType.Table; } } + + private SqlField _all; + public SqlField All + { + get + { + if (_all == null) + { + _all = new SqlField(null, "*", "*", true, -1, null, null); + ((IChild<ISqlTableSource>)_all).Parent = this; + } + + return _all; + } + + internal set + { + _all = value; + + if (_all != null) + ((IChild<ISqlTableSource>)_all).Parent = this; + } + } + + List<ISqlExpression> _keys; + + public IList<ISqlExpression> GetKeys(bool allIfEmpty) + { + if (_keys == null && From.Tables.Count == 1 && From.Tables[0].Joins.Count == 0) + { + _keys = new List<ISqlExpression>(); + + var q = + from key in ((ISqlTableSource)From.Tables[0]).GetKeys(allIfEmpty) + from col in Select.Columns + where col.Expression == key + select col as ISqlExpression; + + _keys = q.ToList(); + } + + return _keys; + } + + #endregion + + #region IQueryElement Members + + public QueryElementType ElementType { get { return QueryElementType.SqlQuery; } } + + StringBuilder IQueryElement.ToString(StringBuilder sb, Dictionary<IQueryElement,IQueryElement> dic) + { + if (dic.ContainsKey(this)) + return sb.Append("..."); + + dic.Add(this, this); + + sb + .Append("(") + .Append(SourceID) + .Append(") "); + + ((IQueryElement)Select). ToString(sb, dic); + ((IQueryElement)From). ToString(sb, dic); + ((IQueryElement)Where). ToString(sb, dic); + ((IQueryElement)GroupBy).ToString(sb, dic); + ((IQueryElement)Having). ToString(sb, dic); + ((IQueryElement)OrderBy).ToString(sb, dic); + + if (HasUnion) + foreach (IQueryElement u in Unions) + u.ToString(sb, dic); + + dic.Remove(this); + + return sb; + } + + #endregion + } +}