package org.apache.torque.sql;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.List;
import org.apache.torque.TorqueException;
import org.apache.torque.criteria.CriteriaInterface;
import org.apache.torque.criteria.Criterion;
import org.apache.torque.criteria.FromElement;
import org.apache.torque.criteria.Join;
import org.apache.torque.criteria.JoinType;
import org.apache.torque.criteria.PreparedStatementPart;
import org.apache.torque.util.UniqueList;
/**
* Generates SQL for joins.
*
* @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @version $Id: JoinBuilder.java 1351125 2012-06-17 16:51:03Z tv $
*/
@SuppressWarnings("deprecation")
public final class JoinBuilder
{
/**
* Private constructor to prevent initialisation.
*
* Class contains only static methods and should therefore not be
* instantiated.
*/
private JoinBuilder()
{
// empty
}
/**
* Adds the Joins from the criteria to the query.
*
* @param criteria the criteria from which the Joins are taken.
* @param query the query to which the Joins should be added.
*
* @throws TorqueException if the Joins can not be processed
*/
public static void processJoins(
final CriteriaInterface<?> criteria,
final Query query)
throws TorqueException
{
List<Join> criteriaJoins = criteria.getJoins();
if (criteriaJoins.isEmpty())
{
return;
}
UniqueList<FromElement> queryFromClause = query.getFromClause();
UniqueList<String> queryWhereClause = query.getWhereClause();
for (int i = 0; i < criteriaJoins.size(); i++)
{
Join join = criteriaJoins.get(i);
// Check the join type and add the join to the
// appropriate places in the query
JoinType joinType = join.getJoinType();
if (joinType == null)
{
// Do not treat join as explicit join, but add
// the join condition to the where clauses
StringBuilder joinConditionStringBuilder = new StringBuilder();
SqlBuilder.appendCriterion(
join.getJoinCondition(),
criteria,
joinConditionStringBuilder,
query);
queryWhereClause.add(joinConditionStringBuilder.toString());
}
else
{
Criterion joinCondition = join.getJoinCondition();
// get the table names
// (and the alias names for them if necessary))
PreparedStatementPart leftExpression;
if (join.getLeftTable() != null)
{
leftExpression = join.getLeftTable();
}
else
{
if (joinCondition.isComposite())
{
throw new TorqueException(
"join condition is composite "
+ "and there is no leftTable defined "
+ "in the join. "
+ "Please define a leftTable in the join");
}
Object lValue = joinCondition.getLValue();
leftExpression = SqlBuilder.getExpressionForFromClause(
lValue,
criteria);
}
PreparedStatementPart rightExpression;
if (join.getRightTable() != null)
{
rightExpression = join.getRightTable();
}
else
{
if (joinCondition.isComposite())
{
throw new TorqueException(
"join condition is composite "
+ "and there is no rightTable defined "
+ "in the join. "
+ "Please define a rightTable in the join");
}
Object rValue = joinCondition.getRValue();
rightExpression = SqlBuilder.getExpressionForFromClause(
rValue,
criteria);
}
// check whether the order of the join must be "reversed"
// This if the case if the fromClause already contains
// rightTableName
if (!SqlBuilder.fromClauseContainsExpression(
queryFromClause,
rightExpression))
{
if (!SqlBuilder.fromClauseContainsExpression(
queryFromClause,
leftExpression))
{
FromElement fromElement = new FromElement(
leftExpression.getSql().toString(),
null,
null,
leftExpression.getPreparedStatementReplacements());
queryFromClause.add(fromElement);
}
FromElement fromElement = new FromElement(
rightExpression.getSql().toString(),
joinType,
buildJoinCondition(joinCondition, criteria));
queryFromClause.add(fromElement);
}
else
{
if (SqlBuilder.fromClauseContainsExpression(
queryFromClause,
leftExpression))
{
// We cannot add an explicit join if both tables
// are already present in the from clause
throw new TorqueException(
"Unable to create a" + joinType
+ "because both expressions "
+ leftExpression.getSql()
+ " and " + rightExpression.getSql()
+ " are already in use. "
+ "Try to create an(other) alias.");
}
// now add the join in reverse order
// rightTableName must not be added
// because it is already present
FromElement fromElement = new FromElement(
leftExpression.getSql().toString(),
reverseJoinType(joinType),
buildJoinCondition(joinCondition, criteria));
queryFromClause.add(fromElement);
}
}
}
}
/**
* Returns the reversed Join type, i.e. the join type which would produce
* the same result if also the joined tables were exchanged:
* Example:<br />
* table_a left join table_b <br />
* produces the same result as <br />
* table_b right join table_a<br />
* So "left join" is the reverse of "right join".
*
* @param joinType the join type to be reversed.
*
* @return the reversed join type.
*/
private static JoinType reverseJoinType(final JoinType joinType)
{
if (JoinType.LEFT_JOIN.equals(joinType))
{
return JoinType.RIGHT_JOIN;
}
else if (JoinType.RIGHT_JOIN.equals(joinType))
{
return JoinType.LEFT_JOIN;
}
else
{
return joinType;
}
}
/**
* Creates the join condition string for a join.
*
* @param joinCondition the join condition.
* @param criteria the enclosing criteria.
*
* @return A join expression, e.g. table_a.column_a=table_b.column_b.
*
* @throws TorqueException if the join condition cannot be built.
*/
private static PreparedStatementPart buildJoinCondition(
Criterion joinCondition,
CriteriaInterface<?> criteria)
throws TorqueException
{
PreparedStatementPart joinPart = new PreparedStatementPart();
appendJoinCondition(joinCondition, criteria, joinPart);
return joinPart;
}
/**
* Appends a join condition to a join part.
*
* @param joinCondition the join condition.
* @param criteria the enclosing criteria.
* @param joinPart the join part to append to.
*
* @return A join expression, e.g. table_a.column_a=table_b.column_b.
*
* @throws TorqueException if the join condition cannot be built.
*/
private static void appendJoinCondition(
Criterion joinCondition,
CriteriaInterface<?> criteria,
PreparedStatementPart joinPart)
throws TorqueException
{
if (joinCondition.isComposite())
{
joinPart.getSql().append('(');
boolean firstPart = true;
for (Criterion part : joinCondition.getParts())
{
if (!firstPart)
{
joinPart.getSql().append(joinCondition.getConjunction());
}
appendJoinCondition(
part,
criteria,
joinPart);
firstPart = false;
}
joinPart.getSql().append(')');
return;
}
PreparedStatementPart joinConditionStatementPart
= SqlBuilder.processCriterion(joinCondition, criteria);
joinPart.append(joinConditionStatementPart);
}
}