View Javadoc

1   package org.apache.torque.sql;
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.ArrayList;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.torque.Column;
30  import org.apache.torque.ColumnImpl;
31  import org.apache.torque.Database;
32  import org.apache.torque.Torque;
33  import org.apache.torque.TorqueException;
34  import org.apache.torque.adapter.Adapter;
35  import org.apache.torque.criteria.Criteria;
36  import org.apache.torque.criteria.CriteriaInterface;
37  import org.apache.torque.criteria.Criterion;
38  import org.apache.torque.criteria.FromElement;
39  import org.apache.torque.criteria.PreparedStatementPart;
40  import org.apache.torque.criteria.SqlEnum;
41  import org.apache.torque.map.ColumnMap;
42  import org.apache.torque.map.DatabaseMap;
43  import org.apache.torque.map.MapHelper;
44  import org.apache.torque.map.TableMap;
45  import org.apache.torque.om.ObjectKey;
46  import org.apache.torque.sql.whereclausebuilder.CurrentDateTimePsPartBuilder;
47  import org.apache.torque.sql.whereclausebuilder.CustomBuilder;
48  import org.apache.torque.sql.whereclausebuilder.InBuilder;
49  import org.apache.torque.sql.whereclausebuilder.LikeBuilder;
50  import org.apache.torque.sql.whereclausebuilder.NullValueBuilder;
51  import org.apache.torque.sql.whereclausebuilder.StandardBuilder;
52  import org.apache.torque.sql.whereclausebuilder.VerbatimSqlConditionBuilder;
53  import org.apache.torque.sql.whereclausebuilder.WhereClausePsPartBuilder;
54  import org.apache.torque.util.UniqueColumnList;
55  import org.apache.torque.util.UniqueList;
56  
57  /**
58   * Factored out code that is used to process SQL tables. This code comes
59   * from BasePeer and is put here to reduce complexity in the BasePeer class.
60   * You should not use the methods here directly!
61   *
62   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
63   * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
64   * @version $Id: SqlBuilder.java 1355228 2012-06-29 03:38:08Z tfischer $
65   */
66  @SuppressWarnings("deprecation")
67  public final class SqlBuilder
68  {
69      /** Logging */
70      protected static final Log log = LogFactory.getLog(SqlBuilder.class);
71  
72      /** Delimiters for SQL functions. */
73      public static final String[] FUNCTION_DELIMITERS
74              = {" ", ",", "(", ")", "<", ">"};
75  
76      /** The backslash character*/
77      private static final char BACKSLASH = '\\';
78  
79      /**
80       * The list of WhereClausePsPartBuilders which can build the where clause.
81       */
82      private static List<WhereClausePsPartBuilder> whereClausePsPartBuilders
83          = new ArrayList<WhereClausePsPartBuilder>();
84  
85      static
86      {
87          whereClausePsPartBuilders.add(new VerbatimSqlConditionBuilder());
88          whereClausePsPartBuilders.add(new CustomBuilder());
89          whereClausePsPartBuilders.add(new CurrentDateTimePsPartBuilder());
90          whereClausePsPartBuilders.add(new NullValueBuilder());
91          whereClausePsPartBuilders.add(new LikeBuilder());
92          whereClausePsPartBuilders.add(new InBuilder());
93          whereClausePsPartBuilders.add(new StandardBuilder());
94      }
95  
96      /**
97       * Private constructor to prevent instantiation.
98       *
99       * Class contains only static method and should therefore not be
100      * instantiated.
101      */
102     private SqlBuilder()
103     {
104         // empty
105     }
106 
107     /**
108      * Returns the Builders which are responsible to render single where clause
109      * conditions. The returned list can be modified in order to change
110      * the rendered SQL.
111      *
112      * @return the current WhereClausePsPartBuilders, not null.
113      */
114     public static List<WhereClausePsPartBuilder> getWhereClausePsPartBuilders()
115     {
116         return whereClausePsPartBuilders;
117     }
118 
119     /**
120      * Builds a Query from a criteria.
121      *
122      * @param crit the criteria to build the query from, not null.
123      *
124      * @return the corresponding query to the criteria.
125      *
126      * @exception TorqueException if an error occurs
127      * @deprecated please use org.apache.torque.criteria.Criteria
128      *             instead of org.apache.torque.util.Criteria.
129      */
130     @Deprecated
131     public static Query buildQuery(final org.apache.torque.util.Criteria crit)
132             throws TorqueException
133     {
134         Query sqlStatement = new Query();
135 
136         JoinBuilder.processJoins(crit, sqlStatement);
137         processModifiers(crit, sqlStatement);
138         processSelectColumns(crit, sqlStatement);
139         processAsColumns(crit, sqlStatement);
140         processCriterions(crit, sqlStatement);
141         processGroupBy(crit, sqlStatement);
142         processHaving(crit, sqlStatement);
143         processOrderBy(crit, sqlStatement);
144         processLimits(crit, sqlStatement);
145 
146         return sqlStatement;
147     }
148 
149     /**
150      * Builds a Query from a criteria.
151      *
152      * @param crit the criteria to build the query from, not null.
153      *
154      * @return the corresponding query to the criteria.
155      *
156      * @exception TorqueException if an error occurs
157      */
158     public static Query buildQuery(final Criteria crit)
159             throws TorqueException
160     {
161         Query sqlStatement = new Query();
162 
163         JoinBuilder.processJoins(crit, sqlStatement);
164         processModifiers(crit, sqlStatement);
165         processSelectColumns(crit, sqlStatement);
166         processAsColumns(crit, sqlStatement);
167         processCriterions(crit, sqlStatement);
168         processGroupBy(crit, sqlStatement);
169         processHaving(crit, sqlStatement);
170         processOrderBy(crit, sqlStatement);
171         processLimits(crit, sqlStatement);
172         processFromElements(crit, sqlStatement);
173         sqlStatement.setFetchSize(crit.getFetchSize());
174 
175         return sqlStatement;
176     }
177 
178     /**
179      * Adds the select columns from the criteria to the query.
180      *
181      * @param criteria the criteria from which the select columns are taken.
182      * @param query the query to which the select columns should be added.
183      *
184      * @throws TorqueException if the select columns can not be processed.
185      */
186     private static void processSelectColumns(
187                 final CriteriaInterface<?> criteria,
188                 final Query query)
189             throws TorqueException
190     {
191         UniqueList<String> selectClause = query.getSelectClause();
192         UniqueColumnList selectColumns = criteria.getSelectColumns();
193 
194         for (Column column : selectColumns)
195         {
196             String sqlExpression = column.getSqlExpression();
197             Column resolvedAlias = criteria.getAsColumns().get(sqlExpression);
198             if (resolvedAlias != null)
199             {
200                 // will be handled by processAsColumns
201                 continue;
202             }
203             selectClause.add(sqlExpression);
204             addTableToFromClause(
205                     column,
206                     criteria,
207                     query);
208         }
209     }
210 
211     /**
212      * Adds the As-columns (Aliases for columns) from the criteria
213      * to the query's select clause.
214      *
215      * @param criteria the criteria from which the As-columns are taken,
216      *        not null.
217      * @param query the query to which the As-columns should be added,
218      *        not null.
219      *
220      * @throws TorqueException if the as columns can not be processed.
221      */
222     private static void processAsColumns(
223                 final CriteriaInterface<?> criteria,
224                 final Query query)
225             throws TorqueException
226     {
227         UniqueList<String> querySelectClause = query.getSelectClause();
228         Map<String, Column> criteriaAsColumns = criteria.getAsColumns();
229 
230         for (Map.Entry<String, Column> entry : criteriaAsColumns.entrySet())
231         {
232             Column column = entry.getValue();
233             querySelectClause.add(
234                     column.getSqlExpression()
235                         + SqlEnum.AS
236                         + entry.getKey());
237             addTableToFromClause(
238                     column,
239                     criteria,
240                     query);
241         }
242     }
243 
244     /**
245      * Adds the select modifiers from the criteria to the query.
246      *
247      * @param criteria the criteria from which the Modifiers are taken,
248      *        not null.
249      * @param query the query to which the Modifiers should be added,
250      *        not null.
251      */
252     private static void processModifiers(
253             final CriteriaInterface<?> criteria,
254             final Query query)
255     {
256         UniqueList<String> selectModifiers = query.getSelectModifiers();
257         UniqueList<String> modifiers = criteria.getSelectModifiers();
258         for (String modifier : modifiers)
259         {
260             selectModifiers.add(modifier);
261         }
262     }
263 
264     /**
265      * Adds the Criterions from the criteria to the query.
266      *
267      * @param criteria the criteria from which the Criterion-objects are taken
268      * @param query the query to which the Criterion-objects should be added.
269      *
270      * @throws TorqueException if the Criterion-objects can not be processed
271      */
272     private static void processCriterions(
273             final Criteria criteria,
274             final Query query)
275         throws TorqueException
276     {
277         if (criteria.getTopLevelCriterion() == null)
278         {
279             return;
280         }
281         StringBuilder where = new StringBuilder();
282         appendCriterion(
283                 criteria.getTopLevelCriterion(),
284                 criteria,
285                 where,
286                 query);
287         query.getWhereClause().add(where.toString());
288     }
289 
290     static void appendCriterion(
291                 Criterion criterion,
292                 CriteriaInterface<?>  criteria,
293                 StringBuilder where,
294                 Query query)
295             throws TorqueException
296     {
297         if (criterion.isComposite())
298         {
299             where.append('(');
300             boolean firstPart = true;
301             for (Criterion part : criterion.getParts())
302             {
303                 if (!firstPart)
304                 {
305                     where.append(criterion.getConjunction());
306                 }
307                 appendCriterion(
308                         part,
309                         criteria,
310                         where,
311                         query);
312                 firstPart = false;
313             }
314             where.append(')');
315             return;
316         }
317         // add the table to the from clause, if it is not already
318         // contained there
319         // it is important that this piece of code is executed AFTER
320         // the joins are processed
321         addTableToFromClause(
322                 criterion.getLValue(),
323                 criteria,
324                 query);
325         addTableToFromClause(
326                 criterion.getRValue(),
327                 criteria,
328                 query);
329 
330         PreparedStatementPart whereClausePartOutput
331             = processCriterion(criterion, criteria);
332 
333         where.append(whereClausePartOutput.getSql());
334         query.getWhereClausePreparedStatementReplacements().addAll(
335                 whereClausePartOutput.getPreparedStatementReplacements());
336     }
337 
338     static PreparedStatementPart processCriterion(
339                 Criterion criterion,
340                 CriteriaInterface<?> criteria)
341             throws TorqueException
342     {
343         final String dbName = criteria.getDbName();
344         final Database database = Torque.getDatabase(dbName);
345         final Adapter adapter = Torque.getAdapter(dbName);
346 
347         boolean ignoreCase
348                 = isIgnoreCase(criterion, criteria, database);
349 
350         WhereClauseExpression whereClausePartInput
351                 = new WhereClauseExpression(
352                         criterion.getLValue(),
353                         criterion.getComparison(),
354                         criterion.getRValue(),
355                         criterion.getSql(),
356                         criterion.getPreparedStatementReplacements());
357         PreparedStatementPart whereClausePartOutput = null;
358         for (WhereClausePsPartBuilder builder : whereClausePsPartBuilders)
359         {
360             if (builder.isApplicable(whereClausePartInput, adapter))
361             {
362                 whereClausePartOutput = builder.buildPs(
363                         whereClausePartInput,
364                         ignoreCase,
365                         adapter);
366                 break;
367             }
368         }
369 
370         if (whereClausePartOutput == null)
371         {
372             // should not happen as last element in list is standardHandler
373             // which takes all
374             throw new RuntimeException("No handler found for whereClausePart "
375                     + whereClausePartInput);
376         }
377         return whereClausePartOutput;
378     }
379 
380     /**
381      * Adds the Criterions from the criteria to the query.
382      *
383      * @param criteria the criteria from which the Criterion-objects are taken
384      * @param query the query to which the Criterion-objects should be added.
385      *
386      * @throws TorqueException if the Criterion-objects can not be processed
387      * @deprecated please use org.apache.torque.criteria.Criteria
388      *             instead of org.apache.torque.util.Criteria.
389      */
390     @Deprecated
391     private static void processCriterions(
392             final org.apache.torque.util.Criteria criteria,
393             final Query query)
394         throws TorqueException
395     {
396         UniqueList<String> whereClause = query.getWhereClause();
397 
398         for (org.apache.torque.util.Criteria.Criterion criterion
399                 : criteria.values())
400         {
401             StringBuilder sb = new StringBuilder();
402             appendCriterionToPs(
403                     criterion,
404                     criteria,
405                     sb,
406                     query);
407             whereClause.add(sb.toString());
408         }
409     }
410 
411     /**
412      * @deprecated please use org.apache.torque.criteria.Criteria
413      *             instead of org.apache.torque.util.Criteria.
414      */
415     @Deprecated
416     private static void appendCriterionToPs(
417                 org.apache.torque.util.Criteria.Criterion criterion,
418                 org.apache.torque.util.Criteria criteria,
419                 StringBuilder sb,
420                 Query query)
421             throws TorqueException
422     {
423         Column column = criterion.getColumn();
424 
425         // add the table to the from clause, if it is not already
426         // contained there
427         // it is important that this piece of code is executed AFTER
428         // the joins are processed
429         addTableToFromClause(
430             column,
431             criteria,
432             query);
433 
434         boolean ignoreCase
435                 = criteria.isIgnoreCase() || criterion.isIgnoreCase();
436         final String dbName = criteria.getDbName();
437         final Adapter adapter = Torque.getAdapter(dbName);
438         final Database database = Torque.getDatabase(dbName);
439         {
440             Column databaseColumn = resolveAliasAndAsColumnAndSchema(
441                     column,
442                     criteria);
443             ColumnMap columnMap = null;
444             {
445                 DatabaseMap databaseMap = database.getDatabaseMap();
446                 TableMap tableMap = databaseMap.getTable(
447                         databaseColumn.getTableName());
448                 if (tableMap != null)
449                 {
450                     columnMap = tableMap.getColumn(
451                             databaseColumn.getColumnName());
452                 }
453             }
454             if (columnMap != null)
455             {
456                 // do not use ignoreCase on columns
457                 // which do not contain String values
458                 ignoreCase = ignoreCase
459                     && columnMap.getType() instanceof String;
460             }
461         }
462 
463         for (int j = 0; j < criterion.getClauses().size(); j++)
464         {
465             sb.append('(');
466         }
467         String columnName = criterion.getColumn().getSqlExpression();
468         WhereClauseExpression whereClausePartInput
469                 = new WhereClauseExpression(
470                         columnName,
471                         criterion.getComparison(),
472                         criterion.getValue(),
473                         null,
474                         null);
475         PreparedStatementPart whereClausePartOutput
476             = buildPs(
477                 whereClausePartInput,
478                 ignoreCase,
479                 adapter);
480         sb.append(whereClausePartOutput.getSql());
481         query.getWhereClausePreparedStatementReplacements().addAll(
482                 whereClausePartOutput.getPreparedStatementReplacements());
483 
484         for (int i = 0; i < criterion.getClauses().size(); i++)
485         {
486             sb.append(criterion.getConjunctions().get(i));
487             org.apache.torque.util.Criteria.Criterion clause
488                 = criterion.getClauses().get(i);
489             appendCriterionToPs(
490                     clause,
491                     criteria,
492                     sb,
493                     query);
494             sb.append(')');
495         }
496     }
497     /**
498      * adds the OrderBy-Columns from the criteria to the query
499      * @param criteria the criteria from which the OrderBy-Columns are taken
500      * @param query the query to which the OrderBy-Columns should be added
501      * @throws TorqueException if the OrderBy-Columns can not be processed
502      */
503     private static void processOrderBy(
504             final CriteriaInterface<?> crit,
505             final Query query)
506             throws TorqueException
507     {
508         UniqueList<String> orderByClause = query.getOrderByClause();
509         UniqueList<String> selectClause = query.getSelectClause();
510 
511         UniqueList<OrderBy> orderByList = crit.getOrderByColumns();
512 
513         // Check for each String/Character column and apply
514         // toUpperCase().
515         for (OrderBy orderBy : orderByList)
516         {
517             Column column = orderBy.getColumn();
518             ColumnMap columnMap = MapHelper.getColumnMap(column, crit);
519             String sqlExpression = column.getSqlExpression();
520 
521             // Either we are not able to look up the column in the
522             // databaseMap, then simply use the case in orderBy and
523             // hope the user knows what he is
524             // doing.
525             // Or we only ignore case in order by for string columns
526             // which do not have a function around them
527             if (columnMap == null
528                     || (columnMap.getType() instanceof String
529                         && sqlExpression.indexOf('(') == -1))
530             {
531                 if (orderBy.isIgnoreCase() || crit.isIgnoreCase())
532                 {
533                     final Adapter adapter = Torque.getAdapter(crit.getDbName());
534                     orderByClause.add(
535                             adapter.ignoreCaseInOrderBy(sqlExpression)
536                                 + ' ' + orderBy.getOrder());
537                     selectClause.add(
538                             adapter.ignoreCaseInOrderBy(sqlExpression));
539                 }
540                 else
541                 {
542                     orderByClause.add(sqlExpression + ' ' + orderBy.getOrder());
543                     if (crit.getAsColumns().get(sqlExpression) == null)
544                     {
545                         selectClause.add(sqlExpression);
546                     }
547                 }
548             }
549             else
550             {
551                 orderByClause.add(sqlExpression + ' ' + orderBy.getOrder());
552                 if (crit.getAsColumns().get(sqlExpression) == null)
553                 {
554                     selectClause.add(sqlExpression);
555                 }
556             }
557             addTableToFromClause(
558                    column,
559                    crit,
560                    query);
561         }
562     }
563 
564     /**
565      * Adds the GroupBy-Columns from the criteria to the query.
566      *
567      * @param criteria the criteria from which the GroupBy-Columns are taken.
568      * @param query the query to which the GroupBy-Columns should be added.
569      *
570      * @throws TorqueException if the GroupBy-Columns can not be processed
571      */
572     private static void processGroupBy(
573             final CriteriaInterface<?> criteria,
574             final Query query)
575             throws TorqueException
576     {
577         UniqueList<String> groupByClause = query.getGroupByClause();
578         UniqueList<String> selectClause = query.getSelectClause();
579         UniqueColumnList groupBy = criteria.getGroupByColumns();
580 
581         for (Column groupByColumn : groupBy)
582         {
583             Column column = criteria.getAsColumns().get(
584                     groupByColumn.getSqlExpression());
585 
586             if (column == null)
587             {
588                 column = groupByColumn;
589             }
590 
591             groupByClause.add(column.getSqlExpression());
592             selectClause.add(column.getSqlExpression());
593             addTableToFromClause(column, criteria, query);
594         }
595     }
596 
597     /**
598      * adds the Having-Columns from the criteria to the query
599      * @param criteria the criteria from which the Having-Columns are taken
600      * @param query the query to which the Having-Columns should be added
601      * @throws TorqueException if the Having-Columns can not be processed
602      */
603     private static void processHaving(
604             final Criteria crit,
605             final Query query)
606             throws TorqueException
607     {
608         Criterion having = crit.getHaving();
609         if (having != null)
610         {
611             query.setHaving(having.toString());
612         }
613     }
614 
615     /**
616      * adds the Having-Columns from the criteria to the query
617      * @param criteria the criteria from which the Having-Columns are taken
618      * @param query the query to which the Having-Columns should be added
619      * @throws TorqueException if the Having-Columns can not be processed
620      */
621     private static void processHaving(
622             final org.apache.torque.util.Criteria crit,
623             final Query query)
624             throws TorqueException
625     {
626         org.apache.torque.util.Criteria.Criterion having = crit.getHaving();
627         if (having != null)
628         {
629             query.setHaving(having.toString());
630         }
631     }
632 
633     /**
634      * Adds a Limit clause to the query if supported by the database
635      * @param criteria the criteria from which the Limit and Offset values
636      *        are taken
637      * @param query the query to which the Limit clause should be added
638      * @throws TorqueException if the Database adapter cannot be obtained
639      */
640     private static void processLimits(
641             final CriteriaInterface<?> crit,
642             final Query query)
643             throws TorqueException
644     {
645         int limit = crit.getLimit();
646         long offset = crit.getOffset();
647 
648         if (offset > 0 || limit >= 0)
649         {
650             Adapter adapter = Torque.getAdapter(crit.getDbName());
651             adapter.generateLimits(query, offset, limit);
652         }
653     }
654 
655     /**
656      * Checks the fromElements in the criteria and replaces the automatically
657      * calculated fromElements in the query by them, if they are filled.
658      *
659      * @param criteria the criteria from which the query should be built.
660      * @param query the query to build.
661      */
662     private static void processFromElements(
663             final Criteria criteria,
664             final Query query)
665     {
666         if (criteria.getFromElements().isEmpty())
667         {
668             log.trace("criteria's from Elements is empty,"
669                     + " using automatically calculated from clause");
670             return;
671         }
672         query.getFromClause().clear();
673         query.getFromClause().addAll(criteria.getFromElements());
674     }
675 
676     /**
677      * Returns the tablename which can be added to a From Clause.
678      * This takes care of any aliases that might be defined.
679      * For example, if an alias "a" for the table AUTHOR is defined
680      * in the Criteria criteria, getTableNameForFromClause("a", criteria)
681      * returns "AUTHOR a".
682      *
683      * @param toAddToFromClause the column to extract the table name from,
684      *        or a literal object value.
685      * @param criteria a criteria object to resolve a possible alias.
686      *
687      * @return A prepared statement part containing either the table name
688      *         itself if tableOrAliasName is not an alias,
689      *         or a String of the form "tableName tableOrAliasName"
690      *         if tableOrAliasName is an alias for a table name,
691      *         or a ? with the replacement if toAddToFromClause is not a Column.
692      */
693     static PreparedStatementPart getExpressionForFromClause(
694             final Object toAddToFromClause,
695             final CriteriaInterface<?> criteria)
696             throws TorqueException
697     {
698         if (!(toAddToFromClause instanceof Column))
699         {
700             // toAddToFromClause is a literal Value
701             return new PreparedStatementPart("?", toAddToFromClause);
702         }
703         Column column = (Column) toAddToFromClause;
704         Column resolvedColumn
705                 = resolveAliasAndAsColumnAndSchema(column, criteria);
706         String fullTableName
707                 = resolvedColumn.getFullTableName();
708 
709         if (!StringUtils.equals(
710                 resolvedColumn.getTableName(),
711                 column.getTableName()))
712         {
713             // If the tables have an alias, add an "<xxx> <yyy> statement"
714             // <xxx> AS <yyy> causes problems on oracle
715             PreparedStatementPart result = new PreparedStatementPart();
716             result.getSql()
717                     .append(fullTableName)
718                     .append(" ")
719                     .append(column.getTableName());
720             return result;
721         }
722         Object resolvedAlias = criteria.getAliases().get(
723                 resolvedColumn.getTableName());
724         if (resolvedAlias != null)
725         {
726             if (resolvedAlias instanceof Criteria)
727             {
728                 Criteria subquery = (Criteria) resolvedAlias;
729                 Query renderedSubquery = SqlBuilder.buildQuery(subquery);
730                 PreparedStatementPart result = new PreparedStatementPart();
731                 result.getSql().append("(")
732                         .append(renderedSubquery.toString())
733                         .append(") ")
734                         .append(resolvedColumn.getTableName());
735                 result.getPreparedStatementReplacements().addAll(
736                         renderedSubquery.getPreparedStatementReplacements());
737                 return result;
738             }
739             else
740             {
741                 throw new TorqueException("Table name "
742                     + resolvedColumn.getTableName()
743                     + " resolved to an unhandleable class "
744                     + resolvedAlias.getClass().getName());
745             }
746         }
747 
748         return new PreparedStatementPart(fullTableName);
749     }
750 
751     /**
752      * Fully qualify a table name with an optional schema reference.
753      *
754      * @param table The table name to use.
755      *              If null is passed in, null is returned.
756      * @param dbName The name of the database to which this tables belongs.
757      *               If null is passed, the default database is used.
758      *
759      * @return The table name to use inside the SQL statement.
760      *         If null is passed into this method, null is returned.
761      * @exception TorqueException if an error occurs
762      */
763     public static String getFullTableName(
764             final String table,
765             final String dbName)
766         throws TorqueException
767     {
768         if (table == null)
769         {
770             return table;
771         }
772 
773         int dotIndex = table.indexOf(".");
774         if (dotIndex == -1) // No schema given
775         {
776             String targetDBName = (dbName == null)
777                     ? Torque.getDefaultDB()
778                     : dbName;
779 
780             String targetSchema = Torque.getSchema(targetDBName);
781 
782             // If we have a default schema, fully qualify the
783             // table and return.
784             if (StringUtils.isNotEmpty(targetSchema))
785             {
786                 return new StringBuffer()
787                         .append(targetSchema)
788                         .append(".")
789                         .append(table)
790                         .toString();
791             }
792         }
793 
794         return table;
795     }
796 
797     /**
798      * Unqualify a table or column name.
799      *
800      * @param name the name to unqualify.
801      *        If null is passed in, null is returned.
802      *
803      * @return The unqualified name.
804      */
805     public static String getUnqualifiedName(
806             final String name,
807             final String dbName)
808         throws TorqueException
809     {
810         if (name == null)
811         {
812             return null;
813         }
814 
815         int dotIndex = name.lastIndexOf(".");
816         if (dotIndex == -1)
817         {
818             return name;
819         }
820 
821         return name.substring(dotIndex + 1);
822     }
823 
824     /**
825      * Guesses a table name from a criteria by inspecting the first
826      * column in the criteria.
827      *
828      * @param criteria the criteria to guess the table name from.
829      *
830      * @return the table name, not null.
831      *
832      * @throws TorqueException if the table name cannot be determined.
833      */
834     public static String guessFullTableFromCriteria(Criteria criteria)
835             throws TorqueException
836     {
837         org.apache.torque.criteria.Criterion criterion
838                 = criteria.getTopLevelCriterion();
839         if (criterion == null)
840         {
841             throw new TorqueException("Could not determine table name "
842                     + " as criteria contains no criterion");
843         }
844         while (criterion.isComposite())
845         {
846             criterion = criterion.getParts().iterator().next();
847         }
848         String tableName = null;
849 
850         Object lValue = criterion.getLValue();
851         if (lValue instanceof Column)
852         {
853             Column column = (Column) lValue;
854             tableName = column.getFullTableName();
855         }
856         if (tableName == null)
857         {
858             throw new TorqueException("Could not determine table name "
859                     + " as first criterion contains no table name");
860         }
861         return tableName;
862     }
863 
864     /**
865      * Returns the table map for a table.
866      *
867      * @param tableName the name of the table.
868      * @param dbName the name of the database, null for the default db.
869      *
870      * @return the table map for the table, not null.
871      *
872      * @throws TorqueException if the database or table is unknown.
873      */
874     public static TableMap getTableMap(String tableName, String dbName)
875             throws TorqueException
876     {
877 
878         if (dbName == null)
879         {
880             dbName = Torque.getDefaultDB();
881         }
882         DatabaseMap databaseMap = Torque.getDatabaseMap(dbName);
883         if (databaseMap == null)
884         {
885             throw new TorqueException("Could not find database map"
886                     + " for database "
887                     + dbName);
888         }
889         String unqualifiedTableName = getUnqualifiedName(tableName, dbName);
890         TableMap result = databaseMap.getTable(unqualifiedTableName);
891         if (result == null)
892         {
893             throw new TorqueException("Could not find table "
894                     + tableName
895                     + " in database map of database "
896                     + dbName);
897         }
898         return result;
899     }
900 
901     /**
902      * Returns the database name of a column.
903      *
904      * @param tableName the name of a table or the alias for a table.
905      * @param criteria a criteria object to resolve a possible alias.
906      *
907      * @return either the tablename itself if tableOrAliasName is not an alias,
908      *         or a String of the form "tableName tableOrAliasName"
909      *         if tableOrAliasName is an alias for a table name
910      */
911     static Column resolveAliasAndAsColumnAndSchema(
912             final Column columnToResolve,
913             final CriteriaInterface<?> criteria)
914             throws TorqueException
915     {
916         String columnNameToResolve = columnToResolve.getColumnName();
917         Column resolvedColumn = criteria.getAsColumns().get(columnNameToResolve);
918         boolean sqlExpressionModified = false;
919         if (resolvedColumn == null)
920         {
921             resolvedColumn = columnToResolve;
922         }
923         else
924         {
925             sqlExpressionModified = true;
926         }
927         String tableNameToResolve = resolvedColumn.getTableName();
928         Object resolvedAlias = criteria.getAliases().get(tableNameToResolve);
929         String resolvedTableName;
930         if (resolvedAlias == null || !(resolvedAlias instanceof String))
931         {
932             resolvedTableName = tableNameToResolve;
933         }
934         else
935         {
936             resolvedTableName = (String) resolvedAlias;
937             sqlExpressionModified = true;
938         }
939         String resolvedSchemaName = resolvedColumn.getSchemaName();
940         if (resolvedSchemaName == null)
941         {
942             final String dbName = criteria.getDbName();
943             final Database database = Torque.getDatabase(dbName);
944             resolvedSchemaName = database.getSchema();
945         }
946         if (sqlExpressionModified)
947         {
948             return new ColumnImpl(
949                     resolvedSchemaName,
950                     resolvedTableName,
951                     resolvedColumn.getColumnName());
952         }
953         else
954         {
955             return new ColumnImpl(
956                     resolvedSchemaName,
957                     resolvedTableName,
958                     resolvedColumn.getColumnName(),
959                     resolvedColumn.getSqlExpression());
960         }
961     }
962 
963     /**
964      * Checks if a fromExpression is already contained in a from clause.
965      * Different aliases for the same table are treated
966      * as different tables: E.g.
967      * fromClauseContainsTableName(fromClause, "table_a a") returns false if
968      * fromClause contains only another alias for table_a ,
969      * e.g. "table_a aa" and the unaliased tablename "table_a".
970      * Special case: If fromClause is null or empty, false is returned.
971      *
972      * @param fromClause The list to check against.
973      * @param fromExpression the fromExpression to check, not null.
974      *
975      * @return whether the fromExpression is already contained in the from
976      *         clause.
977      */
978     static boolean fromClauseContainsExpression(
979             final UniqueList<FromElement> fromClause,
980             final PreparedStatementPart fromExpression)
981     {
982         if (fromExpression == null || fromExpression.getSql().length() == 0)
983         {
984             return false;
985         }
986         String fromExpressionSql = fromExpression.getSql().toString();
987         for (FromElement fromElement : fromClause)
988         {
989             if (fromExpressionSql.equals(fromElement.getFromExpression()))
990             {
991                 return true;
992             }
993         }
994         return false;
995     }
996 
997     /**
998      * Adds a table to the from clause of a query, if it is not already
999      * contained there.
1000      *
1001      * @param tableOrAliasName the name of a table
1002      *        or the alias for a table. If null, the from clause is left
1003      *        unchanged.
1004      * @param criteria a criteria object to resolve a possible alias
1005      * @param query the query where the the table name should be added
1006      *        to the from clause
1007      */
1008     static void addTableToFromClause(
1009                 final Object possibleColumn,
1010                 final CriteriaInterface<?> criteria,
1011                 Query query)
1012             throws TorqueException
1013     {
1014         if (possibleColumn == null)
1015         {
1016             return;
1017         }
1018         if (!(possibleColumn instanceof Column))
1019         {
1020             return;
1021         }
1022         Column column = (Column) possibleColumn;
1023         if (column.getTableName() == null)
1024         {
1025             return;
1026         }
1027         PreparedStatementPart fromClauseExpression = getExpressionForFromClause(
1028                 column,
1029                 criteria);
1030 
1031         UniqueList<FromElement> queryFromClause = query.getFromClause();
1032 
1033         // it is important that this piece of code is executed AFTER
1034         // the joins are processed
1035         if (!fromClauseContainsExpression(
1036             queryFromClause,
1037             fromClauseExpression))
1038         {
1039             FromElement fromElement = new FromElement(
1040                     fromClauseExpression.getSql().toString(),
1041                     null,
1042                     null,
1043                     fromClauseExpression.getPreparedStatementReplacements());
1044             queryFromClause.add(fromElement);
1045         }
1046     }
1047 
1048     /**
1049      * Checks whether ignoreCase is used for this criterion.
1050      * This is the case if ignoreCase is either set on the criterion
1051      * or the criteria and if ignoreCase is applicable for both values.
1052      *
1053      * @param criterion the value to check.
1054      * @param criteria the criteria where the criterion stems from.
1055      * @param database The database to check.
1056      *
1057      * @return Whether to use ignoreCase for the passed criterion.
1058      *
1059      * @throws TorqueException in the case of an error.
1060      */
1061     static boolean isIgnoreCase(
1062                 Criterion criterion,
1063                 CriteriaInterface<?> criteria,
1064                 Database database)
1065             throws TorqueException
1066     {
1067         boolean ignoreCase
1068             = criteria.isIgnoreCase() || criterion.isIgnoreCase();
1069         ignoreCase = ignoreCase
1070                 && ignoreCaseApplicable(
1071                         criterion.getLValue(),
1072                         criteria,
1073                         database)
1074                 && ignoreCaseApplicable(
1075                         criterion.getRValue(),
1076                         criteria,
1077                         database);
1078         return ignoreCase;
1079     }
1080 
1081     /**
1082      * Checks whether ignoreCase is applicable for this column.
1083      * This is not the case if the value is a column and the column type is
1084      * not varchar, or if the object is no column and not a String;
1085      * in all other cases ignoreCase is applicable.
1086      *
1087      * @param value the value to check.
1088      * @param criteria the criteria where the value stems from.
1089      * @param database The database to check.
1090      *
1091      * @return false if ignoreCase is not applicable, true otherwise.
1092      *
1093      * @throws TorqueException in the case of an error.
1094      */
1095     private static boolean ignoreCaseApplicable(
1096                 Object value,
1097                 CriteriaInterface<?> criteria,
1098                 Database database)
1099             throws TorqueException
1100     {
1101         if (value == null)
1102         {
1103             return true;
1104         }
1105         if (!(value instanceof Column))
1106         {
1107             if (value instanceof String
1108                     || value instanceof Iterable
1109                     || value.getClass().isArray())
1110             {
1111                 return true;
1112             }
1113             return false;
1114         }
1115         Column column = (Column) value;
1116         Column databaseColumn = resolveAliasAndAsColumnAndSchema(
1117                 column,
1118                 criteria);
1119         ColumnMap columnMap = null;
1120         {
1121             DatabaseMap databaseMap = database.getDatabaseMap();
1122             TableMap tableMap = databaseMap.getTable(
1123                     databaseColumn.getTableName());
1124             if (tableMap != null)
1125             {
1126                 columnMap = tableMap.getColumn(
1127                         databaseColumn.getColumnName());
1128             }
1129         }
1130         if (columnMap == null)
1131         {
1132             return true;
1133         }
1134         // do not use ignoreCase on columns
1135         // which do not contain String values
1136         return columnMap.getType() instanceof String;
1137     }
1138 
1139     /**
1140      * Builds an element of the where clause of a prepared statement.
1141      *
1142      * @param whereClausePart the part of the where clause to build.
1143      *        Can be modified during this method.
1144      * @param ignoreCase If true and columns represent Strings, the appropriate
1145      *        function defined for the database will be used to ignore
1146      *        differences in case.
1147      * @param adapter The adapter for the database for which the SQL
1148      *        should be created, not null.
1149      *
1150      * @deprecated remove when util.Criteria is removed
1151      */
1152     @Deprecated
1153     private static PreparedStatementPart buildPs(
1154                 WhereClauseExpression whereClausePart,
1155                 boolean ignoreCase,
1156                 Adapter adapter)
1157             throws TorqueException
1158     {
1159         PreparedStatementPart result = new PreparedStatementPart();
1160 
1161         // Handle SqlEnum.Custom
1162         if (SqlEnum.CUSTOM == whereClausePart.getOperator())
1163         {
1164             result.getSql().append(whereClausePart.getRValue());
1165             return result;
1166         }
1167 
1168         // Handle SqlEnum.CURRENT_DATE and SqlEnum.CURRENT_TIME
1169         if (whereClausePart.getRValue() instanceof SqlEnum)
1170         {
1171             result.getSql().append(whereClausePart.getLValue())
1172                 .append(whereClausePart.getOperator())
1173                 .append(whereClausePart.getRValue());
1174             return result;
1175         }
1176 
1177         // If rValue is an ObjectKey, take the value of that ObjectKey.
1178         if (whereClausePart.getRValue() instanceof ObjectKey)
1179         {
1180             whereClausePart.setRValue(
1181                     ((ObjectKey) whereClausePart.getRValue()).getValue());
1182         }
1183 
1184         /*  If rValue is null, check to see if the operator
1185          *  is an =, <>, or !=.  If so, replace the comparison
1186          *  with SqlEnum.ISNULL or SqlEnum.ISNOTNULL.
1187          */
1188         if (whereClausePart.getRValue() == null)
1189         {
1190             if (whereClausePart.getOperator().equals(SqlEnum.EQUAL))
1191             {
1192                 result.getSql().append(whereClausePart.getLValue())
1193                         .append(SqlEnum.ISNULL);
1194                 return result;
1195             }
1196             if (whereClausePart.getOperator().equals(SqlEnum.NOT_EQUAL)
1197                 || whereClausePart.getOperator().equals(
1198                         SqlEnum.ALT_NOT_EQUAL))
1199             {
1200                 result.getSql().append(whereClausePart.getLValue())
1201                     .append(SqlEnum.ISNOTNULL);
1202                 return result;
1203             }
1204         }
1205 
1206         // Handle SqlEnum.ISNULL and SqlEnum.ISNOTNULL
1207         if (whereClausePart.getOperator().equals(SqlEnum.ISNULL)
1208             || whereClausePart.getOperator().equals(SqlEnum.ISNOTNULL))
1209         {
1210             result.getSql().append(whereClausePart.getLValue())
1211                     .append(whereClausePart.getOperator());
1212             return result;
1213         }
1214 
1215         // handle Subqueries
1216         if (whereClausePart.getRValue() instanceof Criteria)
1217         {
1218             Query subquery = SqlBuilder.buildQuery(
1219                     (Criteria) whereClausePart.getRValue());
1220             result.getPreparedStatementReplacements().addAll(
1221                     subquery.getPreparedStatementReplacements());
1222             result.getSql().append(whereClausePart.getLValue())
1223                     .append(whereClausePart.getOperator())
1224                     .append("(").append(subquery.toString()).append(")");
1225                 return result;
1226         }
1227         if (whereClausePart.getRValue()
1228                 instanceof org.apache.torque.util.Criteria)
1229         {
1230             Query subquery = SqlBuilder.buildQuery(
1231                     (org.apache.torque.util.Criteria)
1232                         whereClausePart.getRValue());
1233             result.getPreparedStatementReplacements().addAll(
1234                     subquery.getPreparedStatementReplacements());
1235             result.getSql().append(whereClausePart.getLValue())
1236                     .append(whereClausePart.getOperator())
1237                     .append("(").append(subquery.toString()).append(")");
1238                 return result;
1239         }
1240 
1241         // handle LIKE and similar
1242         if (whereClausePart.getOperator().equals(Criteria.LIKE)
1243             || whereClausePart.getOperator().equals(Criteria.NOT_LIKE)
1244             || whereClausePart.getOperator().equals(Criteria.ILIKE)
1245             || whereClausePart.getOperator().equals(Criteria.NOT_ILIKE))
1246         {
1247             return buildPsLike(whereClausePart, ignoreCase, adapter);
1248         }
1249 
1250         // handle IN and similar
1251         if (whereClausePart.getOperator().equals(Criteria.IN)
1252                 || whereClausePart.getOperator().equals(Criteria.NOT_IN))
1253         {
1254             return buildPsIn(whereClausePart, ignoreCase, adapter);
1255         }
1256 
1257         // Standard case
1258         result.getPreparedStatementReplacements().add(
1259                 whereClausePart.getRValue());
1260         if (ignoreCase
1261             && whereClausePart.getRValue() instanceof String)
1262         {
1263             result.getSql().append(
1264                     adapter.ignoreCase((String) whereClausePart.getLValue()))
1265                 .append(whereClausePart.getOperator())
1266                 .append(adapter.ignoreCase("?"));
1267         }
1268         else
1269         {
1270             result.getSql().append(whereClausePart.getLValue())
1271                 .append(whereClausePart.getOperator())
1272                 .append("?");
1273         }
1274         return result;
1275     }
1276 
1277     /**
1278      * Takes a WhereClauseExpression with a LIKE operator
1279      * and builds an SQL phrase based on whether wildcards are present
1280      * and the state of theignoreCase flag.
1281      * Multicharacter wildcards % and * may be used
1282      * as well as single character wildcards, _ and ?.  These
1283      * characters can be escaped with \.
1284      *
1285      * e.g. criteria = "fre%" -> columnName LIKE 'fre%'
1286      *                        -> UPPER(columnName) LIKE UPPER('fre%')
1287      *      criteria = "50\%" -> columnName = '50%'
1288      *
1289      * @param whereClausePart the part of the where clause to build.
1290      *        Can be modified in this method.
1291      * @param ignoreCase If true and columns represent Strings, the appropriate
1292      *        function defined for the database will be used to ignore
1293      *        differences in case.
1294      * @param adapter The adapter for the database for which the SQL
1295      *        should be created, not null.
1296      *
1297      * @return the rendered SQL for the WhereClauseExpression
1298      *
1299      * @deprecated remove when util.Criteria is removed
1300      */
1301     @Deprecated
1302     static PreparedStatementPart buildPsLike(
1303                 WhereClauseExpression whereClausePart,
1304                 boolean ignoreCase,
1305                 Adapter adapter)
1306             throws TorqueException
1307     {
1308         if (!(whereClausePart.getRValue() instanceof String))
1309         {
1310             throw new TorqueException(
1311                 "rValue must be a String for the operator "
1312                     + whereClausePart.getOperator());
1313         }
1314         String value = (String) whereClausePart.getRValue();
1315         // If selection criteria contains wildcards use LIKE otherwise
1316         // use = (equals).  Wildcards can be escaped by prepending
1317         // them with \ (backslash). However, if we switch from
1318         // like to equals, we need to remove the escape characters.
1319         // from the wildcards.
1320         // So we need two passes: The first replaces * and ? by % and _,
1321         // and checks whether we switch to equals,
1322         // the second removes escapes if we have switched to equals.
1323         int position = 0;
1324         StringBuffer sb = new StringBuffer();
1325         boolean replaceWithEquals = true;
1326         while (position < value.length())
1327         {
1328             char checkWildcard = value.charAt(position);
1329 
1330             switch (checkWildcard)
1331             {
1332             case BACKSLASH:
1333                 if (position + 1 >= value.length())
1334                 {
1335                     // ignore backslashes at end
1336                     break;
1337                 }
1338                 position++;
1339                 char escapedChar = value.charAt(position);
1340                 if (escapedChar != '*' && escapedChar != '?')
1341                 {
1342                     sb.append(checkWildcard);
1343                 }
1344                 // code below copies escaped character into sb
1345                 checkWildcard = escapedChar;
1346                 break;
1347             case '%':
1348             case '_':
1349                 replaceWithEquals = false;
1350                 break;
1351             case '*':
1352                 replaceWithEquals = false;
1353                 checkWildcard = '%';
1354                 break;
1355             case '?':
1356                 replaceWithEquals = false;
1357                 checkWildcard = '_';
1358                 break;
1359             default:
1360                 break;
1361             }
1362 
1363             sb.append(checkWildcard);
1364             position++;
1365         }
1366         value = sb.toString();
1367 
1368         if (ignoreCase)
1369         {
1370             if (adapter.useIlike() && !replaceWithEquals)
1371             {
1372                 if (SqlEnum.LIKE.equals(whereClausePart.getOperator()))
1373                 {
1374                     whereClausePart.setOperator(SqlEnum.ILIKE);
1375                 }
1376                 else if (SqlEnum.NOT_LIKE.equals(whereClausePart.getOperator()))
1377                 {
1378                     whereClausePart.setOperator(SqlEnum.NOT_ILIKE);
1379                 }
1380             }
1381             else
1382             {
1383                 // no native case insensitive like is offered by the DB,
1384                 // or the LIKE was replaced with equals.
1385                 // need to ignore case manually.
1386                 whereClausePart.setLValue(
1387                         adapter.ignoreCase((String) whereClausePart.getLValue()));
1388             }
1389         }
1390 
1391         PreparedStatementPart result = new PreparedStatementPart();
1392         result.getSql().append(whereClausePart.getLValue());
1393 
1394         if (replaceWithEquals)
1395         {
1396             if (whereClausePart.getOperator().equals(SqlEnum.NOT_LIKE)
1397                     || whereClausePart.getOperator().equals(SqlEnum.NOT_ILIKE))
1398             {
1399                 result.getSql().append(SqlEnum.NOT_EQUAL);
1400             }
1401             else
1402             {
1403                 result.getSql().append(SqlEnum.EQUAL);
1404             }
1405 
1406             // remove escape backslashes from String
1407             position = 0;
1408             sb = new StringBuffer();
1409             while (position < value.length())
1410             {
1411                 char checkWildcard = value.charAt(position);
1412 
1413                 if (checkWildcard == BACKSLASH
1414                         && position + 1 < value.length())
1415                 {
1416                     position++;
1417                     // code below copies escaped character into sb
1418                     checkWildcard = value.charAt(position);
1419                 }
1420                 sb.append(checkWildcard);
1421                 position++;
1422             }
1423             value = sb.toString();
1424         }
1425         else
1426         {
1427             result.getSql().append(whereClausePart.getOperator());
1428         }
1429 
1430         String rValueSql = "?";
1431         // handle ignoreCase if necessary
1432         if (ignoreCase && (!(adapter.useIlike()) || replaceWithEquals))
1433         {
1434             rValueSql = adapter.ignoreCase(rValueSql);
1435         }
1436         // handle escape clause if necessary
1437         if (!replaceWithEquals && adapter.useEscapeClauseForLike())
1438         {
1439             rValueSql = rValueSql + SqlEnum.ESCAPE + "'\\'";
1440         }
1441 
1442         result.getPreparedStatementReplacements().add(value);
1443         result.getSql().append(rValueSql);
1444         return result;
1445     }
1446 
1447     /**
1448      * Takes a columnName and criteria and
1449      * builds a SQL 'IN' expression taking into account the ignoreCase
1450      * flag.
1451      *
1452      * @param whereClausePart the part of the where clause to build.
1453      *        Can be modified in this method.
1454      * @param ignoreCase If true and columns represent Strings, the appropriate
1455      *        function defined for the database will be used to ignore
1456      *        differences in case.
1457      * @param adapter The adapter for the database for which the SQL
1458      *        should be created, not null.
1459      *
1460      * @return the built part.
1461      *
1462      * @deprecated remove when util.Criteria is removed
1463      */
1464     @Deprecated
1465     static PreparedStatementPart buildPsIn(
1466             WhereClauseExpression whereClausePart,
1467             boolean ignoreCase,
1468             Adapter adapter)
1469     {
1470         PreparedStatementPart result = new PreparedStatementPart();
1471 
1472         boolean ignoreCaseApplied = false;
1473         List<String> inClause = new ArrayList<String>();
1474         boolean nullContained = false;
1475         if (whereClausePart.getRValue() instanceof Iterable)
1476         {
1477             for (Object listValue : (Iterable<?>) whereClausePart.getRValue())
1478             {
1479                 if (listValue == null)
1480                 {
1481                     nullContained = true;
1482                     continue;
1483                 }
1484                 result.getPreparedStatementReplacements().add(listValue);
1485                 if (ignoreCase && listValue instanceof String)
1486                 {
1487                     inClause.add(adapter.ignoreCase("?"));
1488                     ignoreCaseApplied = true;
1489                 }
1490                 else
1491                 {
1492                     inClause.add("?");
1493                 }
1494             }
1495         }
1496         else if (whereClausePart.getRValue().getClass().isArray())
1497         {
1498             for (Object arrayValue : (Object[]) whereClausePart.getRValue())
1499             {
1500                 if (arrayValue == null)
1501                 {
1502                     nullContained = true;
1503                     continue;
1504                 }
1505                 result.getPreparedStatementReplacements().add(arrayValue);
1506                 if (ignoreCase && arrayValue instanceof String)
1507                 {
1508                     inClause.add(adapter.ignoreCase("?"));
1509                     ignoreCaseApplied = true;
1510                 }
1511                 else
1512                 {
1513                     inClause.add("?");
1514                 }
1515             }
1516         }
1517         else
1518         {
1519             throw new IllegalArgumentException(
1520                     "Unknown rValue type "
1521                     + whereClausePart.getRValue().getClass().getName()
1522                     + ". rValue must be an instance of "
1523                     + " Iterable or Array");
1524         }
1525 
1526         if (nullContained)
1527         {
1528             result.getSql().append('(');
1529         }
1530 
1531         if (ignoreCaseApplied)
1532         {
1533             result.getSql().append(
1534                     adapter.ignoreCase((String) whereClausePart.getLValue()));
1535         }
1536         else
1537         {
1538             result.getSql().append(whereClausePart.getLValue());
1539         }
1540 
1541         result.getSql().append(whereClausePart.getOperator())
1542                 .append('(')
1543                 .append(StringUtils.join(inClause.iterator(), ","))
1544                 .append(')');
1545         if (nullContained)
1546         {
1547             if (whereClausePart.getOperator() == SqlEnum.IN)
1548             {
1549                 result.getSql().append(Criterion.OR)
1550                     .append(whereClausePart.getLValue()).append(SqlEnum.ISNULL);
1551             }
1552             else if (whereClausePart.getOperator() == SqlEnum.NOT_IN)
1553             {
1554                 result.getSql().append(Criterion.AND)
1555                     .append(whereClausePart.getLValue()).append(
1556                             SqlEnum.ISNOTNULL);
1557             }
1558             result.getSql().append(')');
1559         }
1560         return result;
1561     }
1562 }