mirror of
https://bitbucket.org/anguist/ntpa
synced 2026-10-01 15:58:16 +00:00
912 lines
30 KiB
C#
912 lines
30 KiB
C#
// Copyright © 2008, 2015, Oracle and/or its affiliates. All rights reserved.
|
|||
|
|
//
|
||
|
|
// MySQL Connector/NET is licensed under the terms of the GPLv2
|
||
|
|
// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
|
||
|
|
// MySQL Connectors. There are special exceptions to the terms and
|
||
|
|
// conditions of the GPLv2 as it is applied to this software, see the
|
||
|
|
// FLOSS License Exception
|
||
|
|
// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
|
||
|
|
//
|
||
|
|
// This program is free software; you can redistribute it and/or modify
|
||
|
|
// it under the terms of the GNU General Public License as published
|
||
|
|
// by the Free Software Foundation; version 2 of the License.
|
||
|
|
//
|
||
|
|
// This program is distributed in the hope that it will be useful, but
|
||
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||
|
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
|
// for more details.
|
||
|
|
//
|
||
|
|
// You should have received a copy of the GNU General Public License along
|
||
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||
|
|
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Diagnostics;
|
||
|
|
using System.Text;
|
||
|
|
using System.Data;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using MySql.Data.MySqlClient;
|
||
|
|
using System.Globalization;
|
||
|
|
#if EF6
|
||
|
|
using System.Data.Entity.Core.Common.CommandTrees;
|
||
|
|
using System.Data.Entity.Core.Metadata.Edm;
|
||
|
|
#else
|
||
|
|
using System.Data.Common.CommandTrees;
|
||
|
|
using System.Data.Metadata.Edm;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
|
||
|
|
namespace MySql.Data.Entity
|
||
|
|
{
|
||
|
|
abstract class SqlGenerator : DbExpressionVisitor<SqlFragment>
|
||
|
|
{
|
||
|
|
protected string tabs = String.Empty;
|
||
|
|
private int parameterCount = 1;
|
||
|
|
protected Scope scope = new Scope();
|
||
|
|
protected int propertyLevel;
|
||
|
|
protected Dictionary<EdmMember, SqlFragment> values;
|
||
|
|
protected internal Stack<OpType> Ops = new Stack<OpType>();
|
||
|
|
|
||
|
|
public SqlGenerator()
|
||
|
|
{
|
||
|
|
Parameters = new List<MySqlParameter>();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected internal OpType GetTopOp()
|
||
|
|
{
|
||
|
|
if (Ops.Count == 0)
|
||
|
|
return OpType.Join;
|
||
|
|
else
|
||
|
|
return Ops.Peek();
|
||
|
|
}
|
||
|
|
|
||
|
|
#region Properties
|
||
|
|
|
||
|
|
public List<MySqlParameter> Parameters { get; private set; }
|
||
|
|
// protected SymbolTable Symbols { get; private set; }
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
public virtual string GenerateSQL(DbCommandTree commandTree)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected string CreateUniqueParameterName()
|
||
|
|
{
|
||
|
|
return String.Format("@gp{0}", parameterCount++);
|
||
|
|
}
|
||
|
|
|
||
|
|
#region DbExpressionVisitor Base Implementations
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbVariableReferenceExpression expression)
|
||
|
|
{
|
||
|
|
PropertyFragment fragment = new PropertyFragment();
|
||
|
|
fragment.Properties.Add(expression.VariableName);
|
||
|
|
return fragment;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbPropertyExpression expression)
|
||
|
|
{
|
||
|
|
propertyLevel++;
|
||
|
|
PropertyFragment fragment = expression.Instance.Accept(this) as PropertyFragment;
|
||
|
|
fragment.Properties.Add(expression.Property.Name);
|
||
|
|
propertyLevel--;
|
||
|
|
|
||
|
|
// if we are not at the top level property then just return
|
||
|
|
if (propertyLevel > 0) return fragment;
|
||
|
|
|
||
|
|
ColumnFragment column = new ColumnFragment(null, fragment.LastProperty);
|
||
|
|
column.PropertyFragment = fragment;
|
||
|
|
InputFragment input = scope.FindInputFromProperties(fragment);
|
||
|
|
if (input != null)
|
||
|
|
column.TableName = input.Name;
|
||
|
|
|
||
|
|
// now we need to check if our column name was possibly renamed
|
||
|
|
if (input is TableFragment )
|
||
|
|
{
|
||
|
|
if (!string.IsNullOrEmpty(input.Name))
|
||
|
|
{
|
||
|
|
SelectStatement sf = scope.GetFragment(input.Name) as SelectStatement;
|
||
|
|
if (sf != null)
|
||
|
|
{
|
||
|
|
// Special case: undo alias in case of query fusing
|
||
|
|
for (int i = 0; i < sf.Columns.Count; i++)
|
||
|
|
{
|
||
|
|
ColumnFragment cf = sf.Columns[i];
|
||
|
|
if (column.ColumnName == cf.ColumnAlias)
|
||
|
|
{
|
||
|
|
column.ColumnName = cf.ColumnName;
|
||
|
|
column.ColumnAlias = cf.ColumnAlias;
|
||
|
|
column.TableName = input.Name;
|
||
|
|
return column;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return column;
|
||
|
|
}
|
||
|
|
|
||
|
|
SelectStatement select = input as SelectStatement;
|
||
|
|
UnionFragment union = input as UnionFragment;
|
||
|
|
|
||
|
|
if (select != null)
|
||
|
|
select.HasDifferentNameForColumn(column);
|
||
|
|
else if (union != null)
|
||
|
|
union.HasDifferentNameForColumn(column);
|
||
|
|
|
||
|
|
// input is a table, selectstatement, or unionstatement
|
||
|
|
return column;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbScanExpression expression)
|
||
|
|
{
|
||
|
|
EntitySetBase target = expression.Target;
|
||
|
|
TableFragment fragment = new TableFragment();
|
||
|
|
|
||
|
|
MetadataProperty property;
|
||
|
|
bool propExists = target.MetadataProperties.TryGetValue("DefiningQuery", true, out property);
|
||
|
|
if (propExists && property.Value != null)
|
||
|
|
{
|
||
|
|
|
||
|
|
MetadataProperty prop2;
|
||
|
|
|
||
|
|
if( target.MetadataProperties.TryGetValue( "http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Type", true, out prop2 ) && ( prop2.Value as string == "Views" ))
|
||
|
|
|
||
|
|
{
|
||
|
|
|
||
|
|
// avoid storing view query as DefiningQuery because that hurts query fusing.
|
||
|
|
fragment.Schema = target.MetadataProperties.GetValue("http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Schema", true).Value as string;
|
||
|
|
fragment.Table = target.Name;
|
||
|
|
|
||
|
|
} else {
|
||
|
|
fragment.DefiningQuery = new LiteralFragment(property.Value as string);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
else
|
||
|
|
{
|
||
|
|
fragment.Schema = target.EntityContainer.Name;
|
||
|
|
fragment.Table = target.Name;
|
||
|
|
|
||
|
|
propExists = target.MetadataProperties.TryGetValue("Schema", true, out property);
|
||
|
|
if (propExists && property.Value != null)
|
||
|
|
fragment.Schema = property.Value as string;
|
||
|
|
propExists = target.MetadataProperties.TryGetValue("Table", true, out property);
|
||
|
|
if (propExists && property.Value != null)
|
||
|
|
fragment.Table = property.Value as string;
|
||
|
|
}
|
||
|
|
return fragment;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbParameterReferenceExpression expression)
|
||
|
|
{
|
||
|
|
return new LiteralFragment("@" + expression.ParameterName);
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbNotExpression expression)
|
||
|
|
{
|
||
|
|
SqlFragment f = expression.Argument.Accept(this);
|
||
|
|
Debug.Assert(f is NegatableFragment);
|
||
|
|
NegatableFragment nf = f as NegatableFragment;
|
||
|
|
nf.Negate();
|
||
|
|
return nf;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbIsEmptyExpression expression)
|
||
|
|
{
|
||
|
|
ExistsFragment f = new ExistsFragment(expression.Argument.Accept(this));
|
||
|
|
f.Negate();
|
||
|
|
return f;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbFunctionExpression expression)
|
||
|
|
{
|
||
|
|
FunctionProcessor gen = new FunctionProcessor();
|
||
|
|
return gen.Generate(expression, this);
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbConstantExpression expression)
|
||
|
|
{
|
||
|
|
PrimitiveTypeKind pt = ((PrimitiveType)expression.ResultType.EdmType).PrimitiveTypeKind;
|
||
|
|
string literal = Metadata.GetNumericLiteral(pt, expression.Value);
|
||
|
|
if (literal != null)
|
||
|
|
return new LiteralFragment(literal);
|
||
|
|
else if (pt == PrimitiveTypeKind.Boolean)
|
||
|
|
return new LiteralFragment((bool)expression.Value ? "1" : "0");
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// use a parameter for non-numeric types so we get proper
|
||
|
|
// quoting
|
||
|
|
MySqlParameter p = new MySqlParameter();
|
||
|
|
p.ParameterName = CreateUniqueParameterName();
|
||
|
|
p.DbType = Metadata.GetDbType(expression.ResultType);
|
||
|
|
p.Value = Metadata.NormalizeValue(expression.ResultType, expression.Value);
|
||
|
|
Parameters.Add(p);
|
||
|
|
return new LiteralFragment(p.ParameterName);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbComparisonExpression expression)
|
||
|
|
{
|
||
|
|
return VisitBinaryExpression(expression.Left, expression.Right,
|
||
|
|
Metadata.GetOperator(expression.ExpressionKind));
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbAndExpression expression)
|
||
|
|
{
|
||
|
|
return VisitBinaryExpression(expression.Left, expression.Right, "AND");
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbOrExpression expression)
|
||
|
|
{
|
||
|
|
return VisitBinaryExpression(expression.Left, expression.Right, "OR");
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbCastExpression expression)
|
||
|
|
{
|
||
|
|
//TODO: handle casting
|
||
|
|
return expression.Argument.Accept(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
#if EF6
|
||
|
|
public override SqlFragment Visit(DbInExpression expression)
|
||
|
|
{
|
||
|
|
SqlFragment sf = expression.Item.Accept(this);
|
||
|
|
InFragment inf = new InFragment();
|
||
|
|
inf.Argument = sf;
|
||
|
|
for( int i = 0; i < expression.List.Count; i++ )
|
||
|
|
{
|
||
|
|
LiteralFragment lf = Visit( expression.List[ i ] as DbConstantExpression ) as LiteralFragment;
|
||
|
|
inf.InList.Add( lf );
|
||
|
|
}
|
||
|
|
return inf;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbLambdaExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbLikeExpression expression)
|
||
|
|
{
|
||
|
|
LikeFragment f = new LikeFragment();
|
||
|
|
|
||
|
|
f.Argument = expression.Argument.Accept(this);
|
||
|
|
f.Pattern = expression.Pattern.Accept(this);
|
||
|
|
|
||
|
|
return f;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbCaseExpression expression)
|
||
|
|
{
|
||
|
|
CaseFragment c = new CaseFragment();
|
||
|
|
|
||
|
|
Debug.Assert(expression.When.Count == expression.Then.Count);
|
||
|
|
|
||
|
|
for (int i = 0; i < expression.When.Count; ++i)
|
||
|
|
{
|
||
|
|
c.When.Add(expression.When[i].Accept(this));
|
||
|
|
c.Then.Add(expression.Then[i].Accept(this));
|
||
|
|
}
|
||
|
|
if (expression.Else != null && !(expression.Else is DbNullExpression))
|
||
|
|
c.Else = expression.Else.Accept(this);
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbIsNullExpression expression)
|
||
|
|
{
|
||
|
|
IsNullFragment f = new IsNullFragment();
|
||
|
|
f.Argument = expression.Argument.Accept(this);
|
||
|
|
return f;
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbIntersectExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbNullExpression expression)
|
||
|
|
{
|
||
|
|
return new LiteralFragment("NULL");
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbArithmeticExpression expression)
|
||
|
|
{
|
||
|
|
if (expression.ExpressionKind == DbExpressionKind.UnaryMinus)
|
||
|
|
{
|
||
|
|
ListFragment f = new ListFragment();
|
||
|
|
f.Append("-(");
|
||
|
|
f.Append(expression.Arguments[0].Accept(this));
|
||
|
|
f.Append(")");
|
||
|
|
return f;
|
||
|
|
}
|
||
|
|
|
||
|
|
string op = String.Empty;
|
||
|
|
switch (expression.ExpressionKind)
|
||
|
|
{
|
||
|
|
case DbExpressionKind.Divide:
|
||
|
|
op = "/"; break;
|
||
|
|
case DbExpressionKind.Minus:
|
||
|
|
op = "-"; break;
|
||
|
|
case DbExpressionKind.Modulo:
|
||
|
|
op = "%"; break;
|
||
|
|
case DbExpressionKind.Multiply:
|
||
|
|
op = "*"; break;
|
||
|
|
case DbExpressionKind.Plus:
|
||
|
|
op = "+"; break;
|
||
|
|
default:
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
return VisitBinaryExpression(expression.Arguments[0], expression.Arguments[1], op);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected void VisitNewInstanceExpression(SelectStatement select,
|
||
|
|
DbNewInstanceExpression expression)
|
||
|
|
{
|
||
|
|
Debug.Assert(expression.ResultType.EdmType is RowType);
|
||
|
|
|
||
|
|
RowType row = expression.ResultType.EdmType as RowType;
|
||
|
|
|
||
|
|
for (int i = 0; i < expression.Arguments.Count; i++)
|
||
|
|
{
|
||
|
|
ColumnFragment col;
|
||
|
|
|
||
|
|
SqlFragment fragment = expression.Arguments[i].Accept(this);
|
||
|
|
if (fragment is ColumnFragment)
|
||
|
|
col = fragment as ColumnFragment;
|
||
|
|
else
|
||
|
|
{
|
||
|
|
col = new ColumnFragment(null, null);
|
||
|
|
col.Literal = fragment;
|
||
|
|
}
|
||
|
|
|
||
|
|
col.ColumnAlias = row.Properties[i].Name;
|
||
|
|
select.Columns.Add(col);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbTreatExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbRelationshipNavigationExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbRefExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbOfTypeExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbIsOfExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbRefKeyExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbEntityRefExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbExceptExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbExpression expression)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbDerefExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbApplyExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
#region DBExpressionVisitor methods normally overridden
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbUnionAllExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbSortExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbSkipExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbQuantifierExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbProjectExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbNewInstanceExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbLimitExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbJoinExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbGroupByExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbFilterExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbElementExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbDistinctExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override SqlFragment Visit(DbCrossJoinExpression expression)
|
||
|
|
{
|
||
|
|
throw new NotImplementedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
#region "Optimization"
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// If current fragment is select and its from clause is another select, try fuse the inner select with the outer select.
|
||
|
|
/// (Thus removing a nested query, which may have bad performance in Mysql).
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="f">The fragment to probe and posibly optimize</param>
|
||
|
|
/// <returns>The fragment fused, or the original one.</returns>
|
||
|
|
protected internal InputFragment TryFusingSelect(InputFragment f)
|
||
|
|
{
|
||
|
|
SelectStatement result = f as SelectStatement;
|
||
|
|
if (!CanFuseSelect(f as SelectStatement)) return f;
|
||
|
|
result = FuseSelectWithInnerSelect(result, result.From as SelectStatement);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected internal SelectStatement FuseSelectWithInnerSelect(SelectStatement outer, SelectStatement inner)
|
||
|
|
{
|
||
|
|
string oldTableName = (inner.From as TableFragment).Name;
|
||
|
|
string newTableName = inner.Name;
|
||
|
|
Dictionary<string, ColumnFragment> dicColumns = new Dictionary<string, ColumnFragment>();
|
||
|
|
|
||
|
|
foreach (ColumnFragment cf in inner.Columns)
|
||
|
|
{
|
||
|
|
if (cf.ColumnAlias != null)
|
||
|
|
dicColumns.Add(cf.ColumnAlias, cf);
|
||
|
|
}
|
||
|
|
outer.From = inner.From;
|
||
|
|
(outer.From as TableFragment).Name = newTableName;
|
||
|
|
// Dispatch Where
|
||
|
|
if (outer.Where == null)
|
||
|
|
{
|
||
|
|
outer.Where = inner.Where;
|
||
|
|
}
|
||
|
|
else if (inner.Where != null)
|
||
|
|
{
|
||
|
|
outer.Where = new BinaryFragment() { Left = outer.Where, Right = inner.Where, Operator = "AND" };
|
||
|
|
}
|
||
|
|
VisitAndReplaceTableName(outer.Where, oldTableName, newTableName, dicColumns);
|
||
|
|
// For the next constructions, either is defined on outer or at inner, not both
|
||
|
|
// Dispatch Limit
|
||
|
|
if (outer.Limit == null)
|
||
|
|
{
|
||
|
|
outer.Limit = inner.Limit;
|
||
|
|
VisitAndReplaceTableName(outer.Limit, oldTableName, newTableName, dicColumns);
|
||
|
|
}
|
||
|
|
// Dispatch GroupBy
|
||
|
|
if (outer.GroupBy == null && inner.GroupBy != null)
|
||
|
|
{
|
||
|
|
foreach (SqlFragment sf in inner.GroupBy)
|
||
|
|
outer.AddGroupBy(sf);
|
||
|
|
foreach (SqlFragment sf in outer.GroupBy)
|
||
|
|
VisitAndReplaceTableName(sf, oldTableName, newTableName, dicColumns);
|
||
|
|
}
|
||
|
|
// Dispatch OrderBy
|
||
|
|
if (outer.OrderBy != null || inner.OrderBy != null)
|
||
|
|
{
|
||
|
|
if (inner.OrderBy != null)
|
||
|
|
{
|
||
|
|
foreach (SortFragment sf in inner.OrderBy)
|
||
|
|
outer.AddOrderBy(sf);
|
||
|
|
}
|
||
|
|
foreach (SortFragment sf in outer.OrderBy)
|
||
|
|
VisitAndReplaceTableName(sf, oldTableName, newTableName, dicColumns);
|
||
|
|
}
|
||
|
|
// Dispatch Skip
|
||
|
|
if (outer.Skip == null)
|
||
|
|
outer.Skip = inner.Skip;
|
||
|
|
return outer;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected internal bool CanFuseSelect(SelectStatement select)
|
||
|
|
{
|
||
|
|
SelectStatement innerSelect = null;
|
||
|
|
if (select == null || select.Columns.Count != 0) return false;
|
||
|
|
innerSelect = select.From as SelectStatement;
|
||
|
|
if ((innerSelect == null) || !( innerSelect.From is TableFragment )) return false;
|
||
|
|
// Cannot fuse, unless construction is semantically compatible
|
||
|
|
// ie. Where's can be combined, Group by's no.
|
||
|
|
if ((select.Limit == null || innerSelect.Limit == null) &&
|
||
|
|
(select.GroupBy == null || innerSelect.GroupBy == null) &&
|
||
|
|
(select.OrderBy == null || innerSelect.OrderBy == null) &&
|
||
|
|
(select.Skip == null || innerSelect.Skip == null) &&
|
||
|
|
( select.IsDistinct == innerSelect.IsDistinct ) )
|
||
|
|
{
|
||
|
|
List<ColumnFragment> cols = innerSelect.Columns;
|
||
|
|
for (int i = 0; i < cols.Count; i++)
|
||
|
|
if (cols[i].Literal != null)
|
||
|
|
return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected internal void VisitAndReplaceTableName(SqlFragment sf, string oldTable, string newTable,
|
||
|
|
Dictionary<string, ColumnFragment> dicColumns)
|
||
|
|
{
|
||
|
|
if (sf == null) return;
|
||
|
|
ReplaceTableNameVisitor visitor = new ReplaceTableNameVisitor(oldTable, newTable, dicColumns);
|
||
|
|
sf.Accept(visitor);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
protected InputFragment VisitInputExpression(DbExpression e, string name, TypeUsage type)
|
||
|
|
{
|
||
|
|
SqlFragment f = e.Accept(this);
|
||
|
|
Debug.Assert(f is InputFragment);
|
||
|
|
|
||
|
|
InputFragment inputFragment = f as InputFragment;
|
||
|
|
inputFragment.Name = name;
|
||
|
|
TryFusingSelect(inputFragment);
|
||
|
|
|
||
|
|
if (inputFragment is TableFragment && type != null)
|
||
|
|
(inputFragment as TableFragment).Type = type;
|
||
|
|
|
||
|
|
SelectStatement select = inputFragment as SelectStatement;
|
||
|
|
if ((select != null) && (select.From is TableFragment))
|
||
|
|
{
|
||
|
|
(select.From as TableFragment).Type = type;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (name != null)
|
||
|
|
scope.Add(name, inputFragment);
|
||
|
|
|
||
|
|
return inputFragment;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual SelectStatement GenerateReturningSql(DbModificationCommandTree tree, DbExpression returning)
|
||
|
|
{
|
||
|
|
SelectStatement select = new SelectStatement(this);
|
||
|
|
|
||
|
|
Debug.Assert(returning is DbNewInstanceExpression);
|
||
|
|
VisitNewInstanceExpression(select, returning as DbNewInstanceExpression);
|
||
|
|
|
||
|
|
select.From = (InputFragment)tree.Target.Expression.Accept(this);
|
||
|
|
|
||
|
|
ListFragment where = new ListFragment();
|
||
|
|
select.Where = where;
|
||
|
|
return select;
|
||
|
|
}
|
||
|
|
|
||
|
|
#region Private Methods
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Examines a binary expression to see if it is an special case suitable to conversion
|
||
|
|
/// to a more efficient and equivalent LIKE sql expression.
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="left"></param>
|
||
|
|
/// <param name="right"></param>
|
||
|
|
/// <param name="op"></param>
|
||
|
|
/// <returns></returns>
|
||
|
|
protected LikeFragment TryPromoteToLike(DbExpression left, DbExpression right, string op)
|
||
|
|
{
|
||
|
|
DbFunctionExpression fl = left as DbFunctionExpression;
|
||
|
|
if ((fl != null) && (right is DbConstantExpression))
|
||
|
|
{
|
||
|
|
LikeFragment like = new LikeFragment();
|
||
|
|
if (fl.Function.FullName == "Edm.IndexOf")
|
||
|
|
{
|
||
|
|
DbParameterReferenceExpression par;
|
||
|
|
DbPropertyExpression prop;
|
||
|
|
int value = Convert.ToInt32(((DbConstantExpression)right).Value);
|
||
|
|
like.Argument = fl.Arguments[1].Accept(this);
|
||
|
|
if ((value == 1) && (op == "="))
|
||
|
|
{
|
||
|
|
DbFunctionExpression fr1;
|
||
|
|
DbFunctionExpression fr2;
|
||
|
|
if ((fl.Arguments[0] is DbConstantExpression))
|
||
|
|
{
|
||
|
|
// Case LIKE 'pattern%'
|
||
|
|
DbConstantExpression c = (DbConstantExpression)fl.Arguments[0];
|
||
|
|
like.Pattern = new LiteralFragment(string.Format("'{0}%'", c.Value.ToString().Replace( "'", "''" ) ));
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
else if ((fl.Arguments.Count == 2) &&
|
||
|
|
((fr1 = fl.Arguments[0] as DbFunctionExpression) != null) &&
|
||
|
|
((fr2 = fl.Arguments[1] as DbFunctionExpression) != null) &&
|
||
|
|
(fr1.Function.FullName == "Edm.Reverse") &&
|
||
|
|
(fr2.Function.FullName == "Edm.Reverse"))
|
||
|
|
{
|
||
|
|
// Case LIKE '%pattern' in EF .NET 4.0
|
||
|
|
if (fr1.Arguments[0] is DbConstantExpression)
|
||
|
|
{
|
||
|
|
DbConstantExpression c = (DbConstantExpression)fr1.Arguments[0];
|
||
|
|
like.Pattern = new LiteralFragment(string.Format("'%{0}'", c.Value.ToString().Replace("'", "''") ));
|
||
|
|
like.Argument = fr2.Arguments[0].Accept(this);
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
else if( /* For EF6 */
|
||
|
|
(( par = fr1.Arguments[0] as DbParameterReferenceExpression ) != null ) &&
|
||
|
|
(( prop = fr2.Arguments[0] as DbPropertyExpression) != null ))
|
||
|
|
{
|
||
|
|
// Pattern LIKE "%..." in EF6
|
||
|
|
like.Pattern = par.Accept(this);
|
||
|
|
like.Argument = prop.Accept(this);
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if( ( fl.Arguments.Count == 2) &&
|
||
|
|
(( par = fl.Arguments[0] as DbParameterReferenceExpression ) != null ) &&
|
||
|
|
(( prop = fl.Arguments[1] as DbPropertyExpression ) != null ) )
|
||
|
|
{
|
||
|
|
// Case LIKE "pattern%" in EF6
|
||
|
|
like.Pattern = par.Accept(this);
|
||
|
|
like.Argument = prop.Accept(this);
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (value == 0)
|
||
|
|
{
|
||
|
|
if ( op == ">")
|
||
|
|
{
|
||
|
|
if (fl.Arguments[0] is DbConstantExpression)
|
||
|
|
{
|
||
|
|
// Case LIKE '%pattern%'
|
||
|
|
DbConstantExpression c = (DbConstantExpression)fl.Arguments[0];
|
||
|
|
like.Pattern = new LiteralFragment(string.Format("%{0}%", c.Value.ToString()));
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
else if ((par = fl.Arguments[0] as DbParameterReferenceExpression) != null)
|
||
|
|
{
|
||
|
|
// Case LIKE "%pattern%" in EF6
|
||
|
|
like.Pattern = fl.Arguments[0].Accept(this);
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Like '%pattern' in EF .NET 3.5 (yes, is different than in .NET 4.0)
|
||
|
|
else if (fl.Function.FullName == "Edm.Right")
|
||
|
|
{
|
||
|
|
DbFunctionExpression fLength = fl.Arguments[1] as DbFunctionExpression;
|
||
|
|
if ((fLength != null) && (fLength.Function.FullName == "Edm.Length") && (fLength.Arguments[0] is DbConstantExpression))
|
||
|
|
{
|
||
|
|
DbConstantExpression c2 = fLength.Arguments[0] as DbConstantExpression;
|
||
|
|
DbConstantExpression c1 = (DbConstantExpression)right;
|
||
|
|
if (c1.Value == c2.Value)
|
||
|
|
{
|
||
|
|
like.Argument = fl.Arguments[0].Accept(this);
|
||
|
|
like.Pattern = new LiteralFragment(string.Format("'%{0}'", c1.Value.ToString().Replace("'", "''") ));
|
||
|
|
return like;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual SqlFragment VisitBinaryExpression(DbExpression left, DbExpression right, string op)
|
||
|
|
{
|
||
|
|
// Optimization: try to use 'like' instead of 'locate' (Edm.IndexOf) for these
|
||
|
|
// cases: (like 'word%'), (like '%word') & (like '%word%').
|
||
|
|
LikeFragment like = TryPromoteToLike(left, right, op);
|
||
|
|
if (like != null)
|
||
|
|
return like;
|
||
|
|
// normal flow
|
||
|
|
BinaryFragment f = new BinaryFragment();
|
||
|
|
f.Operator = op;
|
||
|
|
f.Left = left.Accept(this);
|
||
|
|
f.WrapLeft = ShouldWrapExpression(left);
|
||
|
|
f.Right = right.Accept(this);
|
||
|
|
f.WrapRight = ShouldWrapExpression(right);
|
||
|
|
// Optimization, try to promote to In expression
|
||
|
|
// NOTE: In EF6, this optimization is already done, we just implement Visit(DbInExpression).
|
||
|
|
#if !EF6
|
||
|
|
return TryToPromoteToIn(f);
|
||
|
|
#else
|
||
|
|
return f;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual SqlFragment TryToPromoteToIn(BinaryFragment bf)
|
||
|
|
{
|
||
|
|
// TODO: Remember Morgan's theorem
|
||
|
|
// Only try to merge if they are OR'ed.
|
||
|
|
if ((bf.Operator == "OR") )
|
||
|
|
{
|
||
|
|
InFragment inf = bf.Left as InFragment;
|
||
|
|
InFragment inf2 = bf.Right as InFragment;
|
||
|
|
if (inf == null)
|
||
|
|
{
|
||
|
|
BinaryFragment bfLeft = bf.Left as BinaryFragment;
|
||
|
|
if( bfLeft == null )
|
||
|
|
return bf;
|
||
|
|
if (inf2 == null)
|
||
|
|
{
|
||
|
|
// try to create a new infragment
|
||
|
|
BinaryFragment bfRight = bf.Right as BinaryFragment;
|
||
|
|
if (bfRight == null)
|
||
|
|
return bf;
|
||
|
|
InFragment inff = TryMergeTwoBinaryFragments(bfLeft, bfRight);
|
||
|
|
if (inff != null)
|
||
|
|
return inff;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// try to merge an existing infragment & a binaryfragment.
|
||
|
|
SqlFragment sf = TryMergeBinaryFragmentAndInFragment(bfLeft, inf2);
|
||
|
|
if (sf != null)
|
||
|
|
return sf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (inf2 == null)
|
||
|
|
{
|
||
|
|
BinaryFragment bfRight = bf.Right as BinaryFragment;
|
||
|
|
if (bfRight == null)
|
||
|
|
return bf;
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// try to merge an existing infragment & a binaryfragment.
|
||
|
|
SqlFragment sf = TryMergeBinaryFragmentAndInFragment(bfRight, inf);
|
||
|
|
if (sf != null)
|
||
|
|
return sf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// try to merge both InFragments
|
||
|
|
SqlFragment sf = TryMergeTwoInFragments( inf, inf2 );
|
||
|
|
if (sf != null)
|
||
|
|
return sf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return bf;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected InFragment TryMergeTwoInFragments(InFragment infLeft, InFragment infRight)
|
||
|
|
{
|
||
|
|
if (infLeft.Argument.Equals(infRight.Argument) &&
|
||
|
|
( infLeft.IsNegated == infRight.IsNegated ) && ( !infLeft.IsNegated ) )
|
||
|
|
{
|
||
|
|
infLeft.InList.AddRange(infRight.InList);
|
||
|
|
return infLeft;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected InFragment TryMergeTwoBinaryFragments(BinaryFragment left, BinaryFragment right)
|
||
|
|
{
|
||
|
|
if( ( left.IsNegated == right.IsNegated ) && ( ! left.IsNegated ) &&
|
||
|
|
( left.Operator == "=" ) && ( right.Operator == "=" ) )
|
||
|
|
{
|
||
|
|
ColumnFragment cf;
|
||
|
|
LiteralFragment lf;
|
||
|
|
GetBinaryFragmentPartsForIn(left, out lf, out cf);
|
||
|
|
if ((lf != null) && (cf != null))
|
||
|
|
{
|
||
|
|
ColumnFragment cf2;
|
||
|
|
LiteralFragment lf2;
|
||
|
|
GetBinaryFragmentPartsForIn(right, out lf2, out cf2);
|
||
|
|
if ( (lf2 != null) && (cf2 != null) && cf.Equals(cf2))
|
||
|
|
{
|
||
|
|
InFragment inf = new InFragment();
|
||
|
|
inf.Argument = cf;
|
||
|
|
inf.InList.Add(lf);
|
||
|
|
inf.InList.Add(lf2);
|
||
|
|
return inf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected InFragment TryMergeBinaryFragmentAndInFragment(BinaryFragment bf, InFragment inf)
|
||
|
|
{
|
||
|
|
if( !bf.IsNegated && ( bf.Operator == "=" ))
|
||
|
|
{
|
||
|
|
ColumnFragment cf;
|
||
|
|
LiteralFragment lf;
|
||
|
|
GetBinaryFragmentPartsForIn(bf, out lf, out cf);
|
||
|
|
if ((lf != null) && (cf != null))
|
||
|
|
{
|
||
|
|
if (inf.Argument.Equals(cf))
|
||
|
|
{
|
||
|
|
if (!inf.InList.Contains(lf))
|
||
|
|
inf.InList.Add(lf);
|
||
|
|
return inf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected void GetBinaryFragmentPartsForIn(BinaryFragment bf, out LiteralFragment lf, out ColumnFragment cf)
|
||
|
|
{
|
||
|
|
cf = bf.Right as ColumnFragment;
|
||
|
|
lf = bf.Left as LiteralFragment;
|
||
|
|
if (lf == null)
|
||
|
|
{
|
||
|
|
lf = bf.Right as LiteralFragment;
|
||
|
|
cf = bf.Left as ColumnFragment;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected bool ShouldWrapExpression(DbExpression e)
|
||
|
|
{
|
||
|
|
switch (e.ExpressionKind)
|
||
|
|
{
|
||
|
|
case DbExpressionKind.Property:
|
||
|
|
case DbExpressionKind.ParameterReference:
|
||
|
|
case DbExpressionKind.Constant:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|