diff --git a/samples/AvalonEdit.Sample/article.html b/samples/AvalonEdit.Sample/article.html index 52be569bb3..6e8c9d574e 100644 --- a/samples/AvalonEdit.Sample/article.html +++ b/samples/AvalonEdit.Sample/article.html @@ -108,8 +108,9 @@ Is the syntax highlighting part of the model?
In my quest for a good representation of the model, I decided on a radical strategy:
if it's not a char
, it's not in the model!
-
The main class of the model is ICSharpCode.AvalonEdit.Document.TextDocument
,
-and it's sort of a StringBuilder
with events.
+
The main class of the model is ICSharpCode.AvalonEdit.Document.TextDocument
.
+Basically, the document is a StringBuilder
with events.
+However, the Document
namespace also contains several features that are useful to applications working with the text editor.
Simplified definition of TextDocument:
public sealed class TextDocument : ITextSource
diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs
index 124a9b528e..79c013eca0 100644
--- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs
+++ b/src/AddIns/Misc/Profiler/Controller/Data/Linq/OptimizeQueryExpressionVisitor.cs
@@ -19,6 +19,10 @@ using System.Text;
namespace ICSharpCode.Profiler.Controller.Data.Linq
{
+ ///
+ /// Performs query optimizations.
+ /// See the documentation on SQLiteQueryProvider for the list of optimizations being performed.
+ ///
sealed class OptimizeQueryExpressionVisitor : System.Linq.Expressions.ExpressionVisitor
{
QueryNode Visit(QueryNode queryNode)
@@ -52,11 +56,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
QueryNode ReorderFilter(Filter 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;
return ReorderFilter(new Filter(innerFilter.Target, innerFilter.Conditions.Concat(filter.Conditions).ToArray()));
} else if (filter.Target is MergeByName) {
- // Filter(MergeByName(x), ) => MergeByName(Filter(x, )) for some safe criterias
+ // x.MergeByName().Filter() -> x.Filter(x, ).MergeByName() for some safe criterias
QueryNode innerTarget = filter.Target.Target;
var conditionsToMoveIntoFilter = filter.Conditions.Where(c => IsConditionSafeForMoveIntoMergeByName.Test(c)).ToArray();
if (conditionsToMoveIntoFilter.Length != 0) {
@@ -83,23 +87,21 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
}
static readonly MemberInfo[] SafeMembers = {
- KnownMembers.CallTreeNode_NameMapping,
- KnownMembers.NameMapping_ID,
- KnownMembers.ListOfInt_Contains,
+ KnownMembers.CallTreeNode_NameMapping
};
bool IsSafe = true;
protected override Expression VisitMember(MemberExpression node)
{
- if (!SafeMembers.Contains(node.Member))
+ if (node.Expression.NodeType == ExpressionType.Parameter && !SafeMembers.Contains(node.Member))
IsSafe = false;
return base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
- if (!SafeMembers.Contains(node.Method))
+ if (node.Object.NodeType == ExpressionType.Parameter && !SafeMembers.Contains(node.Method))
IsSafe = false;
return base.VisitMethodCall(node);
}
diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs
index 5ccd3c54b4..fa6b527c4f 100644
--- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/QueryAst.cs
+++ b/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 visitor);
+ ///
+ /// SQL construction documentation see SQLiteQueryProvider documentation.
+ ///
public abstract SqlStatementKind BuildSql(StringBuilder b, SqlQueryContext context);
///
@@ -87,6 +90,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
context.CurrentNameSet = newNames;
}
+ ///
+ /// Helper function that builds the text 'expression AS newName'
+ ///
protected static string SqlAs(string expression, string newName)
{
if (expression == newName)
diff --git a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs b/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs
index 5203494db3..79014f81ac 100644
--- a/src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs
+++ b/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)
/// 5. Execution of remaining query using LINQ-to-Objects
///
+ ///
+ /// 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) && 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: < <= > >= == !=
+ /// - 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 <= c.DataSetId && c.DataSetId <= @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 '&&' operator, a filter with multiple conditions is created.
+ /// input.Where(c => c.CallCount > 10 && c.TimeSpent > 1)
+ /// -> input.Filter(c => c.CallCount > 10 && 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 && 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.
+ ///
+ ///
sealed class SQLiteQueryProvider : QueryProvider
{
readonly ProfilingDataSQLiteProvider sqliteProvider;