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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
// <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 @@ |
|||||||
|
// <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 @@ |
|||||||
|
// <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