<commit_before>using System;

namespace LinqToDB.SqlProvider
{
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;

	using JetBrains.Annotations;

	using SqlQuery;

	internal class JoinOptimizer
	{
		Dictionary<SelectQuery.SearchCondition, SelectQuery.SearchCondition>                     _additionalFilter;
		Dictionary<VirtualField, HashSet<Tuple<int, VirtualField>>>                              _equalityMap;
		Dictionary<Tuple<SelectQuery.TableSource, SelectQuery.TableSource>, List<FoundEquality>> _fieldPairCache;
		Dictionary<int, List<List<string>>>                                                      _keysCache;
		HashSet<int>                                                                             _removedSources;
		Dictionary<VirtualField, VirtualField>                                                   _replaceMap;
		SelectQuery                                                                              _selectQuery;

		static bool IsEqualTables(SqlTable table1, SqlTable table2)
		{
			var result =
				   table1          != null 
				&& table2          != null
				&& table1.Database == table2.Database
				&& table1.Owner    == table2.Owner
				&& table1.Name     == table2.Name;

			return result;
		}

		void FlattenJoins(SelectQuery.TableSource table)
		{
			for (var i = 0; i < table.Joins.Count; i++)
			{
				var j = table.Joins[i];
				FlattenJoins(j.Table);

				if (j.JoinType == SelectQuery.JoinType.Inner)
					for (var si = 0; si < j.Table.Joins.Count; si++)
					{
						var sj = j.Table.Joins[si];
						if ((sj.JoinType == SelectQuery.JoinType.Inner || sj.JoinType == SelectQuery.JoinType.Left)
							&& table != j.Table && !HasDependencyWithParent(j, sj))
						{
							table.Joins.Add(sj);
							j.Table.Joins.RemoveAt(si);
							--si;
						}
					}
			}
		}

		bool IsDependedBetweenJoins(SelectQuery.TableSource table,
			SelectQuery.JoinedTable testedJoin)
		{
			var testedSources = new HashSet<int>(testedJoin.Table.GetTables().Select(t => t.SourceID));

			foreach (var tableJoin in table.Joins)
			{
				if (testedSources.Contains(tableJoin.Table.SourceID))
					continue;

				if (IsDependedOnJoin(table, tableJoin, testedSources))
					return true;
			}

			return IsDependedExcludeJoins(testedSources);
		}

		bool IsDepended(SelectQuery.JoinedTable join, SelectQuery.JoinedTable toIgnore)
		{
			var testedSources = new HashSet<int>(join.Table.GetTables().Select(t => t.SourceID));
			if (toIgnore != null)
				foreach (var sourceId in toIgnore.Table.GetTables().Select(t => t.SourceID))
					testedSources.Add(sourceId);

			var dependent = false;

			new QueryVisitor().VisitParentFirst(_selectQuery, e =>
			{
				if (dependent)
					return false;

				// ignore non searchable parts
				if (  e.ElementType == QueryElementType.SelectClause 
				   || e.ElementType == QueryElementType.GroupByClause 
				   || e.ElementType == QueryElementType.OrderByClause)
					return false;

				if (e.ElementType == QueryElementType.JoinedTable)
					if (testedSources.Contains(((SelectQuery.JoinedTable) e).Table.SourceID))
						return false;

				var expression = e as ISqlExpression;

				if (expression != null)
				{
					var field = GetUnderlayingField(expression);
					if (field != null)
					{
						var newField = GetNewField(field);
						var local = testedSources.Contains(newField.SourceID);
						if (local)
							dependent = !CanWeReplaceField(null, newField, testedSources, -1);
					}
				}

				return !dependent;
			});

			return dependent;
		}

		bool IsDependedExcludeJoins(SelectQuery.JoinedTable join)
		{
			var testedSources = new HashSet<int>(join.Table.GetTables().Select(t => t.SourceID));
			return IsDependedExcludeJoins(testedSources);
		}

		bool IsDependedExcludeJoins(HashSet<int> testedSources)
		{
			var dependent = false;

			new QueryVisitor().VisitParentFirst(_selectQuery, e =>
			{
				if (dependent)
					return false;

				if (e.ElementType == QueryElementType.JoinedTable)
					return false;

				var expression = e as ISqlExpression;
				if (expression != null)
				{
					var field = GetUnderlayingField(expression);

					if (field != null)
					{
						var newField = GetNewField(field);
						var local = testedSources.Contains(newField.SourceID);
						if (local)
							dependent = !CanWeReplaceField(null, newField, testedSources, -1);
					}
				}

				return !dependent;
			});

			return dependent;
		}

