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.index.RowIterator;
069: import org.hsqldb.lib.ArrayUtil;
070: import org.hsqldb.lib.HashMappedList;
071:
072: // fredt@users 20030813 - patch 1.7.2 - fix for column comparison within same table bugs #572075 and 722443
073: // fredt@users 20031012 - patch 1.7.2 - better OUTER JOIN implementation
074: // fredt@users 20031026 - patch 1.7.2 - more efficient findfirst - especially for multi-column equijoins
075: // implemented optimisations similart to patch 465542 by hjbush@users
076:
077: /**
078: * This class iterates over table rows to select the rows that fulfil join
079: * or other conditions. It uses indexes if they are availabe.
080: *
081: * Extended in successive versions of HSQLDB.
082: *
083: * @author Thomas Mueller (Hypersonic SQL Group)
084: * @version 1.8.0
085: * @since Hypersonic SQL
086: */
087: final class TableFilter {
088:
089: static final int CONDITION_NONE = -1; // not a condition expression
090: static final int CONDITION_UNORDERED = 0; // not candidate for eStart or eEnd
091: static final int CONDITION_START_END = 1; // candidate for eStart and eEnd
092: static final int CONDITION_START = 2; // candidate for eStart
093: static final int CONDITION_END = 3; // candidate for eEnd
094: static final int CONDITION_OUTER = 4; // add to this
095: Table filterTable;
096: private String tableAlias;
097: HashMappedList columnAliases;
098: Index filterIndex;
099: private Object[] emptyData;
100: boolean[] usedColumns;
101: private Expression eStart, eEnd;
102:
103: //
104: Expression eAnd;
105:
106: //
107: boolean isOuterJoin; // table joined with OUTER JOIN
108: boolean isAssigned; // conditions have been assigned to this
109: boolean isMultiFindFirst; // findFirst() uses multi-column index
110: Expression[] findFirstExpressions; // expressions for column values
111:
112: //
113: private RowIterator it;
114: Object[] currentData;
115: Row currentRow;
116:
117: //
118: Object[] currentJoinData;
119:
120: // addendum to the result of findFirst() and next() with isOuterJoin==true
121: // when the result is false, it indicates if a non-join condition caused the failure
122: boolean nonJoinIsNull;
123:
124: // indicates current data is empty data produced for an outer join
125: boolean isCurrentOuter;
126:
127: /**
128: * Constructor declaration
129: *
130: *
131: * @param t
132: * @param alias
133: * @param outerjoin
134: */
135: TableFilter(Table t, String alias, HashMappedList columnList,
136: boolean outerjoin) {
137:
138: filterTable = t;
139: tableAlias = alias == null ? t.getName().name : alias;
140: columnAliases = columnList;
141: isOuterJoin = outerjoin;
142: emptyData = filterTable.getEmptyRowData();
143: usedColumns = filterTable.getNewColumnCheckList();
144: }
145:
146: /**
147: * Returns the alias or the table name.
148: * Never returns null;
149: * @return
150: */
151: String getName() {
152: return tableAlias;
153: }
154:
155: /**
156: * Retrieves this object's filter Table object.
157: *
158: * @return this object's filter Table object
159: */
160: Table getTable() {
161: return filterTable;
162: }
163:
164: /**
165: * Retrieves a CONDITION_XXX code indicating how a condition
166: * expression can be used for a TableFilter.
167: *
168: * @param exprType an expression type code
169: * @return
170: */
171: static int getConditionType(Expression e) {
172:
173: int exprType = e.getType();
174:
175: switch (exprType) {
176:
177: case Expression.NOT_EQUAL:
178: case Expression.LIKE:
179: return CONDITION_UNORDERED;
180:
181: case Expression.IN: {
182: return e.isQueryCorrelated ? CONDITION_NONE
183: : CONDITION_UNORDERED;
184: }
185: case Expression.IS_NULL:
186: case Expression.EQUAL: {
187: return CONDITION_START_END;
188: }
189: case Expression.BIGGER:
190: case Expression.BIGGER_EQUAL: {
191: return CONDITION_START;
192: }
193: case Expression.SMALLER:
194: case Expression.SMALLER_EQUAL: {
195: return CONDITION_END;
196: }
197: default: {
198:
199: // not a condition so forget it
200: return CONDITION_NONE;
201: }
202: }
203: }
204:
205: // TODO: Optimize
206: //
207: // The current way always chooses eStart, eEnd conditions
208: // using first encountered eligible index
209: //
210: // We should check if current index offers better selectivity/access
211: // path than previously assigned iIndex.
212: //
213: // EXAMPLE 1:
214: //
215: // CREATE TABLE t (c1 int, c2 int primary key)
216: // CREATE INDEX I1 ON t(c1)
217: // SELECT
218: // *
219: // FROM
220: // t
221: // WHERE
222: // c1 = | < | <= | >= | > ...
223: // AND
224: // c2 = | < | <= | >= | > ...
225: //
226: // currently always chooses iIndex / condition (c1/I1), over
227: // index / condition (c2/pk), whereas index / condition (c2/pk)
228: // may well be better, especially if condition on c2 is equality
229: // (condition_start_end) and conditionon(s) on c1 involve range
230: // (condition_start, condition_end, or some composite).
231: //
232: // Currently, the developer/client software must somehow know facts
233: // both about the table, the query and the way HSQLDB forms its
234: // plans and, based on this knowlege, perhaps decide to reverse
235: // order by explicitly issuing instead:
236: //
237: // SELECT
238: // *
239: // FROM
240: // t
241: // WHERE
242: // c2 = | < | <= | >= | > ...
243: // AND
244: // c1 = | < | <= | >= | > ...
245: //
246: // to get optimal index choice.
247: //
248: // The same thing applies to and is even worse for joins.
249: //
250: // Consider the following (highly artificial, but easy to
251: // understand) case:
252: //
253: // CREATE TABLE T1(ID INTEGER PRIMARY KEY, C1 INTEGER)
254: // CREATE INDEX I1 ON T1(C1)
255: // CREATE TABLE T2(ID INTEGER PRIMARY KEY, C1 INTEGER)
256: // CREATE INDEX I2 ON T2(C1)
257: //
258: // select * from t1, t2 where t1.c1 = t2.c1 and t1.id = t2.id
259: //
260: // Consider the worst value distribution where t1 and t2 are both
261: // 10,000 rows, c1 selectivity is nil (all values are identical)
262: // for both tables, and, say, id values span the range 0..9999
263: // for both tables.
264: //
265: // Then time to completion on 500 MHz Athlon testbed using memory
266: // tables is:
267: //
268: // 10000 row(s) in 309114 ms
269: //
270: // whereas for:
271: //
272: // select * from t1, t2 where t1.id = t2.id and t1.c1 = t2.c1
273: //
274: // time to completion is:
275: //
276: // 10000 row(s) in 471 ms
277: //
278: // Hence, the unoptimized query takes 656 times as long as the
279: // optimized one!!!
280: //
281: // EXAMPLE 2:
282: //
283: // If there are, say, two non-unique candidate indexes,
284: // and some range or equality predicates against
285: // them, preference should be given to the one with
286: // better selectivity (if the total row count of the
287: // table is large, otherwise the overhead of making
288: // the choice is probably large w.r.t. any possible
289: // savings). Might require maintaining some basic
290: // statistics or performing appropriate index probes
291: // at the time the plan is being generated.
292:
293: /**
294: * Chooses certain query conditions and assigns a copy of them to this
295: * filter. The original condition is set to Expression.TRUE once assigned.
296: *
297: * @param condition
298: *
299: * @throws HsqlException
300: */
301: void setConditions(Session session, Expression condition)
302: throws HsqlException {
303:
304: setCondition(session, condition);
305:
306: if (filterIndex == null) {
307: filterIndex = filterTable.getPrimaryIndex();
308: }
309:
310: if (filterIndex.getVisibleColumns() == 1 || eStart == null
311: || eAnd == null || eStart.exprType != Expression.EQUAL) {
312: return;
313: }
314:
315: boolean[] check = filterTable.getNewColumnCheckList();
316: Expression[] expr = new Expression[check.length];
317: int colindex = eStart.getArg().getColumnNr();
318:
319: check[colindex] = true;
320: expr[colindex] = eStart.getArg2();
321:
322: eAnd.getEquiJoinColumns(this , check, expr);
323:
324: if (ArrayUtil.containsAllTrueElements(check,
325: filterIndex.colCheck)) {
326: isMultiFindFirst = true;
327: findFirstExpressions = expr;
328: }
329: }
330:
331: private void setCondition(Session session, Expression e)
332: throws HsqlException {
333:
334: int type = e.getType();
335: Expression e1 = e.getArg();
336: Expression e2 = e.getArg2();
337:
338: isAssigned = true;
339:
340: if (type == Expression.AND) {
341: setCondition(session, e1);
342: setCondition(session, e2);
343:
344: return;
345: }
346:
347: if (type == Expression.OR && isOuterJoin && e.isInJoin
348: && e.outerFilter == this ) {
349: addAndCondition(e);
350: e.setTrue();
351:
352: return;
353: }
354:
355: int conditionType = getConditionType(e);
356:
357: if (conditionType == CONDITION_NONE) {
358:
359: // not a condition expression
360: return;
361: }
362:
363: // fredt@users 20030813 - patch 1.7.2 - fix for column comparison within same table bugs #572075 and 722443
364: if (e1.getFilter() == this && e2.getFilter() == this ) {
365: conditionType = CONDITION_UNORDERED;
366: } else if (e1.getFilter() == this ) {
367: if (!e.isInJoin && isOuterJoin) {
368:
369: // do not use a where condition on the second table in outer joins
370: return;
371: }
372:
373: // ok include this
374: } else if ((e2.getFilter() == this )
375: && (conditionType != CONDITION_UNORDERED)) {
376:
377: // swap and try again to allow index usage
378: e.swapCondition();
379: setCondition(session, e);
380:
381: return;
382: } else if (e1.outerFilter == this ) {
383:
384: // fredt - this test is last to allow swapping the terms above
385: conditionType = CONDITION_OUTER;
386: } else {
387:
388: // unrelated: don't include
389: return;
390: }
391:
392: // Trace.doAssert(e1.getFilter() == this, "setCondition");
393: if (!e2.isResolved()) {
394: return;
395: }
396:
397: // fredt - condition defined in outer but not this one
398: if (e1.outerFilter != null && e1.outerFilter != this ) {
399: return;
400: }
401:
402: if (conditionType == CONDITION_UNORDERED) {
403: addAndCondition(e);
404:
405: return;
406: }
407:
408: if (conditionType == CONDITION_OUTER) {
409: addAndCondition(e);
410:
411: return;
412: }
413:
414: int i = e1.getColumnNr();
415: Index index = filterTable.getIndexForColumn(session, i);
416:
417: if (index == null
418: || (filterIndex != index && filterIndex != null)) {
419: addAndCondition(e);
420:
421: return;
422: }
423:
424: filterIndex = index;
425:
426: switch (conditionType) {
427:
428: case CONDITION_START_END: {
429:
430: // candidate for both start and end
431: if ((eStart != null) || (eEnd != null)) {
432: addAndCondition(e);
433:
434: return;
435: }
436:
437: eStart = new Expression(e);
438: eEnd = eStart;
439:
440: break;
441: }
442: case CONDITION_START: {
443:
444: // candidate for start
445: if (eStart != null) {
446: addAndCondition(e);
447:
448: return;
449: }
450:
451: eStart = new Expression(e);
452:
453: break;
454: }
455: case CONDITION_END: {
456:
457: // candidate for end
458: if (eEnd != null) {
459: addAndCondition(e);
460:
461: return;
462: }
463:
464: eEnd = new Expression(e);
465:
466: break;
467: }
468: }
469:
470: e.setTrue();
471: }
472:
473: /**
474: * Finds the first row in the table (using an index if there is one) and
475: * checks it against the eEnd (range) and eAnd (other conditions)
476: * Expression objects. (fredt)
477: *
478: * @return true if first row was found, else false
479: */
480: boolean findFirst(Session session) throws HsqlException {
481:
482: nonJoinIsNull = false;
483: isCurrentOuter = false;
484:
485: if (filterIndex == null) {
486: filterIndex = filterTable.getPrimaryIndex();
487: }
488:
489: if (isMultiFindFirst) {
490: boolean convertible = true;
491: int[] types = filterTable.getColumnTypes();
492:
493: currentJoinData = filterTable.getEmptyRowData();
494:
495: for (int i = 0; i < findFirstExpressions.length; i++) {
496: Expression e = findFirstExpressions[i];
497:
498: if (e != null) {
499: Object value = e.getValue(session);
500:
501: if (Column.compareToTypeRange(value, types[i]) != 0) {
502: convertible = false;
503:
504: break;
505: }
506:
507: value = Column.convertObject(value, types[i]);
508: currentJoinData[i] = e.getValue(session, types[i]);
509: }
510: }
511:
512: it = convertible ? filterIndex.findFirstRow(session,
513: currentJoinData) : filterIndex.emptyIterator();
514:
515: if (!it.hasNext()) {
516: ArrayUtil.clearArray(ArrayUtil.CLASS_CODE_OBJECT,
517: currentJoinData, 0, currentJoinData.length);
518: }
519: } else if (eStart == null) {
520: it = eEnd == null ? filterIndex.firstRow(session)
521: : filterIndex.findFirstRowNotNull(session);
522: } else {
523: Object value = eStart.getArg2().getValue(session);
524: int valuetype = eStart.getArg2().getDataType();
525: int targettype = eStart.getArg().getDataType();
526:
527: it = getFirstIterator(session, eStart.getType(), value,
528: valuetype, filterIndex, targettype);
529: }
530:
531: while (true) {
532: currentRow = it.next();
533:
534: if (currentRow == null) {
535: break;
536: }
537:
538: currentData = currentRow.getData();
539:
540: if (!(eEnd == null || eEnd.testCondition(session))) {
541: break;
542: }
543:
544: if (eAnd == null || eAnd.testCondition(session)) {
545: return true;
546: }
547: }
548:
549: currentRow = null;
550: currentData = emptyData;
551:
552: return false;
553: }
554:
555: static RowIterator getFirstIterator(Session session, int eType,
556: Object value, int valueType, Index index, int targetType)
557: throws HsqlException {
558:
559: RowIterator it;
560: int range = 0;
561:
562: if (targetType != valueType) {
563: range = Column.compareToTypeRange(value, targetType);
564: }
565:
566: if (range == 0) {
567: value = Column.convertObject(value, targetType);
568: it = index.findFirstRow(session, value, eType);
569: } else {
570: switch (eType) {
571:
572: case Expression.BIGGER_EQUAL:
573: case Expression.BIGGER:
574: if (range < 0) {
575: it = index.findFirstRowNotNull(session);
576:
577: break;
578: }
579: default:
580: it = index.emptyIterator();
581: }
582: }
583:
584: return it;
585: }
586:
587: /**
588: * Advances to the next available value. <p>
589: *
590: * @return true if a next value is available upon exit
591: *
592: * @throws HsqlException if a database access error occurs
593: */
594: boolean next(Session session) throws HsqlException {
595:
596: boolean result = false;
597:
598: nonJoinIsNull = false;
599: isCurrentOuter = false;
600:
601: while (true) {
602: currentRow = it.next();
603:
604: if (currentRow == null) {
605: break;
606: }
607:
608: currentData = currentRow.getData();
609:
610: if (!(eEnd == null || eEnd.testCondition(session))) {
611: break;
612: }
613:
614: if (eAnd == null || eAnd.testCondition(session)) {
615: result = true;
616:
617: break;
618: }
619: }
620:
621: if (result) {
622: return true;
623: }
624:
625: currentRow = null;
626: currentData = emptyData;
627:
628: return false;
629: }
630:
631: boolean nextOuter(Session session) throws HsqlException {
632:
633: nonJoinIsNull = false;
634: isCurrentOuter = true;
635: currentData = emptyData;
636: currentRow = null;
637:
638: return eAnd == null
639: || (eAnd.getFilter() != this && eAnd.isInJoin)
640: || eAnd.testCondition(session);
641: }
642:
643: /**
644: * Forms a new conjunction using the given condition and this filter's
645: * pre-existing AND condition, or sets the given condition as this filter's
646: * AND condition when there is no such pre-exisiting object.
647: *
648: * @param e the condition to add
649: */
650: private void addAndCondition(Expression e) {
651:
652: Expression e2 = new Expression(e);
653:
654: if (eAnd == null) {
655: eAnd = e2;
656: } else {
657: Expression and = new Expression(Expression.AND, eAnd, e2);
658:
659: eAnd = and;
660: }
661:
662: e.setTrue();
663: }
664:
665: /**
666: * Removes reference to Index to avoid possible memory leaks after alter
667: * table or drop index
668: */
669: void setAsCheckFilter() {
670: filterIndex = null;
671: }
672:
673: // boucheb@users 20030415 - added for debugging support
674:
675: /**
676: * Retreives a String representation of this obejct. <p>
677: *
678: * The returned String describes this object's table, alias
679: * access mode, index, join mode, Start, End and And conditions.
680: *
681: * @return a String representation of this object
682: */
683: public String describe(Session session) {
684:
685: StringBuffer sb;
686: String temp;
687: Index index;
688: Index primaryIndex;
689: int[] primaryKey;
690: boolean hidden;
691: boolean fullScan;
692:
693: sb = new StringBuffer();
694: index = filterIndex;
695: primaryIndex = filterTable.getPrimaryIndex();
696: primaryKey = filterTable.getPrimaryKey();
697: hidden = false;
698: fullScan = (eStart == null && eEnd == null);
699:
700: if (index == null) {
701: index = primaryIndex;
702: }
703:
704: if (index == primaryIndex && primaryKey.length == 0) {
705: hidden = true;
706: fullScan = true;
707: }
708:
709: sb.append(super .toString()).append('\n');
710: sb.append("table=[").append(filterTable.getName().name).append(
711: "]\n");
712: sb.append("alias=[").append(tableAlias).append("]\n");
713: sb.append("access=[").append(
714: fullScan ? "FULL SCAN" : "INDEX PRED").append("]\n");
715: sb.append("index=[");
716: sb.append(index == null ? "NONE"
717: : index.getName() == null ? "UNNAMED"
718: : index.getName().name);
719: sb.append(hidden ? "[HIDDEN]]\n" : "]\n");
720: sb.append("isOuterJoin=[").append(isOuterJoin).append("]\n");
721:
722: temp = eStart == null ? "null" : eStart.describe(session);
723:
724: sb.append("eStart=[").append(temp).append("]\n");
725:
726: temp = eEnd == null ? "null" : eEnd.describe(session);
727:
728: sb.append("eEnd=[").append(temp).append("]\n");
729:
730: temp = eAnd == null ? "null" : eAnd.describe(session);
731:
732: sb.append("eAnd=[").append(temp).append("]");
733:
734: return sb.toString();
735: }
736: }
|