/*
* Copyright (c) 2011-2015 EPFL DATA Laboratory
* Copyright (c) 2014-2015 The Squall Collaboration (see NOTICE)
*
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.epfl.data.squall.api.sql.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Table;
/*
* **Only conjunctive join conditions are supported!**
*
* R,S: R.A=S.A is represented as:
* {R, {S, R.A = S.A}} and {S, {R, R.A = S.A}}
* so that we can inquire for both tables
*
* Expression are always EqualsTo, unless from ThetaJoinComponent
*/
public class JoinTablesExprs {
private final Map<String, Map<String, List<Expression>>> _tablesJoinExp = new HashMap<String, Map<String, List<Expression>>>();
private final Map<String, Map<String, List<Expression>>> _singleDirTablesJoinExp = new HashMap<String, Map<String, List<Expression>>>();
public void addEntry(Table leftTable, Table rightTable, Expression exp) {
final String leftName = ParserUtil.getComponentName(leftTable);
final String rightName = ParserUtil.getComponentName(rightTable);
// adding to bi-directional data structure - structure used by default
// in methods of this class
addToJoinMap(leftName, rightName, exp, _tablesJoinExp);
addToJoinMap(rightName, leftName, exp, _tablesJoinExp);
// adding to a single-directional data strucure
addToJoinMap(leftName, rightName, exp, _singleDirTablesJoinExp);
}
private void addToJoinMap(String tblName1, String tblName2, Expression exp,
Map<String, Map<String, List<Expression>>> collection) {
if (collection.containsKey(tblName1)) {
final Map<String, List<Expression>> inner = collection
.get(tblName1);
if (inner.containsKey(tblName2)) {
final List<Expression> expList = inner.get(tblName2);
expList.add(exp);
} else {
final List<Expression> expList = new ArrayList<Expression>();
expList.add(exp);
inner.put(tblName2, expList);
}
} else {
final List<Expression> expList = new ArrayList<Expression>();
expList.add(exp);
final Map<String, List<Expression>> newInner = new HashMap<String, List<Expression>>();
newInner.put(tblName2, expList);
collection.put(tblName1, newInner);
}
}
/*
* Get a list of join condition expressions. For EquiJoin, it's in form of
* EqualsTo. We support only conjunctive join conditions.
*/
public List<Expression> getExpressions(List<String> tables1,
List<String> tables2) {
final List<Expression> result = new ArrayList<Expression>();
for (final String table1 : tables1) {
final List<Expression> delta = getExpressions(table1, tables2);
if (delta != null)
result.addAll(delta);
}
if (result.isEmpty())
return null;
else
return result;
}
public List<Expression> getExpressions(String table1, List<String> tables2) {
final List<Expression> result = new ArrayList<Expression>();
for (final String table2 : tables2) {
final List<Expression> delta = getExpressions(table1, table2);
if (delta != null)
result.addAll(delta);
}
if (result.isEmpty())
return null;
else
return result;
}
public List<Expression> getExpressions(String tableName1, String tableName2) {
final Map<String, List<Expression>> inner = _tablesJoinExp
.get(tableName1);
return inner.get(tableName2);
}
/*
* Get a list of tables joinable form a set of
* DataSourceComponents(ancestors) This might return duplicates: For
* example, R.A=S.A and S.B=T.B and R.A=T.C If we want to join R-S with T,
* then getJoinedWith(R, S) will return (T, T) To fix the problem, we used
* sets, and then we converted it back to List<String> For the same example
* R-S joined T, getJoinedWith(R, S) could (among other results) return R,
* S, but this is filtered at the end of this method. We don't want Sources
* in the results which are already in ancestors.
*/
public List<String> getJoinedWith(List<String> ancestors) {
final Set<String> result = new HashSet<String>();
for (final String ancestor : ancestors) {
final List<String> singleJoinedWith = getJoinedWith(ancestor);
result.addAll(singleJoinedWith);
}
final List<String> resultList = new ArrayList<String>(result);
return ParserUtil.getDifference(resultList, ancestors);
}
private List<String> getJoinedWith(
Map<String, Map<String, List<Expression>>> collection,
String tblCompName) {
if (!collection.containsKey(tblCompName))
return null;
final List<String> joinedWith = new ArrayList<String>();
final Map<String, List<Expression>> innerMap = collection
.get(tblCompName);
for (final Map.Entry<String, List<Expression>> entry : innerMap
.entrySet())
joinedWith.add(entry.getKey());
return joinedWith;
}
/*
* Get a list of tables DataSourceComponent named tblCompName can join with
*/
public List<String> getJoinedWith(String tblCompName) {
final List<String> result = getJoinedWith(_tablesJoinExp, tblCompName);
if (result == null)
throw new RuntimeException("Table doesn't exist in JoinTablesExp: "
+ tblCompName);
return result;
}
// single-directional method
public List<String> getJoinedWithSingleDir(String tblCompName) {
return getJoinedWith(_singleDirTablesJoinExp, tblCompName);
}
public boolean joinExistsBetween(List<String> firstAncestors,
List<String> secondAncestors) {
for (final String firstSource : firstAncestors)
if (joinExistsBetween(firstSource, secondAncestors))
return true;
return false;
}
public boolean joinExistsBetween(String firstSource,
List<String> secondAncestors) {
final List<String> joinedWith = getJoinedWith(firstSource);
final List<String> intersection = ParserUtil.getIntersection(
joinedWith, secondAncestors);
return !intersection.isEmpty();
}
}