		bool HasDependencyWithParent(SelectQuery.JoinedTable parent,
			SelectQuery.JoinedTable child)
		{
			var sources = new HashSet<int>(child.Table.GetTables().Select(t => t.SourceID));
			var dependent = false;

			new QueryVisitor().VisitParentFirst(parent, e =>
			{
				if (dependent)
					return false;

				if (e == child)
					return false;

				var expression = e as ISqlExpression;

				if (expression != null)
				{
					var field = GetUnderlayingField(expression);
					if (field != null)
						dependent = sources.Contains(field.SourceID);
				}

				return !dependent;
			});
<commit_msg>Fixed bug with moving join up.<commit_after>using System;

namespace LinqToDB.SqlProvider
{
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;

	using JetBrains.Annotations;

	using SqlQuery;

	internal class JoinOptimizer
	{
		Dictionary<SelectQuery.SearchCondition, SelectQuery.SearchCondition>                     _additionalFilter;
		Dictionary<VirtualField, HashSet<Tuple<int, VirtualField>>>                              _equalityMap;
		Dictionary<Tuple<SelectQuery.TableSource, SelectQuery.TableSource>, List<FoundEquality>> _fieldPairCache;
		Dictionary<int, List<List<string>>>                                                      _keysCache;
		HashSet<int>                                                                             _removedSources;
		Dictionary<VirtualField, VirtualField>                                                   _replaceMap;
		SelectQuery                                                                              _selectQuery;

		static bool IsEqualTables(SqlTable table1, SqlTable table2)
		{
			var result =
				   table1          != null 
				&& table2          != null
				&& table1.Database == table2.Database
				&& table1.Owner    == table2.Owner
				&& table1.Name     == table2.Name;

			return result;
		}

		void FlattenJoins(SelectQuery.TableSource table)
		{
			for (var i = 0; i < table.Joins.Count; i++)
			{
				var j = table.Joins[i];
				FlattenJoins(j.Table);

				if (j.JoinType == SelectQuery.JoinType.Inner)
					for (var si = 0; si < j.Table.Joins.Count; si++)
					{
						var sj = j.Table.Joins[si];
						if ((sj.JoinType == SelectQuery.JoinType.Inner || sj.JoinType == SelectQuery.JoinType.Left)
							&& table != j.Table && !HasDependencyWithParent(j, sj))
						{
							table.Joins.Insert(i + 1, sj);
							j.Table.Joins.RemoveAt(si);
							--si;
						}
					}
			}
		}

		bool IsDependedBetweenJoins(SelectQuery.TableSource table,
			SelectQuery.JoinedTable testedJoin)
		{
			var testedSources = new HashSet<int>(testedJoin.Table.GetTables().Select(t => t.SourceID));

			foreach (var tableJoin in table.Joins)
			{
				if (testedSources.Contains(tableJoin.Table.SourceID))
					continue;

				if (IsDependedOnJoin(table, tableJoin, testedSources))
					return true;
			}

			return IsDependedExcludeJoins(testedSources);
		}

		bool IsDepended(SelectQuery.JoinedTable join, SelectQuery.JoinedTable toIgnore)
		{
			var testedSources = new HashSet<int>(join.Table.GetTables().Select(t => t.SourceID));
			if (toIgnore != null)
				foreach (var sourceId in toIgnore.Table.GetTables().Select(t => t.SourceID))
					testedSources.Add(sourceId);

			var dependent = false;

			new QueryVisitor().VisitParentFirst(_selectQuery, e =>
			{
				if (dependent)
					return false;

				// ignore non searchable parts
				if (  e.ElementType == QueryElementType.SelectClause 
				   || e.ElementType == QueryElementType.GroupByClause 
				   || e.ElementType == QueryElementType.OrderByClause)
					return false;

				if (e.ElementType == QueryElementType.JoinedTable)
					if (testedSources.Contains(((SelectQuery.JoinedTable) e).Table.SourceID))
						return false;

				var expression = e as ISqlExpression;

				if (expression != null)
				{
					var field = GetUnderlayingField(expression);
					if (field != null)
					{
						var newField = GetNewField(field);
						var local = testedSources.Contains(newField.SourceID);
						if (local)
							dependent = !CanWeReplaceField(null, newField, testedSources, -1);
					}
				}

				return !dependent;
			});

			return dependent;
		}

		bool IsDependedExcludeJoins(SelectQuery.JoinedTable join)
		{
			var testedSources = new HashSet<int>(join.Table.GetTables().Select(t => t.SourceID));
			return IsDependedExcludeJoins(testedSources);
		}

		bool IsDependedExcludeJoins(HashSet<int> testedSources)
		{
			var dependent = false;

			new QueryVisitor().VisitParentFirst(_selectQuery, e =>
			{
				if (dependent)
					return false;

				if (e.ElementType == QueryElementType.JoinedTable)
					return false;

				var expression = e as ISqlExpression;
				if (expression != null)
				{
					var field = GetUnderlayingField(expression);

					if (field != null)
					{
						var newField = GetNewField(field);
						var local = testedSources.Contains(newField.SourceID);
						if (local)
							dependent = !CanWeReplaceField(null, newField, testedSources, -1);
					}
				}

				return !dependent;
			});

			return dependent;
		}

		bool HasDependencyWithParent(SelectQuery.JoinedTable parent,
			SelectQuery.JoinedTable child)
		{
			var sources   = new HashSet<int>(child.Table.GetTables().Select(t => t.SourceID));
			var dependent = false;

			// check that parent has dependency on child
			new QueryVisitor().VisitParentFirst(parent, e =>
			{
				if (dependent)
					return false;

				if (e == child)
					return false;

				var expression = e as ISqlExpression;

				if (expression != null)
				{
					var field = GetUnderlayingField(expression);
					if (field != null)
						dependent = sources.Contains(field.SourceID);
				}

				return !dependent;
			});

{'fraction_non_alphanumeric': 0.10573423822169765, 'fraction_numerical': 0.002852365496213239, 'mean_word_length': 2.5465643529822115, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2958233', 'n_tokens_mistral': 3797, 'n_tokens_neox': 2933, 'n_words': 628}