mirror of
https://bitbucket.org/anguist/ntpa
synced 2026-10-01 15:58:16 +00:00
361 lines
11 KiB
C#
361 lines
11 KiB
C#
// Copyright © 2008, 2014, 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.Collections.Generic;
|
||
|
|
using System.Text;
|
||
|
|
using System;
|
||
|
|
using System.Collections;
|
||
|
|
using System.Diagnostics;
|
||
|
|
#if EF6
|
||
|
|
using System.Data.Entity.Core.Common.CommandTrees;
|
||
|
|
using System.Data.Entity.Core.Metadata.Edm;
|
||
|
|
#else
|
||
|
|
using System.Data.Metadata.Edm;
|
||
|
|
using System.Data.Common.CommandTrees;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
|
||
|
|
namespace MySql.Data.Entity
|
||
|
|
{
|
||
|
|
class SelectStatement : InputFragment
|
||
|
|
{
|
||
|
|
private Dictionary<string, ColumnFragment> columnHash;
|
||
|
|
private bool hasRenamedColumns;
|
||
|
|
private SqlGenerator generator;
|
||
|
|
|
||
|
|
public SelectStatement(SqlGenerator generator)
|
||
|
|
: base(null)
|
||
|
|
{
|
||
|
|
Columns = new List<ColumnFragment>();
|
||
|
|
this.generator = generator;
|
||
|
|
}
|
||
|
|
|
||
|
|
public InputFragment From;
|
||
|
|
public List<ColumnFragment> Columns { get; private set; }
|
||
|
|
public SqlFragment Where;
|
||
|
|
public SqlFragment Limit;
|
||
|
|
public SqlFragment Skip;
|
||
|
|
public List<SqlFragment> GroupBy { get; internal set; }
|
||
|
|
public List<SortFragment> OrderBy { get; internal set; }
|
||
|
|
public bool IsDistinct;
|
||
|
|
|
||
|
|
public void AddGroupBy(SqlFragment f)
|
||
|
|
{
|
||
|
|
if (GroupBy == null)
|
||
|
|
GroupBy = new List<SqlFragment>();
|
||
|
|
GroupBy.Add(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void AddOrderBy(SortFragment f)
|
||
|
|
{
|
||
|
|
if (OrderBy == null)
|
||
|
|
OrderBy = new List<SortFragment>();
|
||
|
|
OrderBy.Add(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
public override void WriteSql(StringBuilder sql)
|
||
|
|
{
|
||
|
|
if (IsWrapped)
|
||
|
|
sql.Append("(");
|
||
|
|
sql.Append("SELECT");
|
||
|
|
if (IsDistinct)
|
||
|
|
sql.Append(" DISTINCT ");
|
||
|
|
|
||
|
|
WriteList(Columns, sql);
|
||
|
|
|
||
|
|
if (From != null)
|
||
|
|
{
|
||
|
|
sql.Append("\r\nFROM ");
|
||
|
|
From.WriteSql(sql);
|
||
|
|
}
|
||
|
|
if (Where != null)
|
||
|
|
{
|
||
|
|
sql.Append("\r\n WHERE ");
|
||
|
|
Where.WriteSql(sql);
|
||
|
|
}
|
||
|
|
if (GroupBy != null)
|
||
|
|
{
|
||
|
|
sql.Append("\r\n GROUP BY ");
|
||
|
|
WriteList(GroupBy, sql);
|
||
|
|
}
|
||
|
|
WriteOrderBy(sql);
|
||
|
|
if (Limit != null || Skip != null)
|
||
|
|
{
|
||
|
|
sql.Append(" LIMIT ");
|
||
|
|
if (Skip != null)
|
||
|
|
sql.AppendFormat("{0},", Skip);
|
||
|
|
if (Limit == null)
|
||
|
|
sql.Append("18446744073709551615");
|
||
|
|
else
|
||
|
|
sql.AppendFormat("{0}", Limit);
|
||
|
|
}
|
||
|
|
if (IsWrapped)
|
||
|
|
{
|
||
|
|
sql.Append(")");
|
||
|
|
if (Name != null)
|
||
|
|
sql.AppendFormat(" AS {0}", QuoteIdentifier(Name));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void WriteOrderBy(StringBuilder sql)
|
||
|
|
{
|
||
|
|
if (OrderBy == null) return;
|
||
|
|
sql.Append("\r\n ORDER BY ");
|
||
|
|
WriteList(OrderBy, sql);
|
||
|
|
}
|
||
|
|
|
||
|
|
public override void Wrap(Scope scope)
|
||
|
|
{
|
||
|
|
base.Wrap(scope);
|
||
|
|
|
||
|
|
// now we need to add default columns if necessary
|
||
|
|
if (Columns.Count == 0)
|
||
|
|
AddDefaultColumns(scope);
|
||
|
|
|
||
|
|
// next we need to remove child extents of the select from scope
|
||
|
|
if (Name != null)
|
||
|
|
{
|
||
|
|
scope.Remove(this);
|
||
|
|
scope.Add(Name, this);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AddDefaultColumns(Scope scope)
|
||
|
|
{
|
||
|
|
if (columnHash == null)
|
||
|
|
columnHash = new Dictionary<string, ColumnFragment>();
|
||
|
|
|
||
|
|
List<ColumnFragment> columns = GetDefaultColumnsForFragment(From);
|
||
|
|
bool Exists = false;
|
||
|
|
if (From is TableFragment && scope.GetFragment((From as TableFragment).Table) == null)
|
||
|
|
{
|
||
|
|
scope.Add((From as TableFragment).Table, From);
|
||
|
|
Exists = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (ColumnFragment column in columns)
|
||
|
|
{
|
||
|
|
// first we need to set the input for this column
|
||
|
|
InputFragment input = scope.FindInputFromProperties(column.PropertyFragment);
|
||
|
|
column.TableName = input.Name;
|
||
|
|
|
||
|
|
// then we rename the column if necessary
|
||
|
|
if (columnHash.ContainsKey(column.ColumnName.ToUpper()))
|
||
|
|
{
|
||
|
|
column.ColumnAlias = MakeColumnNameUnique(column.ColumnName);
|
||
|
|
columnHash.Add(column.ColumnAlias, column);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
columnHash.Add(column.ColumnName.ToUpper(), column);
|
||
|
|
Columns.Add(column);
|
||
|
|
}
|
||
|
|
if (Exists)
|
||
|
|
{
|
||
|
|
scope.Remove((From as TableFragment).Table, From);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
internal void AddColumn(ColumnFragment column, Scope scope)
|
||
|
|
{
|
||
|
|
InputFragment input = scope.FindInputFromProperties(column.PropertyFragment);
|
||
|
|
column.TableName = input.Name;
|
||
|
|
|
||
|
|
// then we rename the column if necessary
|
||
|
|
if (columnHash.ContainsKey(column.ColumnName.ToUpper()))
|
||
|
|
{
|
||
|
|
column.ColumnAlias = MakeColumnNameUnique(column.ColumnName);
|
||
|
|
columnHash.Add(column.ColumnAlias, column);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if( !string.IsNullOrEmpty(column.ColumnAlias) )
|
||
|
|
columnHash.Add(column.ColumnAlias.ToUpper(), column);
|
||
|
|
else
|
||
|
|
columnHash.Add(column.ColumnName.ToUpper(), column);
|
||
|
|
}
|
||
|
|
Columns.Add(column);
|
||
|
|
}
|
||
|
|
|
||
|
|
List<ColumnFragment> GetDefaultColumnsForFragment(InputFragment input)
|
||
|
|
{
|
||
|
|
List<ColumnFragment> columns = new List<ColumnFragment>();
|
||
|
|
|
||
|
|
if (input is TableFragment)
|
||
|
|
{
|
||
|
|
return GetDefaultColumnsForTable(input as TableFragment);
|
||
|
|
}
|
||
|
|
else if (input is JoinFragment || input is UnionFragment)
|
||
|
|
{
|
||
|
|
Debug.Assert(input.Left != null);
|
||
|
|
if (input is UnionFragment)
|
||
|
|
{
|
||
|
|
generator.Ops.Push(OpType.Union);
|
||
|
|
}
|
||
|
|
columns = GetDefaultColumnsForFragment(input.Left);
|
||
|
|
if (input is JoinFragment && input.Right != null)
|
||
|
|
{
|
||
|
|
List<ColumnFragment> right = GetDefaultColumnsForFragment(input.Right);
|
||
|
|
columns.AddRange(right);
|
||
|
|
}
|
||
|
|
if (input is UnionFragment)
|
||
|
|
{
|
||
|
|
generator.Ops.Pop();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (input is SelectStatement)
|
||
|
|
{
|
||
|
|
SelectStatement select = input as SelectStatement;
|
||
|
|
foreach (ColumnFragment cf in select.Columns)
|
||
|
|
{
|
||
|
|
ColumnFragment newColumn = new ColumnFragment(cf.TableName,
|
||
|
|
string.IsNullOrEmpty(cf.ColumnAlias) ? cf.ActualColumnName : cf.ColumnAlias
|
||
|
|
);
|
||
|
|
if ( generator.GetTopOp() == OpType.Join )
|
||
|
|
{
|
||
|
|
newColumn.ColumnAlias = cf.ColumnAlias;
|
||
|
|
newColumn.PushInput(cf.ColumnName);
|
||
|
|
if (cf.TableName != null)
|
||
|
|
newColumn.PushInput(cf.TableName);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
newColumn.PushInput(cf.ActualColumnName);
|
||
|
|
if (cf.TableName != null && cf.ColumnAlias == null)
|
||
|
|
newColumn.PushInput(cf.TableName);
|
||
|
|
}
|
||
|
|
if (select.Name != null)
|
||
|
|
{
|
||
|
|
newColumn.PushInput(select.Name); // add the scope
|
||
|
|
}
|
||
|
|
columns.Add(newColumn);
|
||
|
|
}
|
||
|
|
return columns;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
throw new NotImplementedException();
|
||
|
|
if (!String.IsNullOrEmpty(input.Name) && input.Name != From.Name)
|
||
|
|
foreach (ColumnFragment c in columns)
|
||
|
|
{
|
||
|
|
c.PushInput(input.Name);
|
||
|
|
}
|
||
|
|
return columns;
|
||
|
|
}
|
||
|
|
|
||
|
|
List<ColumnFragment> GetDefaultColumnsForTable(TableFragment table)
|
||
|
|
{
|
||
|
|
List<ColumnFragment> columns = new List<ColumnFragment>();
|
||
|
|
|
||
|
|
foreach (EdmProperty property in Metadata.GetProperties(table.Type.EdmType))
|
||
|
|
{
|
||
|
|
ColumnFragment col = new ColumnFragment(table.Name, property.Name);
|
||
|
|
col.PushInput(property.Name);
|
||
|
|
col.PushInput((table.Name != null) ? table.Name : table.Table);
|
||
|
|
columns.Add(col);
|
||
|
|
}
|
||
|
|
return columns;
|
||
|
|
}
|
||
|
|
|
||
|
|
private string MakeColumnNameUnique(string baseName)
|
||
|
|
{
|
||
|
|
int i = 1;
|
||
|
|
baseName = baseName.ToUpper();
|
||
|
|
hasRenamedColumns = true;
|
||
|
|
while (true)
|
||
|
|
{
|
||
|
|
string name = String.Format("{0}{1}", baseName, i);
|
||
|
|
if (!columnHash.ContainsKey(name)) return name;
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool HasDifferentNameForColumn(ColumnFragment column)
|
||
|
|
{
|
||
|
|
if (!hasRenamedColumns) return false;
|
||
|
|
foreach (ColumnFragment c in Columns)
|
||
|
|
{
|
||
|
|
if (!c.Equals(column)) continue;
|
||
|
|
if (String.IsNullOrEmpty(c.ColumnAlias)) return false;
|
||
|
|
column.ColumnName = c.ColumnAlias;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool IsCompatible(DbExpressionKind expressionKind)
|
||
|
|
{
|
||
|
|
switch (expressionKind)
|
||
|
|
{
|
||
|
|
case DbExpressionKind.Filter:
|
||
|
|
return Where == null && Columns.Count == 0;
|
||
|
|
case DbExpressionKind.Project:
|
||
|
|
return Columns.Count == 0;
|
||
|
|
case DbExpressionKind.Limit:
|
||
|
|
return Limit == null;
|
||
|
|
case DbExpressionKind.Skip:
|
||
|
|
return Skip == null;
|
||
|
|
case DbExpressionKind.Sort:
|
||
|
|
return Columns.Count == 0 &&
|
||
|
|
GroupBy == null &&
|
||
|
|
OrderBy == null;
|
||
|
|
case DbExpressionKind.GroupBy:
|
||
|
|
return Columns.Count == 0 &&
|
||
|
|
GroupBy == null &&
|
||
|
|
OrderBy == null &&
|
||
|
|
Limit == null;
|
||
|
|
}
|
||
|
|
throw new InvalidOperationException();
|
||
|
|
}
|
||
|
|
|
||
|
|
internal override void Accept(SqlFragmentVisitor visitor)
|
||
|
|
{
|
||
|
|
if (From != null) From.Accept(visitor);
|
||
|
|
if (Columns != null)
|
||
|
|
{
|
||
|
|
foreach (ColumnFragment cf in Columns)
|
||
|
|
{
|
||
|
|
cf.Accept(visitor);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (Where != null) Where.Accept(visitor);
|
||
|
|
if (Limit != null) Limit.Accept(visitor);
|
||
|
|
if (Skip != null) Skip.Accept(visitor);
|
||
|
|
if (GroupBy != null)
|
||
|
|
{
|
||
|
|
foreach (SqlFragment grp in GroupBy)
|
||
|
|
{
|
||
|
|
grp.Accept(visitor);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (OrderBy != null)
|
||
|
|
{
|
||
|
|
foreach (SortFragment sort in OrderBy)
|
||
|
|
{
|
||
|
|
sort.Accept(visitor);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
visitor.Visit(this);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|