devdaily home | apple | java | perl | unix | directory | blog

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/* Copyrights and Licenses
 *
 * This product includes Hypersonic SQL.
 * Originally developed by Thomas Mueller and the Hypersonic SQL Group. 
 *
 * Copyright (c) 1995-2000 by the Hypersonic SQL Group. All rights reserved. 
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met: 
 *     -  Redistributions of source code must retain the above copyright notice, this list of conditions
 *         and the following disclaimer. 
 *     -  Redistributions in binary form must reproduce the above copyright notice, this list of
 *         conditions and the following disclaimer in the documentation and/or other materials
 *         provided with the distribution. 
 *     -  All advertising materials mentioning features or use of this software must display the
 *        following acknowledgment: "This product includes Hypersonic SQL." 
 *     -  Products derived from this software may not be called "Hypersonic SQL" nor may
 *        "Hypersonic SQL" appear in their names without prior written permission of the
 *         Hypersonic SQL Group. 
 *     -  Redistributions of any form whatsoever must retain the following acknowledgment: "This
 *          product includes Hypersonic SQL." 
 * This software is provided "as is" and any expressed or implied warranties, including, but
 * not limited to, the implied warranties of merchantability and fitness for a particular purpose are
 * disclaimed. In no event shall the Hypersonic SQL Group or its contributors be liable for any
 * direct, indirect, incidental, special, exemplary, or consequential damages (including, but
 * not limited to, procurement of substitute goods or services; loss of use, data, or profits;
 * or business interruption). However caused any on any theory of liability, whether in contract,
 * strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this
 * software, even if advised of the possibility of such damage. 
 * This software consists of voluntary contributions made by many individuals on behalf of the
 * Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2004, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer, including earlier
 * license statements (above) and comply with all above license conditions.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution, including earlier
 * license statements (above) and comply with all above license conditions.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, 
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import org.hsqldb.lib.ArrayUtil;

// fredt@users 20030813 - patch 1.7.2 - fix for column comparison within same table bugs #572075 and 722443
// fredt@users 20031012 - patch 1.7.2 - better OUTER JOIN implementation
// fredt@users 20031026 - patch 1.7.2 - more efficient findfirst - especially for multi-column equijoins
// implemented optimisations similart to patch 465542 by hjbush@users

/**
 * This class iterates over table rows to select the rows that fulfil join
 * or other conditions. It uses indexes if they are availabe.
 *
 * @version 1.7.2
 */
class TableFilter {

    static final int CONDITION_NONE      = -1;     // not a condition expression
    static final int CONDITION_UNORDERED = 0;      // not candidate for eStart or eEnd
    static final int   CONDITION_START_END = 1;    // candidate for eStart and eEnd
    static final int   CONDITION_START     = 2;    // candidate for eStart
    static final int   CONDITION_END       = 3;    // candidate for eEnd
    static final int   CONDITION_OUTER     = 4;    // add to this
    Table              filterTable;
    private String     tableAlias;
    private Index      filterIndex;
    private Node       currentNode;
    private Object     emptyData[];
    private Expression eStart, eEnd;

    //
    Expression eAnd;

    //
    boolean      isOuterJoin;                      // table joined with OUTER JOIN
    boolean      isAssigned;                       // conditions have been assigned to this
    boolean      isMultiFindFirst;                 // findFirst() uses multi-column index
    Expression[] findFirstExpressions;             // expressions for column values

    //
    Object currentData[];
    Row    currentRow;

    // addendum to the result of findFirst() and next() with isOuterJoin==true
    // when the result is false, it indicates if a non-join condition caused the failure
    boolean nonJoinIsNull;

    // indicates current data is empty data produced for an outer join
    boolean isCurrentOuter;

    /**
     * Constructor declaration
     *
     *
     * @param t
     * @param alias
     * @param outerjoin
     */
    TableFilter(Table t, String alias, boolean outerjoin) {

        filterTable = t;
        tableAlias  = alias == null ? t.getName().name
                                    : alias;
        isOuterJoin = outerjoin;
        emptyData   = filterTable.getNewRow();
    }

