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 }