You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ja...@apache.org on 2014/01/27 23:15:30 UTC

[14/51] [partial] Initial commit of master branch from github

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java b/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
new file mode 100644
index 0000000..0649daf
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.hadoop.hbase.client.HTableInterface;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.client.KeyValueBuilder;
+import org.apache.phoenix.compile.MutationPlan;
+import org.apache.phoenix.coprocessor.MetaDataProtocol.MetaDataMutationResult;
+import org.apache.phoenix.execute.MutationState;
+import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PMetaData;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableType;
+import org.apache.phoenix.schema.SequenceKey;
+
+
+public class DelegateConnectionQueryServices extends DelegateQueryServices implements ConnectionQueryServices {
+
+    public DelegateConnectionQueryServices(ConnectionQueryServices delegate) {
+        super(delegate);
+    }
+    
+    @Override
+    protected ConnectionQueryServices getDelegate() {
+        return (ConnectionQueryServices)super.getDelegate();
+    }
+    
+    @Override
+    public ConnectionQueryServices getChildQueryServices(ImmutableBytesWritable tenantId) {
+        return getDelegate().getChildQueryServices(tenantId);
+    }
+
+    @Override
+    public HTableInterface getTable(byte[] tableName) throws SQLException {
+        return getDelegate().getTable(tableName);
+    }
+
+    @Override
+    public StatsManager getStatsManager() {
+        return getDelegate().getStatsManager();
+    }
+
+    @Override
+    public List<HRegionLocation> getAllTableRegions(byte[] tableName) throws SQLException {
+        return getDelegate().getAllTableRegions(tableName);
+    }
+
+    @Override
+    public PMetaData addTable(PTable table) throws SQLException {
+        return getDelegate().addTable(table);
+    }
+
+    @Override
+    public PMetaData addColumn(String tableName, List<PColumn> columns, long tableTimeStamp, long tableSeqNum,
+            boolean isImmutableRows) throws SQLException {
+        return getDelegate().addColumn(tableName, columns, tableTimeStamp, tableSeqNum, isImmutableRows);
+    }
+
+    @Override
+    public PMetaData removeTable(String tableName)
+            throws SQLException {
+        return getDelegate().removeTable(tableName);
+    }
+
+    @Override
+    public PMetaData removeColumn(String tableName, String familyName, String columnName, long tableTimeStamp,
+            long tableSeqNum) throws SQLException {
+        return getDelegate().removeColumn(tableName, familyName, columnName, tableTimeStamp, tableSeqNum);
+    }
+
+    @Override
+    public PhoenixConnection connect(String url, Properties info) throws SQLException {
+        return getDelegate().connect(url, info);
+    }
+
+    @Override
+    public MetaDataMutationResult getTable(byte[] tenantId, byte[] schemaBytes, byte[] tableBytes, long tableTimestamp, long clientTimestamp) throws SQLException {
+        return getDelegate().getTable(tenantId, schemaBytes, tableBytes, tableTimestamp, clientTimestamp);
+    }
+
+    @Override
+    public MetaDataMutationResult createTable(List<Mutation> tableMetaData, byte[] physicalName,
+            PTableType tableType, Map<String, Object> tableProps, List<Pair<byte[], Map<String, Object>>> families, byte[][] splits)
+            throws SQLException {
+        return getDelegate().createTable(tableMetaData, physicalName, tableType, tableProps, families, splits);
+    }
+
+    @Override
+    public MetaDataMutationResult dropTable(List<Mutation> tabeMetaData, PTableType tableType) throws SQLException {
+        return getDelegate().dropTable(tabeMetaData, tableType);
+    }
+
+    @Override
+    public MetaDataMutationResult addColumn(List<Mutation> tabeMetaData, PTableType tableType, List<Pair<byte[],Map<String,Object>>> families ) throws SQLException {
+        return getDelegate().addColumn(tabeMetaData, tableType, families);
+    }
+
+
+    @Override
+    public MetaDataMutationResult dropColumn(List<Mutation> tabeMetaData, PTableType tableType) throws SQLException {
+        return getDelegate().dropColumn(tabeMetaData, tableType);
+    }
+
+    @Override
+    public MetaDataMutationResult updateIndexState(List<Mutation> tableMetadata, String parentTableName) throws SQLException {
+        return getDelegate().updateIndexState(tableMetadata, parentTableName);
+    }
+    
+    @Override
+    public void init(String url, Properties props) throws SQLException {
+        getDelegate().init(url, props);
+    }
+
+    @Override
+    public MutationState updateData(MutationPlan plan) throws SQLException {
+        return getDelegate().updateData(plan);
+    }
+
+    @Override
+    public int getLowestClusterHBaseVersion() {
+        return getDelegate().getLowestClusterHBaseVersion();
+    }
+
+    @Override
+    public HBaseAdmin getAdmin() throws SQLException {
+        return getDelegate().getAdmin();
+    }
+
+    @Override
+    public HTableDescriptor getTableDescriptor(byte[] tableName) throws SQLException {
+        return getDelegate().getTableDescriptor(tableName);
+    }
+
+    @Override
+    public void clearTableRegionCache(byte[] tableName) throws SQLException {
+        getDelegate().clearTableRegionCache(tableName);
+    }
+
+    @Override
+    public boolean hasInvalidIndexConfiguration() {
+        return getDelegate().hasInvalidIndexConfiguration();
+    }
+
+    @Override
+    public long createSequence(String tenantId, String schemaName, String sequenceName, long startWith,
+            long incrementBy, int batchSize, long timestamp) throws SQLException {
+        return getDelegate().createSequence(tenantId, schemaName, sequenceName, startWith, incrementBy, batchSize, timestamp);
+    }
+
+    @Override
+    public long dropSequence(String tenantId, String schemaName, String sequenceName, long timestamp)
+            throws SQLException {
+        return getDelegate().dropSequence(tenantId, schemaName, sequenceName, timestamp);
+    }
+
+    @Override
+    public void reserveSequenceValues(List<SequenceKey> sequenceKeys, long timestamp, long[] values,
+            SQLException[] exceptions) throws SQLException {
+        getDelegate().reserveSequenceValues(sequenceKeys, timestamp, values, exceptions);
+    }
+
+    @Override
+    public void incrementSequenceValues(List<SequenceKey> sequenceKeys, long timestamp, long[] values,
+            SQLException[] exceptions) throws SQLException {
+        getDelegate().incrementSequenceValues(sequenceKeys, timestamp, values, exceptions);
+    }
+
+    @Override
+    public long getSequenceValue(SequenceKey sequenceKey, long timestamp) throws SQLException {
+        return getDelegate().getSequenceValue(sequenceKey, timestamp);
+    }
+
+    @Override
+    public void returnSequenceValues(List<SequenceKey> sequenceKeys, long timestamp, SQLException[] exceptions)
+            throws SQLException {
+        getDelegate().returnSequenceValues(sequenceKeys, timestamp, exceptions);
+    }
+
+    @Override
+    public void addConnection(PhoenixConnection connection) throws SQLException {
+        getDelegate().addConnection(connection);
+    }
+
+    @Override
+    public void removeConnection(PhoenixConnection connection) throws SQLException {
+        getDelegate().removeConnection(connection);
+    }
+
+    @Override
+    public KeyValueBuilder getKeyValueBuilder() {
+        return getDelegate().getKeyValueBuilder();
+    }
+
+    @Override
+    public boolean supportsFeature(Feature feature) {
+        return getDelegate().supportsFeature(feature);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateQueryServices.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateQueryServices.java b/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateQueryServices.java
new file mode 100644
index 0000000..6ddf5c5
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateQueryServices.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import java.sql.SQLException;
+import java.util.concurrent.ExecutorService;
+
+import org.apache.phoenix.memory.MemoryManager;
+import org.apache.phoenix.optimize.QueryOptimizer;
+import org.apache.phoenix.util.ReadOnlyProps;
+
+
+
+/**
+ * 
+ * Class that delegates QueryService calls through to
+ * a parent QueryService.
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public class DelegateQueryServices implements QueryServices {
+    private final QueryServices parent;
+    
+    public DelegateQueryServices(QueryServices queryServices) {
+        parent = queryServices;
+    }
+    
+    protected QueryServices getDelegate() {
+        return parent;
+    }
+    
+    @Override
+    public ExecutorService getExecutor() {
+        return parent.getExecutor();
+    }
+
+    @Override
+    public MemoryManager getMemoryManager() {
+        return parent.getMemoryManager();
+    }
+
+    @Override
+    public void close() throws SQLException {
+        parent.close();
+    }
+
+    @Override
+    public ReadOnlyProps getProps() {
+        return parent.getProps();
+    }
+
+    @Override
+    public QueryOptimizer getOptimizer() {
+        return parent.getOptimizer();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/HBaseFactoryProvider.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/HBaseFactoryProvider.java b/phoenix-core/src/main/java/org/apache/phoenix/query/HBaseFactoryProvider.java
new file mode 100644
index 0000000..08e8575
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/HBaseFactoryProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import org.apache.phoenix.util.InstanceResolver;
+
+/**
+ * Manages factories that provide extension points for HBase.
+ * <p/>
+ * Dependent modules may register their own implementations of the following using {@link java.util.ServiceLoader}:
+ * <ul>
+ *     <li>{@link ConfigurationFactory}</li>
+ *     <li>{@link HTableFactory}</li>
+ *     <li> {@link HConnectionFactory} </li>
+ * </ul>
+ *
+ * If a custom implementation is not registered, the default implementations will be used.
+ *
+ * @author aaraujo
+ * @since 0.2
+ */
+public class HBaseFactoryProvider {
+
+    private static final HTableFactory DEFAULT_HTABLE_FACTORY = new HTableFactory.HTableFactoryImpl();
+    private static final HConnectionFactory DEFAULT_HCONNECTION_FACTORY =
+        new HConnectionFactory.HConnectionFactoryImpl();
+    private static final ConfigurationFactory DEFAULT_CONFIGURATION_FACTORY = new ConfigurationFactory.ConfigurationFactoryImpl();
+
+    public static HTableFactory getHTableFactory() {
+        return InstanceResolver.getSingleton(HTableFactory.class, DEFAULT_HTABLE_FACTORY);
+    }
+
+    public static HConnectionFactory getHConnectionFactory() {
+        return InstanceResolver.getSingleton(HConnectionFactory.class, DEFAULT_HCONNECTION_FACTORY);
+    }
+
+    public static ConfigurationFactory getConfigurationFactory() {
+        return InstanceResolver.getSingleton(ConfigurationFactory.class, DEFAULT_CONFIGURATION_FACTORY);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/HConnectionFactory.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/HConnectionFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/query/HConnectionFactory.java
new file mode 100644
index 0000000..d40c540
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/HConnectionFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.client.HConnection;
+import org.apache.hadoop.hbase.client.HConnectionManager;
+
+/**
+ * Factory for creating {@link HConnection}
+ *
+ * @author ukuchibhotla
+ */
+public interface HConnectionFactory {
+
+    /**
+     * Creates HConnection to access HBase clusters.
+     * 
+     * @param configuration object
+     * @return A HConnection instance
+     */
+    HConnection createConnection(Configuration conf) throws ZooKeeperConnectionException;
+
+    /**
+     * Default implementation.  Uses standard HBase HConnections.
+     */
+    static class HConnectionFactoryImpl implements HConnectionFactory {
+        @Override
+        public HConnection createConnection(Configuration conf) throws ZooKeeperConnectionException {
+            return HConnectionManager.createConnection(conf);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/HTableFactory.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/HTableFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/query/HTableFactory.java
new file mode 100644
index 0000000..cf5b4da
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/HTableFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+
+import org.apache.hadoop.hbase.client.HConnection;
+import org.apache.hadoop.hbase.client.HTable;
+import org.apache.hadoop.hbase.client.HTableInterface;
+
+/**
+ * Creates clients to access HBase tables.
+ *
+ * @author aaraujo
+ * @since 0.2
+ */
+public interface HTableFactory {
+    /**
+     * Creates an HBase client using an externally managed HConnection and Thread pool.
+     *
+     * @param tableName Name of the table.
+     * @param connection HConnection to use.
+     * @param pool ExecutorService to use.
+     * @return An client to access an HBase table.
+     * @throws IOException if a server or network exception occurs
+     */
+    HTableInterface getTable(byte[] tableName, HConnection connection, ExecutorService pool) throws IOException;
+
+    /**
+     * Default implementation.  Uses standard HBase HTables.
+     */
+    static class HTableFactoryImpl implements HTableFactory {
+        @Override
+        public HTableInterface getTable(byte[] tableName, HConnection connection, ExecutorService pool) throws IOException {
+            return new HTable(tableName, connection, pool);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/KeyRange.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/KeyRange.java b/phoenix-core/src/main/java/org/apache/phoenix/query/KeyRange.java
new file mode 100644
index 0000000..bc37c57
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/KeyRange.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import static org.apache.phoenix.query.QueryConstants.SEPARATOR_BYTE_ARRAY;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.io.WritableUtils;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import org.apache.phoenix.schema.ColumnModifier;
+import org.apache.phoenix.util.ByteUtil;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ *
+ * Class that represents an upper/lower bound key range.
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public class KeyRange implements Writable {
+    public enum Bound { LOWER, UPPER };
+    private static final byte[] DEGENERATE_KEY = new byte[] {1};
+    public static final byte[] UNBOUND = new byte[0];
+    /**
+     * KeyRange for variable length null values. Since we need to represent this using an empty byte array (which
+     * is what we use for upper/lower bound), we create this range using the private constructor rather than
+     * going through the static creation method (where this would not be possible).
+     */
+    public static final KeyRange IS_NULL_RANGE = new KeyRange(ByteUtil.EMPTY_BYTE_ARRAY, true, ByteUtil.EMPTY_BYTE_ARRAY, true);
+    /**
+     * KeyRange for non null variable length values. Since we need to represent this using an empty byte array (which
+     * is what we use for upper/lower bound), we create this range using the private constructor rather than going
+     * through the static creation method (where this would not be possible).
+     */
+    public static final KeyRange IS_NOT_NULL_RANGE = new KeyRange(ByteUtil.nextKey(QueryConstants.SEPARATOR_BYTE_ARRAY), true, UNBOUND, false);
+    
+    /**
+     * KeyRange for an empty key range
+     */
+    public static final KeyRange EMPTY_RANGE = new KeyRange(DEGENERATE_KEY, false, DEGENERATE_KEY, false);
+    
+    /**
+     * KeyRange that contains all values
+     */
+    public static final KeyRange EVERYTHING_RANGE = new KeyRange(UNBOUND, false, UNBOUND, false);
+    
+    public static final Function<byte[], KeyRange> POINT = new Function<byte[], KeyRange>() {
+        @Override 
+        public KeyRange apply(byte[] input) {
+            return new KeyRange(input, true, input, true);
+        }
+    };
+    public static final Comparator<KeyRange> COMPARATOR = new Comparator<KeyRange>() {
+        @SuppressWarnings("deprecation")
+        @Override public int compare(KeyRange o1, KeyRange o2) {
+            return ComparisonChain.start()
+//                    .compareFalseFirst(o1.lowerUnbound(), o2.lowerUnbound())
+                    .compare(o2.lowerUnbound(), o1.lowerUnbound())
+                    .compare(o1.getLowerRange(), o2.getLowerRange(), Bytes.BYTES_COMPARATOR)
+                    // we want o1 lower inclusive to come before o2 lower inclusive, but
+                    // false comes before true, so we have to negate
+//                    .compareTrueFirst(o1.isLowerInclusive(), o2.isLowerInclusive())
+                    .compare(o2.isLowerInclusive(), o1.isLowerInclusive())
+                    // for the same lower bounding, we want a finite upper bound to
+                    // be ordered before an infinite upper bound
+//                    .compareTrueFirst(o1.upperUnbound(), o2.upperUnbound())
+                    .compare(o1.upperUnbound(), o2.upperUnbound())
+                    .compare(o1.getUpperRange(), o2.getUpperRange(), Bytes.BYTES_COMPARATOR)
+//                    .compareFalseFirst(o1.isUpperInclusive(), o2.isUpperInclusive())
+                    .compare(o2.isUpperInclusive(), o1.isUpperInclusive())
+                    .result();
+        }
+    };
+
+    private byte[] lowerRange;
+    private boolean lowerInclusive;
+    private byte[] upperRange;
+    private boolean upperInclusive;
+    private boolean isSingleKey;
+
+    public static KeyRange getKeyRange(byte[] point) {
+        return getKeyRange(point, true, point, true);
+    }
+    
+    public static KeyRange getKeyRange(byte[] lowerRange, byte[] upperRange) {
+        return getKeyRange(lowerRange, true, upperRange, false);
+    }
+
+    // TODO: make non public and move to org.apache.phoenix.type soon
+    public static KeyRange getKeyRange(byte[] lowerRange, boolean lowerInclusive,
+            byte[] upperRange, boolean upperInclusive) {
+        if (lowerRange == null || upperRange == null) {
+            return EMPTY_RANGE;
+        }
+        boolean unboundLower = false;
+        boolean unboundUpper = false;
+        if (lowerRange.length == 0) {
+            lowerRange = UNBOUND;
+            lowerInclusive = false;
+            unboundLower = true;
+        }
+        if (upperRange.length == 0) {
+            upperRange = UNBOUND;
+            upperInclusive = false;
+            unboundUpper = true;
+        }
+
+        if (unboundLower && unboundUpper) {
+            return EVERYTHING_RANGE;
+        }
+        if (!unboundLower && !unboundUpper) {
+            int cmp = Bytes.compareTo(lowerRange, upperRange);
+            if (cmp > 0 || (cmp == 0 && !(lowerInclusive && upperInclusive))) {
+                return EMPTY_RANGE;
+            }
+        }
+        return new KeyRange(lowerRange, unboundLower ? false : lowerInclusive,
+                upperRange, unboundUpper ? false : upperInclusive);
+    }
+
+    public KeyRange() {
+        this.lowerRange = DEGENERATE_KEY;
+        this.lowerInclusive = false;
+        this.upperRange = DEGENERATE_KEY;
+        this.upperInclusive = false;
+        this.isSingleKey = false;
+    }
+    
+    private KeyRange(byte[] lowerRange, boolean lowerInclusive, byte[] upperRange, boolean upperInclusive) {
+        this.lowerRange = lowerRange;
+        this.lowerInclusive = lowerInclusive;
+        this.upperRange = upperRange;
+        this.upperInclusive = upperInclusive;
+        init();
+    }
+    
+    private void init() {
+        this.isSingleKey = lowerRange != UNBOUND && upperRange != UNBOUND
+                && lowerInclusive && upperInclusive && Bytes.compareTo(lowerRange, upperRange) == 0;
+    }
+
+    public byte[] getRange(Bound bound) {
+        return bound == Bound.LOWER ? getLowerRange() : getUpperRange();
+    }
+    
+    public boolean isInclusive(Bound bound) {
+        return bound == Bound.LOWER ? isLowerInclusive() : isUpperInclusive();
+    }
+    
+    public boolean isUnbound(Bound bound) {
+        return bound == Bound.LOWER ? lowerUnbound() : upperUnbound();
+    }
+    
+    public boolean isSingleKey() {
+        return isSingleKey;
+    }
+    
+    public int compareLowerToUpperBound(ImmutableBytesWritable ptr, boolean isInclusive) {
+        return compareLowerToUpperBound(ptr.get(), ptr.getOffset(), ptr.getLength(), isInclusive);
+    }
+    
+    public int compareLowerToUpperBound(ImmutableBytesWritable ptr) {
+        return compareLowerToUpperBound(ptr, true);
+    }
+    
+    public int compareUpperToLowerBound(ImmutableBytesWritable ptr, boolean isInclusive) {
+        return compareUpperToLowerBound(ptr.get(), ptr.getOffset(), ptr.getLength(), isInclusive);
+    }
+    
+    public int compareUpperToLowerBound(ImmutableBytesWritable ptr) {
+        return compareUpperToLowerBound(ptr, true);
+    }
+    
+    public int compareLowerToUpperBound( byte[] b, int o, int l) {
+        return compareLowerToUpperBound(b,o,l,true);
+    }
+
+    /**
+     * Compares a lower bound against an upper bound
+     * @param b upper bound byte array
+     * @param o upper bound offset
+     * @param l upper bound length
+     * @param isInclusive upper bound inclusive
+     * @return -1 if the lower bound is less than the upper bound,
+     *          1 if the lower bound is greater than the upper bound,
+     *          and 0 if they are equal.
+     */
+    public int compareLowerToUpperBound( byte[] b, int o, int l, boolean isInclusive) {
+        if (lowerUnbound() || b == KeyRange.UNBOUND) {
+            return -1;
+        }
+        int cmp = Bytes.compareTo(lowerRange, 0, lowerRange.length, b, o, l);
+        if (cmp > 0) {
+            return 1;
+        }
+        if (cmp < 0) {
+            return -1;
+        }
+        if (lowerInclusive && isInclusive) {
+            return 0;
+        }
+        return 1;
+    }
+    
+    public int compareUpperToLowerBound(byte[] b, int o, int l) {
+        return compareUpperToLowerBound(b,o,l, true);
+    }
+    
+    public int compareUpperToLowerBound(byte[] b, int o, int l, boolean isInclusive) {
+        if (upperUnbound() || b == KeyRange.UNBOUND) {
+            return 1;
+        }
+        int cmp = Bytes.compareTo(upperRange, 0, upperRange.length, b, o, l);
+        if (cmp > 0) {
+            return 1;
+        }
+        if (cmp < 0) {
+            return -1;
+        }
+        if (upperInclusive && isInclusive) {
+            return 0;
+        }
+        return -1;
+    }
+    
+    public byte[] getLowerRange() {
+        return lowerRange;
+    }
+
+    public boolean isLowerInclusive() {
+        return lowerInclusive;
+    }
+
+    public byte[] getUpperRange() {
+        return upperRange;
+    }
+
+    public boolean isUpperInclusive() {
+        return upperInclusive;
+    }
+
+    public boolean isUnbound() {
+        return lowerUnbound() || upperUnbound();
+    }
+
+    public boolean upperUnbound() {
+        return upperRange == UNBOUND;
+    }
+
+    public boolean lowerUnbound() {
+        return lowerRange == UNBOUND;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(lowerRange);
+        if (lowerRange != null)
+            result = prime * result + (lowerInclusive ? 1231 : 1237);
+        result = prime * result + Arrays.hashCode(upperRange);
+        if (upperRange != null)
+            result = prime * result + (upperInclusive ? 1231 : 1237);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        if (isSingleKey()) {
+            return Bytes.toStringBinary(lowerRange);
+        }
+        return (lowerInclusive ? "[" : 
+            "(") + (lowerUnbound() ? "*" : 
+                Bytes.toStringBinary(lowerRange)) + " - " + (upperUnbound() ? "*" : 
+                    Bytes.toStringBinary(upperRange)) + (upperInclusive ? "]" : ")" );
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof KeyRange)) {
+            return false;
+        }
+        KeyRange that = (KeyRange)o;
+        return Bytes.compareTo(this.lowerRange,that.lowerRange) == 0 && this.lowerInclusive == that.lowerInclusive &&
+               Bytes.compareTo(this.upperRange, that.upperRange) == 0 && this.upperInclusive == that.upperInclusive;
+    }
+
+    public KeyRange intersect(KeyRange range) {
+        byte[] newLowerRange;
+        byte[] newUpperRange;
+        boolean newLowerInclusive;
+        boolean newUpperInclusive;
+        if (lowerUnbound()) {
+            newLowerRange = range.lowerRange;
+            newLowerInclusive = range.lowerInclusive;
+        } else if (range.lowerUnbound()) {
+            newLowerRange = lowerRange;
+            newLowerInclusive = lowerInclusive;
+        } else {
+            int cmp = Bytes.compareTo(lowerRange, range.lowerRange);
+            if (cmp != 0 || lowerInclusive == range.lowerInclusive) {
+                if (cmp <= 0) {
+                    newLowerRange = range.lowerRange;
+                    newLowerInclusive = range.lowerInclusive;
+                } else {
+                    newLowerRange = lowerRange;
+                    newLowerInclusive = lowerInclusive;
+                }
+            } else { // Same lower range, but one is not inclusive
+                newLowerRange = range.lowerRange;
+                newLowerInclusive = false;
+            }
+        }
+        if (upperUnbound()) {
+            newUpperRange = range.upperRange;
+            newUpperInclusive = range.upperInclusive;
+        } else if (range.upperUnbound()) {
+            newUpperRange = upperRange;
+            newUpperInclusive = upperInclusive;
+        } else {
+            int cmp = Bytes.compareTo(upperRange, range.upperRange);
+            if (cmp != 0 || upperInclusive == range.upperInclusive) {
+                if (cmp >= 0) {
+                    newUpperRange = range.upperRange;
+                    newUpperInclusive = range.upperInclusive;
+                } else {
+                    newUpperRange = upperRange;
+                    newUpperInclusive = upperInclusive;
+                }
+            } else { // Same upper range, but one is not inclusive
+                newUpperRange = range.upperRange;
+                newUpperInclusive = false;
+            }
+        }
+        if (newLowerRange == lowerRange && newLowerInclusive == lowerInclusive
+                && newUpperRange == upperRange && newUpperInclusive == upperInclusive) {
+            return this;
+        }
+        return getKeyRange(newLowerRange, newLowerInclusive, newUpperRange, newUpperInclusive);
+    }
+
+    public static boolean isDegenerate(byte[] lowerRange, byte[] upperRange) {
+        return lowerRange == KeyRange.EMPTY_RANGE.getLowerRange() && upperRange == KeyRange.EMPTY_RANGE.getUpperRange();
+    }
+
+    public KeyRange appendSeparator() {
+        byte[] lowerBound = getLowerRange();
+        byte[] upperBound = getUpperRange();
+        if (lowerBound != UNBOUND) {
+            lowerBound = ByteUtil.concat(lowerBound, SEPARATOR_BYTE_ARRAY);
+        }
+        if (upperBound != UNBOUND) {
+            upperBound = ByteUtil.concat(upperBound, SEPARATOR_BYTE_ARRAY);
+        }
+        return getKeyRange(lowerBound, lowerInclusive, upperBound, upperInclusive);
+    }
+
+    /**
+     * @return list of at least size 1
+     */
+    @NonNull
+    public static List<KeyRange> coalesce(List<KeyRange> keyRanges) {
+        List<KeyRange> tmp = new ArrayList<KeyRange>();
+        for (KeyRange keyRange : keyRanges) {
+            if (EMPTY_RANGE == keyRange) {
+                continue;
+            }
+            if (EVERYTHING_RANGE == keyRange) {
+                tmp.clear();
+                tmp.add(keyRange);
+                break;
+            }
+            tmp.add(keyRange);
+        }
+        if (tmp.size() == 1) {
+            return tmp;
+        }
+        if (tmp.size() == 0) {
+            return Collections.singletonList(EMPTY_RANGE);
+        }
+
+        Collections.sort(tmp, COMPARATOR);
+        List<KeyRange> tmp2 = new ArrayList<KeyRange>();
+        KeyRange range = tmp.get(0);
+        for (int i=1; i<tmp.size(); i++) {
+            KeyRange otherRange = tmp.get(i);
+            KeyRange intersect = range.intersect(otherRange);
+            if (EMPTY_RANGE == intersect) {
+                tmp2.add(range);
+                range = otherRange;
+            } else {
+                range = range.union(otherRange);
+            }
+        }
+        tmp2.add(range);
+        List<KeyRange> tmp3 = new ArrayList<KeyRange>();
+        range = tmp2.get(0);
+        for (int i=1; i<tmp2.size(); i++) {
+            KeyRange otherRange = tmp2.get(i);
+            assert !range.upperUnbound();
+            assert !otherRange.lowerUnbound();
+            if (range.isUpperInclusive() != otherRange.isLowerInclusive()
+                    && Bytes.equals(range.getUpperRange(), otherRange.getLowerRange())) {
+                range = KeyRange.getKeyRange(range.getLowerRange(), range.isLowerInclusive(), otherRange.getUpperRange(), otherRange.isUpperInclusive());
+            } else {
+                tmp3.add(range);
+                range = otherRange;
+            }
+        }
+        tmp3.add(range);
+        
+        return tmp3;
+    }
+
+    public KeyRange union(KeyRange other) {
+        if (EMPTY_RANGE == other) return this;
+        if (EMPTY_RANGE == this) return other;
+        byte[] newLower, newUpper;
+        boolean newLowerInclusive, newUpperInclusive;
+        if (this.lowerUnbound() || other.lowerUnbound()) {
+            newLower = UNBOUND;
+            newLowerInclusive = false;
+        } else {
+            int lowerCmp = Bytes.compareTo(this.lowerRange, other.lowerRange);
+            if (lowerCmp < 0) {
+                newLower = lowerRange;
+                newLowerInclusive = lowerInclusive;
+            } else if (lowerCmp == 0) {
+                newLower = lowerRange;
+                newLowerInclusive = this.lowerInclusive || other.lowerInclusive;
+            } else {
+                newLower = other.lowerRange;
+                newLowerInclusive = other.lowerInclusive;
+            }
+        }
+
+        if (this.upperUnbound() || other.upperUnbound()) {
+            newUpper = UNBOUND;
+            newUpperInclusive = false;
+        } else {
+            int upperCmp = Bytes.compareTo(this.upperRange, other.upperRange);
+            if (upperCmp > 0) {
+                newUpper = upperRange;
+                newUpperInclusive = this.upperInclusive;
+            } else if (upperCmp == 0) {
+                newUpper = upperRange;
+                newUpperInclusive = this.upperInclusive || other.upperInclusive;
+            } else {
+                newUpper = other.upperRange;
+                newUpperInclusive = other.upperInclusive;
+            }
+        }
+        return KeyRange.getKeyRange(newLower, newLowerInclusive, newUpper, newUpperInclusive);
+    }
+
+    public static List<KeyRange> of(List<byte[]> keys) {
+        return Lists.transform(keys, POINT);
+    }
+
+    public static List<KeyRange> intersect(List<KeyRange> keyRanges, List<KeyRange> keyRanges2) {
+        List<KeyRange> tmp = new ArrayList<KeyRange>();
+        for (KeyRange r1 : keyRanges) {
+            for (KeyRange r2 : keyRanges2) {
+                KeyRange r = r1.intersect(r2);
+                if (EMPTY_RANGE != r) {
+                    tmp.add(r);
+                }
+            }
+        }
+        if (tmp.size() == 0) {
+            return Collections.singletonList(KeyRange.EMPTY_RANGE);
+        }
+        Collections.sort(tmp, KeyRange.COMPARATOR);
+        List<KeyRange> tmp2 = new ArrayList<KeyRange>();
+        KeyRange r = tmp.get(0);
+        for (int i=1; i<tmp.size(); i++) {
+            if (EMPTY_RANGE == r.intersect(tmp.get(i))) {
+                tmp2.add(r);
+                r = tmp.get(i);
+            } else {
+                r = r.intersect(tmp.get(i));
+            }
+        }
+        tmp2.add(r);
+        return tmp2;
+    }
+    
+    /**
+     * Fill both upper and lower range of keyRange to keyLength bytes.
+     * If the upper bound is inclusive, it must be filled such that an
+     * intersection with a longer key would still match if the shorter
+     * length matches.  For example: (*,00C] intersected with [00Caaa,00Caaa]
+     * should still return [00Caaa,00Caaa] since the 00C matches and is
+     * inclusive.
+     * @param keyLength
+     * @return the newly filled KeyRange
+     */
+    public KeyRange fill(int keyLength) {
+        byte[] lowerRange = this.getLowerRange();
+        byte[] newLowerRange = lowerRange;
+        if (!this.lowerUnbound()) {
+            // If lower range is inclusive, fill with 0x00 since conceptually these bytes are included in the range
+            newLowerRange = ByteUtil.fillKey(lowerRange, keyLength);
+        }
+        byte[] upperRange = this.getUpperRange();
+        byte[] newUpperRange = upperRange;
+        if (!this.upperUnbound()) {
+            // If upper range is inclusive, fill with 0xFF since conceptually these bytes are included in the range
+            newUpperRange = ByteUtil.fillKey(upperRange, keyLength);
+        }
+        if (newLowerRange != lowerRange || newUpperRange != upperRange) {
+            return KeyRange.getKeyRange(newLowerRange, this.isLowerInclusive(), newUpperRange, this.isUpperInclusive());
+        }
+        return this;
+    }
+    
+    public KeyRange invert() {
+        byte[] lower = this.getLowerRange();
+        if (!this.lowerUnbound()) {
+            lower = ColumnModifier.SORT_DESC.apply(lower, 0, lower.length);
+        }
+        byte[] upper;
+        if (this.isSingleKey()) {
+            upper = lower;
+        } else {
+            upper = this.getUpperRange();
+            if (!this.upperUnbound()) {
+                upper = ColumnModifier.SORT_DESC.apply(upper, 0, upper.length);
+            }
+        }
+        return KeyRange.getKeyRange(lower, this.isLowerInclusive(), upper, this.isUpperInclusive());
+    }
+
+    @Override
+    public void readFields(DataInput in) throws IOException {
+        int len = WritableUtils.readVInt(in);
+        if (len == 0) {
+            lowerRange = KeyRange.UNBOUND;
+            lowerInclusive = false;
+        } else {
+            if (len < 0) {
+                lowerInclusive = false;
+                lowerRange = new byte[-len - 1];
+                in.readFully(lowerRange);
+            } else {
+                lowerInclusive = true;
+                lowerRange = new byte[len - 1];
+                in.readFully(lowerRange);
+            }
+        }
+        len = WritableUtils.readVInt(in);
+        if (len == 0) {
+            upperRange = KeyRange.UNBOUND;
+            upperInclusive = false;
+        } else {
+            if (len < 0) {
+                upperInclusive = false;
+                upperRange = new byte[-len - 1];
+                in.readFully(upperRange);
+            } else {
+                upperInclusive = true;
+                upperRange = new byte[len - 1];
+                in.readFully(upperRange);
+            }
+        }
+        init();
+    }
+
+    private void writeBound(Bound bound, DataOutput out) throws IOException {
+        // Encode unbound by writing a zero
+        if (isUnbound(bound)) {
+            WritableUtils.writeVInt(out, 0);
+            return;
+        }
+        // Otherwise, inclusive is positive and exclusive is negative, offset by 1
+        byte[] range = getRange(bound);
+        if (isInclusive(bound)){
+            WritableUtils.writeVInt(out, range.length+1);
+        } else {
+            WritableUtils.writeVInt(out, -(range.length+1));
+        }
+        out.write(range);
+    }
+    
+    @Override
+    public void write(DataOutput out) throws IOException {
+        writeBound(Bound.LOWER, out);
+        writeBound(Bound.UPPER, out);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/MetaDataMutated.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/MetaDataMutated.java b/phoenix-core/src/main/java/org/apache/phoenix/query/MetaDataMutated.java
new file mode 100644
index 0000000..0ed1d56
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/MetaDataMutated.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PMetaData;
+import org.apache.phoenix.schema.PTable;
+
+
+/**
+ * 
+ * Interface for applying schema mutations to our client-side schema cache
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public interface MetaDataMutated {
+    PMetaData addTable(PTable table) throws SQLException;
+    PMetaData removeTable(String tableName) throws SQLException;
+    PMetaData addColumn(String tableName, List<PColumn> columns, long tableTimeStamp, long tableSeqNum, boolean isImmutableRows) throws SQLException;
+    PMetaData removeColumn(String tableName, String familyName, String columnName, long tableTimeStamp, long tableSeqNum) throws SQLException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
new file mode 100644
index 0000000..48bd7f7
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ARRAY_SIZE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.BUFFER_LENGTH;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.CACHE_SIZE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.CHAR_OCTET_LENGTH;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_COUNT;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_DEF;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_MODIFIER;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_SIZE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.CURRENT_VALUE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DATA_TABLE_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DATA_TYPE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DECIMAL_DIGITS;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DEFAULT_COLUMN_FAMILY_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DISABLE_WAL;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_ROWS;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INCREMENT_BY;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_STATE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IS_AUTOINCREMENT;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IS_NULLABLE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LINK_TYPE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.MULTI_TENANT;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.NULLABLE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.NUM_PREC_RADIX;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ORDINAL_POSITION;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.PK_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.REF_GENERATION_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.REMARKS_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SALT_BUCKETS;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SCOPE_CATALOG;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SCOPE_SCHEMA;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SCOPE_TABLE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SELF_REFERENCING_COL_NAME_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SEQUENCE_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SEQUENCE_SCHEMA;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SOURCE_DATA_TYPE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SQL_DATA_TYPE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SQL_DATETIME_SUB;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.START_WITH;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_CAT_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_NAME_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_SCHEM_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_SEQ_NUM;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_TYPE_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TENANT_ID;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TYPE_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TYPE_SCHEMA;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TYPE_SEQUENCE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TYPE_TABLE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.VIEW_STATEMENT;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.VIEW_TYPE;
+
+import java.math.BigDecimal;
+
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.index.util.ImmutableBytesPtr;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.coprocessor.MetaDataProtocol;
+import org.apache.phoenix.schema.MetaDataSplitPolicy;
+import org.apache.phoenix.schema.PName;
+import org.apache.phoenix.schema.PNormalizedName;
+
+
+/**
+ * 
+ * Constants used during querying
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public interface QueryConstants {
+    public static final String NAME_SEPARATOR = ".";
+    public final static byte[] NAME_SEPARATOR_BYTES = Bytes.toBytes(NAME_SEPARATOR);
+    public static final byte NAME_SEPARATOR_BYTE = NAME_SEPARATOR_BYTES[0];
+    public static final String NULL_SCHEMA_NAME = "";
+    public static final String NULL_DISPLAY_TEXT = "<null>";
+    public static final long UNSET_TIMESTAMP = -1;
+    
+    public enum JoinType {INNER, LEFT_OUTER}
+    public final static String PHOENIX_SCHEMA = "system";
+    public final static String PHOENIX_METADATA = "table";
+
+    public final static PName SINGLE_COLUMN_NAME = new PNormalizedName("s");
+    public final static PName SINGLE_COLUMN_FAMILY_NAME = new PNormalizedName("s");
+    public final static byte[] SINGLE_COLUMN = SINGLE_COLUMN_NAME.getBytes();
+    public final static byte[] SINGLE_COLUMN_FAMILY = SINGLE_COLUMN_FAMILY_NAME.getBytes();
+
+    public static final long AGG_TIMESTAMP = HConstants.LATEST_TIMESTAMP;
+    /**
+     * Key used for a single row aggregation where there is no group by
+     */
+    public final static byte[] UNGROUPED_AGG_ROW_KEY = Bytes.toBytes("a");
+    public final static PName AGG_COLUMN_NAME = SINGLE_COLUMN_NAME;
+    public final static PName AGG_COLUMN_FAMILY_NAME = SINGLE_COLUMN_FAMILY_NAME;
+
+    public static final byte[] TRUE = new byte[] {1};
+
+    /**
+     * Separator used between variable length keys for a composite key.
+     * Variable length data types may not use this byte value.
+     */
+    public static final byte SEPARATOR_BYTE = (byte) 0;
+    public static final byte[] SEPARATOR_BYTE_ARRAY = new byte[] {SEPARATOR_BYTE};
+    
+    public static final String DEFAULT_COPROCESS_PATH = "phoenix.jar";
+    public final static int MILLIS_IN_DAY = 1000 * 60 * 60 * 24;
+
+    public static final String EMPTY_COLUMN_NAME = "_0";
+    public static final byte[] EMPTY_COLUMN_BYTES = Bytes.toBytes(EMPTY_COLUMN_NAME);
+    public static final ImmutableBytesPtr EMPTY_COLUMN_BYTES_PTR = new ImmutableBytesPtr(
+            EMPTY_COLUMN_BYTES);
+    public static final String DEFAULT_COLUMN_FAMILY = EMPTY_COLUMN_NAME;
+    public static final byte[] DEFAULT_COLUMN_FAMILY_BYTES = EMPTY_COLUMN_BYTES;
+    public static final String ALL_FAMILY_PROPERTIES_KEY = "";
+    public static final String SYSTEM_TABLE_PK_NAME = "pk";
+    
+    public static final double MILLIS_TO_NANOS_CONVERTOR = Math.pow(10, 6);
+    public static final BigDecimal BD_MILLIS_NANOS_CONVERSION = BigDecimal.valueOf(MILLIS_TO_NANOS_CONVERTOR);
+    public static final BigDecimal BD_MILLIS_IN_DAY = BigDecimal.valueOf(QueryConstants.MILLIS_IN_DAY);
+    
+
+    public static final String CREATE_TABLE_METADATA =
+            // Do not use IF NOT EXISTS as we sometimes catch the TableAlreadyExists exception
+            // and add columns to the SYSTEM.TABLE dynamically.
+            "CREATE TABLE " + TYPE_SCHEMA + ".\"" + TYPE_TABLE + "\"(\n" +
+            // PK columns
+            TENANT_ID + " VARCHAR NULL," +
+            TABLE_SCHEM_NAME + " VARCHAR NULL," +
+            TABLE_NAME_NAME + " VARCHAR NOT NULL," +
+            COLUMN_NAME + " VARCHAR NULL," + // null only for table row
+            TABLE_CAT_NAME + " VARCHAR NULL," + // using for CF - ensures uniqueness for columns
+            // Table metadata (will be null for column rows)
+            TABLE_TYPE_NAME + " CHAR(1)," +
+            REMARKS_NAME + " VARCHAR," +
+            DATA_TYPE + " INTEGER," +
+            PK_NAME + " VARCHAR," +
+            TYPE_NAME + " VARCHAR," +
+            SELF_REFERENCING_COL_NAME_NAME + " VARCHAR," +
+            REF_GENERATION_NAME + " VARCHAR," +
+            TABLE_SEQ_NUM + " BIGINT," +
+            COLUMN_COUNT + " INTEGER," +
+            // Column metadata (will be null for table row)
+            COLUMN_SIZE + " INTEGER," +
+            BUFFER_LENGTH + " INTEGER," +
+            DECIMAL_DIGITS + " INTEGER," +
+            NUM_PREC_RADIX + " INTEGER," +
+            NULLABLE + " INTEGER," +
+            COLUMN_DEF + " VARCHAR," +
+            SQL_DATA_TYPE + " INTEGER," +
+            SQL_DATETIME_SUB + " INTEGER," +
+            CHAR_OCTET_LENGTH + " INTEGER," +
+            ORDINAL_POSITION + " INTEGER," +
+            IS_NULLABLE + " VARCHAR," +
+            SCOPE_CATALOG + " VARCHAR," +
+            SCOPE_SCHEMA + " VARCHAR," +
+            SCOPE_TABLE + " VARCHAR," +
+            SOURCE_DATA_TYPE + " INTEGER," + // supposed to be SHORT
+            IS_AUTOINCREMENT + " VARCHAR," +
+            // Columns added in 1.2.1
+            COLUMN_MODIFIER + " INTEGER," +
+            SALT_BUCKETS + " INTEGER," +
+            // Columns added in 2.0.0
+            DATA_TABLE_NAME + " VARCHAR," +
+            INDEX_STATE + " CHAR(1),\n" +
+            IMMUTABLE_ROWS + " BOOLEAN,\n" +
+            // Columns added in 3.0.0
+            VIEW_STATEMENT + " VARCHAR,\n" +
+            DEFAULT_COLUMN_FAMILY_NAME + " VARCHAR,\n" +
+            DISABLE_WAL + " BOOLEAN,\n" +
+            MULTI_TENANT + " BOOLEAN,\n" +
+            VIEW_TYPE + " UNSIGNED_TINYINT,\n" +
+            LINK_TYPE + " UNSIGNED_TINYINT,\n" +
+            ARRAY_SIZE + " INTEGER,\n" +
+            "CONSTRAINT " + SYSTEM_TABLE_PK_NAME + " PRIMARY KEY (" + TENANT_ID + ","
+            + TABLE_SCHEM_NAME + "," + TABLE_NAME_NAME + "," + COLUMN_NAME + "," + TABLE_CAT_NAME + "))\n" +
+            HConstants.VERSIONS + "=" + MetaDataProtocol.DEFAULT_MAX_META_DATA_VERSIONS + ",\n" +
+            DEFAULT_COLUMN_FAMILY_NAME + "=" + "'_0'" + ",\n" + // Use original default for b/w compat
+            HTableDescriptor.SPLIT_POLICY + "='" + MetaDataSplitPolicy.class.getName() + "'\n";
+    
+    public static final String CREATE_SEQUENCE_METADATA =
+            "CREATE TABLE IF NOT EXISTS " + TYPE_SCHEMA + ".\"" + TYPE_SEQUENCE + "\"(\n" +                                    
+            TENANT_ID + " VARCHAR NULL," +
+    		SEQUENCE_SCHEMA + " VARCHAR NULL, \n" + 
+            SEQUENCE_NAME +  " VARCHAR NOT NULL, \n" +
+            START_WITH + " BIGINT NOT NULL, \n" + 
+    		CURRENT_VALUE + " BIGINT NOT NULL, \n" + 
+            INCREMENT_BY  + " BIGINT NOT NULL, \n" + 
+            CACHE_SIZE  + " INTEGER NOT NULL \n" + 
+    		" CONSTRAINT " + SYSTEM_TABLE_PK_NAME + " PRIMARY KEY (" + TENANT_ID + "," + SEQUENCE_SCHEMA + "," + SEQUENCE_NAME + "))\n" + 
+    		HConstants.VERSIONS + "=" + MetaDataProtocol.DEFAULT_MAX_META_DATA_VERSIONS + "\n";
+	
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
new file mode 100644
index 0000000..320bac0
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.http.annotation.Immutable;
+
+import org.apache.phoenix.iterate.SpoolTooBigToDiskException;
+import org.apache.phoenix.memory.MemoryManager;
+import org.apache.phoenix.optimize.QueryOptimizer;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.apache.phoenix.util.SQLCloseable;
+
+
+
+/**
+ * 
+ * Interface to group together services needed during querying.  The
+ * parameters that may be set in {@link org.apache.hadoop.conf.Configuration}
+ * are documented here: https://github.com/forcedotcom/phoenix/wiki/Tuning
+ *     
+ * @author jtaylor
+ * @since 0.1
+ */
+@Immutable
+public interface QueryServices extends SQLCloseable {
+    public static final String KEEP_ALIVE_MS_ATTRIB = "phoenix.query.keepAliveMs";
+    public static final String THREAD_POOL_SIZE_ATTRIB = "phoenix.query.threadPoolSize";
+    public static final String QUEUE_SIZE_ATTRIB = "phoenix.query.queueSize";
+    public static final String THREAD_TIMEOUT_MS_ATTRIB = "phoenix.query.timeoutMs";
+    public static final String SPOOL_THRESHOLD_BYTES_ATTRIB = "phoenix.query.spoolThresholdBytes";
+    
+    /**
+	 * max size to spool the the result into
+	 * ${java.io.tmpdir}/ResultSpoolerXXX.bin if
+	 * {@link QueryServices#SPOOL_THRESHOLD_BYTES_ATTRIB } is reached.
+	 * <p>
+	 * default is unlimited(-1)
+	 * <p>
+	 * if the threshold is reached, a {@link SpoolTooBigToDiskException } will be thrown 
+	 */
+	public static final String MAX_SPOOL_TO_DISK_BYTES_ATTRIB = "phoenix.query.maxSpoolToDiskBytes";
+    
+    public static final String MAX_MEMORY_PERC_ATTRIB = "phoenix.query.maxGlobalMemoryPercentage";
+    public static final String MAX_MEMORY_WAIT_MS_ATTRIB = "phoenix.query.maxGlobalMemoryWaitMs";
+    public static final String MAX_TENANT_MEMORY_PERC_ATTRIB = "phoenix.query.maxTenantMemoryPercentage";
+    public static final String MAX_SERVER_CACHE_SIZE_ATTRIB = "phoenix.query.maxServerCacheBytes";
+    public static final String TARGET_QUERY_CONCURRENCY_ATTRIB = "phoenix.query.targetConcurrency";
+    public static final String MAX_QUERY_CONCURRENCY_ATTRIB = "phoenix.query.maxConcurrency";
+    public static final String DATE_FORMAT_ATTRIB = "phoenix.query.dateFormat";
+    public static final String NUMBER_FORMAT_ATTRIB = "phoenix.query.numberFormat";
+    public static final String STATS_UPDATE_FREQ_MS_ATTRIB = "phoenix.query.statsUpdateFrequency";
+    public static final String MAX_STATS_AGE_MS_ATTRIB = "phoenix.query.maxStatsAge";
+    public static final String CALL_QUEUE_ROUND_ROBIN_ATTRIB = "ipc.server.callqueue.roundrobin";
+    public static final String SCAN_CACHE_SIZE_ATTRIB = "hbase.client.scanner.caching";
+    public static final String MAX_MUTATION_SIZE_ATTRIB = "phoenix.mutate.maxSize";
+    public static final String MUTATE_BATCH_SIZE_ATTRIB = "phoenix.mutate.batchSize";
+    public static final String MAX_SERVER_CACHE_TIME_TO_LIVE_MS = "phoenix.coprocessor.maxServerCacheTimeToLiveMs";
+    public static final String MAX_INTRA_REGION_PARALLELIZATION_ATTRIB  = "phoenix.query.maxIntraRegionParallelization";
+    public static final String ROW_KEY_ORDER_SALTED_TABLE_ATTRIB  = "phoenix.query.rowKeyOrderSaltedTable";
+    public static final String USE_INDEXES_ATTRIB  = "phoenix.query.useIndexes";
+    public static final String IMMUTABLE_ROWS_ATTRIB  = "phoenix.mutate.immutableRows";
+    public static final String INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB  = "phoenix.index.mutableBatchSizeThreshold";
+    public static final String DROP_METADATA_ATTRIB  = "phoenix.schema.dropMetaData";
+    public static final String GROUPBY_SPILLABLE_ATTRIB  = "phoenix.groupby.spillable";
+    public static final String GROUPBY_SPILL_FILES_ATTRIB = "phoenix.groupby.spillFiles";
+    public static final String GROUPBY_MAX_CACHE_SIZE_ATTRIB = "phoenix.groupby.maxCacheSize";
+
+    public static final String CALL_QUEUE_PRODUCER_ATTRIB_NAME = "CALL_QUEUE_PRODUCER";
+    
+    public static final String MASTER_INFO_PORT_ATTRIB = "hbase.master.info.port";
+    public static final String REGIONSERVER_INFO_PORT_ATTRIB = "hbase.regionserver.info.port";
+    public static final String REGIONSERVER_LEASE_PERIOD_ATTRIB = "hbase.regionserver.lease.period";
+    public static final String RPC_TIMEOUT_ATTRIB = "hbase.rpc.timeout";
+    public static final String ZOOKEEPER_QUARUM_ATTRIB = "hbase.zookeeper.quorum";
+    public static final String ZOOKEEPER_PORT_ATTRIB = "hbase.zookeeper.property.clientPort";
+    public static final String ZOOKEEPER_ROOT_NODE_ATTRIB = "zookeeper.znode.parent";
+    public static final String DISTINCT_VALUE_COMPRESS_THRESHOLD_ATTRIB = "phoenix.distinct.value.compress.threshold";
+    public static final String SEQUENCE_CACHE_SIZE_ATTRIB = "phoenix.sequence.cacheSize";
+
+    
+    /**
+     * Get executor service used for parallel scans
+     */
+    public ExecutorService getExecutor();
+    /**
+     * Get the memory manager used to track memory usage
+     */
+    public MemoryManager getMemoryManager();
+    
+    /**
+     * Get the properties from the HBase configuration in a
+     * read-only structure that avoids any synchronization
+     */
+    public ReadOnlyProps getProps();
+    
+    /**
+     * Get query optimizer used to choose the best query plan
+     */
+    public QueryOptimizer getOptimizer();
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesImpl.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesImpl.java
new file mode 100644
index 0000000..5ebde59
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+
+
+
+
+/**
+ * 
+ * Real implementation of QueryServices for use in runtime and perf testing
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public final class QueryServicesImpl extends BaseQueryServicesImpl {
+    
+    public QueryServicesImpl() {
+        super(QueryServicesOptions.withDefaults());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
new file mode 100644
index 0000000..7f3025c
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import static org.apache.phoenix.query.QueryServices.CALL_QUEUE_PRODUCER_ATTRIB_NAME;
+import static org.apache.phoenix.query.QueryServices.CALL_QUEUE_ROUND_ROBIN_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.DATE_FORMAT_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.DROP_METADATA_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.GROUPBY_MAX_CACHE_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.GROUPBY_SPILL_FILES_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.GROUPBY_SPILLABLE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.IMMUTABLE_ROWS_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.KEEP_ALIVE_MS_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MASTER_INFO_PORT_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_INTRA_REGION_PARALLELIZATION_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_MEMORY_PERC_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_MEMORY_WAIT_MS_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_MUTATION_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_QUERY_CONCURRENCY_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_SERVER_CACHE_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_SERVER_CACHE_TIME_TO_LIVE_MS;
+import static org.apache.phoenix.query.QueryServices.MAX_SPOOL_TO_DISK_BYTES_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MAX_TENANT_MEMORY_PERC_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.MUTATE_BATCH_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.QUEUE_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.REGIONSERVER_INFO_PORT_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.REGIONSERVER_LEASE_PERIOD_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.ROW_KEY_ORDER_SALTED_TABLE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.RPC_TIMEOUT_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.SCAN_CACHE_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.SEQUENCE_CACHE_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.SPOOL_THRESHOLD_BYTES_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.STATS_UPDATE_FREQ_MS_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.TARGET_QUERY_CONCURRENCY_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.THREAD_POOL_SIZE_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.THREAD_TIMEOUT_MS_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.USE_INDEXES_ATTRIB;
+
+import java.util.Map.Entry;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.regionserver.wal.WALEditCodec;
+
+import org.apache.phoenix.util.DateUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
+
+
+/**
+ * Options for {@link QueryServices}.
+ * 
+ * @author syyang
+ * @since 0.1
+ */
+public class QueryServicesOptions {
+	public static final int DEFAULT_KEEP_ALIVE_MS = 60000;
+	public static final int DEFAULT_THREAD_POOL_SIZE = 128;
+	public static final int DEFAULT_QUEUE_SIZE = 500;
+	public static final int DEFAULT_THREAD_TIMEOUT_MS = 600000; // 10min
+	public static final int DEFAULT_SPOOL_THRESHOLD_BYTES = 1024 * 1024 * 20; // 20m
+	public static final int DEFAULT_MAX_MEMORY_PERC = 50; // 50% of heap
+	public static final int DEFAULT_MAX_MEMORY_WAIT_MS = 10000;
+	public static final int DEFAULT_MAX_TENANT_MEMORY_PERC = 100;
+	public static final long DEFAULT_MAX_SERVER_CACHE_SIZE = 1024*1024*100;  // 100 Mb
+    public static final int DEFAULT_TARGET_QUERY_CONCURRENCY = 32;
+    public static final int DEFAULT_MAX_QUERY_CONCURRENCY = 64;
+    public static final String DEFAULT_DATE_FORMAT = DateUtil.DEFAULT_DATE_FORMAT;
+    public static final int DEFAULT_STATS_UPDATE_FREQ_MS = 15 * 60000; // 15min
+    public static final int DEFAULT_MAX_STATS_AGE_MS = 24 * 60 * 60000; // 1 day
+    public static final boolean DEFAULT_CALL_QUEUE_ROUND_ROBIN = true; 
+    public static final int DEFAULT_MAX_MUTATION_SIZE = 500000;
+    public static final boolean DEFAULT_ROW_KEY_ORDER_SALTED_TABLE = true; // Merge sort on client to ensure salted tables are row key ordered
+    public static final boolean DEFAULT_USE_INDEXES = true; // Use indexes
+    public static final boolean DEFAULT_IMMUTABLE_ROWS = false; // Tables rows may be updated
+    public static final boolean DEFAULT_DROP_METADATA = true; // Drop meta data also.
+    
+    public final static int DEFAULT_MUTATE_BATCH_SIZE = 1000; // Batch size for UPSERT SELECT and DELETE
+	// The only downside of it being out-of-sync is that the parallelization of the scan won't be as balanced as it could be.
+    public static final int DEFAULT_MAX_SERVER_CACHE_TIME_TO_LIVE_MS = 30000; // 30 sec (with no activity)
+    public static final int DEFAULT_SCAN_CACHE_SIZE = 1000;
+    public static final int DEFAULT_MAX_INTRA_REGION_PARALLELIZATION = DEFAULT_MAX_QUERY_CONCURRENCY;
+    public static final int DEFAULT_DISTINCT_VALUE_COMPRESS_THRESHOLD = 1024 * 1024 * 1; // 1 Mb
+    public static final int DEFAULT_INDEX_MUTATE_BATCH_SIZE_THRESHOLD = 5;
+    public static final long DEFAULT_MAX_SPOOL_TO_DISK_BYTES = 1024000000;
+    
+    // 
+    // Spillable GroupBy - SPGBY prefix
+    //
+    // Enable / disable spillable group by
+    public static boolean DEFAULT_GROUPBY_SPILLABLE = true;
+    // Number of spill files / partitions the keys are distributed to
+    // Each spill file fits 2GB of data
+    public static final int DEFAULT_GROUPBY_SPILL_FILES = 2;
+    // Max size of 1st level main memory cache in bytes --> upper bound
+    public static final long DEFAULT_GROUPBY_MAX_CACHE_MAX = 1024L*1024L*100L;  // 100 Mb
+    
+    public static final int DEFAULT_SEQUENCE_CACHE_SIZE = 100;  // reserve 100 sequences at a time
+    
+    
+    private final Configuration config;
+    
+    private QueryServicesOptions(Configuration config) {
+        this.config = config;
+    }
+    
+    public ReadOnlyProps getProps() {
+        // Ensure that HBase RPC time out value is at least as large as our thread time out for query. 
+        int threadTimeOutMS = config.getInt(THREAD_TIMEOUT_MS_ATTRIB, DEFAULT_THREAD_TIMEOUT_MS);
+        int hbaseRPCTimeOut = config.getInt(HConstants.HBASE_RPC_TIMEOUT_KEY, HConstants.DEFAULT_HBASE_RPC_TIMEOUT);
+        if (threadTimeOutMS > hbaseRPCTimeOut) {
+            config.setInt(HConstants.HBASE_RPC_TIMEOUT_KEY, threadTimeOutMS);
+        }
+        return new ReadOnlyProps(config.iterator());
+    }
+    
+    public QueryServicesOptions setAll(ReadOnlyProps props) {
+        for (Entry<String,String> entry : props) {
+            config.set(entry.getKey(), entry.getValue());
+        }
+        return this;
+    }
+
+    public static QueryServicesOptions withDefaults() {
+        Configuration config = HBaseFactoryProvider.getConfigurationFactory().getConfiguration();
+        QueryServicesOptions options = new QueryServicesOptions(config)
+            .setIfUnset(KEEP_ALIVE_MS_ATTRIB, DEFAULT_KEEP_ALIVE_MS)
+            .setIfUnset(THREAD_POOL_SIZE_ATTRIB, DEFAULT_THREAD_POOL_SIZE)
+            .setIfUnset(QUEUE_SIZE_ATTRIB, DEFAULT_QUEUE_SIZE)
+            .setIfUnset(THREAD_TIMEOUT_MS_ATTRIB, DEFAULT_THREAD_TIMEOUT_MS)
+            .setIfUnset(SPOOL_THRESHOLD_BYTES_ATTRIB, DEFAULT_SPOOL_THRESHOLD_BYTES)
+            .setIfUnset(MAX_MEMORY_PERC_ATTRIB, DEFAULT_MAX_MEMORY_PERC)
+            .setIfUnset(MAX_MEMORY_WAIT_MS_ATTRIB, DEFAULT_MAX_MEMORY_WAIT_MS)
+            .setIfUnset(MAX_TENANT_MEMORY_PERC_ATTRIB, DEFAULT_MAX_TENANT_MEMORY_PERC)
+            .setIfUnset(MAX_SERVER_CACHE_SIZE_ATTRIB, DEFAULT_MAX_SERVER_CACHE_SIZE)
+            .setIfUnset(SCAN_CACHE_SIZE_ATTRIB, DEFAULT_SCAN_CACHE_SIZE)
+            .setIfUnset(TARGET_QUERY_CONCURRENCY_ATTRIB, DEFAULT_TARGET_QUERY_CONCURRENCY)
+            .setIfUnset(MAX_QUERY_CONCURRENCY_ATTRIB, DEFAULT_MAX_QUERY_CONCURRENCY)
+            .setIfUnset(DATE_FORMAT_ATTRIB, DEFAULT_DATE_FORMAT)
+            .setIfUnset(STATS_UPDATE_FREQ_MS_ATTRIB, DEFAULT_STATS_UPDATE_FREQ_MS)
+            .setIfUnset(CALL_QUEUE_ROUND_ROBIN_ATTRIB, DEFAULT_CALL_QUEUE_ROUND_ROBIN)
+            .setIfUnset(MAX_MUTATION_SIZE_ATTRIB, DEFAULT_MAX_MUTATION_SIZE)
+            .setIfUnset(MAX_INTRA_REGION_PARALLELIZATION_ATTRIB, DEFAULT_MAX_INTRA_REGION_PARALLELIZATION)
+            .setIfUnset(ROW_KEY_ORDER_SALTED_TABLE_ATTRIB, DEFAULT_ROW_KEY_ORDER_SALTED_TABLE)
+            .setIfUnset(USE_INDEXES_ATTRIB, DEFAULT_USE_INDEXES)
+            .setIfUnset(IMMUTABLE_ROWS_ATTRIB, DEFAULT_IMMUTABLE_ROWS)
+            .setIfUnset(INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB, DEFAULT_INDEX_MUTATE_BATCH_SIZE_THRESHOLD)
+            .setIfUnset(MAX_SPOOL_TO_DISK_BYTES_ATTRIB, DEFAULT_MAX_SPOOL_TO_DISK_BYTES)
+            .setIfUnset(DROP_METADATA_ATTRIB, DEFAULT_DROP_METADATA)
+            .setIfUnset(GROUPBY_SPILLABLE_ATTRIB, DEFAULT_GROUPBY_SPILLABLE)
+            .setIfUnset(GROUPBY_MAX_CACHE_SIZE_ATTRIB, DEFAULT_GROUPBY_MAX_CACHE_MAX)
+            .setIfUnset(GROUPBY_SPILL_FILES_ATTRIB, DEFAULT_GROUPBY_SPILL_FILES)
+            .setIfUnset(SEQUENCE_CACHE_SIZE_ATTRIB, DEFAULT_SEQUENCE_CACHE_SIZE)
+            ;
+        // HBase sets this to 1, so we reset it to something more appropriate.
+        // Hopefully HBase will change this, because we can't know if a user set
+        // it to 1, so we'll change it.
+        int scanCaching = config.getInt(SCAN_CACHE_SIZE_ATTRIB, 0);
+        if (scanCaching == 1) {
+            config.setInt(SCAN_CACHE_SIZE_ATTRIB, DEFAULT_SCAN_CACHE_SIZE);
+        } else if (scanCaching <= 0) { // Provides the user with a way of setting it to 1
+            config.setInt(SCAN_CACHE_SIZE_ATTRIB, 1);
+        }
+        return options;
+    }
+    
+    public Configuration getConfiguration() {
+        return config;
+    }
+
+    private QueryServicesOptions setIfUnset(String name, int value) {
+        config.setIfUnset(name, Integer.toString(value));
+        return this;
+    }
+    
+    private QueryServicesOptions setIfUnset(String name, boolean value) {
+        config.setIfUnset(name, Boolean.toString(value));
+        return this;
+    }
+    
+    private QueryServicesOptions setIfUnset(String name, long value) {
+        config.setIfUnset(name, Long.toString(value));
+        return this;
+    }
+    
+    private QueryServicesOptions setIfUnset(String name, String value) {
+        config.setIfUnset(name, value);
+        return this;
+    }
+    
+    public QueryServicesOptions setKeepAliveMs(int keepAliveMs) {
+        return set(KEEP_ALIVE_MS_ATTRIB, keepAliveMs);
+    }
+    
+    public QueryServicesOptions setThreadPoolSize(int threadPoolSize) {
+        return set(THREAD_POOL_SIZE_ATTRIB, threadPoolSize);
+    }
+    
+    public QueryServicesOptions setQueueSize(int queueSize) {
+        config.setInt(QUEUE_SIZE_ATTRIB, queueSize);
+        return this;
+    }
+    
+    public QueryServicesOptions setThreadTimeoutMs(int threadTimeoutMs) {
+        return set(THREAD_TIMEOUT_MS_ATTRIB, threadTimeoutMs);
+    }
+    
+    public QueryServicesOptions setSpoolThresholdBytes(int spoolThresholdBytes) {
+        return set(SPOOL_THRESHOLD_BYTES_ATTRIB, spoolThresholdBytes);
+    }
+    
+    public QueryServicesOptions setMaxMemoryPerc(int maxMemoryPerc) {
+        return set(MAX_MEMORY_PERC_ATTRIB, maxMemoryPerc);
+    }
+    
+    public QueryServicesOptions setMaxMemoryWaitMs(int maxMemoryWaitMs) {
+        return set(MAX_MEMORY_WAIT_MS_ATTRIB, maxMemoryWaitMs);
+    }
+    
+    public QueryServicesOptions setMaxTenantMemoryPerc(int maxTenantMemoryPerc) {
+        return set(MAX_TENANT_MEMORY_PERC_ATTRIB, maxTenantMemoryPerc);
+    }
+    
+    public QueryServicesOptions setMaxServerCacheSize(long maxServerCacheSize) {
+        return set(MAX_SERVER_CACHE_SIZE_ATTRIB, maxServerCacheSize);
+    }
+
+    public QueryServicesOptions setScanFetchSize(int scanFetchSize) {
+        return set(SCAN_CACHE_SIZE_ATTRIB, scanFetchSize);
+    }
+    
+    public QueryServicesOptions setMaxQueryConcurrency(int maxQueryConcurrency) {
+        return set(MAX_QUERY_CONCURRENCY_ATTRIB, maxQueryConcurrency);
+    }
+
+    public QueryServicesOptions setTargetQueryConcurrency(int targetQueryConcurrency) {
+        return set(TARGET_QUERY_CONCURRENCY_ATTRIB, targetQueryConcurrency);
+    }
+    
+    public QueryServicesOptions setDateFormat(String dateFormat) {
+        return set(DATE_FORMAT_ATTRIB, dateFormat);
+    }
+    
+    public QueryServicesOptions setStatsUpdateFrequencyMs(int frequencyMs) {
+        return set(STATS_UPDATE_FREQ_MS_ATTRIB, frequencyMs);
+    }
+    
+    public QueryServicesOptions setCallQueueRoundRobin(boolean isRoundRobin) {
+        return set(CALL_QUEUE_PRODUCER_ATTRIB_NAME, isRoundRobin);
+    }
+    
+    public QueryServicesOptions setMaxMutateSize(int maxMutateSize) {
+        return set(MAX_MUTATION_SIZE_ATTRIB, maxMutateSize);
+    }
+    
+    public QueryServicesOptions setMutateBatchSize(int mutateBatchSize) {
+        return set(MUTATE_BATCH_SIZE_ATTRIB, mutateBatchSize);
+    }
+    
+    public QueryServicesOptions setMaxIntraRegionParallelization(int maxIntraRegionParallelization) {
+        return set(MAX_INTRA_REGION_PARALLELIZATION_ATTRIB, maxIntraRegionParallelization);
+    }
+    
+    public QueryServicesOptions setRowKeyOrderSaltedTable(boolean rowKeyOrderSaltedTable) {
+        return set(ROW_KEY_ORDER_SALTED_TABLE_ATTRIB, rowKeyOrderSaltedTable);
+    }
+    
+    public QueryServicesOptions setDropMetaData(boolean dropMetadata) {
+        return set(DROP_METADATA_ATTRIB, dropMetadata);
+    }
+    
+    public QueryServicesOptions setSPGBYEnabled(boolean enabled) {
+        return set(GROUPBY_SPILLABLE_ATTRIB, enabled);
+    }
+
+    public QueryServicesOptions setSPGBYMaxCacheSize(long size) {
+        return set(GROUPBY_MAX_CACHE_SIZE_ATTRIB, size);
+    }
+    
+    public QueryServicesOptions setSPGBYNumSpillFiles(long num) {
+        return set(GROUPBY_SPILL_FILES_ATTRIB, num);
+    }
+
+    
+    private QueryServicesOptions set(String name, boolean value) {
+        config.set(name, Boolean.toString(value));
+        return this;
+    }
+    
+    private QueryServicesOptions set(String name, int value) {
+        config.set(name, Integer.toString(value));
+        return this;
+    }
+    
+    private QueryServicesOptions set(String name, String value) {
+        config.set(name, value);
+        return this;
+    }
+    
+    private QueryServicesOptions set(String name, long value) {
+        config.set(name, Long.toString(value));
+        return this;
+    }
+
+    public int getKeepAliveMs() {
+        return config.getInt(KEEP_ALIVE_MS_ATTRIB, DEFAULT_KEEP_ALIVE_MS);
+    }
+    
+    public int getThreadPoolSize() {
+        return config.getInt(THREAD_POOL_SIZE_ATTRIB, DEFAULT_THREAD_POOL_SIZE);
+    }
+    
+    public int getQueueSize() {
+        return config.getInt(QUEUE_SIZE_ATTRIB, DEFAULT_QUEUE_SIZE);
+    }
+    
+    public int getMaxMemoryPerc() {
+        return config.getInt(MAX_MEMORY_PERC_ATTRIB, DEFAULT_MAX_MEMORY_PERC);
+    }
+    
+    public int getMaxMemoryWaitMs() {
+        return config.getInt(MAX_MEMORY_WAIT_MS_ATTRIB, DEFAULT_MAX_MEMORY_WAIT_MS);
+    }
+
+    public int getMaxMutateSize() {
+        return config.getInt(MAX_MUTATION_SIZE_ATTRIB, DEFAULT_MAX_MUTATION_SIZE);
+    }
+
+    public int getMutateBatchSize() {
+        return config.getInt(MUTATE_BATCH_SIZE_ATTRIB, DEFAULT_MUTATE_BATCH_SIZE);
+    }
+    
+    public int getMaxIntraRegionParallelization() {
+        return config.getInt(MAX_INTRA_REGION_PARALLELIZATION_ATTRIB, DEFAULT_MAX_INTRA_REGION_PARALLELIZATION);
+    }
+    
+    public boolean isUseIndexes() {
+        return config.getBoolean(USE_INDEXES_ATTRIB, DEFAULT_USE_INDEXES);
+    }
+
+    public boolean isImmutableRows() {
+        return config.getBoolean(IMMUTABLE_ROWS_ATTRIB, DEFAULT_IMMUTABLE_ROWS);
+    }
+    
+    public boolean isDropMetaData() {
+        return config.getBoolean(DROP_METADATA_ATTRIB, DEFAULT_DROP_METADATA);
+    }
+    
+    public boolean isSpillableGroupByEnabled() {
+        return config.getBoolean(GROUPBY_SPILLABLE_ATTRIB, DEFAULT_GROUPBY_SPILLABLE);
+    }
+    
+    public long getSpillableGroupByMaxCacheSize() {
+        return config.getLong(GROUPBY_MAX_CACHE_SIZE_ATTRIB, DEFAULT_GROUPBY_MAX_CACHE_MAX);
+    }
+    
+    public int getSpillableGroupByNumSpillFiles() {
+        return config.getInt(GROUPBY_SPILL_FILES_ATTRIB, DEFAULT_GROUPBY_SPILL_FILES);
+    }
+
+    public QueryServicesOptions setMaxServerCacheTTLMs(int ttl) {
+        return set(MAX_SERVER_CACHE_TIME_TO_LIVE_MS, ttl);
+    }
+    
+    public QueryServicesOptions setMasterInfoPort(int port) {
+        return set(MASTER_INFO_PORT_ATTRIB, port);
+    }
+    
+    public QueryServicesOptions setRegionServerInfoPort(int port) {
+        return set(REGIONSERVER_INFO_PORT_ATTRIB, port);
+    }
+    
+    public QueryServicesOptions setRegionServerLeasePeriodMs(int period) {
+        return set(REGIONSERVER_LEASE_PERIOD_ATTRIB, period);
+    }
+    
+    public QueryServicesOptions setRpcTimeoutMs(int timeout) {
+        return set(RPC_TIMEOUT_ATTRIB, timeout);
+    }
+    
+    public QueryServicesOptions setUseIndexes(boolean useIndexes) {
+        return set(USE_INDEXES_ATTRIB, useIndexes);
+    }
+    
+    public QueryServicesOptions setImmutableRows(boolean isImmutableRows) {
+        return set(IMMUTABLE_ROWS_ATTRIB, isImmutableRows);
+    }
+
+    public QueryServicesOptions setWALEditCodec(String walEditCodec) {
+        return set(WALEditCodec.WAL_EDIT_CODEC_CLASS_KEY, walEditCodec);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/query/StatsManager.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/StatsManager.java b/phoenix-core/src/main/java/org/apache/phoenix/query/StatsManager.java
new file mode 100644
index 0000000..7d02e02
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/StatsManager.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * 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.query;
+
+import java.sql.SQLException;
+
+import org.apache.phoenix.schema.TableRef;
+
+
+/**
+ * 
+ * Interface for managing and caching table statistics.
+ * The frequency of updating the table statistics are controlled
+ * by {@link org.apache.phoenix.query.QueryServices#STATS_UPDATE_FREQ_MS_ATTRIB}.
+ * Table stats may also be manually updated through {@link #updateStats(TableRef)}.
+ * 
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public interface StatsManager {
+    /**
+     * Get the minimum key for the given table
+     * @param table the table
+     * @return the minimum key or null if unknown
+     */
+    byte[] getMinKey(TableRef table);
+    
+    /**
+     * Get the maximum key for the given table
+     * @param table the table
+     * @return the maximum key or null if unknown
+     */
+    byte[] getMaxKey(TableRef table);
+    
+    /**
+     * Manually update the cached table statistics
+     * @param table the table
+     * @throws SQLException
+     */
+    void updateStats(TableRef table) throws SQLException;
+}