/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.qp.expression; import com.foundationdb.qp.row.ValuesHolderRow; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.server.api.dml.ColumnSelector; import com.foundationdb.server.api.dml.ConstantColumnSelector; public class IndexKeyRange { public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append('('); if (lo != null && boundColumns > 0) { buffer.append(loInclusive() ? ">=" : ">"); buffer.append(lo.toString()); } buffer.append(','); if (hi != null && boundColumns > 0) { buffer.append(hiInclusive() ? "<=" : "<"); buffer.append(hi.toString()); } buffer.append(')'); return buffer.toString(); } public IndexRowType indexRowType() { return indexRowType; } public IndexBound lo() { return lo; } public IndexBound hi() { return hi; } public boolean loInclusive() { return loInclusive; } public boolean hiInclusive() { return hiInclusive; } public boolean unbounded() { return (boundColumns == 0); } public int boundColumns() { return boundColumns; } /** * Describes a full index scan. * @param indexRowType The row type of index keys. * @return IndexKeyRange covering all keys of the index. */ public static IndexKeyRange unbounded(IndexRowType indexRowType) { IndexBound unbounded = new IndexBound(new ValuesHolderRow(indexRowType), ConstantColumnSelector.ALL_OFF); return new IndexKeyRange(indexRowType, unbounded, false, unbounded, false, IndexKind.CONVENTIONAL); } /** * Describes a range of keys between lo and hi. The bounds are inclusive or not depending on * loInclusive and hiInclusive. lo and hi must both be non-null. There are constraints on the bounds: * - The ColumnSelectors for lo and hi must select for the same columns. * - The selected columns must be leading columns of the index. * * @param indexRowType The row type of index keys. * @param lo Lower bound of the range. * @param loInclusive True if the lower bound is inclusive, false if exclusive. * @param hi Upper bound of the range. * @param hiInclusive True if the upper bound is inclusive, false if exclusive. * @return IndexKeyRange covering the keys lying between lo and hi, subject to the loInclusive and * hiInclusive flags. */ public static IndexKeyRange bounded(IndexRowType indexRowType, IndexBound lo, boolean loInclusive, IndexBound hi, boolean hiInclusive) { if (lo == null || hi == null) { throw new IllegalArgumentException("IndexBound arguments must not be null"); } return new IndexKeyRange(indexRowType, lo, loInclusive, hi, hiInclusive, IndexKind.CONVENTIONAL); } /** * Describes a range of keys between lo and hi. lo and hi must both be non-null. * They describe the lower-left and upper-right corners of a query box. * The ColumnSelectors for lo and hi must select for the same columns. * * @param indexRowType The row type of index keys. Contains a spatial object. * @param indexBound Upper bound of the range. * @return IndexKeyRange covering the keys lying between lo and hi. */ public static IndexKeyRange spatialObject(IndexRowType indexRowType, IndexBound indexBound) { if (indexBound == null) { throw new IllegalArgumentException("spatialObjectIndex must not be null"); } return new IndexKeyRange(indexRowType, indexBound, true, indexBound, true, IndexKind.SPATIAL_OBJECT); } /** * Describes a range of keys starting at lo and expanding out, * * @param indexRowType The row type of index keys. * @param lo Lower bound of the range. * @return IndexKeyRange covering the keys lying starting at lo. */ public static IndexKeyRange around(IndexRowType indexRowType, IndexBound lo) { if (lo == null) { throw new IllegalArgumentException("IndexBound argument must not be null"); } return new IndexKeyRange(indexRowType, lo, true, null, false, IndexKind.SPATIAL_COORDS); } /** * Describes a range of keys starting at lo and expanding out, * * @param indexRowType The row type of index keys. * @param lo Lower bound of the range. * @return IndexKeyRange covering the keys lying starting at lo. */ public static IndexKeyRange aroundObject(IndexRowType indexRowType, IndexBound lo) { if (lo == null) { throw new IllegalArgumentException("IndexBound argument must not be null"); } return new IndexKeyRange(indexRowType, lo, true, null, false, IndexKind.SPATIAL_OBJECT); } public boolean spatialCoordsIndex() { return indexKind == IndexKind.SPATIAL_COORDS; } public boolean spatialObjectIndex() { return indexKind == IndexKind.SPATIAL_OBJECT; } public IndexKeyRange resetLo(IndexBound newLo) { IndexKeyRange restart = new IndexKeyRange(this); restart.boundColumns = boundColumns(indexRowType, newLo); restart.lo = newLo; restart.loInclusive = true; return restart; } public IndexKeyRange resetHi(IndexBound newHi) { IndexKeyRange restart = new IndexKeyRange(this); restart.boundColumns = boundColumns(indexRowType, newHi); restart.hi = newHi; restart.hiInclusive = true; return restart; } private IndexKeyRange(IndexRowType indexRowType, IndexBound lo, boolean loInclusive, IndexBound hi, boolean hiInclusive, IndexKind indexKind) { this.boundColumns = lo == null ? boundColumns(indexRowType, hi) : hi == null ? boundColumns(indexRowType, lo) : boundColumns(indexRowType, lo, hi); this.indexRowType = indexRowType; this.lo = lo; this.loInclusive = loInclusive; this.hi = hi; this.hiInclusive = hiInclusive; this.indexKind = indexKind; } private IndexKeyRange(IndexKeyRange indexKeyRange) { this.indexRowType = indexKeyRange.indexRowType; this.boundColumns = indexKeyRange.boundColumns; this.lo = indexKeyRange.lo; this.loInclusive = indexKeyRange.loInclusive; this.hi = indexKeyRange.hi; this.hiInclusive = indexKeyRange.hiInclusive; this.indexKind = indexKeyRange.indexKind; } private static int boundColumns(IndexRowType indexRowType, IndexBound lo, IndexBound hi) { ColumnSelector loSelector = lo.columnSelector(); ColumnSelector hiSelector = hi.columnSelector(); boolean selected = true; int boundColumns = 0; for (int i = 0; i < indexRowType.nFields(); i++) { if (loSelector.includesColumn(i) != hiSelector.includesColumn(i)) { throw new IllegalArgumentException( String.format("IndexBound arguments specify different fields of index %s", indexRowType)); } if (selected) { // loSelector.includesColumn(i) will equal hiSelector.includesColumn(i) for non-lexicographic // ranges. For lexicographic, we want boundColumns to indicate the maximum value, relying on // SortCursorUnidirectionalLexicographic to take care of the shorter one. if (loSelector.includesColumn(i) || hiSelector.includesColumn(i)) { boundColumns++; } else { selected = false; } } else { if (loSelector.includesColumn(i)) { throw new IllegalArgumentException( String.format("IndexBound arguments for index %s specify non-leading fields", indexRowType)); } } } return boundColumns; } private static int boundColumns(IndexRowType indexRowType, IndexBound bound) { ColumnSelector selector = bound.columnSelector(); boolean selected = true; int boundColumns = 0; for (int i = 0; i < indexRowType.nFields(); i++) { if (selected) { if (selector.includesColumn(i)) { boundColumns++; } else { selected = false; } } else { if (selector.includesColumn(i)) { throw new IllegalArgumentException( String.format("IndexBound arguments for index %s specify non-leading fields", indexRowType)); } } } assert boundColumns > 0; return boundColumns; } // Object state private final IndexRowType indexRowType; private int boundColumns; private IndexBound lo; private boolean loInclusive; private IndexBound hi; private boolean hiInclusive; private final IndexKind indexKind; // A CONVENTIONAL (SQL Layer) index scan normally allows a range for only the last specified part of the bound. E.g., // (1, 10, 800) - (1, 10, 888) is legal, but (1, 10, 800) - (1, 20, 888) is not, because there are two ranges, // 10-20 and 800-888. // // A SPATIAL_COORDS index is for (lat, lon) points, and requires has no requirements other than specifying a match // or range on any restricted column. // // A SPATIAL_OBJECT index is similar to SPATIAL_COORDS, but the index is on a column storing an encoded // spatial object. public enum IndexKind { CONVENTIONAL, SPATIAL_COORDS, SPATIAL_OBJECT } }