View Javadoc

1   package org.apache.torque.sql.whereclausebuilder;
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 org.apache.torque.TorqueException;
23  import org.apache.torque.adapter.Adapter;
24  import org.apache.torque.criteria.PreparedStatementPart;
25  import org.apache.torque.criteria.SqlEnum;
26  import org.apache.torque.sql.WhereClauseExpression;
27  
28  /**
29   * Builds a PreparedStatementPart from a WhereClauseExpression containing
30   * a Like operator.
31   *
32   * @version $Id: LikeBuilder.java 1377555 2012-08-27 02:52:28Z tfischer $
33   */
34  public class LikeBuilder extends AbstractWhereClausePsPartBuilder
35  {
36      /** The backslash character*/
37      private static final char BACKSLASH = '\\';
38  
39      /**
40       * Builds the PS part for a WhereClauseExpression with a LIKE operator.
41       * Multicharacter wildcards % and * may be used
42       * as well as single character wildcards, _ and ?.  These
43       * characters can be escaped with \.
44       *
45       * e.g. criteria = "fre%" -> columnName LIKE 'fre%'
46       *                        -> UPPER(columnName) LIKE UPPER('fre%')
47       *      criteria = "50\%" -> columnName = '50%'
48       *
49       * @param whereClausePart the part of the where clause to build.
50       *        Can be modified in this method.
51       * @param ignoreCase If true and columns represent Strings, the appropriate
52       *        function defined for the database will be used to ignore
53       *        differences in case.
54       * @param adapter The adapter for the database for which the SQL
55       *        should be created, not null.
56       *
57       * @return the rendered SQL for the WhereClauseExpression
58       */
59      public PreparedStatementPart buildPs(
60                  WhereClauseExpression whereClausePart,
61                  boolean ignoreCase,
62                  Adapter adapter)
63              throws TorqueException
64      {
65          if (!(whereClausePart.getRValue() instanceof String))
66          {
67              throw new TorqueException(
68                  "rValue must be a String for the operator "
69                      + whereClausePart.getOperator());
70          }
71          String value = (String) whereClausePart.getRValue();
72          // If selection criteria contains wildcards use LIKE otherwise
73          // use = (equals).  Wildcards can be escaped by prepending
74          // them with \ (backslash). However, if we switch from
75          // like to equals, we need to remove the escape characters.
76          // from the wildcards.
77          // So we need two passes: The first replaces * and ? by % and _,
78          // and checks whether we switch to equals,
79          // the second removes escapes if we have switched to equals.
80          int position = 0;
81          StringBuffer sb = new StringBuffer();
82          boolean replaceWithEquals = true;
83          while (position < value.length())
84          {
85              char checkWildcard = value.charAt(position);
86  
87              switch (checkWildcard)
88              {
89              case BACKSLASH:
90                  if (position + 1 >= value.length())
91                  {
92                      // ignore backslashes at end
93                      break;
94                  }
95                  position++;
96                  char escapedChar = value.charAt(position);
97                  if (escapedChar != '*' && escapedChar != '?')
98                  {
99                      sb.append(checkWildcard);
100                 }
101                 // code below copies escaped character into sb
102                 checkWildcard = escapedChar;
103                 break;
104             case '%':
105             case '_':
106                 replaceWithEquals = false;
107                 break;
108             case '*':
109                 replaceWithEquals = false;
110                 checkWildcard = '%';
111                 break;
112             case '?':
113                 replaceWithEquals = false;
114                 checkWildcard = '_';
115                 break;
116             default:
117                 break;
118             }
119 
120             sb.append(checkWildcard);
121             position++;
122         }
123         value = sb.toString();
124 
125         PreparedStatementPart result;
126         if (ignoreCase)
127         {
128             if (adapter.useIlike() && !replaceWithEquals)
129             {
130                 if (SqlEnum.LIKE.equals(whereClausePart.getOperator()))
131                 {
132                     whereClausePart.setOperator(SqlEnum.ILIKE);
133                 }
134                 else if (SqlEnum.NOT_LIKE.equals(whereClausePart.getOperator()))
135                 {
136                     whereClausePart.setOperator(SqlEnum.NOT_ILIKE);
137                 }
138                 result = getObjectOrColumnPsPartBuilder().buildPs(
139                         whereClausePart.getLValue(), false, adapter);
140             }
141             else
142             {
143                 // no native case insensitive like is offered by the DB,
144                 // or the LIKE was replaced with equals.
145                 // need to ignore case manually.
146                 result = getObjectOrColumnPsPartBuilder().buildPs(
147                         whereClausePart.getLValue(), true, adapter);
148             }
149         }
150         else
151         {
152             result = getObjectOrColumnPsPartBuilder().buildPs(
153                     whereClausePart.getLValue(), ignoreCase, adapter);
154         }
155 
156         if (replaceWithEquals)
157         {
158             if (whereClausePart.getOperator().equals(SqlEnum.NOT_LIKE)
159                     || whereClausePart.getOperator().equals(SqlEnum.NOT_ILIKE))
160             {
161                 result.getSql().append(SqlEnum.NOT_EQUAL);
162             }
163             else
164             {
165                 result.getSql().append(SqlEnum.EQUAL);
166             }
167 
168             // remove escape backslashes from String
169             position = 0;
170             sb = new StringBuffer();
171             while (position < value.length())
172             {
173                 char checkWildcard = value.charAt(position);
174 
175                 if (checkWildcard == BACKSLASH
176                         && position + 1 < value.length())
177                 {
178                     position++;
179                     // code below copies escaped character into sb
180                     checkWildcard = value.charAt(position);
181                 }
182                 sb.append(checkWildcard);
183                 position++;
184             }
185             value = sb.toString();
186         }
187         else
188         {
189             result.getSql().append(whereClausePart.getOperator());
190         }
191 
192         String rValueSql = "?";
193         // handle ignoreCase if necessary
194         if (ignoreCase && (!(adapter.useIlike()) || replaceWithEquals))
195         {
196             rValueSql = adapter.ignoreCase(rValueSql);
197         }
198         // handle escape clause if necessary
199         if (!replaceWithEquals && adapter.useEscapeClauseForLike())
200         {
201             rValueSql = rValueSql + SqlEnum.ESCAPE + "'\\'";
202         }
203 
204         result.getPreparedStatementReplacements().add(value);
205         result.getSql().append(rValueSql);
206         return result;
207     }
208 
209     /**
210      * {@inheritDoc}
211      */
212     public boolean isApplicable(
213             WhereClauseExpression whereClauseExpression,
214             Adapter adapter)
215     {
216         if (whereClauseExpression.getOperator().equals(SqlEnum.LIKE)
217             || whereClauseExpression.getOperator().equals(SqlEnum.NOT_LIKE)
218             || whereClauseExpression.getOperator().equals(SqlEnum.ILIKE)
219             || whereClauseExpression.getOperator().equals(SqlEnum.NOT_ILIKE))
220         {
221             return true;
222         }
223         return false;
224     }
225 }