Browse Source
Delay load ID-list in functions view. The "Top 20" view now opens more than twice as fast. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5036 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
17 changed files with 860 additions and 506 deletions
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// Query AST node representing the whole 'FunctionData' table.
|
||||
/// This is the source of all queries.
|
||||
/// Produces SELECT .. FROM .. in SQL.
|
||||
/// </summary>
|
||||
sealed class AllCalls : QueryNode |
||||
{ |
||||
public static readonly AllCalls Instance = new AllCalls(); |
||||
|
||||
private AllCalls() : base(null) |
||||
{ |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
return this; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return "AllCalls"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context); |
||||
context.SetCurrent(newNames, SqlTableType.Calls, true); |
||||
|
||||
b.AppendLine("SELECT " |
||||
+ SqlAs("nameid", newNames.NameID) + ", " |
||||
+ SqlAs("timespent", newNames.CpuCyclesSpent) + ", " // TODO : change to CpuCyclesSpent
|
||||
+ SqlAs("callcount", newNames.CallCount) + ", " |
||||
+ SqlAs("(id != endid)", newNames.HasChildren) + ", " |
||||
+ SqlAs("((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount) + ", " |
||||
+ SqlAs("id", newNames.ID)); |
||||
b.AppendLine("FROM FunctionData"); |
||||
return SqlStatementKind.Select; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Linq.Expressions; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// Query AST node representing the whole 'FunctionData' table.
|
||||
/// This is the source of all queries.
|
||||
/// Produces SELECT .. FROM .. in SQL.
|
||||
/// </summary>
|
||||
sealed class AllFunctions : QueryNode |
||||
{ |
||||
public readonly int StartDataSet, EndDataSet; |
||||
|
||||
public AllFunctions() : base(null) |
||||
{ |
||||
this.StartDataSet = -1; |
||||
this.EndDataSet = -1; |
||||
} |
||||
|
||||
public AllFunctions(int startDataSet, int endDataSet) : base(null) |
||||
{ |
||||
this.StartDataSet = startDataSet; |
||||
this.EndDataSet = endDataSet; |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
return this; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
if (StartDataSet < 0) |
||||
return "AllFunctions"; |
||||
else |
||||
return "AllFunctions(" + StartDataSet + ", " + EndDataSet + ")"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
if (context.RequireIDList) |
||||
throw new NotSupportedException(); |
||||
|
||||
CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context); |
||||
context.SetCurrent(newNames, SqlTableType.None, false); |
||||
|
||||
b.AppendLine("SELECT " |
||||
+ SqlAs("nameid", newNames.NameID) + ", " |
||||
+ SqlAs("SUM(timespent)", newNames.CpuCyclesSpent) + ", " |
||||
+ SqlAs("SUM(callcount)", newNames.CallCount) + ", " |
||||
+ SqlAs("MAX(id != endid)", newNames.HasChildren) + ", " |
||||
+ SqlAs("SUM((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount)); |
||||
b.AppendLine("FROM FunctionData"); |
||||
if (StartDataSet >= 0) |
||||
b.AppendLine("WHERE datasetid BETWEEN " + StartDataSet + " AND " + EndDataSet); |
||||
b.AppendLine("GROUP BY nameid"); |
||||
return SqlStatementKind.SelectGroupBy; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// Query node that filters the input using conditions. Produces WHERE or HAVING in SQL.
|
||||
/// </summary>
|
||||
sealed class Filter : QueryNode |
||||
{ |
||||
/// <summary>
|
||||
/// List of conditions. The operator between these is AND.
|
||||
/// </summary>
|
||||
public readonly ReadOnlyCollection<LambdaExpression> Conditions; |
||||
|
||||
public Filter(QueryNode target, params LambdaExpression[] conditions) |
||||
: base(target) |
||||
{ |
||||
Debug.Assert(target != null); |
||||
foreach (LambdaExpression l in conditions) { |
||||
Debug.Assert(l.Parameters.Count == 1); |
||||
} |
||||
this.Conditions = Array.AsReadOnly(conditions); |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
LambdaExpression[] newConditions = new LambdaExpression[Conditions.Count]; |
||||
bool unchanged = (newTarget == Target); |
||||
for (int i = 0; i < newConditions.Length; i++) { |
||||
newConditions[i] = (LambdaExpression)visitor(Conditions[i]); |
||||
unchanged &= newConditions[i] == Conditions[i]; |
||||
} |
||||
if (unchanged) |
||||
return this; |
||||
else |
||||
return new Filter(newTarget, newConditions); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringBuilder b = new StringBuilder(); |
||||
b.Append(Target.ToString()); |
||||
b.Append(".Filter("); |
||||
for (int i = 0; i < Conditions.Count; i++) { |
||||
if (i > 0) |
||||
b.Append(" && "); |
||||
b.Append(Conditions[i].ToString()); |
||||
} |
||||
b.Append(')'); |
||||
return b.ToString(); |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
SqlStatementKind kind = Target.BuildSql(b, context); |
||||
SqlStatementKind resultKind; |
||||
switch (kind) { |
||||
case SqlStatementKind.Select: |
||||
b.Append(" WHERE "); |
||||
resultKind = SqlStatementKind.SelectWhere; |
||||
break; |
||||
case SqlStatementKind.SelectGroupBy: |
||||
b.Append(" HAVING "); |
||||
resultKind = SqlStatementKind.SelectGroupByHaving; |
||||
break; |
||||
default: |
||||
WrapSqlIntoNestedStatement(b, context); |
||||
b.Append(" WHERE "); |
||||
resultKind = SqlStatementKind.SelectWhere; |
||||
break; |
||||
} |
||||
for (int i = 0; i < Conditions.Count; i++) { |
||||
if (i > 0) |
||||
b.Append(" AND "); |
||||
BuildSqlForCondition(b, context, Conditions[i]); |
||||
} |
||||
b.AppendLine(); |
||||
return resultKind; |
||||
} |
||||
|
||||
static void BuildSqlForCondition(StringBuilder b, SqlQueryContext context, LambdaExpression condition) |
||||
{ |
||||
Debug.Assert(condition.Parameters.Count == 1); |
||||
StringWriter w = new StringWriter(CultureInfo.InvariantCulture); |
||||
ExpressionSqlWriter writer = new ExpressionSqlWriter(w, context, condition.Parameters[0]); |
||||
writer.Write(condition.Body); |
||||
b.Append(w.ToString()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// Query node that limits the amount of data selected. Produces LIMIT in SQL.
|
||||
/// </summary>
|
||||
sealed class Limit : QueryNode |
||||
{ |
||||
public int Start { get; private set; } |
||||
public int Length { get; private set; } |
||||
|
||||
public Limit(QueryNode target, int start, int length) |
||||
: base(target) |
||||
{ |
||||
this.Start = start; |
||||
this.Length = length; |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
if (newTarget == Target) |
||||
return this; |
||||
else |
||||
return new Limit(newTarget, Start, Length); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return Target + ".Limit(" + Start + "," + Length + ")"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
SqlStatementKind kind = Target.BuildSql(b, context); |
||||
if (kind == SqlStatementKind.SelectLimit) |
||||
WrapSqlIntoNestedStatement(b, context); |
||||
|
||||
b.Append(" LIMIT " + Length); |
||||
b.AppendLine(" OFFSET " + Start); |
||||
|
||||
return SqlStatementKind.SelectLimit; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// Query node that represents the 'group by nameid and merge' operation. Produces SELECT ... GROUP BY .. in SQL.
|
||||
/// </summary>
|
||||
sealed class MergeByName : QueryNode |
||||
{ |
||||
public MergeByName(QueryNode target) |
||||
: base(target) |
||||
{ |
||||
Debug.Assert(target != null); |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
if (newTarget == Target) |
||||
return this; |
||||
else |
||||
return new MergeByName(newTarget); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return Target + ".MergeByName()"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
Target.BuildSql(b, context); |
||||
|
||||
CallTreeNodeSqlNameSet oldNames = context.CurrentNameSet; |
||||
CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context); |
||||
|
||||
string query = "SELECT " |
||||
+ SqlAs(oldNames.NameID, newNames.NameID) + ", " |
||||
+ SqlAs("SUM(" + oldNames.CpuCyclesSpent + ")", newNames.CpuCyclesSpent) + ", " |
||||
+ SqlAs("SUM(" + oldNames.CallCount + ")", newNames.CallCount) + ", " |
||||
+ SqlAs("MAX(" + oldNames.HasChildren + ")", newNames.HasChildren) + ", " |
||||
+ SqlAs("SUM(" + oldNames.ActiveCallCount + ")", newNames.ActiveCallCount); |
||||
if (context.HasIDList) { |
||||
query += ", " + SqlAs("GROUP_CONCAT(" + oldNames.ID + ")", newNames.ID); |
||||
} |
||||
query += Environment.NewLine + "FROM (" + Environment.NewLine; |
||||
b.Insert(0, query); |
||||
b.AppendLine(") GROUP BY " + oldNames.NameID); |
||||
context.SetCurrent(newNames, SqlTableType.None, context.HasIDList); |
||||
|
||||
return SqlStatementKind.SelectGroupBy; |
||||
} |
||||
} |
||||
} |
@ -1,422 +0,0 @@
@@ -1,422 +0,0 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// The SingleCall class is never instanciated; it only used to represent database rows
|
||||
/// inside expression trees.
|
||||
/// </summary>
|
||||
abstract class SingleCall |
||||
{ |
||||
// Warning CS0649: Field is never assigned to, and will always have its default value 0
|
||||
#pragma warning disable 649
|
||||
public int ID; |
||||
public int ParentID; |
||||
public int DataSetID; |
||||
#pragma warning restore 649
|
||||
|
||||
public static readonly FieldInfo IDField = typeof(SingleCall).GetField("ID"); |
||||
public static readonly FieldInfo ParentIDField = typeof(SingleCall).GetField("ParentID"); |
||||
public static readonly FieldInfo DataSetIdField = typeof(SingleCall).GetField("DataSetID"); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Base class for nodes in the Query AST.
|
||||
/// </summary>
|
||||
abstract class QueryNode : Expression |
||||
{ |
||||
public enum SqlStatementKind |
||||
{ |
||||
Select, |
||||
SelectWhere, |
||||
SelectGroupBy, |
||||
SelectGroupByHaving, |
||||
SelectOrderBy, |
||||
SelectLimit |
||||
} |
||||
|
||||
public readonly QueryNode Target; |
||||
|
||||
protected QueryNode(QueryNode target) |
||||
{ |
||||
this.Target = target; |
||||
} |
||||
|
||||
protected override ExpressionType NodeTypeImpl() |
||||
{ |
||||
return ExpressionType.Extension; |
||||
} |
||||
|
||||
protected override Type TypeImpl() |
||||
{ |
||||
return typeof(IQueryable<CallTreeNode>); |
||||
} |
||||
|
||||
protected abstract override Expression VisitChildren(Func<Expression, Expression> visitor); |
||||
|
||||
/// <summary>
|
||||
/// SQL construction documentation see SQLiteQueryProvider documentation.
|
||||
/// </summary>
|
||||
public abstract SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context); |
||||
|
||||
/// <summary>
|
||||
/// Wraps the current SQL statement into an inner select, allowing to continue with "WHERE" queries
|
||||
/// even after ORDER BY or LIMIT.
|
||||
/// </summary>
|
||||
protected static void WrapSqlIntoNestedStatement(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
CallTreeNodeSqlNameSet oldNames = context.CurrentNameSet; |
||||
CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context, false); |
||||
b.Insert(0, "SELECT " + SqlAs(oldNames.ID, newNames.ID) + ", " |
||||
+ SqlAs(oldNames.NameID, newNames.NameID) + ", " |
||||
+ SqlAs(oldNames.CpuCyclesSpent, newNames.CpuCyclesSpent) + ", " |
||||
+ SqlAs(oldNames.CallCount, newNames.CallCount) + ", " |
||||
+ SqlAs(oldNames.HasChildren, newNames.HasChildren) + ", " |
||||
+ SqlAs(oldNames.ActiveCallCount, newNames.ActiveCallCount) |
||||
+ Environment.NewLine + "FROM (" + Environment.NewLine); |
||||
b.AppendLine(")"); |
||||
context.CurrentNameSet = newNames; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Helper function that builds the text 'expression AS newName'
|
||||
/// </summary>
|
||||
protected static string SqlAs(string expression, string newName) |
||||
{ |
||||
if (expression == newName) |
||||
return newName; |
||||
else |
||||
return expression + " AS " + newName; |
||||
} |
||||
|
||||
public IQueryable<CallTreeNode> Execute(SQLiteQueryProvider provider, QueryExecutionOptions options) |
||||
{ |
||||
StringBuilder b = new StringBuilder(); |
||||
BuildSql(b, new SqlQueryContext(provider)); |
||||
if (options.HasLoggers) |
||||
options.WriteLogLine(b.ToString()); |
||||
Stopwatch w = Stopwatch.StartNew(); |
||||
IList<CallTreeNode> result = provider.RunSQLNodeList(b.ToString()); |
||||
w.Stop(); |
||||
if (options.HasLoggers) { |
||||
options.WriteLogLine("Query returned " + result.Count + " rows in " + w.Elapsed); |
||||
} |
||||
return result.AsQueryable(); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Query AST node representing the whole 'FunctionData' table.
|
||||
/// This is the source of all queries.
|
||||
/// Produces SELECT .. FROM .. in SQL.
|
||||
/// </summary>
|
||||
sealed class AllCalls : QueryNode |
||||
{ |
||||
public static readonly AllCalls Instance = new AllCalls(); |
||||
|
||||
private AllCalls() : base(null) |
||||
{ |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
return this; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return "AllCalls"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
CallTreeNodeSqlNameSet newNames = context.CurrentNameSet = new CallTreeNodeSqlNameSet(context, true); |
||||
|
||||
b.AppendLine("SELECT " |
||||
+ SqlAs("id", newNames.ID) + ", " |
||||
+ SqlAs("nameid", newNames.NameID) + ", " |
||||
+ SqlAs("timespent", newNames.CpuCyclesSpent) + ", " // TODO : change to CpuCyclesSpent
|
||||
+ SqlAs("callcount", newNames.CallCount) + ", " |
||||
+ SqlAs("(id != endid)", newNames.HasChildren) + ", " |
||||
+ SqlAs("((datasetid = " + context.StartDataSetID + ") AND isActiveAtStart)", newNames.ActiveCallCount)); |
||||
b.AppendLine("FROM FunctionData"); |
||||
return SqlStatementKind.Select; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Query node that represents the 'group by nameid and merge' operation. Produces SELECT ... GROUP BY .. in SQL.
|
||||
/// </summary>
|
||||
sealed class MergeByName : QueryNode |
||||
{ |
||||
public MergeByName(QueryNode target) |
||||
: base(target) |
||||
{ |
||||
Debug.Assert(target != null); |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
if (newTarget == Target) |
||||
return this; |
||||
else |
||||
return new MergeByName(newTarget); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return Target + ".MergeByName()"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
Target.BuildSql(b, context); |
||||
|
||||
CallTreeNodeSqlNameSet oldNames = context.CurrentNameSet; |
||||
CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context, false); |
||||
b.Insert(0, "SELECT " |
||||
+ SqlAs("GROUP_CONCAT(" + oldNames.ID + ")", newNames.ID) + ", " |
||||
+ SqlAs(oldNames.NameID, newNames.NameID) + ", " |
||||
+ SqlAs("SUM(" + oldNames.CpuCyclesSpent + ")", newNames.CpuCyclesSpent) + ", " |
||||
+ SqlAs("SUM(" + oldNames.CallCount + ")", newNames.CallCount) + ", " |
||||
+ SqlAs("MAX(" + oldNames.HasChildren + ")", newNames.HasChildren) + ", " |
||||
+ SqlAs("SUM(" + oldNames.ActiveCallCount + ")", newNames.ActiveCallCount) |
||||
+ Environment.NewLine + "FROM (" + Environment.NewLine); |
||||
b.AppendLine(") GROUP BY " + oldNames.NameID); |
||||
context.CurrentNameSet = newNames; |
||||
|
||||
return SqlStatementKind.SelectGroupBy; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Query node that filters the input using conditions. Produces WHERE or HAVING in SQL.
|
||||
/// </summary>
|
||||
sealed class Filter : QueryNode |
||||
{ |
||||
/// <summary>
|
||||
/// List of conditions. The operator between these is AND.
|
||||
/// </summary>
|
||||
public readonly ReadOnlyCollection<LambdaExpression> Conditions; |
||||
|
||||
public Filter(QueryNode target, params LambdaExpression[] conditions) |
||||
: base(target) |
||||
{ |
||||
Debug.Assert(target != null); |
||||
foreach (LambdaExpression l in conditions) { |
||||
Debug.Assert(l.Parameters.Count == 1); |
||||
} |
||||
this.Conditions = Array.AsReadOnly(conditions); |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
LambdaExpression[] newConditions = new LambdaExpression[Conditions.Count]; |
||||
bool unchanged = (newTarget == Target); |
||||
for (int i = 0; i < newConditions.Length; i++) { |
||||
newConditions[i] = (LambdaExpression)visitor(Conditions[i]); |
||||
unchanged &= newConditions[i] == Conditions[i]; |
||||
} |
||||
if (unchanged) |
||||
return this; |
||||
else |
||||
return new Filter(newTarget, newConditions); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringBuilder b = new StringBuilder(); |
||||
b.Append(Target.ToString()); |
||||
b.Append(".Filter("); |
||||
for (int i = 0; i < Conditions.Count; i++) { |
||||
if (i > 0) |
||||
b.Append(" && "); |
||||
b.Append(Conditions[i].ToString()); |
||||
} |
||||
b.Append(')'); |
||||
return b.ToString(); |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
SqlStatementKind kind = Target.BuildSql(b, context); |
||||
SqlStatementKind resultKind; |
||||
switch (kind) { |
||||
case SqlStatementKind.Select: |
||||
b.Append(" WHERE "); |
||||
resultKind = SqlStatementKind.SelectWhere; |
||||
break; |
||||
case SqlStatementKind.SelectGroupBy: |
||||
b.Append(" HAVING "); |
||||
resultKind = SqlStatementKind.SelectGroupByHaving; |
||||
break; |
||||
default: |
||||
WrapSqlIntoNestedStatement(b, context); |
||||
b.Append(" WHERE "); |
||||
resultKind = SqlStatementKind.SelectWhere; |
||||
break; |
||||
} |
||||
for (int i = 0; i < Conditions.Count; i++) { |
||||
if (i > 0) |
||||
b.Append(" AND "); |
||||
BuildSqlForCondition(b, context, Conditions[i]); |
||||
} |
||||
b.AppendLine(); |
||||
return resultKind; |
||||
} |
||||
|
||||
static void BuildSqlForCondition(StringBuilder b, SqlQueryContext context, LambdaExpression condition) |
||||
{ |
||||
Debug.Assert(condition.Parameters.Count == 1); |
||||
StringWriter w = new StringWriter(CultureInfo.InvariantCulture); |
||||
ExpressionSqlWriter writer = new ExpressionSqlWriter(w, context.CurrentNameSet, condition.Parameters[0]); |
||||
writer.Write(condition.Body); |
||||
b.Append(w.ToString()); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Query node that limits the amount of data selected. Produces LIMIT in SQL.
|
||||
/// </summary>
|
||||
sealed class Limit : QueryNode |
||||
{ |
||||
public int Start { get; private set; } |
||||
public int Length { get; private set; } |
||||
|
||||
public Limit(QueryNode target, int start, int length) |
||||
: base(target) |
||||
{ |
||||
this.Start = start; |
||||
this.Length = length; |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
if (newTarget == Target) |
||||
return this; |
||||
else |
||||
return new Limit(newTarget, Start, Length); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return Target + ".Limit(" + Start + "," + Length + ")"; |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
SqlStatementKind kind = Target.BuildSql(b, context); |
||||
if (kind == SqlStatementKind.SelectLimit) |
||||
WrapSqlIntoNestedStatement(b, context); |
||||
|
||||
b.Append(" LIMIT " + Length); |
||||
b.AppendLine(" OFFSET " + Start); |
||||
|
||||
return SqlStatementKind.SelectLimit; |
||||
} |
||||
} |
||||
|
||||
class SortArgument |
||||
{ |
||||
readonly LambdaExpression arg; |
||||
readonly bool desc; |
||||
|
||||
public SortArgument(LambdaExpression arg, bool desc) |
||||
{ |
||||
this.arg = arg; |
||||
this.desc = desc; |
||||
} |
||||
|
||||
public bool Descending { |
||||
get { return desc; } |
||||
} |
||||
public LambdaExpression Argument { |
||||
get { return arg; } |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
if (Descending) |
||||
return Argument.ToString() + " DESC"; |
||||
else |
||||
return Argument.ToString(); |
||||
} |
||||
} |
||||
|
||||
sealed class Sort : QueryNode |
||||
{ |
||||
ReadOnlyCollection<SortArgument> arguments; |
||||
|
||||
public Sort(QueryNode target, IList<SortArgument> args) |
||||
: base(target) |
||||
{ |
||||
this.arguments = new ReadOnlyCollection<SortArgument>(args); |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
SqlStatementKind kind = Target.BuildSql(b, context); |
||||
if (kind == SqlStatementKind.SelectOrderBy) |
||||
WrapSqlIntoNestedStatement(b, context); |
||||
|
||||
b.Append(" ORDER BY "); |
||||
|
||||
for (int i = 0; i < arguments.Count; i++) { |
||||
StringWriter w = new StringWriter(); |
||||
ExpressionSqlWriter writer = new ExpressionSqlWriter(w, context.CurrentNameSet, arguments[i].Argument.Parameters[0]); |
||||
writer.Write(arguments[i].Argument.Body); |
||||
|
||||
if (i == 0) |
||||
b.Append(w.ToString()); |
||||
else |
||||
b.Append(", " + w.ToString()); |
||||
|
||||
if (arguments[i].Descending) |
||||
b.Append(" DESC"); |
||||
else |
||||
b.Append(" ASC"); |
||||
} |
||||
|
||||
return SqlStatementKind.SelectOrderBy; |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
if (newTarget == Target) |
||||
return this; |
||||
else |
||||
return new Sort(newTarget, arguments); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringBuilder builder = new StringBuilder(); |
||||
for (int i = 0; i < arguments.Count; i++) { |
||||
if (i > 0) |
||||
builder.Append(", "); |
||||
builder.Append(arguments[i].ToString()); |
||||
} |
||||
return Target + ".Sort(" + builder.ToString() + ")"; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
/// <summary>
|
||||
/// The SingleCall class is never instanciated; it only used to represent database rows
|
||||
/// inside expression trees.
|
||||
/// </summary>
|
||||
abstract class SingleCall |
||||
{ |
||||
// Warning CS0649: Field is never assigned to, and will always have its default value 0
|
||||
#pragma warning disable 649
|
||||
public int ID; |
||||
public int ParentID; |
||||
public int DataSetID; |
||||
#pragma warning restore 649
|
||||
|
||||
public static readonly FieldInfo IDField = typeof(SingleCall).GetField("ID"); |
||||
public static readonly FieldInfo ParentIDField = typeof(SingleCall).GetField("ParentID"); |
||||
public static readonly FieldInfo DataSetIdField = typeof(SingleCall).GetField("DataSetID"); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Base class for nodes in the Query AST.
|
||||
/// </summary>
|
||||
abstract class QueryNode : Expression |
||||
{ |
||||
public enum SqlStatementKind |
||||
{ |
||||
Select, |
||||
SelectWhere, |
||||
SelectGroupBy, |
||||
SelectGroupByHaving, |
||||
SelectOrderBy, |
||||
SelectLimit |
||||
} |
||||
|
||||
public readonly QueryNode Target; |
||||
|
||||
protected QueryNode(QueryNode target) |
||||
{ |
||||
this.Target = target; |
||||
} |
||||
|
||||
protected override ExpressionType NodeTypeImpl() |
||||
{ |
||||
return ExpressionType.Extension; |
||||
} |
||||
|
||||
protected override Type TypeImpl() |
||||
{ |
||||
return typeof(IQueryable<CallTreeNode>); |
||||
} |
||||
|
||||
protected abstract override Expression VisitChildren(Func<Expression, Expression> visitor); |
||||
|
||||
/// <summary>
|
||||
/// SQL construction documentation see SQLiteQueryProvider documentation.
|
||||
/// </summary>
|
||||
public abstract SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context); |
||||
|
||||
/// <summary>
|
||||
/// Wraps the current SQL statement into an inner select, allowing to continue with "WHERE" queries
|
||||
/// even after ORDER BY or LIMIT.
|
||||
/// </summary>
|
||||
protected static void WrapSqlIntoNestedStatement(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
CallTreeNodeSqlNameSet oldNames = context.CurrentNameSet; |
||||
CallTreeNodeSqlNameSet newNames = new CallTreeNodeSqlNameSet(context); |
||||
|
||||
string query = "SELECT " |
||||
+ SqlAs(oldNames.NameID, newNames.NameID) + ", " |
||||
+ SqlAs(oldNames.CpuCyclesSpent, newNames.CpuCyclesSpent) + ", " |
||||
+ SqlAs(oldNames.CallCount, newNames.CallCount) + ", " |
||||
+ SqlAs(oldNames.HasChildren, newNames.HasChildren) + ", " |
||||
+ SqlAs(oldNames.ActiveCallCount, newNames.ActiveCallCount); |
||||
if (context.HasIDList) { |
||||
query += ", " + SqlAs(oldNames.ID, newNames.ID); |
||||
} |
||||
query += Environment.NewLine + "FROM (" + Environment.NewLine; |
||||
b.Insert(0, query); |
||||
b.AppendLine(")"); |
||||
context.SetCurrent(newNames, SqlTableType.None, context.HasIDList); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Helper function that builds the text 'expression AS newName'
|
||||
/// </summary>
|
||||
protected static string SqlAs(string expression, string newName) |
||||
{ |
||||
if (expression == newName) |
||||
return newName; |
||||
else |
||||
return expression + " AS " + newName; |
||||
} |
||||
|
||||
public IQueryable<CallTreeNode> Execute(SQLiteQueryProvider provider, QueryExecutionOptions options) |
||||
{ |
||||
StringBuilder b = new StringBuilder(); |
||||
SqlQueryContext context = new SqlQueryContext(provider); |
||||
BuildSql(b, context); |
||||
if (options.HasLoggers) |
||||
options.WriteLogLine(b.ToString()); |
||||
Stopwatch w = Stopwatch.StartNew(); |
||||
IList<CallTreeNode> result = provider.RunSQLNodeList(b.ToString(), context.HasIDList); |
||||
w.Stop(); |
||||
if (options.HasLoggers) { |
||||
options.WriteLogLine("Query returned " + result.Count + " rows in " + w.Elapsed); |
||||
} |
||||
return result.AsQueryable(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Globalization; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Linq.Expressions; |
||||
using System.Reflection; |
||||
using System.Text; |
||||
|
||||
namespace ICSharpCode.Profiler.Controller.Data.Linq |
||||
{ |
||||
class SortArgument |
||||
{ |
||||
readonly LambdaExpression arg; |
||||
readonly bool desc; |
||||
|
||||
public SortArgument(LambdaExpression arg, bool desc) |
||||
{ |
||||
this.arg = arg; |
||||
this.desc = desc; |
||||
} |
||||
|
||||
public bool Descending { |
||||
get { return desc; } |
||||
} |
||||
public LambdaExpression Argument { |
||||
get { return arg; } |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
if (Descending) |
||||
return Argument.ToString() + " DESC"; |
||||
else |
||||
return Argument.ToString(); |
||||
} |
||||
} |
||||
|
||||
sealed class Sort : QueryNode |
||||
{ |
||||
ReadOnlyCollection<SortArgument> arguments; |
||||
|
||||
public Sort(QueryNode target, IList<SortArgument> args) |
||||
: base(target) |
||||
{ |
||||
this.arguments = new ReadOnlyCollection<SortArgument>(args); |
||||
} |
||||
|
||||
public override SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context) |
||||
{ |
||||
SqlStatementKind kind = Target.BuildSql(b, context); |
||||
if (kind == SqlStatementKind.SelectOrderBy) |
||||
WrapSqlIntoNestedStatement(b, context); |
||||
|
||||
b.Append(" ORDER BY "); |
||||
|
||||
for (int i = 0; i < arguments.Count; i++) { |
||||
StringWriter w = new StringWriter(); |
||||
ExpressionSqlWriter writer = new ExpressionSqlWriter(w, context, arguments[i].Argument.Parameters[0]); |
||||
writer.Write(arguments[i].Argument.Body); |
||||
|
||||
if (i == 0) |
||||
b.Append(w.ToString()); |
||||
else |
||||
b.Append(", " + w.ToString()); |
||||
|
||||
if (arguments[i].Descending) |
||||
b.Append(" DESC"); |
||||
else |
||||
b.Append(" ASC"); |
||||
} |
||||
|
||||
return SqlStatementKind.SelectOrderBy; |
||||
} |
||||
|
||||
protected override Expression VisitChildren(Func<Expression, Expression> visitor) |
||||
{ |
||||
QueryNode newTarget = (QueryNode)visitor(Target); |
||||
if (newTarget == Target) |
||||
return this; |
||||
else |
||||
return new Sort(newTarget, arguments); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringBuilder builder = new StringBuilder(); |
||||
for (int i = 0; i < arguments.Count; i++) { |
||||
if (i > 0) |
||||
builder.Append(", "); |
||||
builder.Append(arguments[i].ToString()); |
||||
} |
||||
return Target + ".Sort(" + builder.ToString() + ")"; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue