Browse Source

LINQ-to-Profiler documentation

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5014 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
9426732de9
  1. 5
      samples/AvalonEdit.Sample/article.html
  2. 16
      src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs
  3. 6
      src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs
  4. 70
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs

5
samples/AvalonEdit.Sample/article.html

@ -108,8 +108,9 @@ Is the syntax highlighting part of the model?
<p>In my quest for a good representation of the model, I decided on a radical strategy: <p>In my quest for a good representation of the model, I decided on a radical strategy:
<b>if it's not a <code>char</code>, it's not in the model</b>! <b>if it's not a <code>char</code>, it's not in the model</b>!
<p>The main class of the model is <code>ICSharpCode.AvalonEdit.Document.TextDocument</code>, <p>The main class of the model is <code>ICSharpCode.AvalonEdit.Document.TextDocument</code>.
and it's sort of a <code>StringBuilder</code> with events. Basically, the document is a <code>StringBuilder</code> with events.
However, the <code>Document</code> namespace also contains several features that are useful to applications working with the text editor.
<p><i>Simplified</i> definition of TextDocument: <p><i>Simplified</i> definition of TextDocument:
<pre lang="cs">public sealed class TextDocument : ITextSource <pre lang="cs">public sealed class TextDocument : ITextSource

16
src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs

@ -19,6 +19,10 @@ using System.Text;
namespace ICSharpCode.Profiler.Controller.Data.Linq namespace ICSharpCode.Profiler.Controller.Data.Linq
{ {
/// <summary>
/// Performs query optimizations.
/// See the documentation on SQLiteQueryProvider for the list of optimizations being performed.
/// </summary>
sealed class OptimizeQueryExpressionVisitor : System.Linq.Expressions.ExpressionVisitor sealed class OptimizeQueryExpressionVisitor : System.Linq.Expressions.ExpressionVisitor
{ {
QueryNode Visit(QueryNode queryNode) QueryNode Visit(QueryNode queryNode)
@ -52,11 +56,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
QueryNode ReorderFilter(Filter filter) QueryNode ReorderFilter(Filter filter)
{ {
if (filter.Target is Filter) { if (filter.Target is Filter) {
// Filter(Filter(x, y), z) => Filter(x, y AND z) // x.Filter(y).Filter(z) -> x.Filter(y && z)
Filter innerFilter = (Filter)filter.Target; Filter innerFilter = (Filter)filter.Target;
return ReorderFilter(new Filter(innerFilter.Target, innerFilter.Conditions.Concat(filter.Conditions).ToArray())); return ReorderFilter(new Filter(innerFilter.Target, innerFilter.Conditions.Concat(filter.Conditions).ToArray()));
} else if (filter.Target is MergeByName) { } else if (filter.Target is MergeByName) {
// Filter(MergeByName(x), <criteria>) => MergeByName(Filter(x, <criteria>)) for some safe criterias // x.MergeByName().Filter(<criteria>) -> x.Filter(x, <criteria>).MergeByName() for some safe criterias
QueryNode innerTarget = filter.Target.Target; QueryNode innerTarget = filter.Target.Target;
var conditionsToMoveIntoFilter = filter.Conditions.Where(c => IsConditionSafeForMoveIntoMergeByName.Test(c)).ToArray(); var conditionsToMoveIntoFilter = filter.Conditions.Where(c => IsConditionSafeForMoveIntoMergeByName.Test(c)).ToArray();
if (conditionsToMoveIntoFilter.Length != 0) { if (conditionsToMoveIntoFilter.Length != 0) {
@ -83,23 +87,21 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
} }
static readonly MemberInfo[] SafeMembers = { static readonly MemberInfo[] SafeMembers = {
KnownMembers.CallTreeNode_NameMapping, KnownMembers.CallTreeNode_NameMapping
KnownMembers.NameMapping_ID,
KnownMembers.ListOfInt_Contains,
}; };
bool IsSafe = true; bool IsSafe = true;
protected override Expression VisitMember(MemberExpression node) protected override Expression VisitMember(MemberExpression node)
{ {
if (!SafeMembers.Contains(node.Member)) if (node.Expression.NodeType == ExpressionType.Parameter && !SafeMembers.Contains(node.Member))
IsSafe = false; IsSafe = false;
return base.VisitMember(node); return base.VisitMember(node);
} }
protected override Expression VisitMethodCall(MethodCallExpression node) protected override Expression VisitMethodCall(MethodCallExpression node)
{ {
if (!SafeMembers.Contains(node.Method)) if (node.Object.NodeType == ExpressionType.Parameter && !SafeMembers.Contains(node.Method))
IsSafe = false; IsSafe = false;
return base.VisitMethodCall(node); return base.VisitMethodCall(node);
} }

6
src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs

@ -66,6 +66,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
protected abstract override Expression VisitChildren(Func<Expression, Expression> visitor); 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); public abstract SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context);
/// <summary> /// <summary>
@ -87,6 +90,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
context.CurrentNameSet = newNames; context.CurrentNameSet = newNames;
} }
/// <summary>
/// Helper function that builds the text 'expression AS newName'
/// </summary>
protected static string SqlAs(string expression, string newName) protected static string SqlAs(string expression, string newName)
{ {
if (expression == newName) if (expression == newName)

70
src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs

@ -31,6 +31,76 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
/// 4. Execution of Queries (by converting them to SQL and running it on the DB) /// 4. Execution of Queries (by converting them to SQL and running it on the DB)
/// 5. Execution of remaining query using LINQ-to-Objects /// 5. Execution of remaining query using LINQ-to-Objects
/// </summary> /// </summary>
/// <remarks>
/// The base class of all QueryAst nodes is QueryNode. A QueryNode represents a query of type IQueryable{CallTreeNode}.
/// For QueryNodes that have input, that input must be another QueryNode.
///
/// QueryAst nodes:
/// AllCalls: represents the whole FunctionData table
/// input.Filter(x => condition1(x) &amp;&amp; y => condition2(y)): WHERE clause with multiple conditions
/// input.MergeByName(): GROUP BY nameid
///
/// Valid expressions in QueryAst nodes:
/// Only a limited set of expressions are valid in conditions and sort descriptors.
/// These are checked by the SafeExpressionImporter.
/// - Integer constants
/// - Binary operators: &lt; &lt;= &gt; &gt;= == !=
/// - value(List{int}).Contains(validExpr)
/// - if c is the lambda parameter, then these expressions are valid:
/// c.NameMapping.ID
///
/// Additionally, field references on a lambda parameter of type SingleCall are valid inside
/// filters that operate directly on "AllCalls" (e.g. AllCalls.Filter()).
/// In other cases (other filters, sort descriptors), SingleCall usage is invalid.
/// SingleCall usage cannot be imported using SafeExpressionImporter; but is created directly for
/// some expressions on SQLiteCallTreeNode (see translation rules below).
///
/// Translation rules from CallTreeNode object model to QueryAst:
/// Properties serving as query roots:
/// sqliteCallTreeNode.Children = AllCalls.Filter((SingleCall c) -> sqliteCallTreeNode.ids.Contains(c.ParentID))
/// profilingDataSQLiteProvider.GetFunctions = AllCalls.Filter((SingleCall c) -> @start &lt;= c.DataSetId &amp;&amp; c.DataSetId &lt;= @end).MergeByName()
///
/// Translation rules for query nodes:
/// input.Where(x => f(x)) -> input.Filter(x => f'(x)), if f(x) is a safe expression
/// Note: if the root expression of a filter condition is the '&amp;&amp;' operator, a filter with multiple conditions is created.
/// input.Where(c => c.CallCount > 10 &amp;&amp; c.TimeSpent > 1)
/// -> input.Filter(c => c.CallCount > 10 &amp;&amp; c => c.TimeSpent > 1)
///
/// input.Select(x => x) -> input
/// This rule is necessary to remove degenerate selects so that the parts of the query continuing after the select
/// can also be represented as QueryNodes.
///
/// Translation rules for expression importer:
/// Any valid expressions (as defined in 'valid expressions in QueryAst nodes') are copied over directly.
/// Moreover, these expressions are be converted into valid expressions:
/// c.IsUserCode -> c.NameMapping.ID > 0
///
/// Optimization of QueryAst:
/// The OptimizeQueryExpressionVisitor is performing these optimizations:
/// x.Filter(y).Filter(z) -> x.Filter(y &amp;&amp; z)
/// x.MergeByName().Filter(criteria) -> x.Filter(x, criteria).MergeByName() for some safe criterias
/// Criterias are safe if they access no CallTreeNode properties except for NameMapping
///
/// SQL string building and execution:
/// It must be possible to create SQL for every combination of QueryNodes, even if they do strange things like merging multiple times.
/// To solve this, we define that every SQL query must have the same set of result fields, which are dynamically named to
/// ensure unique names even with nested queries. The current set of names is held in the SqlQueryContext.
/// Indeed, conceptually we build a nested query for every QueryNode.
///
/// The query is built inside-out: the innermost nested query is appended first to the StringBuilder, the outer queries will
/// then insert "SELECT ... FROM (" into the beginning of the StringBuilder and append ") outer query" at the end.
///
/// The return value of the QueryNode.BuildSql method contains the kind of SQL statement that is currently in the StringBuilder.
/// This allows us to simply append clauses in the majority of cases, only rarely the QueryNode.WrapSqlIntoNestedStatement
/// method will be used to create an outer query.
/// For example, a Filter will simply append a WHERE to a "SELECT .. FROM .." query. To a "SELECT .. FROM .. GROUP BY .." query,
/// a Filter will append HAVING. Only in rare cases like filtering after sorting or after limiting the number of elements,
/// a Filter query node will create a nested query.
///
/// Because all constructed SELECT queries always select fields with the same meaning in the same order, executing the query is
/// a matter of simply filling the SQLiteCallTreeNodes with the query results.
///
/// </remarks>
sealed class SQLiteQueryProvider : QueryProvider sealed class SQLiteQueryProvider : QueryProvider
{ {
readonly ProfilingDataSQLiteProvider sqliteProvider; readonly ProfilingDataSQLiteProvider sqliteProvider;

Loading…
Cancel
Save