Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.phoenix.parse; import java.util.HashMap; import java.util.Map; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.StringUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.util.Pair; import com.google.common.collect.ImmutableMap; /** * Node representing optimizer hints in SQL */ public class HintNode { public static final HintNode EMPTY_HINT_NODE = new HintNode(); private static final Log LOG = LogFactory.getLog(HintNode.class); public static final char SEPARATOR = ' '; public static final String PREFIX = "("; public static final String SUFFIX = ")"; // Split on whitespace and parenthesis, keeping the parenthesis in the token array private static final String SPLIT_REGEXP = "\\s+|((?<=\\" + PREFIX + ")|(?=\\" + PREFIX + "))|((?<=\\" + SUFFIX + ")|(?=\\" + SUFFIX + "))"; public enum Hint { /** * Forces a range scan to be used to process the query. */ RANGE_SCAN, /** * Forces a skip scan to be used to process the query. */ SKIP_SCAN, /** * Prevents the usage of child-parent-join optimization. */ NO_CHILD_PARENT_JOIN_OPTIMIZATION, /** * Prevents the usage of indexes, forcing usage * of the data table for a query. */ NO_INDEX, /** * Hint of the form INDEX(<table_name> <index_name>...) * to suggest usage of the index if possible. The first * usable index in the list of indexes will be choosen. * Table and index names may be surrounded by double quotes * if they are case sensitive. */ INDEX, /** * All things being equal, use the data table instead of * the index table when optimizing. */ USE_DATA_OVER_INDEX_TABLE, /** * All things being equal, use the index table instead of * the data table when optimizing. */ USE_INDEX_OVER_DATA_TABLE, /** * Avoid caching any HBase blocks loaded by this query. */ NO_CACHE, /** * Use sort-merge join algorithm instead of broadcast join (hash join) algorithm. */ USE_SORT_MERGE_JOIN, /** * Avoid using star-join optimization. Used for broadcast join (hash join) only. */ NO_STAR_JOIN, /** * Avoid using the no seek optimization. When there are many columns which are not selected coming in between 2 * selected columns and/or versions of columns, this should be used. */ SEEK_TO_COLUMN, /** * Avoid seeks to select specified columns. When there are very less number of columns which are not selected in * between 2 selected columns this will be give better performance. */ NO_SEEK_TO_COLUMN, /** * Saves an RPC call on the scan. See Scan.setSmall(true) in HBase documentation. */ SMALL, /** * Enforces a serial scan. */ SERIAL, /** * Use HBase native Scan time range so that we can take advantage of a scan optimization to skip HFiles which * are not in range [lower time stamp, higher time stamp) where time stamp values are represented in Epoch time. * * If higher time stamp isn't specified, Long.MAX_VALUE will be used. * * Example: NATIVE_TIME_RANGE(1416260321) or NATIVE_TIME_RANGE(1416260321, 2416260321) */ NATIVE_TIME_RANGE, }; private final Map<Hint, String> hints; public static HintNode create(HintNode hintNode, Hint hint) { return create(hintNode, hint, ""); } public static HintNode create(HintNode hintNode, Hint hint, String value) { Map<Hint, String> hints = new HashMap<Hint, String>(hintNode.hints); hints.put(hint, value); return new HintNode(hints); } public static HintNode combine(HintNode hintNode, HintNode override) { Map<Hint, String> hints = new HashMap<Hint, String>(hintNode.hints); hints.putAll(override.hints); return new HintNode(hints); } public static HintNode subtract(HintNode hintNode, Hint[] remove) { Map<Hint, String> hints = new HashMap<Hint, String>(hintNode.hints); for (Hint hint : remove) { hints.remove(hint); } return new HintNode(hints); } private HintNode() { hints = new HashMap<Hint, String>(); } private HintNode(Map<Hint, String> hints) { this.hints = ImmutableMap.copyOf(hints); } public HintNode(String hint) { Map<Hint, String> hints = new HashMap<Hint, String>(); // Split on whitespace or parenthesis. We do not need to handle escaped or // embedded whitespace/parenthesis, since we are parsing what will be HBase // table names which are not allowed to contain whitespace or parenthesis. String[] hintWords = hint.split(SPLIT_REGEXP); for (int i = 0; i < hintWords.length; i++) { String hintWord = hintWords[i]; if (hintWord.isEmpty()) { continue; } try { Hint key = Hint.valueOf(hintWord.toUpperCase()); String hintValue = ""; if (i + 1 < hintWords.length && PREFIX.equals(hintWords[i + 1])) { StringBuffer hintValueBuf = new StringBuffer(hint.length()); hintValueBuf.append(PREFIX); i += 2; while (i < hintWords.length && !SUFFIX.equals(hintWords[i])) { hintValueBuf.append(SchemaUtil.normalizeIdentifier(hintWords[i++])); hintValueBuf.append(SEPARATOR); } // Replace trailing separator with suffix hintValueBuf.replace(hintValueBuf.length() - 1, hintValueBuf.length(), SUFFIX); hintValue = hintValueBuf.toString(); } String oldValue = hints.put(key, hintValue); // Concatenate together any old value with the new value if (oldValue != null) { hints.put(key, oldValue + hintValue); } } catch (IllegalArgumentException e) { // Ignore unknown/invalid hints } } this.hints = ImmutableMap.copyOf(hints); } /** * Return HBase Scan time range * * @return null if NATIVE_TIME_RANGE hint doesn't exit */ public Pair<Long, Long> getNativeTimeStampRange() { if (!hasHint(Hint.NATIVE_TIME_RANGE)) { return null; } String val = hints.get(Hint.NATIVE_TIME_RANGE).replaceAll("[()\\s]", ""); Pair<Long, Long> result = new Pair<Long, Long>(0L, Long.MAX_VALUE); if (val != null && !val.isEmpty()) { String[] vals = val.split(","); try { Long tmp = Long.parseLong(vals[0]); result.setFirst(tmp); if (vals.length > 1) { tmp = Long.parseLong(vals[1]); result.setSecond(tmp); } } catch (NumberFormatException ignored) { if (LOG.isDebugEnabled()) { LOG.debug("parseNativeTimeStampRange failed due to", ignored); } } } return result; } public boolean isEmpty() { return hints.isEmpty(); } /** * Gets the value of the hint or null if the hint is not present. * @param hint the hint * @return the value specified in parenthesis following the hint or null * if the hint is not present. * */ public String getHint(Hint hint) { return hints.get(hint); } /** * Tests for the presence of a hint in a query * @param hint the hint * @return true if the hint is present and false otherwise */ public boolean hasHint(Hint hint) { return hints.containsKey(hint); } @Override public String toString() { if (hints.isEmpty()) { return StringUtil.EMPTY_STRING; } StringBuilder buf = new StringBuilder("/*+ "); for (Map.Entry<Hint, String> entry : hints.entrySet()) { buf.append(entry.getKey()); buf.append(entry.getValue()); buf.append(' '); } buf.append("*/ "); return buf.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((hints == null) ? 0 : hints.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; HintNode other = (HintNode) obj; if (hints == null) { if (other.hints != null) return false; } else if (!hints.equals(other.hints)) return false; return true; } }