internal SelectExpression(LambdaExpression projection, FromExpression from, WhereExpression where, string alias, bool isProjection, Expression take, OrderByClause orderBy)
			: base(CompleteExpressionType.Select, typeof(IEnumerable<>).MakeGenericType(projection.ReturnType))
		{
			this.Projection = projection;
			this.Alias = alias;
			this.From = from;
			this.Where = where;
			this.Take = take;
			this.OrderBy = orderBy;
			this.IsProjection = isProjection;

			var ce = ColumnExtractor.ExtractColumns(this, projection);
			this._Columns = ce.Columns.ToList();
			this._Assignments = ce.Assignments.ToList();
		}
		internal static OrderByClause CombineOrderByClauses(OrderByClause left, OrderByClause right)
		{
			// if left is null, then take right (null or no) and we're done
			if (left == null)
				return right;

			// left is not null, start with clone of left
			var orderBy = new OrderByClause(left.SecondarySortingClause);
			orderBy._SortColumns.AddRange(left._SortColumns);

			// if right is null, then we're done
			if (right != null)
			{
				// right is not null; merge in sort columns from right at top level
				left._SortColumns.AddRange(right._SortColumns);

				// start with the highest level orderBy to figure out where to inject the right side sorting clause
				// don't care about destroying existing clauses: these are cloned at every level anyway
				// these order by clauses exist only for current level
				var parent = orderBy;
				while (parent.SecondarySortingClause != null)
					parent = parent.SecondarySortingClause;
				parent.SecondarySortingClause = right.SecondarySortingClause;
			}

			return orderBy;
		}
		internal OrderByClause(OrderByClause secondarySorts)
		{
			_SortColumns = new List<Tuple<Expression, SortDirection>>();
			SecondarySortingClause = secondarySorts;
		}
		internal void InjectSecondarySortingClause(OrderByClause right)
		{
			var parent = this;
			while (parent.SecondarySortingClause != null)
				parent = parent.SecondarySortingClause;
			parent.SecondarySortingClause = right;
		}
		protected virtual OrderByClause VisitOrderBy(OrderByClause orderBy)
		{
			if (orderBy == null)
				return null;

			var secondary = this.VisitOrderBy(orderBy.SecondarySortingClause);
			var flag = secondary != orderBy.SecondarySortingClause;

			var newOrderBy = new OrderByClause(secondary);
			foreach (var col in orderBy.SortColumns)
			{
				var newCol = this.Visit(col.Item1);
				newOrderBy.AppendColumn(Tuple.Create(newCol, col.Item2));

				if (newCol != col.Item1)
					flag = true;
			}

			if (flag)
				return newOrderBy;
			return orderBy;
		}
		protected override OrderByClause VisitOrderBy(OrderByClause orderBy)
		{
			var add_comma = false;
			foreach (var col in orderBy.SortColumns)
			{
				if (add_comma)
					sb.Append(", ");
				else
					add_comma = true;

				this.Visit(col.Item1);
				if (col.Item2 == OrderByClause.SortDirection.Ascending)
					sb.Append(" asc");
				else
					sb.Append(" desc");
			}

			if (orderBy.SecondarySortingClause != null)
			{
				if (add_comma)
					sb.Append(", ");
				this.VisitOrderBy(orderBy.SecondarySortingClause);
			}
			this.AppendNewLine(Indentation.Same);
			return orderBy;
		}
		protected virtual Expression VisitSelectManyMethod(MethodCallExpression m)
		{
			var select = VisitSource(m.Arguments[0]);
			var selectSource = select.From.From as SourceExpression;

			var collection = VisitSourceReferencedProjection(select.From, m.Arguments[1]);
			var apply = collection.Body as SelectExpression;

			var joinType = apply.DefaultIfEmpty ? JoinType.OuterApply : JoinType.CrossApply;
			apply.DefaultIfEmpty = true;

			var join = CompleteExpression.Join(selectSource, apply, joinType);
			var from = CompleteExpression.From(join);

			var orderBy = select.OrderBy;
			var rightOrder = BuildNestedOrderBy(apply);
			if (orderBy != null)
				orderBy.InjectSecondarySortingClause(rightOrder);
			else if (rightOrder != null)
				orderBy = new OrderByClause(rightOrder);

			var projection =
				m.Arguments.Count() > 2 ?
					VisitSourceReferencedProjection(from, m.Arguments[2]) :
					ColumnReplacer.ReplaceColumns(apply.Projection) as LambdaExpression;

			return CompleteExpression.Select(
				GetNextAlias(),
				projection,
				from,
				orderBy: orderBy,
				where: select.Where,
				isProjection: true);
		}
		private OrderByClause BuildNestedOrderBy(SelectExpression select, OrderByClause orderBy)
		{
			if (orderBy == null)
				return null;

			var newOrderBy = new OrderByClause(BuildNestedOrderBy(select, orderBy.SecondarySortingClause));
			var columns = orderBy.SortColumns;
			var unmatchedColumns = new List<Expression>();
			foreach (var col in columns)
			{
				Expression expr = select.TryFindColumn(col.Item1);
				if (expr == null)
					expr = select.AddUnmappedColumn(col.Item1);
				newOrderBy.AppendColumn(Tuple.Create(expr, col.Item2));
			}
			return newOrderBy;
		}
		public static SelectExpression Select(string alias, LambdaExpression projection, FromExpression from, OrderByClause orderBy, WhereExpression where = null, bool isProjection = true, Expression take = null)
		{
			return new SelectExpression(projection, from, where, alias, isProjection, take, orderBy);
		}