    /**
     * Method declaration
     *
     *
     * @return
     */
    String getName() {
        return tableAlias;
    }

    /**
     * Retrieves this object's filter Table object.
     *
     * @return this object's filter Table object
     */
    Table getTable() {
        return filterTable;
    }

    /**
     * Retrieves a CONDITION_XXX code indicating how a condition
     * expression can be used for a TableFilter.
     *
     * @param exprType an expression type code
     * @return
     */
    static final int toConditionType(int exprType) {

        switch (exprType) {

            case Expression.NOT_EQUAL :
            case Expression.LIKE :
            case Expression.IN : {
                return CONDITION_UNORDERED;
            }
            case Expression.EQUAL : {
                return CONDITION_START_END;
            }
            case Expression.BIGGER :
            case Expression.BIGGER_EQUAL : {
                return CONDITION_START;
            }
            case Expression.SMALLER :
            case Expression.SMALLER_EQUAL : {
                return CONDITION_END;
            }
            default : {

                // not a condition so forget it
                return CONDITION_NONE;
            }
        }
    }

    // TODO: Optimize
    //
    // The current way always chooses eStart, eEnd conditions
    // using first encountered eligible index
    //
    // We should check if current index offers better selectivity/access
    // path than previously assigned iIndex.
    //
    // EXAMPLE 1:
    //
    // CREATE TABLE t (c1 int, c2 int primary key)
    // CREATE INDEX I1 ON t(c1)
    // SELECT
    //      *
    // FROM
    //      t
    // WHERE
    //     c1 = | < | <= | >= | > ...
    // AND
    //     c2 = | < | <= | >= | > ...
    //
    // currently always chooses iIndex / condition (c1/I1), over
    // index / condition (c2/pk), whereas index / condition (c2/pk)
    // may well be better, especially if condition on c2 is equality
    // (condition_start_end) and conditionon(s) on c1 involve range
    // (condition_start, condition_end, or some composite).
    //
    // Currently, the developer/client software must somehow know facts
    // both about the table, the query and the way HSQLDB forms its
    // plans and, based on this knowlege, perhaps decide to reverse
    // order by explicitly issuing instead:
    //
    // SELECT
    //      *
    // FROM
    //      t
    // WHERE
    //     c2 = | < | <= | >= | > ...
    // AND
    //     c1 = | < | <= | >= | > ...
    //
    // to get optimal index choice.
    //
    // The same thing applies to and is even worse for joins.
    //
    // Consider the following (highly artificial, but easy to
    // understand) case:
    //
    // CREATE TABLE T1(ID INTEGER PRIMARY KEY, C1 INTEGER)
    // CREATE INDEX I1 ON T1(C1)
    // CREATE TABLE T2(ID INTEGER PRIMARY KEY, C1 INTEGER)
    // CREATE INDEX I2 ON T2(C1)
    //
    // select * from t1, t2 where t1.c1 = t2.c1 and t1.id = t2.id
    //
    // Consider the worst value distribution where t1 and t2 are both
    // 10,000 rows, c1 selectivity is nil (all values are identical)
    // for both tables, and, say, id values span the range 0..9999
    // for both tables.
    //
    // Then time to completion on 500 MHz Athlon testbed using memory
    // tables is:
    //
    // 10000 row(s) in 309114 ms
    //
    // whereas for:
    //
    // select * from t1, t2 where t1.id = t2.id and t1.c1 = t2.c1
    //
    // time to completion is:
    //
    // 10000 row(s) in 471 ms
    //
    // Hence, the unoptimized query takes 656 times as long as the
    // optimized one!!!
    //
    // EXAMPLE 2:
    //
    // If there are, say, two non-unique candidate indexes,
    // and some range or equality predicates against
    // them, preference should be given to the one with
    // better selectivity (if the total row count of the
    // table is large, otherwise the overhead of making
    // the choice is probably large w.r.t. any possible
    // savings).  Might require maintaining some basic
    // statistics or performing appropriate index probes
    // at the time the plan is being generated.

