5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
\r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
\r
8 // of this software and associated documentation files (the "Software"), to deal
\r
9 // in the Software without restriction, including without limitation the rights
\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
11 // copies of the Software, and to permit persons to whom the Software is
\r
12 // furnished to do so, subject to the following conditions:
\r
14 // The above copyright notice and this permission notice shall be included in
\r
15 // all copies or substantial portions of the Software.
\r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
\r
28 using System.Collections.Generic;
\r
29 using System.Diagnostics;
\r
31 using System.Linq.Expressions;
\r
32 using System.Text.RegularExpressions;
\r
34 using DbLinq.Data.Linq.Sugar.ExpressionMutator;
\r
35 using DbLinq.Data.Linq.Sugar.Expressions;
\r
36 using DbLinq.Factory;
\r
39 namespace DbLinq.Data.Linq.Sugar.Implementation
\r
42 /// Full query builder, with cache management
\r
43 /// 1. Parses Linq Expression
\r
44 /// 2. Generates SQL
\r
46 internal partial class QueryBuilder : IQueryBuilder
\r
48 public IExpressionLanguageParser ExpressionLanguageParser { get; set; }
\r
49 public IExpressionDispatcher ExpressionDispatcher { get; set; }
\r
50 public IPrequelAnalyzer PrequelAnalyzer { get; set; }
\r
51 public IExpressionOptimizer ExpressionOptimizer { get; set; }
\r
52 public ISpecialExpressionTranslator SpecialExpressionTranslator { get; set; }
\r
53 public ISqlBuilder SqlBuilder { get; set; }
\r
55 public QueryBuilder()
\r
57 ExpressionLanguageParser = ObjectFactory.Get<IExpressionLanguageParser>();
\r
58 ExpressionDispatcher = ObjectFactory.Get<IExpressionDispatcher>();
\r
59 PrequelAnalyzer = ObjectFactory.Get<IPrequelAnalyzer>();
\r
60 ExpressionOptimizer = ObjectFactory.Get<IExpressionOptimizer>();
\r
61 SpecialExpressionTranslator = ObjectFactory.Get<ISpecialExpressionTranslator>();
\r
62 SqlBuilder = ObjectFactory.Get<ISqlBuilder>();
\r
66 /// Builds the ExpressionQuery:
\r
67 /// - parses Expressions and builds row creator
\r
68 /// - checks names unicity
\r
70 /// <param name="expressions"></param>
\r
71 /// <param name="queryContext"></param>
\r
72 /// <returns></returns>
\r
73 protected virtual ExpressionQuery BuildExpressionQuery(ExpressionChain expressions, QueryContext queryContext)
\r
75 var builderContext = new BuilderContext(queryContext);
\r
76 BuildExpressionQuery(expressions, builderContext);
\r
77 CheckTablesAlias(builderContext);
\r
78 CheckParametersAlias(builderContext);
\r
79 return builderContext.ExpressionQuery;
\r
83 /// Finds all registered tables or columns with the given name.
\r
84 /// We exclude parameter because they won't be prefixed/suffixed the same way (well, that's a guess, I hope it's a good one)
\r
86 /// <param name="name"></param>
\r
87 /// <param name="builderContext"></param>
\r
88 /// <returns></returns>
\r
89 protected virtual IList<Expression> FindExpressionsByName(string name, BuilderContext builderContext)
\r
91 var expressions = new List<Expression>();
\r
92 expressions.AddRange((from t in builderContext.EnumerateAllTables() where t.Alias == name select (Expression)t).Distinct());
\r
93 expressions.AddRange(from c in builderContext.EnumerateScopeColumns() where c.Alias == name select (Expression)c);
\r
97 protected virtual string MakeName(string aliasBase, int index, string anonymousBase, BuilderContext builderContext)
\r
99 if (string.IsNullOrEmpty(aliasBase))
\r
100 aliasBase = anonymousBase;
\r
101 return string.Format("{0}{1}", aliasBase, index);
\r
104 protected virtual string MakeTableName(string aliasBase, int index, BuilderContext builderContext)
\r
106 return MakeName(aliasBase, index, "t", builderContext);
\r
109 protected virtual string MakeParameterName(string aliasBase, int index, BuilderContext builderContext)
\r
111 return MakeName(aliasBase, index, "p", builderContext);
\r
115 /// Give all non-aliased tables a name
\r
117 /// <param name="builderContext"></param>
\r
118 protected virtual void CheckTablesAlias(BuilderContext builderContext)
\r
120 var tables = builderContext.EnumerateAllTables().Distinct().ToList();
\r
121 // just to be nice: if we have only one table involved, there's no need to alias it
\r
122 if (tables.Count == 1)
\r
124 tables[0].Alias = null;
\r
128 foreach (var tableExpression in tables)
\r
130 // if no alias, or duplicate alias
\r
131 if (string.IsNullOrEmpty(tableExpression.Alias) ||
\r
132 FindExpressionsByName(tableExpression.Alias, builderContext).Count > 1)
\r
134 int anonymousIndex = 0;
\r
135 var aliasBase = tableExpression.Alias;
\r
136 // we try to assign one until we have a unique alias
\r
139 tableExpression.Alias = MakeTableName(aliasBase, ++anonymousIndex, builderContext);
\r
140 } while (FindExpressionsByName(tableExpression.Alias, builderContext).Count != 1);
\r
146 protected virtual IList<InputParameterExpression> FindParametersByName(string name, BuilderContext builderContext)
\r
148 return (from p in builderContext.ExpressionQuery.Parameters where p.Alias == name select p).ToList();
\r
152 /// Gives anonymous parameters a name and checks for names unicity
\r
153 /// The fact of giving a nice name just helps for readability
\r
155 /// <param name="builderContext"></param>
\r
156 protected virtual void CheckParametersAlias(BuilderContext builderContext)
\r
158 foreach (var externalParameterExpression in builderContext.ExpressionQuery.Parameters)
\r
160 if (string.IsNullOrEmpty(externalParameterExpression.Alias)
\r
161 || FindParametersByName(externalParameterExpression.Alias, builderContext).Count > 1)
\r
163 int anonymousIndex = 0;
\r
164 var aliasBase = externalParameterExpression.Alias;
\r
165 // we try to assign one until we have a unique alias
\r
168 externalParameterExpression.Alias = MakeTableName(aliasBase, ++anonymousIndex, builderContext);
\r
169 } while (FindParametersByName(externalParameterExpression.Alias, builderContext).Count > 1);
\r
175 /// Builds and chains the provided Expressions
\r
177 /// <param name="expressions"></param>
\r
178 /// <param name="builderContext"></param>
\r
179 protected virtual void BuildExpressionQuery(ExpressionChain expressions, BuilderContext builderContext)
\r
181 var previousExpression = ExpressionDispatcher.CreateTableExpression(expressions.Expressions[0], builderContext);
\r
182 previousExpression = BuildExpressionQuery(expressions, previousExpression, builderContext);
\r
183 BuildOffsetsAndLimits(builderContext);
\r
184 // then prepare Parts for SQL translation
\r
185 PrepareSqlOperands(builderContext);
\r
186 // now, we optimize anything we can
\r
187 OptimizeQuery(builderContext);
\r
188 // finally, compile our object creation method
\r
189 CompileRowCreator(builderContext);
\r
190 // in the very end, we keep the SELECT clause
\r
191 builderContext.ExpressionQuery.Select = builderContext.CurrentSelect;
\r
195 /// Builds the ExpressionQuery main Expression, given a Table (or projection) expression
\r
197 /// <param name="expressions"></param>
\r
198 /// <param name="tableExpression"></param>
\r
199 /// <param name="builderContext"></param>
\r
200 /// <returns></returns>
\r
201 protected Expression BuildExpressionQuery(ExpressionChain expressions, Expression tableExpression, BuilderContext builderContext)
\r
203 var last = expressions.Last();
\r
204 foreach (var expression in expressions)
\r
206 if (expression == last)
\r
207 builderContext.IsExternalInExpressionChain = true;
\r
209 // write full debug
\r
210 #if DEBUG && !MONO_STRICT
\r
211 var log = builderContext.QueryContext.DataContext.Log;
\r
213 log.WriteExpression(expression);
\r
215 // Convert linq Expressions to QueryOperationExpressions and QueryConstantExpressions
\r
216 // Query expressions language identification
\r
217 var currentExpression = ExpressionLanguageParser.Parse(expression, builderContext);
\r
218 // Query expressions query identification
\r
219 currentExpression = ExpressionDispatcher.Analyze(currentExpression, tableExpression, builderContext);
\r
221 if(! builderContext.IsExternalInExpressionChain)
\r
223 EntitySetExpression setExpression = currentExpression as EntitySetExpression;
\r
224 if (setExpression != null)
\r
225 currentExpression = setExpression.TableExpression;
\r
227 tableExpression = currentExpression;
\r
229 ExpressionDispatcher.BuildSelect(tableExpression, builderContext);
\r
230 return tableExpression;
\r
233 public virtual SelectExpression BuildSelectExpression(ExpressionChain expressions, Expression tableExpression, BuilderContext builderContext)
\r
235 BuildExpressionQuery(expressions, tableExpression, builderContext);
\r
236 return builderContext.CurrentSelect;
\r
240 /// This is a hint for SQL generations
\r
242 /// <param name="builderContext"></param>
\r
243 protected virtual void BuildOffsetsAndLimits(BuilderContext builderContext)
\r
245 foreach (var selectExpression in builderContext.SelectExpressions)
\r
247 if (selectExpression.Offset != null && selectExpression.Limit != null)
\r
249 selectExpression.OffsetAndLimit = Expression.Add(selectExpression.Offset, selectExpression.Limit);
\r
255 /// Builds the delegate to create a row
\r
257 /// <param name="builderContext"></param>
\r
258 protected virtual void CompileRowCreator(BuilderContext builderContext)
\r
260 var reader = builderContext.CurrentSelect.Reader;
\r
261 reader = (LambdaExpression)SpecialExpressionTranslator.Translate(reader);
\r
262 reader = (LambdaExpression)ExpressionOptimizer.Optimize(reader, builderContext);
\r
263 builderContext.ExpressionQuery.RowObjectCreator = reader.Compile();
\r
267 /// Prepares SELECT operands to help SQL transaltion
\r
269 /// <param name="builderContext"></param>
\r
270 protected virtual void PrepareSqlOperands(BuilderContext builderContext)
\r
272 ProcessExpressions(PrequelAnalyzer.Analyze, true, builderContext);
\r
276 /// Processes all expressions in query, with the option to process only SQL targetting expressions
\r
277 /// This method is generic, it receives a delegate which does the real processing
\r
279 /// <param name="processor"></param>
\r
280 /// <param name="processOnlySqlParts"></param>
\r
281 /// <param name="builderContext"></param>
\r
282 protected virtual void ProcessExpressions(Func<Expression, BuilderContext, Expression> processor,
\r
283 bool processOnlySqlParts, BuilderContext builderContext)
\r
285 for (int scopeExpressionIndex = 0; scopeExpressionIndex < builderContext.SelectExpressions.Count; scopeExpressionIndex++)
\r
287 // no need to process the select itself here, all ScopeExpressions that are operands are processed as operands
\r
288 // and the main ScopeExpression (the SELECT) is processed below
\r
289 var scopeExpression = builderContext.SelectExpressions[scopeExpressionIndex];
\r
292 List<int> whereToRemove = new List<int>(); // List of where clausole evaluating TRUE (could be ignored and so removed)
\r
293 bool falseWhere = false; // true when the full where evaluate to FALSE
\r
294 for (int whereIndex = 0; whereIndex < scopeExpression.Where.Count; whereIndex++)
\r
296 Expression whereClausole = processor(scopeExpression.Where[whereIndex], builderContext);
\r
297 ConstantExpression constantWhereClausole = whereClausole as ConstantExpression;
\r
298 if (constantWhereClausole != null)
\r
300 if (constantWhereClausole.Value.Equals(false))
\r
305 else if (constantWhereClausole.Value.Equals(true))
\r
307 whereToRemove.Add(whereIndex);
\r
311 scopeExpression.Where[whereIndex] = whereClausole;
\r
313 if (scopeExpression.Where.Count > 0)
\r
317 scopeExpression.Where.Clear();
\r
318 scopeExpression.Where.Add(Expression.Equal(Expression.Constant(true), Expression.Constant(false)));
\r
321 foreach (int whereIndex in whereToRemove)
\r
322 scopeExpression.Where.RemoveAt(whereIndex);
\r
326 if (scopeExpression.Offset != null)
\r
327 scopeExpression.Offset = processor(scopeExpression.Offset, builderContext);
\r
328 if (scopeExpression.Limit != null)
\r
329 scopeExpression.Limit = processor(scopeExpression.Limit, builderContext);
\r
330 if (scopeExpression.OffsetAndLimit != null)
\r
331 scopeExpression.OffsetAndLimit = processor(scopeExpression.OffsetAndLimit, builderContext);
\r
333 builderContext.SelectExpressions[scopeExpressionIndex] = scopeExpression;
\r
335 // now process the main SELECT
\r
336 if (processOnlySqlParts)
\r
338 // if we process only the SQL Parts, these are the operands
\r
339 var newOperands = new List<Expression>();
\r
340 foreach (var operand in builderContext.CurrentSelect.Operands)
\r
341 newOperands.Add(processor(operand, builderContext));
\r
342 builderContext.CurrentSelect = builderContext.CurrentSelect.ChangeOperands(newOperands);
\r
346 // the output parameters and result builder
\r
347 builderContext.CurrentSelect = (SelectExpression)processor(builderContext.CurrentSelect, builderContext);
\r
352 /// Optimizes the query by optimizing subexpressions, and preparsing constant expressions
\r
354 /// <param name="builderContext"></param>
\r
355 protected virtual void OptimizeQuery(BuilderContext builderContext)
\r
357 ProcessExpressions(ExpressionOptimizer.Optimize, false, builderContext);
\r
360 protected virtual SelectQuery BuildSqlQuery(ExpressionQuery expressionQuery, QueryContext queryContext)
\r
362 var sql = SqlBuilder.BuildSelect(expressionQuery, queryContext);
\r
363 var sqlQuery = new SelectQuery(queryContext.DataContext, sql, expressionQuery.Parameters, expressionQuery.RowObjectCreator, expressionQuery.Select.ExecuteMethodName);
\r
367 private static IQueryCache queryCache;
\r
368 protected IQueryCache QueryCache
\r
372 if (queryCache == null)
\r
373 queryCache = ObjectFactory.Get<IQueryCache>();
\r
378 protected virtual SelectQuery GetFromSelectCache(ExpressionChain expressions)
\r
380 var cache = QueryCache;
\r
381 return cache.GetFromSelectCache(expressions);
\r
384 protected virtual void SetInSelectCache(ExpressionChain expressions, SelectQuery sqlSelectQuery)
\r
386 var cache = QueryCache;
\r
387 cache.SetInSelectCache(expressions, sqlSelectQuery);
\r
390 protected virtual Delegate GetFromTableReaderCache(Type tableType, IList<string> columns)
\r
392 var cache = QueryCache;
\r
393 return cache.GetFromTableReaderCache(tableType, columns);
\r
396 protected virtual void SetInTableReaderCache(Type tableType, IList<string> columns, Delegate tableReader)
\r
398 var cache = queryCache;
\r
399 cache.SetInTableReaderCache(tableType, columns, tableReader);
\r
403 /// Main entry point for the class. Builds or retrive from cache a SQL query corresponding to given Expressions
\r
405 /// <param name="expressions"></param>
\r
406 /// <param name="queryContext"></param>
\r
407 /// <returns></returns>
\r
408 public SelectQuery GetSelectQuery(ExpressionChain expressions, QueryContext queryContext)
\r
410 SelectQuery query = null;
\r
411 if (queryContext.DataContext.QueryCacheEnabled)
\r
413 query = GetFromSelectCache(expressions);
\r
417 Profiler.At("START: GetSelectQuery(), building Expression query");
\r
418 var expressionsQuery = BuildExpressionQuery(expressions, queryContext);
\r
419 Profiler.At("END: GetSelectQuery(), building Expression query");
\r
421 Profiler.At("START: GetSelectQuery(), building Sql query");
\r
422 query = BuildSqlQuery(expressionsQuery, queryContext);
\r
423 Profiler.At("END: GetSelectQuery(), building Sql query");
\r
425 if (queryContext.DataContext.QueryCacheEnabled)
\r
427 SetInSelectCache(expressions, query);
\r
434 /// Returns a Delegate to create a row for a given IDataRecord
\r
435 /// The Delegate is Func<IDataRecord,MappingContext,"tableType">
\r
437 /// <param name="tableType">The table type (must be managed by DataContext)</param>
\r
438 /// <param name="parameters"></param>
\r
439 /// <param name="queryContext"></param>
\r
440 /// <returns></returns>
\r
441 public virtual Delegate GetTableReader(Type tableType, IList<string> parameters, QueryContext queryContext)
\r
443 Delegate reader = null;
\r
444 if (queryContext.DataContext.QueryCacheEnabled)
\r
446 reader = GetFromTableReaderCache(tableType, parameters);
\r
448 if (reader == null)
\r
450 var lambda = ExpressionDispatcher.BuildTableReader(tableType, parameters,
\r
451 new BuilderContext(queryContext));
\r
452 reader = lambda.Compile();
\r
453 if (queryContext.DataContext.QueryCacheEnabled)
\r
455 SetInTableReaderCache(tableType, parameters, reader);
\r
461 private static readonly Regex parameterIdentifierEx = new Regex(@"\{(?<var>[\d.]+)\}", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled);
\r
464 /// Converts a direct SQL query to a safe query with named parameters
\r
466 /// <param name="sql">Raw SQL query</param>
\r
467 /// <param name="queryContext"></param>
\r
468 /// <returns></returns>
\r
469 public virtual DirectQuery GetDirectQuery(string sql, QueryContext queryContext)
\r
472 var safeSql = queryContext.DataContext.Vendor.SqlProvider.GetSafeQuery(sql);
\r
473 var parameters = new List<string>();
\r
474 var parameterizedSql = parameterIdentifierEx.Replace(safeSql, delegate(Match e)
\r
476 var field = e.Groups[1].Value;
\r
477 var parameterIndex = int.Parse(field);
\r
478 while (parameters.Count <= parameterIndex)
\r
479 parameters.Add(string.Empty);
\r
480 var literalParameterName =
\r
481 queryContext.DataContext.Vendor.SqlProvider.GetParameterName(string.Format("p{0}", parameterIndex));
\r
482 parameters[parameterIndex] = literalParameterName;
\r
483 return literalParameterName;
\r
485 return new DirectQuery(queryContext.DataContext, parameterizedSql, parameters);
\r