File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\expression\CompareLike.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.expression;
8
9 import java.util.regex.Pattern;
10 import java.util.regex.PatternSyntaxException;
11 import org.h2.constant.ErrorCode;
12 import org.h2.engine.Database;
13 import org.h2.engine.Session;
14 import org.h2.index.IndexCondition;
15 import org.h2.message.DbException;
16 import org.h2.table.ColumnResolver;
17 import org.h2.table.TableFilter;
18 import org.h2.value.CompareMode;
19 import org.h2.value.Value;
20 import org.h2.value.ValueBoolean;
21 import org.h2.value.ValueNull;
22 import org.h2.value.ValueString;
23
24 /**
25 * Pattern matching comparison expression: WHERE NAME LIKE ?
26 */
27 public class CompareLike extends Condition {
28
29 private static final int MATCH = 0, ONE = 1, ANY = 2;
30
31 private final CompareMode compareMode;
32 private final String defaultEscape;
33 private Expression left;
34 private Expression right;
35 private Expression escape;
36
37 private boolean isInit;
38
39 private char[] patternChars;
40 private String patternString;
41 private int[] patternTypes;
42 private int patternLength;
43
44 private final boolean regexp;
45 private Pattern patternRegexp;
46
47 private boolean ignoreCase;
48 private boolean fastCompare;
49 private boolean invalidPattern;
50
51 public CompareLike(Database db, Expression left, Expression right,
52 Expression escape, boolean regexp) {
53 this(db.getCompareMode(), db.getSettings().defaultEscape, left, right, escape, regexp);
54 }
55
56 public CompareLike(CompareMode compareMode, String defaultEscape,
57 Expression left, Expression right, Expression escape, boolean regexp) {
58 this.compareMode = compareMode;
59 this.defaultEscape = defaultEscape;
60 this.regexp = regexp;
61 this.left = left;
62 this.right = right;
63 this.escape = escape;
64 }
65
66 private static Character getEscapeChar(String s) {
67 return s == null || s.length() == 0 ? null : s.charAt(0);
68 }
69
70 public String getSQL() {
71 String sql;
72 if (regexp) {
73 sql = left.getSQL() + " REGEXP " + right.getSQL();
74 } else {
75 sql = left.getSQL() + " LIKE " + right.getSQL();
76 if (escape != null) {
77 sql += " ESCAPE " + escape.getSQL();
78 }
79 }
80 return "(" + sql + ")";
81 }
82
83 public Expression optimize(Session session) {
84 left = left.optimize(session);
85 right = right.optimize(session);
86 if (left.getType() == Value.STRING_IGNORECASE) {
87 ignoreCase = true;
88 }
89 if (left.isValueSet()) {
90 Value l = left.getValue(session);
91 if (l == ValueNull.INSTANCE) {
92 // NULL LIKE something > NULL
93 return ValueExpression.getNull();
94 }
95 }
96 if (escape != null) {
97 escape = escape.optimize(session);
98 }
99 if (right.isValueSet() && (escape == null || escape.isValueSet())) {
100 if (left.isValueSet()) {
101 return ValueExpression.get(getValue(session));
102 }
103 Value r = right.getValue(session);
104 if (r == ValueNull.INSTANCE) {
105 // something LIKE NULL > NULL
106 return ValueExpression.getNull();
107 }
108 Value e = escape == null ? null : escape.getValue(session);
109 if (e == ValueNull.INSTANCE) {
110 return ValueExpression.getNull();
111 }
112 String p = r.getString();
113 initPattern(p, getEscapeChar(e));
114 if (invalidPattern) {
115 return ValueExpression.getNull();
116 }
117 if ("%".equals(p)) {
118 // optimization for X LIKE '%': convert to X IS NOT NULL
119 return new Comparison(session, Comparison.IS_NOT_NULL, left, null).optimize(session);
120 }
121 if (isFullMatch()) {
122 // optimization for X LIKE 'Hello': convert to X = 'Hello'
123 Value value = ValueString.get(patternString);
124 Expression expr = ValueExpression.get(value);
125 return new Comparison(session, Comparison.EQUAL, left, expr).optimize(session);
126 }
127 isInit = true;
128 }
129 return this;
130 }
131
132 private Character getEscapeChar(Value e) {
133 if (e == null) {
134 return getEscapeChar(defaultEscape);
135 }
136 String es = e.getString();
137 Character esc;
138 if (es == null) {
139 esc = getEscapeChar(defaultEscape);
140 } else if (es.length() == 0) {
141 esc = null;
142 } else if (es.length() > 1) {
143 throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, es);
144 } else {
145 esc = es.charAt(0);
146 }
147 return esc;
148 }
149
150 public void createIndexConditions(Session session, TableFilter filter) {
151 if (regexp) {
152 return;
153 }
154 if (!(left instanceof ExpressionColumn)) {
155 return;
156 }
157 ExpressionColumn l = (ExpressionColumn) left;
158 if (filter != l.getTableFilter()) {
159 return;
160 }
161 // parameters are always evaluatable, but
162 // we need to check if the value is set
163 // (at prepare time)
164 // otherwise we would need to prepare at execute time,
165 // which may be slower (possibly not in this case)
166 if (!right.isEverything(ExpressionVisitor.INDEPENDENT_VISITOR)) {
167 return;
168 }
169 if (escape != null && !escape.isEverything(ExpressionVisitor.INDEPENDENT_VISITOR)) {
170 return;
171 }
172 String p = right.getValue(session).getString();
173 Value e = escape == null ? null : escape.getValue(session);
174 if (e == ValueNull.INSTANCE) {
175 // should already be optimized
176 DbException.throwInternalError();
177 }
178 initPattern(p, getEscapeChar(e));
179 if (invalidPattern) {
180 return;
181 }
182 if (patternLength <= 0 || patternTypes[0] != MATCH) {
183 // can't use an index
184 return;
185 }
186 int dataType = l.getColumn().getType();
187 if (dataType != Value.STRING && dataType != Value.STRING_IGNORECASE && dataType != Value.STRING_FIXED) {
188 // column is not a varchar - can't use the index
189 return;
190 }
191 int maxMatch = 0;
192 StringBuilder buff = new StringBuilder();
193 while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) {
194 buff.append(patternChars[maxMatch++]);
195 }
196 String begin = buff.toString();
197 if (maxMatch == patternLength) {
198 filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL, l, ValueExpression
199 .get(ValueString.get(begin))));
200 } else {
201 // TODO check if this is correct according to Unicode rules
202 // (code points)
203 String end;
204 if (begin.length() > 0) {
205 filter.addIndexCondition(IndexCondition.get(Comparison.BIGGER_EQUAL, l, ValueExpression.get(ValueString
206 .get(begin))));
207 char next = begin.charAt(begin.length() - 1);
208 // search the 'next' unicode character (or at least a character
209 // that is higher)
210 for (int i = 1; i < 2000; i++) {
211 end = begin.substring(0, begin.length() - 1) + (char) (next + i);
212 if (compareMode.compareString(begin, end, ignoreCase) == -1) {
213 filter.addIndexCondition(IndexCondition.get(Comparison.SMALLER, l, ValueExpression
214 .get(ValueString.get(end))));
215 break;
216 }
217 }
218 }
219 }
220 }
221
222 public Value getValue(Session session) {
223 Value l = left.getValue(session);
224 if (l == ValueNull.INSTANCE) {
225 return l;
226 }
227 if (!isInit) {
228 Value r = right.getValue(session);
229 if (r == ValueNull.INSTANCE) {
230 return r;
231 }
232 String p = r.getString();
233 Value e = escape == null ? null : escape.getValue(session);
234 if (e == ValueNull.INSTANCE) {
235 return ValueNull.INSTANCE;
236 }
237 initPattern(p, getEscapeChar(e));
238 }
239 if (invalidPattern) {
240 return ValueNull.INSTANCE;
241 }
242 String value = l.getString();
243 boolean result;
244 if (regexp) {
245 // result = patternRegexp.matcher(value).matches();
246 result = patternRegexp.matcher(value).find();
247 } else {
248 result = compareAt(value, 0, 0, value.length(), patternChars, patternTypes);
249 }
250 return ValueBoolean.get(result);
251 }
252
253 private boolean compare(char[] pattern, String s, int pi, int si) {
254 return pattern[pi] == s.charAt(si) || (!fastCompare && compareMode.equalsChars(patternString, pi, s, si, ignoreCase));
255 }
256
257 private boolean compareAt(String s, int pi, int si, int sLen, char[] pattern, int[] types) {
258 for (; pi < patternLength; pi++) {
259 switch (types[pi]) {
260 case MATCH:
261 if ((si >= sLen) || !compare(pattern, s, pi, si++)) {
262 return false;
263 }
264 break;
265 case ONE:
266 if (si++ >= sLen) {
267 return false;
268 }
269 break;
270 case ANY:
271 if (++pi >= patternLength) {
272 return true;
273 }
274 while (si < sLen) {
275 if (compare(pattern, s, pi, si) && compareAt(s, pi, si, sLen, pattern, types)) {
276 return true;
277 }
278 si++;
279 }
280 return false;
281 default:
282 DbException.throwInternalError();
283 }
284 }
285 return si == sLen;
286 }
287
288 /**
289 * Test if the value matches the pattern.
290 *
291 * @param testPattern the pattern
292 * @param value the value
293 * @param escapeChar the escape character
294 * @return true if the value matches
295 */
296 public boolean test(String testPattern, String value, char escapeChar) {
297 initPattern(testPattern, escapeChar);
298 if (invalidPattern) {
299 return false;
300 }
301 return compareAt(value, 0, 0, value.length(), patternChars, patternTypes);
302 }
303
304 private void initPattern(String p, Character escapeChar) {
305 if (compareMode.getName().equals(CompareMode.OFF) && !ignoreCase) {
306 fastCompare = true;
307 }
308 if (regexp) {
309 patternString = p;
310 try {
311 if (ignoreCase) {
312 patternRegexp = Pattern.compile(p, Pattern.CASE_INSENSITIVE);
313 } else {
314 patternRegexp = Pattern.compile(p);
315 }
316 } catch (PatternSyntaxException e) {
317 throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, p);
318 }
319 return;
320 }
321 patternLength = 0;
322 if (p == null) {
323 patternTypes = null;
324 patternChars = null;
325 return;
326 }
327 int len = p.length();
328 patternChars = new char[len];
329 patternTypes = new int[len];
330 boolean lastAny = false;
331 for (int i = 0; i < len; i++) {
332 char c = p.charAt(i);
333 int type;
334 if (escapeChar != null && escapeChar == c) {
335 if (i >= len - 1) {
336 invalidPattern = true;
337 return;
338 }
339 c = p.charAt(++i);
340 type = MATCH;
341 lastAny = false;
342 } else if (c == '%') {
343 if (lastAny) {
344 continue;
345 }
346 type = ANY;
347 lastAny = true;
348 } else if (c == '_') {
349 type = ONE;
350 } else {
351 type = MATCH;
352 lastAny = false;
353 }
354 patternTypes[patternLength] = type;
355 patternChars[patternLength++] = c;
356 }
357 for (int i = 0; i < patternLength - 1; i++) {
358 if ((patternTypes[i] == ANY) && (patternTypes[i + 1] == ONE)) {
359 patternTypes[i] = ONE;
360 patternTypes[i + 1] = ANY;
361 }
362 }
363 patternString = new String(patternChars, 0, patternLength);
364 }
365
366 private boolean isFullMatch() {
367 if (patternTypes == null) {
368 return false;
369 }
370 for (int type : patternTypes) {
371 if (type != MATCH) {
372 return false;
373 }
374 }
375 return true;
376 }
377
378 public void mapColumns(ColumnResolver resolver, int level) {
379 left.mapColumns(resolver, level);
380 right.mapColumns(resolver, level);
381 if (escape != null) {
382 escape.mapColumns(resolver, level);
383 }
384 }
385
386 public void setEvaluatable(TableFilter tableFilter, boolean b) {
387 left.setEvaluatable(tableFilter, b);
388 right.setEvaluatable(tableFilter, b);
389 if (escape != null) {
390 escape.setEvaluatable(tableFilter, b);
391 }
392 }
393
394 public void updateAggregate(Session session) {
395 left.updateAggregate(session);
396 right.updateAggregate(session);
397 if (escape != null) {
398 escape.updateAggregate(session);
399 }
400 }
401
402 public boolean isEverything(ExpressionVisitor visitor) {
403 return left.isEverything(visitor) && right.isEverything(visitor)
404 && (escape == null || escape.isEverything(visitor));
405 }
406
407 public int getCost() {
408 return left.getCost() + right.getCost() + 3;
409 }
410
411 }
412