    /**
     * Chooses certain query conditions and assigns a copy of them to this
     * filter. The original condition is set to Expression.TRUE once assigned.
     *
     * @param condition
     *
     * @throws HsqlException
     */
    void setConditions(Expression condition) throws HsqlException {

        setCondition(condition);

        if (filterIndex == null) {
            filterIndex = filterTable.getPrimaryIndex();
        }

        if (filterIndex.getVisibleColumns() == 1 || eStart == null
                || eAnd == null || eStart.exprType != Expression.EQUAL) {
            return;
        }

        boolean[]    check    = filterTable.getNewColumnCheckList();
        Expression[] expr     = new Expression[check.length];
        int          colindex = eStart.getArg().getColumnNr();

        check[colindex] = true;
        expr[colindex]  = eStart.getArg2();

        eAnd.getEquiJoinColumns(this, check, expr);

        if (ArrayUtil.containsAllTrueElements(check, filterIndex.colCheck)) {
            isMultiFindFirst     = true;
            findFirstExpressions = expr;
        }
    }

    private void setCondition(Expression e) throws HsqlException {

        int        type = e.getType();
        Expression e1   = e.getArg();
        Expression e2   = e.getArg2();

        this.isAssigned = true;

        if (type == Expression.AND) {
            setCondition(e1);
            setCondition(e2);

            return;
        }

        int conditionType = toConditionType(type);

        if (conditionType == CONDITION_NONE) {

            // not a condition expression
            return;
        }

// fredt@users 20030813 - patch 1.7.2 - fix for column comparison within same table bugs #572075 and 722443
        if (e1.getFilter() == this && e2.getFilter() == this) {
            conditionType = CONDITION_UNORDERED;
        } else if (e1.getFilter() == this) {

            // ok include this
        } else if ((e2.getFilter() == this)
                   && (conditionType != CONDITION_UNORDERED)) {

            // swap and try again to allow index usage
            e.swapCondition();
            setCondition(e);

            return;
        } else if (e1.outerFilter == this) {

            // fredt - this test is last to allow swapping the terms above
            conditionType = CONDITION_OUTER;
        } else {

            // unrelated: don't include
            return;
        }

//        Trace.doAssert(e1.getFilter() == this, "setCondition");
        if (!e2.isResolved()) {
            return;
        }

        // fredt - condition defined in outer but not this one
        if (e1.outerFilter != null && e1.outerFilter != this) {
            return;
        }

        if (conditionType == CONDITION_UNORDERED) {
            if (!isOuterJoin) {
                addAndCondition(e);
            }

            return;
        }

        if (conditionType == CONDITION_OUTER) {
            addAndCondition(e);
            e.setTrue();

            return;
        }

        int   i     = e1.getColumnNr();
        Index index = filterTable.getIndexForColumn(i);

        if (index == null || (filterIndex != index && filterIndex != null)) {
            addAndCondition(e);

            return;
        }

        filterIndex = index;

        switch (conditionType) {

            case CONDITION_START_END : {

                // candidate for both start and end
                if ((eStart != null) || (eEnd != null)) {
                    addAndCondition(e);

                    return;
                }

                eStart = new Expression(e);
                eEnd   = eStart;

                break;
            }
            case CONDITION_START : {

                // candidate for start
                if (eStart != null) {
                    addAndCondition(e);

                    return;
                }

                eStart = new Expression(e);

                break;
            }
            case CONDITION_END : {

                // candidate for end
                if (eEnd != null) {
                    addAndCondition(e);

                    return;
                }

                eEnd = new Expression(e);

                break;
            }
        }

        e.setTrue();
    }

