View Javadoc

1   package org.apache.torque.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.List;
23  
24  import org.apache.torque.TorqueException;
25  import org.apache.torque.adapter.DB;
26  import org.apache.torque.map.DatabaseMap;
27  
28  /***
29   * Factored out code that is used to generate Join Code. This code comes
30   * from BasePeer and is put here to reduce complexity in the BasePeer class.
31   * You should not use the methods here directly!
32   *
33   * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
34   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
35   * @version $Id: JoinBuilder.java 535615 2007-05-06 14:11:05Z tv $
36   */
37  public final class JoinBuilder
38  {
39      /***
40       * Private constructor to prevent initialisation.
41       *
42       * Class contains only static methods and should therefore not be
43       * instantiated.
44       */
45      private JoinBuilder()
46      {
47      }
48  
49      /***
50       * adds the Joins from the criteria to the query
51       * @param criteria the criteria from which the Joins are taken
52       * @param query the query to which the Joins should be added
53       * @throws TorqueException if the Joins can not be processed
54       */
55      public static void processJoins(
56              final DB db,
57              final DatabaseMap dbMap,
58              final Criteria criteria,
59              final Query query)
60              throws TorqueException
61      {
62          List criteriaJoins = criteria.getJoins();
63  
64          if (criteriaJoins.isEmpty())
65          {
66              return;
67          }
68  
69          UniqueList queryFromClause = query.getFromClause();
70          UniqueList queryWhereClause = query.getWhereClause();
71  
72          for (int i = 0; i < criteriaJoins.size(); i++)
73          {
74              Criteria.Join join = (Criteria.Join) criteriaJoins.get(i);
75              String leftColumn = join.getLeftColumn();
76              String rightColumn = join.getRightColumn();
77  
78              // check if the column names make sense
79              if (leftColumn.indexOf('.') == -1)
80              {
81                  SQLBuilder.throwMalformedColumnNameException("join", leftColumn);
82              }
83              if (rightColumn.indexOf('.') == -1)
84              {
85                  SQLBuilder.throwMalformedColumnNameException("join", rightColumn);
86              }
87  
88              // get the table names
89              // (and the alias names for them if necessary))
90              // Also check whether a case insensitive comparison is needed
91              int dot = leftColumn.lastIndexOf('.');
92              String leftTableName = leftColumn.substring(0, dot);
93  
94              leftTableName =
95                      SQLBuilder.getTableNameForFromClause(leftTableName, criteria);
96  
97              dot = rightColumn.lastIndexOf('.');
98              String rightTableName = rightColumn.substring(0, dot);
99              String dbTableName
100                     = criteria.getTableForAlias(rightTableName);
101 
102             if (dbTableName == null)
103             {
104                 dbTableName = rightTableName;
105             }
106 
107             String columnName = rightColumn.substring(
108                     dot + 1,
109                     rightColumn.length());
110 
111             boolean ignoreCase = (criteria.isIgnoreCase()
112                     && (dbMap
113                             .getTable(dbTableName)
114                             .getColumn(columnName)
115                             .getType()
116                             instanceof String));
117 
118             rightTableName = SQLBuilder.getTableNameForFromClause(
119                     rightTableName, criteria);
120 
121             // now check the join type and add the join to the
122             // appropriate places in the query
123             SqlEnum joinType  = join.getJoinType();
124 
125             if (joinType == null)
126             {
127                 // Do not treat join as explicit join, but add
128                 // the join condition to the where clauses
129                 if (!SQLBuilder.fromClauseContainsTableName(
130                             queryFromClause,
131                             leftTableName))
132                 {
133                     Query.FromElement fromElement
134                             = new Query.FromElement(
135                                     leftTableName, null, null);
136                     queryFromClause.add(fromElement);
137                 }
138                 if (!SQLBuilder.fromClauseContainsTableName(
139                             queryFromClause,
140                             rightTableName))
141                 {
142                     Query.FromElement fromElement
143                             = new Query.FromElement(
144                                     rightTableName, null, null);
145                     queryFromClause.add(fromElement);
146                 }
147                 queryWhereClause.add(
148                         SqlExpression.buildInnerJoin(
149                                 leftColumn, rightColumn, ignoreCase, db));
150             }
151             else
152             {
153                 // check whether the order of the join must be "reversed"
154                 // This if the case if the fromClause already contains
155                 // rightTableName
156 
157                 if (!SQLBuilder.fromClauseContainsTableName(
158                             queryFromClause,
159                             rightTableName))
160                 {
161                     if (!SQLBuilder.fromClauseContainsTableName(
162                                 queryFromClause,
163                                 leftTableName))
164                     {
165                         Query.FromElement fromElement
166                                 = new Query.FromElement(
167                                         leftTableName, null, null);
168                         queryFromClause.add(fromElement);
169                     }
170 
171                     Query.FromElement fromElement
172                             = new Query.FromElement(
173                                     rightTableName, joinType,
174                                     SqlExpression.buildInnerJoin(
175                                             leftColumn, rightColumn,
176                                             ignoreCase, db));
177                     queryFromClause.add(fromElement);
178                 }
179                 else
180                 {
181                     if (SQLBuilder.fromClauseContainsTableName(
182                                 queryFromClause,
183                                 leftTableName))
184                     {
185                         // We cannot add an explicit join if both tables
186                         // are alredy present in the from clause
187                         throw new TorqueException(
188                                 "Unable to create a " + joinType
189                                 + "because both table names "
190                                 + leftTableName + " and " + rightTableName
191                                 + " are already in use. "
192                                 + "Try to create an(other) alias.");
193                     }
194                     // now add the join in reverse order
195                     // rightTableName must not be added
196                     // because it is already present
197                     Query.FromElement fromElement
198                             = new Query.FromElement(
199                                     leftTableName, reverseJoinType(joinType),
200                                     SqlExpression.buildInnerJoin(
201                                             rightColumn, leftColumn,
202                                             ignoreCase, db));
203                     queryFromClause.add(fromElement);
204                 }
205             }
206         }
207     }
208 
209     /***
210      * returns the reversed Join type, i.e. the join type which would produce
211      * the same result if also the joined tables were exchanged:
212      * Example:<br />
213      * table_a left join table_b <br />
214      * produces the same result as  <br />
215      * table_b right join table_a<br />
216      * So "left join" is the reverse of "right join"
217      * @param joinType the join type to be reversed
218      * @return the reversed join type
219      */
220     private static SqlEnum reverseJoinType(final SqlEnum joinType)
221     {
222         if (SqlEnum.LEFT_JOIN.equals(joinType))
223         {
224             return SqlEnum.RIGHT_JOIN;
225         }
226         else if (SqlEnum.RIGHT_JOIN.equals(joinType))
227         {
228             return SqlEnum.LEFT_JOIN;
229         }
230         else
231         {
232             return joinType;
233         }
234     }
235 }