001: /* Copyright (c) 1995-2000, The Hypersonic SQL Group.
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the Hypersonic SQL Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: *
030: * This software consists of voluntary contributions made by many individuals
031: * on behalf of the Hypersonic SQL Group.
032: *
033: *
034: * For work added by the HSQL Development Group:
035: *
036: * Copyright (c) 2001-2005, The HSQL Development Group
037: * All rights reserved.
038: *
039: * Redistribution and use in source and binary forms, with or without
040: * modification, are permitted provided that the following conditions are met:
041: *
042: * Redistributions of source code must retain the above copyright notice, this
043: * list of conditions and the following disclaimer.
044: *
045: * Redistributions in binary form must reproduce the above copyright notice,
046: * this list of conditions and the following disclaimer in the documentation
047: * and/or other materials provided with the distribution.
048: *
049: * Neither the name of the HSQL Development Group nor the names of its
050: * contributors may be used to endorse or promote products derived from this
051: * software without specific prior written permission.
052: *
053: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
054: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
055: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
056: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
057: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
058: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
059: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
060: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
061: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
062: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
063: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
064: */
065:
066: package org.hsqldb;
067:
068: import org.hsqldb.lib.StringUtil;
069:
070: /**
071: * Reusable object for processing LIKE queries.
072: *
073: * Enhanced in successive versions of HSQLDB.
074: *
075: * @author Thomas Mueller (Hypersonic SQL Group)
076: * @version 1.8.0
077: * @since Hypersonic SQL
078: */
079:
080: // boucherb@users 20030930 - patch 1.7.2 - optimize into joins if possible
081: // fredt@users 20031006 - patch 1.7.2 - reuse Like objects for all rows
082: class Like {
083:
084: private char[] cLike;
085: private int[] wildCardType;
086: private int iLen;
087: private boolean isIgnoreCase;
088: private int iFirstWildCard;
089: private boolean isNull;
090: Character escapeChar;
091: boolean hasCollation;
092: boolean optimised;
093: static final int UNDERSCORE_CHAR = 1;
094: static final int PERCENT_CHAR = 2;
095:
096: Like(Character escape, boolean collation) {
097: escapeChar = escape;
098: hasCollation = collation;
099: }
100:
101: /**
102: * param setter
103: *
104: * @param s
105: * @param ignorecase
106: */
107: void setParams(Session session, String s, boolean ignorecase) {
108:
109: isIgnoreCase = ignorecase;
110:
111: normalize(session, s);
112:
113: optimised = true;
114: }
115:
116: /**
117: * Resets the search pattern;
118: */
119: void resetPattern(Session session, String s) {
120: normalize(session, s);
121: }
122:
123: private String getStartsWith() {
124:
125: if (iLen == 0) {
126: return "";
127: }
128:
129: StringBuffer s = new StringBuffer();
130: int i = 0;
131:
132: for (; (i < iLen) && (wildCardType[i] == 0); i++) {
133: s.append(cLike[i]);
134: }
135:
136: if (i == 0) {
137: return null;
138: }
139:
140: return s.toString();
141: }
142:
143: /**
144: * Method declaration
145: *
146: *
147: * @param o
148: *
149: * @return
150: */
151: Boolean compare(Session session, String s) {
152:
153: if (s == null) {
154: return null;
155: }
156:
157: if (isIgnoreCase) {
158: s = session.database.collation.toUpperCase(s);
159: }
160:
161: return compareAt(s, 0, 0, s.length()) ? Boolean.TRUE
162: : Boolean.FALSE;
163: }
164:
165: /**
166: * Method declaration
167: *
168: *
169: * @param s
170: * @param i
171: * @param j
172: * @param jLen
173: *
174: * @return
175: */
176: private boolean compareAt(String s, int i, int j, int jLen) {
177:
178: for (; i < iLen; i++) {
179: switch (wildCardType[i]) {
180:
181: case 0: // general character
182: if ((j >= jLen) || (cLike[i] != s.charAt(j++))) {
183: return false;
184: }
185: break;
186:
187: case UNDERSCORE_CHAR: // underscore: do not test this character
188: if (j++ >= jLen) {
189: return false;
190: }
191: break;
192:
193: case PERCENT_CHAR: // percent: none or any character(s)
194: if (++i >= iLen) {
195: return true;
196: }
197:
198: while (j < jLen) {
199: if ((cLike[i] == s.charAt(j))
200: && compareAt(s, i, j, jLen)) {
201: return true;
202: }
203:
204: j++;
205: }
206:
207: return false;
208: }
209: }
210:
211: if (j != jLen) {
212: return false;
213: }
214:
215: return true;
216: }
217:
218: /**
219: * Method declaration
220: *
221: *
222: * @param pattern
223: * @param b
224: */
225: private void normalize(Session session, String pattern) {
226:
227: isNull = pattern == null;
228:
229: if (!isNull && isIgnoreCase) {
230: pattern = session.database.collation.toUpperCase(pattern);
231: }
232:
233: iLen = 0;
234: iFirstWildCard = -1;
235:
236: int l = pattern == null ? 0 : pattern.length();
237:
238: cLike = new char[l];
239: wildCardType = new int[l];
240:
241: boolean bEscaping = false, bPercent = false;
242:
243: for (int i = 0; i < l; i++) {
244: char c = pattern.charAt(i);
245:
246: if (bEscaping == false) {
247: if (escapeChar != null && escapeChar.charValue() == c) {
248: bEscaping = true;
249:
250: continue;
251: } else if (c == '_') {
252: wildCardType[iLen] = UNDERSCORE_CHAR;
253:
254: if (iFirstWildCard == -1) {
255: iFirstWildCard = iLen;
256: }
257: } else if (c == '%') {
258: if (bPercent) {
259: continue;
260: }
261:
262: bPercent = true;
263: wildCardType[iLen] = PERCENT_CHAR;
264:
265: if (iFirstWildCard == -1) {
266: iFirstWildCard = iLen;
267: }
268: } else {
269: bPercent = false;
270: }
271: } else {
272: bPercent = false;
273: bEscaping = false;
274: }
275:
276: cLike[iLen++] = c;
277: }
278:
279: for (int i = 0; i < iLen - 1; i++) {
280: if ((wildCardType[i] == PERCENT_CHAR)
281: && (wildCardType[i + 1] == UNDERSCORE_CHAR)) {
282: wildCardType[i] = UNDERSCORE_CHAR;
283: wildCardType[i + 1] = PERCENT_CHAR;
284: }
285: }
286: }
287:
288: boolean hasWildcards() {
289: return iFirstWildCard != -1;
290: }
291:
292: boolean isEquivalentToFalsePredicate() {
293: return isNull;
294: }
295:
296: boolean isEquivalentToEqualsPredicate() {
297: return iFirstWildCard == -1;
298: }
299:
300: boolean isEquivalentToNotNullPredicate() {
301:
302: if (isNull || !hasWildcards()) {
303: return false;
304: }
305:
306: for (int i = 0; i < wildCardType.length; i++) {
307: if (wildCardType[i] != PERCENT_CHAR) {
308: return false;
309: }
310: }
311:
312: return true;
313: }
314:
315: boolean isEquivalentToBetweenPredicate() {
316:
317: return iFirstWildCard > 0
318: && iFirstWildCard == wildCardType.length - 1
319: && cLike[iFirstWildCard] == '%';
320: }
321:
322: boolean isEquivalentToBetweenPredicateAugmentedWithLike() {
323: return iFirstWildCard > 0 && cLike[iFirstWildCard] == '%';
324: }
325:
326: String getRangeLow() {
327: return getStartsWith();
328: }
329:
330: String getRangeHigh() {
331:
332: String s = getStartsWith();
333:
334: return s == null ? null : s.concat("\uffff");
335: }
336:
337: public String describe(Session session) {
338:
339: StringBuffer sb = new StringBuffer();
340:
341: sb.append(super .toString()).append("[\n");
342: sb.append("escapeChar=").append(escapeChar).append('\n');
343: sb.append("isNull=").append(isNull).append('\n');
344: sb.append("optimised=").append(optimised).append('\n');
345: sb.append("isIgnoreCase=").append(isIgnoreCase).append('\n');
346: sb.append("iLen=").append(iLen).append('\n');
347: sb.append("iFirstWildCard=").append(iFirstWildCard)
348: .append('\n');
349: sb.append("cLike=");
350: sb.append(StringUtil.arrayToString(cLike));
351: sb.append('\n');
352: sb.append("wildCardType=");
353: sb.append(StringUtil.arrayToString(wildCardType));
354: sb.append(']');
355:
356: return sb.toString();
357: }
358: }
|