    /**
     * Finds the first row in the table (using an index if there is one) and
     * checks it against the eEnd (range) and eAnd (other conditions)
     * Expression objects. (fredt)
     *
     * @return true if first row was found, else false
     */
    boolean findFirst() throws HsqlException {

        nonJoinIsNull  = false;
        isCurrentOuter = false;

        if (filterIndex == null) {
            filterIndex = filterTable.getPrimaryIndex();
        }

        if (this.isMultiFindFirst) {
            Object[] data  = filterTable.getNewRow();
            int[]    types = filterTable.getColumnTypes();

            for (int i = 0; i < findFirstExpressions.length; i++) {
                Expression e = findFirstExpressions[i];

                if (e != null) {
                    data[i] = e.getValue(null, types[i]);
                }
            }

            currentNode = filterIndex.findFirst(data);
        } else if (eStart == null) {
            currentNode = eEnd == null ? filterIndex.first()
                                       : filterIndex.findFirstNotNull();
        } else {
            int    type = eStart.getArg().getDataType();
            Object o    = eStart.getArg2().getValue(null, type);

            currentNode = filterIndex.findFirst(o, eStart.getType());
        }

        while (currentNode != null) {
            currentData = currentNode.getData();
            currentRow  = currentNode.getRow();

            if (!(eEnd == null || eEnd.test(null))) {
                break;
            }

            if (eAnd == null || eAnd.test(null)) {
                return true;
            }

            currentNode = filterIndex.next(currentNode);
        }

        currentData = emptyData;
        currentRow  = null;

        return false;
    }

    /**
     * Advances to the next available value. 

* * @return true if a next value is available upon exit * * @throws HsqlException if a database access error occurs */ boolean next() throws HsqlException { nonJoinIsNull = false; isCurrentOuter = false; currentNode = filterIndex.next(currentNode); while (currentNode != null) { currentData = currentNode.getData(); currentRow = currentNode.getRow(); if (!(eEnd == null || eEnd.test(null))) { break; } if (eAnd == null || eAnd.test(null)) { return true; } currentNode = filterIndex.next(currentNode); } currentData = emptyData; currentRow = null; return false; } boolean nextOuter() throws HsqlException { nonJoinIsNull = false; isCurrentOuter = true; currentData = emptyData; currentRow = null; return eAnd == null || (eAnd.getFilter() != this && eAnd.isInJoin) || eAnd.test(null); } /** * Forms a new conjunction using the given condition and this filter's * pre-existing AND condition, or sets the given condition as this filter's * AND condition when there is no such pre-exisiting object. * * @param e the condition to add */ private void addAndCondition(Expression e) { Expression e2 = new Expression(e); if (eAnd == null) { eAnd = e2; } else { Expression and = new Expression(Expression.AND, eAnd, e2); eAnd = and; } e.setTrue(); } /** * Removes reference to Index to avoid possible memory leaks after alter * table or drop index */ void setAsCheckFilter() { filterIndex = null; } // boucheb@users 20030415 - added for debugging support /** * Retreives a String representation of this obejct.

* * The returned String describes this object's table, alias * access mode, index, join mode, Start, End and And conditions. * * @return a String representation of this object */ public String toString() { StringBuffer sb; Index index; Index primaryIndex; int[] primaryKey; boolean hidden; boolean fullScan; sb = new StringBuffer(); index = filterIndex; primaryIndex = filterTable.getPrimaryIndex(); primaryKey = filterTable.getPrimaryKey(); hidden = false; fullScan = (eStart == null && eEnd == null); if (index == null) { index = primaryIndex; } if (index == primaryIndex && primaryKey == null) { hidden = true; fullScan = true; } sb.append(super.toString()).append('\n'); sb.append("table=[").append(filterTable.getName().name).append("]\n"); sb.append("alias=[").append(tableAlias).append("]\n"); sb.append("access=[").append(fullScan ? "FULL SCAN" : "INDEX PRED").append("]\n"); sb.append("index=["); sb.append(index == null ? "NONE" : index.getName() == null ? "UNNAMED" : index.getName() .name); sb.append(hidden ? "[HIDDEN]]\n" : "]\n"); sb.append("isOuterJoin=[").append(isOuterJoin).append("]\n"); sb.append("eStart=[").append(eStart).append("]\n"); sb.append("eEnd=[").append(eEnd).append("]\n"); sb.append("eAnd=[").append(eAnd).append("]"); return sb.toString(); } }




Copyright 1998-2008 Alvin Alexander
All Rights Reserved.
 
devdaily.com is based in louisville, kentucky, and this web site is hosted by godaddy.com