From 9426732de999926bb41ac2764e92e456da79dd8d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 28 Sep 2009 14:08:49 +0000 Subject: [PATCH] LINQ-to-Profiler documentation git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5014 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- samples/AvalonEdit.Sample/article.html | 5 +- .../Linq/OptimizeQueryExpressionVisitor.cs | 16 +++-- .../Profiler/Controller/Data/Linq/QueryAst.cs | 6 ++ .../Data/Linq/SQLiteQueryProvider.cs | 70 +++++++++++++++++++ 4 files changed, 88 insertions(+), 9 deletions(-) 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;