using System.Collections.Generic; using System.Text; namespace Marr.Data.QGen { /// /// Decorates the SelectQuery by wrapping it in a paging query. /// public class PagingQueryDecorator : IQuery { private SelectQuery _innerQuery; private int _firstRow; private int _lastRow; public PagingQueryDecorator(SelectQuery innerQuery, int skip, int take) { if (string.IsNullOrEmpty(innerQuery.OrderBy.ToString())) { throw new DataMappingException("A paged query must specify an order by clause."); } _innerQuery = innerQuery; _firstRow = skip + 1; _lastRow = skip + take; } public string Generate() { // Decide which type of paging query to create if (_innerQuery.IsView || _innerQuery.IsJoin) { return ComplexPaging(); } return SimplePaging(); } /// /// Generates a query that pages a simple inner query. /// /// private string SimplePaging() { // Create paged query StringBuilder sql = new StringBuilder(); sql.AppendLine("WITH RowNumCTE AS"); sql.AppendLine("("); _innerQuery.BuildSelectClause(sql); BuildRowNumberColumn(sql); _innerQuery.BuildFromClause(sql); _innerQuery.BuildJoinClauses(sql); _innerQuery.BuildWhereClause(sql); sql.AppendLine(")"); BuildSimpleOuterSelect(sql); return sql.ToString(); } /// /// Generates a query that pages a view or joined inner query. /// /// private string ComplexPaging() { // Create paged query StringBuilder sql = new StringBuilder(); sql.AppendLine("WITH GroupCTE AS ("); BuildSelectClause(sql); BuildGroupColumn(sql); _innerQuery.BuildFromClause(sql); _innerQuery.BuildJoinClauses(sql); _innerQuery.BuildWhereClause(sql); sql.AppendLine("),"); sql.AppendLine("RowNumCTE AS ("); sql.AppendLine("SELECT *"); BuildRowNumberColumn(sql); sql.AppendLine("FROM GroupCTE"); sql.AppendLine("WHERE GroupRow = 1"); sql.AppendLine(")"); _innerQuery.BuildSelectClause(sql); _innerQuery.BuildFromClause(sql); _innerQuery.BuildJoinClauses(sql); BuildJoinBackToCTE(sql); sql.AppendFormat("WHERE RowNumber BETWEEN {0} AND {1}", _firstRow, _lastRow); return sql.ToString(); } private void BuildJoinBackToCTE(StringBuilder sql) { Table baseTable = GetBaseTable(); sql.AppendLine("INNER JOIN RowNumCTE cte"); int pksAdded = 0; foreach (var pk in baseTable.Columns.PrimaryKeys) { if (pksAdded > 0) sql.Append(" AND "); string cteQueryPkName = _innerQuery.NameOrAltName(pk.ColumnInfo); string outerQueryPkName = _innerQuery.IsJoin ? pk.ColumnInfo.Name : _innerQuery.NameOrAltName(pk.ColumnInfo); sql.AppendFormat("ON cte.{0} = {1} ", cteQueryPkName, _innerQuery.Dialect.CreateToken(string.Concat("t0", ".", outerQueryPkName))); pksAdded++; } sql.AppendLine(); } private void BuildSimpleOuterSelect(StringBuilder sql) { sql.Append("SELECT "); int startIndex = sql.Length; // COLUMNS foreach (Table join in _innerQuery.Tables) { for (int i = 0; i < join.Columns.Count; i++) { var c = join.Columns[i]; if (sql.Length > startIndex) sql.Append(","); string token = _innerQuery.NameOrAltName(c.ColumnInfo); sql.Append(_innerQuery.Dialect.CreateToken(token)); } } sql.AppendLine("FROM RowNumCTE"); sql.AppendFormat("WHERE RowNumber BETWEEN {0} AND {1}", _firstRow, _lastRow).AppendLine(); sql.AppendLine("ORDER BY RowNumber ASC;"); } private void BuildGroupColumn(StringBuilder sql) { bool isView = _innerQuery.IsView; sql.AppendFormat(", ROW_NUMBER() OVER (PARTITION BY {0} {1}) As GroupRow ", BuildBaseTablePKColumns(isView), _innerQuery.OrderBy.BuildQuery(isView)); } private string BuildBaseTablePKColumns(bool useAltName = true) { Table baseTable = GetBaseTable(); StringBuilder sb = new StringBuilder(); foreach (var col in baseTable.Columns.PrimaryKeys) { if (sb.Length > 0) sb.AppendLine(", "); string columnName = useAltName ? _innerQuery.NameOrAltName(col.ColumnInfo) : col.ColumnInfo.Name; sb.AppendFormat(_innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", columnName))); } return sb.ToString(); } private void BuildRowNumberColumn(StringBuilder sql) { string orderBy = _innerQuery.OrderBy.ToString(); // Remove table prefixes from order columns foreach (Table t in _innerQuery.Tables) { orderBy = orderBy.Replace(string.Format("[{0}].", t.Alias), ""); } sql.AppendFormat(", ROW_NUMBER() OVER ({0}) As RowNumber ", orderBy); } private Table GetBaseTable() { Table baseTable = null; if (_innerQuery.Tables[0] is View) { baseTable = (_innerQuery.Tables[0] as View).Tables[0]; } else { baseTable = _innerQuery.Tables[0]; } return baseTable; } public void BuildSelectClause(StringBuilder sql) { List appended = new List(); sql.Append("SELECT "); int startIndex = sql.Length; // COLUMNS foreach (Table join in _innerQuery.Tables) { for (int i = 0; i < join.Columns.Count; i++) { var c = join.Columns[i]; if (sql.Length > startIndex && sql[sql.Length - 1] != ',') sql.Append(","); if (join is View) { string token = _innerQuery.Dialect.CreateToken(string.Concat(join.Alias, ".", _innerQuery.NameOrAltName(c.ColumnInfo))); if (appended.Contains(token)) continue; sql.Append(token); appended.Add(token); } else { string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name); if (appended.Contains(token)) continue; sql.Append(_innerQuery.Dialect.CreateToken(token)); if (_innerQuery.UseAltName && c.ColumnInfo.AltName != null && c.ColumnInfo.AltName != c.ColumnInfo.Name) { string altName = c.ColumnInfo.AltName; sql.AppendFormat(" AS {0}", altName); } } } } } } }