/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.tempdata; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.teiid.core.types.ArrayImpl; import org.teiid.language.Like.MatchMode; import org.teiid.language.SortSpecification.NullOrdering; import org.teiid.query.eval.Evaluator; import org.teiid.query.processor.relational.ListNestedSortComparator; import org.teiid.query.sql.lang.CompareCriteria; import org.teiid.query.sql.lang.CompoundCriteria; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.lang.IsNullCriteria; import org.teiid.query.sql.lang.MatchCriteria; import org.teiid.query.sql.lang.OrderBy; import org.teiid.query.sql.lang.OrderByItem; import org.teiid.query.sql.lang.SetCriteria; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.visitor.ElementCollectorVisitor; import org.teiid.translator.ExecutionFactory.NullOrder; /** * Accumulates information about index usage. */ public class BaseIndexInfo<T extends SearchableTable> { List<Object> lower = null; List<Object> upper = null; ArrayList<List<Object>> valueSet = new ArrayList<List<Object>>(); T table; Boolean ordering; boolean covering; CompoundCriteria nonCoveredCriteria = null; CompoundCriteria coveredCriteria = null; public BaseIndexInfo(T table, final List<? extends Expression> projectedCols, final Criteria condition, OrderBy orderBy, boolean primary) { this.table = table; if (primary || this.table.getColumnMap().keySet().containsAll(projectedCols)) { covering = true; } if (table.getPkLength() > 0) { processCriteria(condition, primary); if (orderBy != null) { ordering = useIndexForOrderBy(orderBy); } } } private void processCriteria(Criteria condition, boolean primary) { List<Criteria> crits = Criteria.separateCriteriaByAnd(condition); if (!primary) { for (Iterator<Criteria> critIter = crits.iterator(); critIter.hasNext();) { Criteria criteria = critIter.next(); if (table.getColumnMap().keySet().containsAll(ElementCollectorVisitor.getElements(criteria, false))) { if (coveredCriteria == null) { coveredCriteria = new CompoundCriteria(); } coveredCriteria.addCriteria(criteria); } else { covering = false; if (nonCoveredCriteria == null) { nonCoveredCriteria = new CompoundCriteria(); } nonCoveredCriteria.addCriteria(criteria); critIter.remove(); } } } for (int i = 0; i < table.getPkLength(); i++) { for (Iterator<Criteria> critIter = crits.iterator(); critIter.hasNext();) { Criteria criteria = critIter.next(); if (criteria instanceof CompareCriteria) { CompareCriteria cc = (CompareCriteria)criteria; Object matchResult = table.matchesPkColumn(i, cc.getLeftExpression()); if (Boolean.FALSE.equals(matchResult)) { continue; } if (cc.getOperator() != CompareCriteria.EQ && !table.supportsOrdering(i, cc.getLeftExpression())) { critIter.remove(); continue; } this.addCondition(i, matchResult, (Constant)cc.getRightExpression(), cc.getOperator()); critIter.remove(); } else if (criteria instanceof IsNullCriteria) { IsNullCriteria inc = (IsNullCriteria)criteria; Object matchResult = table.matchesPkColumn(i, inc.getExpression()); if (Boolean.FALSE.equals(matchResult)) { continue; } this.addCondition(i, matchResult, new Constant(null), CompareCriteria.EQ); critIter.remove(); } else if (criteria instanceof MatchCriteria) { MatchCriteria matchCriteria = (MatchCriteria)criteria; Object matchResult = table.matchesPkColumn(i, matchCriteria.getLeftExpression()); if (Boolean.FALSE.equals(matchResult)) { continue; } Constant value = (Constant)matchCriteria.getRightExpression(); String pattern = (String)value.getValue(); boolean escaped = false; char escapeChar = matchCriteria.getEscapeChar(); if (matchCriteria.getMode() == MatchMode.REGEX) { escapeChar = '\\'; } StringBuilder prefix = new StringBuilder(); if (pattern.length() > 0 && matchCriteria.getMode() == MatchMode.REGEX && pattern.charAt(0) != '^') { //make the assumption that we require an anchor continue; } for (int j = matchCriteria.getMode() == MatchMode.REGEX?1:0; j < pattern.length(); j++) { char character = pattern.charAt(j); if (character == escapeChar && character != MatchCriteria.NULL_ESCAPE_CHAR) { if (escaped) { prefix.append(character); escaped = false; } else { escaped = true; } continue; } if (!escaped) { if (matchCriteria.getMode() == MatchMode.LIKE) { if (character == MatchCriteria.WILDCARD_CHAR || character == MatchCriteria.MATCH_CHAR) { break; } } else { int index = Arrays.binarySearch(Evaluator.REGEX_RESERVED, character); if (index >= 0 && pattern.length() > 0) { getRegexPrefix(pattern, escapeChar, prefix, j, character); break; } } } else { escaped = false; } prefix.append(character); } if (prefix.length() > 0) { this.addCondition(i, matchResult, new Constant(prefix.toString()), CompareCriteria.GE); if (matchCriteria.getLeftExpression() instanceof Function && table.supportsOrdering(i, matchCriteria.getLeftExpression())) { //this comparison needs to be aware of case this.addCondition(i, matchResult, new Constant(prefix.substring(0, prefix.length() -1) + (char) (Character.toLowerCase(prefix.charAt(prefix.length()-1))+1)), CompareCriteria.LE); } else { this.addCondition(i, matchResult, new Constant(prefix.substring(0, prefix.length() -1) + (char) (prefix.charAt(prefix.length()-1)+1)), CompareCriteria.LE); } } else { critIter.remove(); } } else if (criteria instanceof SetCriteria) { SetCriteria setCriteria = (SetCriteria)criteria; if (setCriteria.isNegated()) { continue; } Object matchResult = table.matchesPkColumn(i, setCriteria.getExpression()); if (Boolean.FALSE.equals(matchResult)) { continue; } Collection<Constant> values = (Collection<Constant>) setCriteria.getValues(); this.addSet(i, matchResult, values); critIter.remove(); } } } } private void getRegexPrefix(String pattern, char escapeChar, StringBuilder prefix, int j, char character) { boolean escaped = false; //check the rest of the expression for | int level = 0; for (int k = j; k < pattern.length(); k++) { character = pattern.charAt(k); if (character == escapeChar && character != MatchCriteria.NULL_ESCAPE_CHAR) { escaped = !escaped; } else if (!escaped) { if (character == '(') { level++; } else if (character == ')') { level--; } else if (character == '|' && level == 0) { prefix.setLength(0); //TODO: turn this into an IN condition return; } } } if (character == '{' || character == '?' || character == '*') { prefix.setLength(prefix.length() - 1); } } void addCondition(int i, Object match, Constant value, int comparisionMode) { Object value2 = value.getValue(); switch (comparisionMode) { case CompareCriteria.EQ: if (i == 0) { valueSet.clear(); valueSet.add(new ArrayList<Object>(table.getPkLength())); } if (valueSet.size() == 1) { List<Object> toSearch = valueSet.get(0); buildSearchRow(i, match, value2, toSearch); lower = null; upper = null; } break; case CompareCriteria.GE: case CompareCriteria.GT: if (valueSet.isEmpty()) { if (i == 0) { lower = new ArrayList<Object>(table.getPkLength()); } if (lower != null) { buildSearchRow(i, match, value2, lower); } } break; case CompareCriteria.LE: case CompareCriteria.LT: if (valueSet.isEmpty()) { if (i == 0) { upper = new ArrayList<Object>(table.getPkLength()); } if (upper != null) { buildSearchRow(i, match, value2, upper); } } break; } } private void buildSearchRow(int i, Object match, Object value2, List<Object> toSearch) { if (toSearch.size() != i) { return; } if (value2 instanceof ArrayImpl && match instanceof int[]) { int[] indexes = (int[])match; ArrayImpl array = (ArrayImpl)value2; Object[] arrayVals = array.getValues(); for (int j = 0; j < indexes.length; j++) { int index = indexes[j]; if (index == -1) { break; } toSearch.add(arrayVals[index]); } } else { toSearch.add(value2); } } void addSet(int i, Object match, Collection<Constant> values) { if (!valueSet.isEmpty()) { return; } if (i == 0) { for (Constant constant : values) { List<Object> value = new ArrayList<Object>(table.getPkLength()); Object value2 = constant.getValue(); buildSearchRow(i, match, value2, value); valueSet.add(value); } lower = null; upper = null; } } /** * Return a non-null direction if the index can be used, otherwise null. * @param orderBy * @return */ private Boolean useIndexForOrderBy(OrderBy orderBy) { Boolean direction = null; int size = orderBy.getOrderByItems().size(); if (size > table.getPkLength()) { return null; } for (int i = 0; i < size; i++) { OrderByItem item = orderBy.getOrderByItems().get(i); if (!Boolean.TRUE.equals(table.matchesPkColumn(i, item.getSymbol())) || !table.supportsOrdering(i, item.getSymbol())) { return null; } if (item.getNullOrdering() != null && ((item.isAscending() && item.getNullOrdering() == NullOrdering.LAST) || (!item.isAscending() && item.getNullOrdering() == NullOrdering.FIRST))) { //assumes nulls low return null; } if (item.isAscending()) { if (direction == null) { direction = OrderBy.ASC; } else if (direction != OrderBy.ASC) { return null; } } else if (direction == null) { direction = OrderBy.DESC; } else if (direction != OrderBy.DESC) { return null; } } return direction; } public List<Object> getLower() { return lower; } public List<Object> getUpper() { return upper; } public ArrayList<List<Object>> getValueSet() { return valueSet; } public void sortValueSet(boolean direction, NullOrder nullOrder) { int size = getValueSet().get(0).size(); int[] sortOn = new int[size]; for (int i = 0; i <sortOn.length; i++) { sortOn[i] = i; } Collections.sort(getValueSet(), new ListNestedSortComparator(sortOn, direction).defaultNullOrder(nullOrder)); } public Criteria getCoveredCriteria() { return coveredCriteria; } public Criteria getNonCoveredCriteria() { return nonCoveredCriteria; } public BaseIndexInfo<?> next; }