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:22 UTC

[06/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/util/DateUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java
new file mode 100644
index 0000000..b7460f8
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java
@@ -0,0 +1,154 @@
+/*
+ * 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.util;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+import org.apache.commons.lang.time.FastDateFormat;
+
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.IllegalDataException;
+
+
+
+@SuppressWarnings("serial")
+public class DateUtil {
+    public static final TimeZone DATE_TIME_ZONE = TimeZone.getTimeZone("GMT");
+    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; // This is the format the app sets in NLS settings for every connection.
+    public static final Format DEFAULT_DATE_FORMATTER = FastDateFormat.getInstance(DEFAULT_DATE_FORMAT, DATE_TIME_ZONE);
+
+    public static final String DEFAULT_MS_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
+    public static final Format DEFAULT_MS_DATE_FORMATTER = FastDateFormat.getInstance(DEFAULT_MS_DATE_FORMAT, DATE_TIME_ZONE);
+
+    private DateUtil() {
+    }
+
+    public static Format getDateParser(String pattern) {
+        SimpleDateFormat format = new SimpleDateFormat(pattern) {
+            @Override
+            public java.util.Date parseObject(String source) throws ParseException {
+                java.util.Date date = super.parse(source);
+                return new java.sql.Date(date.getTime());
+            }
+        };
+        format.setTimeZone(DateUtil.DATE_TIME_ZONE);
+        return format;
+    }
+
+    public static Format getTimeParser(String pattern) {
+        SimpleDateFormat format = new SimpleDateFormat(pattern) {
+            @Override
+            public java.util.Date parseObject(String source) throws ParseException {
+                java.util.Date date = super.parse(source);
+                return new java.sql.Time(date.getTime());
+            }
+        };
+        format.setTimeZone(DateUtil.DATE_TIME_ZONE);
+        return format;
+    }
+
+    public static Format getTimestampParser(String pattern) {
+        SimpleDateFormat format = new SimpleDateFormat(pattern) {
+            @Override
+            public java.util.Date parseObject(String source) throws ParseException {
+                java.util.Date date = super.parse(source);
+                return new java.sql.Timestamp(date.getTime());
+            }
+        };
+        format.setTimeZone(DateUtil.DATE_TIME_ZONE);
+        return format;
+    }
+
+    public static Format getDateFormatter(String pattern) {
+        return DateUtil.DEFAULT_DATE_FORMAT.equals(pattern) ? DateUtil.DEFAULT_DATE_FORMATTER : FastDateFormat.getInstance(pattern, DateUtil.DATE_TIME_ZONE);
+    }
+
+    private static ThreadLocal<Format> dateFormat =
+            new ThreadLocal < Format > () {
+        @Override protected Format initialValue() {
+            return getDateParser(DEFAULT_DATE_FORMAT);
+        }
+    };
+
+    public static Date parseDate(String dateValue) {
+        try {
+            return (Date)dateFormat.get().parseObject(dateValue);
+        } catch (ParseException e) {
+            throw new IllegalDataException(e);
+        }
+    }
+
+    private static ThreadLocal<Format> timeFormat =
+            new ThreadLocal < Format > () {
+        @Override protected Format initialValue() {
+            return getTimeParser(DEFAULT_DATE_FORMAT);
+        }
+    };
+
+    public static Time parseTime(String timeValue) {
+        try {
+            return (Time)timeFormat.get().parseObject(timeValue);
+        } catch (ParseException e) {
+            throw new IllegalDataException(e);
+        }
+    }
+
+    private static ThreadLocal<Format> timestampFormat =
+            new ThreadLocal < Format > () {
+        @Override protected Format initialValue() {
+            return getTimestampParser(DEFAULT_DATE_FORMAT);
+        }
+    };
+
+    public static Timestamp parseTimestamp(String timeValue) {
+        try {
+            return (Timestamp)timestampFormat.get().parseObject(timeValue);
+        } catch (ParseException e) {
+            throw new IllegalDataException(e);
+        }
+    }
+
+    /**
+     * Utility function to work around the weirdness of the {@link Timestamp} constructor.
+     * This method takes the milli-seconds that spills over to the nanos part as part of 
+     * constructing the {@link Timestamp} object.
+     * If we just set the nanos part of timestamp to the nanos passed in param, we 
+     * end up losing the sub-second part of timestamp. 
+     */
+    public static Timestamp getTimestamp(long millis, int nanos) {
+        Timestamp ts = new Timestamp(millis);
+        ts.setNanos(ts.getNanos() + nanos);
+        return ts;
+    }
+
+    /**
+     * Utility function to convert a {@link BigDecimal} value to {@link Timestamp}.
+     */
+    public static Timestamp getTimestamp(BigDecimal bd) {
+        return DateUtil.getTimestamp(bd.longValue(), ((bd.remainder(BigDecimal.ONE).multiply(BigDecimal.valueOf(QueryConstants.MILLIS_TO_NANOS_CONVERTOR))).intValue()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
new file mode 100644
index 0000000..a7c975c
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
@@ -0,0 +1,204 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+
+import com.google.common.collect.Lists;
+import org.apache.hadoop.hbase.index.ValueGetter;
+import org.apache.hadoop.hbase.index.covered.update.ColumnReference;
+import org.apache.hadoop.hbase.index.util.ImmutableBytesPtr;
+import org.apache.phoenix.client.KeyValueBuilder;
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.exception.SQLExceptionInfo;
+import org.apache.phoenix.index.IndexMaintainer;
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
+import org.apache.phoenix.schema.ColumnNotFoundException;
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PColumnFamily;
+import org.apache.phoenix.schema.PDataType;
+import org.apache.phoenix.schema.PTable;
+
+public class IndexUtil {
+    public static final String INDEX_COLUMN_NAME_SEP = ":";
+    public static final byte[] INDEX_COLUMN_NAME_SEP_BYTES = Bytes.toBytes(INDEX_COLUMN_NAME_SEP);
+
+    private IndexUtil() {
+    }
+
+    // Since we cannot have nullable fixed length in a row key
+    // we need to translate to variable length.
+    public static PDataType getIndexColumnDataType(PColumn dataColumn) throws SQLException {
+        PDataType type = getIndexColumnDataType(dataColumn.isNullable(),dataColumn.getDataType());
+        if (type == null) {
+            throw new SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_INDEX_COLUMN_ON_TYPE).setColumnName(dataColumn.getName().getString())
+            .setMessage("Type="+dataColumn.getDataType()).build().buildException();
+        }
+        return type;
+    }
+    
+    // Since we cannot have nullable fixed length in a row key
+    // we need to translate to variable length. The verification that we have a valid index
+    // row key was already done, so here we just need to covert from one built-in type to
+    // another.
+    public static PDataType getIndexColumnDataType(boolean isNullable, PDataType dataType) {
+        if (dataType == null || !isNullable || !dataType.isFixedWidth() || dataType == PDataType.BINARY) {
+            return dataType;
+        }
+        // for INT, BIGINT
+        if (dataType.isCoercibleTo(PDataType.TIMESTAMP) || dataType.isCoercibleTo(PDataType.DECIMAL)) {
+            return PDataType.DECIMAL;
+        }
+        // for CHAR
+        if (dataType.isCoercibleTo(PDataType.VARCHAR)) {
+            return PDataType.VARCHAR;
+        }
+        throw new IllegalArgumentException("Unsupported non nullable index type " + dataType);
+    }
+    
+
+    public static String getDataColumnName(String name) {
+        return name.substring(name.indexOf(INDEX_COLUMN_NAME_SEP) + 1);
+    }
+
+    public static String getDataColumnFamilyName(String name) {
+        return name.substring(0,name.indexOf(INDEX_COLUMN_NAME_SEP));
+    }
+
+    public static String getDataColumnFullName(String name) {
+        int index = name.indexOf(INDEX_COLUMN_NAME_SEP) ;
+        if (index == 0) {
+            return name.substring(index+1);
+        }
+        return SchemaUtil.getColumnDisplayName(name.substring(0, index), name.substring(index+1));
+    }
+
+    public static String getIndexColumnName(String dataColumnFamilyName, String dataColumnName) {
+        return (dataColumnFamilyName == null ? "" : dataColumnFamilyName) + INDEX_COLUMN_NAME_SEP + dataColumnName;
+    }
+    
+    public static byte[] getIndexColumnName(byte[] dataColumnFamilyName, byte[] dataColumnName) {
+        return ByteUtil.concat(dataColumnFamilyName == null ?  ByteUtil.EMPTY_BYTE_ARRAY : dataColumnFamilyName, INDEX_COLUMN_NAME_SEP_BYTES, dataColumnName);
+    }
+    
+    public static String getIndexColumnName(PColumn dataColumn) {
+        String dataColumnFamilyName = SchemaUtil.isPKColumn(dataColumn) ? null : dataColumn.getFamilyName().getString();
+        return getIndexColumnName(dataColumnFamilyName, dataColumn.getName().getString());
+    }
+
+    public static PColumn getDataColumn(PTable dataTable, String indexColumnName) {
+        int pos = indexColumnName.indexOf(INDEX_COLUMN_NAME_SEP);
+        if (pos < 0) {
+            throw new IllegalArgumentException("Could not find expected '" + INDEX_COLUMN_NAME_SEP +  "' separator in index column name of \"" + indexColumnName + "\"");
+        }
+        if (pos == 0) {
+            try {
+                return dataTable.getPKColumn(indexColumnName.substring(1));
+            } catch (ColumnNotFoundException e) {
+                throw new IllegalArgumentException("Could not find PK column \"" +  indexColumnName.substring(pos+1) + "\" in index column name of \"" + indexColumnName + "\"", e);
+            }
+        }
+        PColumnFamily family;
+        try {
+            family = dataTable.getColumnFamily(indexColumnName.substring(0, pos));
+        } catch (ColumnFamilyNotFoundException e) {
+            throw new IllegalArgumentException("Could not find column family \"" +  indexColumnName.substring(0, pos) + "\" in index column name of \"" + indexColumnName + "\"", e);
+        }
+        try {
+            return family.getColumn(indexColumnName.substring(pos+1));
+        } catch (ColumnNotFoundException e) {
+            throw new IllegalArgumentException("Could not find column \"" +  indexColumnName.substring(pos+1) + "\" in index column name of \"" + indexColumnName + "\"", e);
+        }
+    }
+
+    private static boolean isEmptyKeyValue(PTable table, ColumnReference ref) {
+        byte[] emptyKeyValueCF = SchemaUtil.getEmptyColumnFamily(table.getColumnFamilies());
+        return (Bytes.compareTo(emptyKeyValueCF, ref.getFamily()) == 0 &&
+                Bytes.compareTo(QueryConstants.EMPTY_COLUMN_BYTES, ref.getQualifier()) == 0);
+    }
+
+    public static List<Mutation> generateIndexData(final PTable table, PTable index,
+            List<Mutation> dataMutations, ImmutableBytesWritable ptr, KeyValueBuilder builder)
+            throws SQLException {
+        try {
+            IndexMaintainer maintainer = index.getIndexMaintainer(table);
+            maintainer.setKvBuilder(builder);
+            List<Mutation> indexMutations = Lists.newArrayListWithExpectedSize(dataMutations.size());
+           for (final Mutation dataMutation : dataMutations) {
+                long ts = MetaDataUtil.getClientTimeStamp(dataMutation);
+                ptr.set(dataMutation.getRow());
+                if (dataMutation instanceof Put) {
+                    // TODO: is this more efficient than looking in our mutation map
+                    // using the key plus finding the PColumn?
+                    ValueGetter valueGetter = new ValueGetter() {
+        
+                        @Override
+                        public ImmutableBytesPtr getLatestValue(ColumnReference ref) {
+                            // Always return null for our empty key value, as this will cause the index
+                            // maintainer to always treat this Put as a new row.
+                            if (isEmptyKeyValue(table, ref)) {
+                                return null;
+                            }
+                            Map<byte [], List<KeyValue>> familyMap = dataMutation.getFamilyMap();
+                            byte[] family = ref.getFamily();
+                            List<KeyValue> kvs = familyMap.get(family);
+                            if (kvs == null) {
+                                return null;
+                            }
+                            byte[] qualifier = ref.getQualifier();
+                            for (KeyValue kv : kvs) {
+                                if (Bytes.compareTo(kv.getBuffer(), kv.getFamilyOffset(), kv.getFamilyLength(), family, 0, family.length) == 0 &&
+                                    Bytes.compareTo(kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength(), qualifier, 0, qualifier.length) == 0) {
+                                    return new ImmutableBytesPtr(kv.getBuffer(), kv.getValueOffset(), kv.getValueLength());
+                                }
+                            }
+                            return null;
+                        }
+                        
+                    };
+                    indexMutations.add(maintainer.buildUpdateMutation(valueGetter, ptr, ts));
+                } else {
+                    if (!maintainer.getIndexedColumns().isEmpty()) {
+                        throw new SQLExceptionInfo.Builder(SQLExceptionCode.NO_DELETE_IF_IMMUTABLE_INDEX).setSchemaName(table.getSchemaName().getString())
+                        .setTableName(table.getTableName().getString()).build().buildException();
+                    }
+                    indexMutations.add(maintainer.buildDeleteMutation(ptr, ts));
+                }
+            }
+            return indexMutations;
+        } catch (IOException e) {
+            throw new SQLException(e);
+        }
+    }
+
+    public static boolean isDataPKColumn(PColumn column) {
+        return column.getName().getString().startsWith(INDEX_COLUMN_NAME_SEP);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/InstanceResolver.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/InstanceResolver.java b/phoenix-core/src/main/java/org/apache/phoenix/util/InstanceResolver.java
new file mode 100644
index 0000000..85cf54d
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/InstanceResolver.java
@@ -0,0 +1,66 @@
+/*
+ * 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.util;
+
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Resolves object instances registered using the JDK 6+ {@link java.util.ServiceLoader}.
+ *
+ * @author aaraujo
+ * @since 2.0
+ */
+public class InstanceResolver {
+    private static final ConcurrentHashMap<Class, Object> RESOLVED_SINGLETONS = new ConcurrentHashMap<Class, Object>();
+
+    private InstanceResolver() {/* not allowed */}
+
+    /**
+     * Resolves an instance of the specified class if it has not already been resolved.
+     * @param clazz The type of instance to resolve
+     * @param defaultInstance The instance to use if a custom instance has not been registered
+     * @return The resolved instance or the default instance provided.
+     *         {@code null} if an instance is not registered and a default is not provided.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getSingleton(Class<T> clazz, T defaultInstance) {
+        Object obj = RESOLVED_SINGLETONS.get(clazz);
+        if(obj != null) {
+            return (T)obj;
+        }
+        if (defaultInstance != null && !clazz.isInstance(defaultInstance)) throw new IllegalArgumentException("defaultInstance is not of type " + clazz.getName());
+        final Object o = resolveSingleton(clazz, defaultInstance);
+        obj = RESOLVED_SINGLETONS.putIfAbsent(clazz, o);
+        if(obj == null) {
+            obj = o;
+        }
+        return (T)obj;
+    }
+    
+    private synchronized static <T> T resolveSingleton(Class<T> clazz, T defaultInstance) {
+        ServiceLoader<T> loader = ServiceLoader.load(clazz);
+        // returns the first registered instance found
+        for (T singleton : loader) {
+            return singleton;
+        }
+        return defaultInstance;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/JDBCUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/JDBCUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/JDBCUtil.java
new file mode 100644
index 0000000..eb973cc
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/JDBCUtil.java
@@ -0,0 +1,83 @@
+/*
+ * 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.util;
+
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.annotation.Nullable;
+
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.query.QueryServicesOptions;
+import org.apache.phoenix.schema.PName;
+import org.apache.phoenix.schema.PNameFactory;
+
+
+
+/**
+ * Utilities for JDBC
+ *
+ * @author jtaylor
+ * @since 178
+ */
+public class JDBCUtil {
+    
+    private JDBCUtil() {
+    }
+
+    /**
+     * Find the propName by first looking in the url string and if not found,
+     * next in the info properties. If not found, null is returned.
+     * @param url JDBC connection URL
+     * @param info JDBC connection properties
+     * @param propName the name of the property to find
+     * @return the property value or null if not found
+     */
+    public static String findProperty(String url, Properties info, String propName) {
+        String urlPropName = ";" + propName + "=";
+        String propValue = info.getProperty(propName);
+        if (propValue == null) {
+            int begIndex = url.indexOf(urlPropName);
+            if (begIndex >= 0) {
+                int endIndex = url.indexOf(';',begIndex + urlPropName.length());
+                if (endIndex < 0) {
+                    endIndex = url.length();
+                }
+                propValue = url.substring(begIndex + urlPropName.length(), endIndex);
+            }
+        }
+        return propValue;
+    }
+
+    public static Long getCurrentSCN(String url, Properties info) throws SQLException {
+        String scnStr = findProperty(url, info, PhoenixRuntime.CURRENT_SCN_ATTRIB);
+        return (scnStr == null ? null : Long.parseLong(scnStr));
+    }
+
+    public static int getMutateBatchSize(String url, Properties info, ReadOnlyProps props) throws SQLException {
+        String batchSizeStr = findProperty(url, info, PhoenixRuntime.UPSERT_BATCH_SIZE_ATTRIB);
+        return (batchSizeStr == null ? props.getInt(QueryServices.MUTATE_BATCH_SIZE_ATTRIB, QueryServicesOptions.DEFAULT_MUTATE_BATCH_SIZE) : Integer.parseInt(batchSizeStr));
+    }
+
+    public static @Nullable PName getTenantId(String url, Properties info) throws SQLException {
+        String tenantId = findProperty(url, info, PhoenixRuntime.TENANT_ID_ATTRIB);
+        return (tenantId == null ? null : PNameFactory.newName(tenantId));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/KeyValueUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/KeyValueUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/KeyValueUtil.java
new file mode 100644
index 0000000..657845d
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/KeyValueUtil.java
@@ -0,0 +1,170 @@
+/*
+ * 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.util;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.KeyValue.Type;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * 
+ * Utilities for KeyValue. Where there's duplication with KeyValue methods,
+ * these avoid creating new objects when not necessary (primary preventing
+ * byte array copying).
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public class KeyValueUtil {
+    private KeyValueUtil() {
+    }
+
+    public static KeyValue newKeyValue(Result r, byte[] cf, byte[] cq, long ts, byte[] value, int valueOffset, int valueLength) {
+        byte[] bytes = ResultUtil.getRawBytes(r);
+        return new KeyValue(bytes, ResultUtil.getKeyOffset(r), ResultUtil.getKeyLength(r),
+                cf, 0, cf.length,
+                cq, 0, cq.length,
+                ts, Type.Put,
+                value, valueOffset, valueLength);
+    }
+
+    public static KeyValue newKeyValue(byte[] key, byte[] cf, byte[] cq, long ts, byte[] value, int valueOffset, int valueLength) {
+        return new KeyValue(key, 0, key.length,
+                cf, 0, cf.length,
+                cq, 0, cq.length,
+                ts, Type.Put,
+                value, valueOffset, valueLength);
+    }
+
+    public static KeyValue newKeyValue(ImmutableBytesWritable key, byte[] cf, byte[] cq, long ts, byte[] value, int valueOffset, int valueLength) {
+        return new KeyValue(key.get(), key.getOffset(), key.getLength(),
+                cf, 0, cf.length,
+                cq, 0, cq.length,
+                ts, Type.Put,
+                value, valueOffset, valueLength);
+    }
+
+    public static KeyValue newKeyValue(byte[] key, int keyOffset, int keyLength, byte[] cf, byte[] cq, long ts, byte[] value, int valueOffset, int valueLength) {
+        return new KeyValue(key, keyOffset, keyLength,
+                cf, 0, cf.length,
+                cq, 0, cq.length,
+                ts, Type.Put,
+                value, valueOffset, valueLength);
+    }
+
+    public static KeyValue newKeyValue(byte[] key, byte[] cf, byte[] cq, long ts, byte[] value) {
+        return newKeyValue(key,cf,cq,ts,value,0,value.length);
+    }
+
+    public static KeyValue newKeyValue(Result r, byte[] cf, byte[] cq, long ts, byte[] value) {
+        return newKeyValue(r,cf,cq,ts,value,0,value.length);
+    }
+
+    /**
+     * Binary search for latest column value without allocating memory in the process
+     * @param kvs
+     * @param family
+     * @param qualifier
+     */
+    public static KeyValue getColumnLatest(List<KeyValue>kvs, byte[] family, byte[] qualifier) {
+        if (kvs.size() == 0) {
+        	return null;
+        }
+        KeyValue row = kvs.get(0);
+        Comparator<KeyValue> comp = new SearchComparator(row.getBuffer(), row.getRowOffset(), row.getRowLength(), family, qualifier);
+        // pos === ( -(insertion point) - 1)
+        int pos = Collections.binarySearch(kvs, null, comp);
+        // never will exact match
+        if (pos < 0) {
+          pos = (pos+1) * -1;
+          // pos is now insertion point
+        }
+        if (pos == kvs.size()) {
+          return null; // doesn't exist
+        }
+    
+        KeyValue kv = kvs.get(pos);
+        if (Bytes.compareTo(kv.getBuffer(), kv.getFamilyOffset(), kv.getFamilyLength(),
+                family, 0, family.length) != 0) {
+            return null;
+        }
+        if (Bytes.compareTo(kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength(),
+                qualifier, 0, qualifier.length) != 0) {
+            return null;
+        }
+        return kv;
+    }
+
+    /*
+     * Special comparator, *only* works for binary search.
+     * Current JDKs only uses the search term on the right side,
+     * Making use of that saves instanceof checks, and allows us
+     * to inline the search term in the comparator
+     */
+	private static class SearchComparator implements Comparator<KeyValue> {
+		private final byte[] row;
+		private final byte[] family;
+		private final byte[] qualifier;
+		private final int rowOff;
+		private final int rowLen;
+
+		public SearchComparator(byte[] r, int rOff, int rLen, byte[] f, byte[] q) {
+			row = r;
+			family = f;
+			qualifier = q;
+			rowOff = rOff;
+			rowLen = rLen;
+		}
+
+		@Override
+        public int compare(final KeyValue l, final KeyValue ignored) {
+			assert ignored == null;
+			final byte[] buf = l.getBuffer();
+			final int rOff = l.getRowOffset();
+			final short rLen = l.getRowLength();
+			// row
+			int val = Bytes.compareTo(buf, rOff, rLen, row, rowOff, rowLen);
+			if (val != 0) {
+				return val;
+			}
+			// family
+			final int fOff = l.getFamilyOffset(rLen);
+			final byte fLen = l.getFamilyLength(fOff);
+			val = Bytes.compareTo(buf, fOff, fLen, family, 0, family.length);
+			if (val != 0) {
+				return val;
+			}
+			// qualifier
+			val = Bytes.compareTo(buf, l.getQualifierOffset(fOff),
+					l.getQualifierLength(rLen, fLen), qualifier, 0, qualifier.length);
+			if (val != 0) {
+				return val;
+			}
+			// want latest TS and type, so we get the first
+			return 1;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
new file mode 100644
index 0000000..0d8a60a
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
@@ -0,0 +1,233 @@
+/*
+ * 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.util;
+
+import static org.apache.phoenix.util.SchemaUtil.getVarChars;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.coprocessor.MetaDataProtocol;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.PDataType;
+import org.apache.phoenix.schema.PTableType;
+
+
+public class MetaDataUtil {
+
+    public static boolean areClientAndServerCompatible(long version) {
+        // A server and client with the same major and minor version number must be compatible.
+        // So it's important that we roll the PHOENIX_MAJOR_VERSION or PHOENIX_MINOR_VERSION
+        // when we make an incompatible change.
+        return areClientAndServerCompatible(MetaDataUtil.decodePhoenixVersion(version), MetaDataProtocol.PHOENIX_MAJOR_VERSION, MetaDataProtocol.PHOENIX_MINOR_VERSION);
+    }
+
+    // For testing
+    static boolean areClientAndServerCompatible(int version, int pMajor, int pMinor) {
+        // A server and client with the same major and minor version number must be compatible.
+        // So it's important that we roll the PHOENIX_MAJOR_VERSION or PHOENIX_MINOR_VERSION
+        // when we make an incompatible change.
+        return MetaDataUtil.encodeMaxPatchVersion(pMajor, pMinor) >= version && MetaDataUtil.encodeMinPatchVersion(pMajor, pMinor) <= version;
+    }
+
+    // Given the encoded integer representing the phoenix version in the encoded version value.
+    // The second byte in int would be the major version, 3rd byte minor version, and 4th byte 
+    // patch version.
+    public static int decodePhoenixVersion(long version) {
+        return (int) ((version << Byte.SIZE * 3) >>> Byte.SIZE * 4);
+    }
+    
+    // TODO: generalize this to use two bytes to return a SQL error code instead
+    public static long encodeMutableIndexConfiguredProperly(long version, boolean isValid) {
+        if (!isValid) {
+            return version | 1;
+        }
+        return version;
+    }
+    
+    public static boolean decodeMutableIndexConfiguredProperly(long version) {
+        return (version & 0xF) == 0;
+    }
+
+    // Given the encoded integer representing the client hbase version in the encoded version value.
+    // The second byte in int would be the major version, 3rd byte minor version, and 4th byte 
+    // patch version.
+    public static int decodeHBaseVersion(long version) {
+        return (int) (version >>> Byte.SIZE * 5);
+    }
+
+    public static String decodeHBaseVersionAsString(int version) {
+        int major = (version >>> Byte.SIZE  * 2) & 0xFF;
+        int minor = (version >>> Byte.SIZE  * 1) & 0xFF;
+        int patch = version & 0xFF;
+        return major + "." + minor + "." + patch;
+    }
+
+    public static long encodeHBaseAndPhoenixVersions(String hbaseVersion) {
+        return (((long) encodeVersion(hbaseVersion)) << (Byte.SIZE * 5)) |
+                (((long) encodeVersion(MetaDataProtocol.PHOENIX_MAJOR_VERSION, MetaDataProtocol.PHOENIX_MINOR_VERSION,
+                        MetaDataProtocol.PHOENIX_PATCH_NUMBER)) << (Byte.SIZE * 1));
+    }
+
+    // Encode a version string in the format of "major.minor.patch" into an integer.
+    public static int encodeVersion(String version) {
+        String[] versionParts = splitHBaseVersionString(version);
+        return encodeVersion(versionParts[0], versionParts.length > 1 ? versionParts[1] : null, versionParts.length > 2 ? versionParts[2] : null);
+    }
+
+    public static String[] splitHBaseVersionString(String version) {
+        return version.split("[-\\.]");
+    }
+
+    // Encode the major as 2nd byte in the int, minor as the first byte and patch as the last byte.
+    public static int encodeVersion(String major, String minor, String patch) {
+        return encodeVersion(major == null ? 0 : Integer.parseInt(major), minor == null ? 0 : Integer.parseInt(minor), 
+                        patch == null ? 0 : Integer.parseInt(patch));
+    }
+
+    public static int encodeVersion(int major, int minor, int patch) {
+        int version = 0;
+        version |= (major << Byte.SIZE * 2);
+        version |= (minor << Byte.SIZE);
+        version |= patch;
+        return version;
+    }
+
+    public static int encodeMaxPatchVersion(int major, int minor) {
+        int version = 0;
+        version |= (major << Byte.SIZE * 2);
+        version |= (minor << Byte.SIZE);
+        version |= 0xFF;
+        return version;
+    }
+
+    public static int encodeMinPatchVersion(int major, int minor) {
+        int version = 0;
+        version |= (major << Byte.SIZE * 2);
+        version |= (minor << Byte.SIZE);
+        return version;
+    }
+
+    public static void getTenantIdAndSchemaAndTableName(List<Mutation> tableMetadata, byte[][] rowKeyMetaData) {
+        Mutation m = getTableHeaderRow(tableMetadata);
+        getVarChars(m.getRow(), 3, rowKeyMetaData);
+    }
+    
+    public static byte[] getParentTableName(List<Mutation> tableMetadata) {
+        if (tableMetadata.size() == 1) {
+            return null;
+        }
+        byte[][] rowKeyMetaData = new byte[3][];
+        getTenantIdAndSchemaAndTableName(tableMetadata, rowKeyMetaData);
+        byte[] tableName = rowKeyMetaData[PhoenixDatabaseMetaData.TABLE_NAME_INDEX];
+        Mutation m = getParentTableHeaderRow(tableMetadata);
+        getVarChars(m.getRow(), 3, rowKeyMetaData);
+        if (Bytes.compareTo(tableName, rowKeyMetaData[PhoenixDatabaseMetaData.TABLE_NAME_INDEX]) == 0) {
+            return null;
+        }
+        return rowKeyMetaData[PhoenixDatabaseMetaData.TABLE_NAME_INDEX];
+    }
+    
+    public static long getSequenceNumber(Mutation tableMutation) {
+        List<KeyValue> kvs = tableMutation.getFamilyMap().get(PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES);
+        if (kvs != null) {
+            for (KeyValue kv : kvs) { // list is not ordered, so search. TODO: we could potentially assume the position
+                if (Bytes.compareTo(kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength(), PhoenixDatabaseMetaData.TABLE_SEQ_NUM_BYTES, 0, PhoenixDatabaseMetaData.TABLE_SEQ_NUM_BYTES.length) == 0) {
+                    return PDataType.LONG.getCodec().decodeLong(kv.getBuffer(), kv.getValueOffset(), null);
+                }
+            }
+        }
+        throw new IllegalStateException();
+    }
+
+    public static long getSequenceNumber(List<Mutation> tableMetaData) {
+        return getSequenceNumber(getTableHeaderRow(tableMetaData));
+    }
+    
+    public static PTableType getTableType(List<Mutation> tableMetaData) {
+        KeyValue kv = getMutationKeyValue(getPutOnlyTableHeaderRow(tableMetaData), PhoenixDatabaseMetaData.TABLE_TYPE_BYTES);
+        return kv == null ? null : PTableType.fromSerializedValue(kv.getBuffer()[kv.getValueOffset()]);
+    }
+    
+    public static long getParentSequenceNumber(List<Mutation> tableMetaData) {
+        return getSequenceNumber(getParentTableHeaderRow(tableMetaData));
+    }
+    
+    public static Mutation getTableHeaderRow(List<Mutation> tableMetaData) {
+        return tableMetaData.get(0);
+    }
+
+    public static byte[] getMutationKVByteValue(Mutation headerRow, byte[] key) {
+        KeyValue kv = getMutationKeyValue(headerRow, key);
+        // FIXME: byte copy
+        return kv == null ? ByteUtil.EMPTY_BYTE_ARRAY : kv.getValue();
+    }
+
+    private static KeyValue getMutationKeyValue(Mutation headerRow, byte[] key) {
+        List<KeyValue> kvs = headerRow.getFamilyMap().get(PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES);
+        if (kvs != null) {
+            for (KeyValue kv : kvs) {
+                if (Bytes.compareTo(kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength(), key, 0,
+                        key.length) == 0) { return kv; }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the first Put element in <code>tableMetaData</code>. There could be leading Delete elements before the
+     * table header row
+     */
+    public static Mutation getPutOnlyTableHeaderRow(List<Mutation> tableMetaData) {
+        for (Mutation m : tableMetaData) {
+            if (m instanceof Put) { return m; }
+        }
+        throw new IllegalStateException("No table header row found in table meatadata");
+    }
+
+    public static Mutation getParentTableHeaderRow(List<Mutation> tableMetaData) {
+        return tableMetaData.get(tableMetaData.size()-1);
+    }
+
+    public static long getClientTimeStamp(List<Mutation> tableMetadata) {
+        Mutation m = tableMetadata.get(0);
+        return getClientTimeStamp(m);
+    }    
+
+    public static long getClientTimeStamp(Mutation m) {
+        Collection<List<KeyValue>> kvs = m.getFamilyMap().values();
+        // Empty if Mutation is a Delete
+        // TODO: confirm that Delete timestamp is reset like Put
+        return kvs.isEmpty() ? m.getTimeStamp() : kvs.iterator().next().get(0).getTimestamp();
+    }    
+
+    public static byte[] getParentLinkKey(String tenantId, String schemaName, String tableName, String indexName) {
+        return ByteUtil.concat(tenantId == null ? ByteUtil.EMPTY_BYTE_ARRAY : Bytes.toBytes(tenantId), QueryConstants.SEPARATOR_BYTE_ARRAY, schemaName == null ? ByteUtil.EMPTY_BYTE_ARRAY : Bytes.toBytes(schemaName), QueryConstants.SEPARATOR_BYTE_ARRAY, Bytes.toBytes(tableName), QueryConstants.SEPARATOR_BYTE_ARRAY, QueryConstants.SEPARATOR_BYTE_ARRAY, Bytes.toBytes(indexName));
+    }
+
+    public static byte[] getParentLinkKey(byte[] tenantId, byte[] schemaName, byte[] tableName, byte[] indexName) {
+        return ByteUtil.concat(tenantId == null ? ByteUtil.EMPTY_BYTE_ARRAY : tenantId, QueryConstants.SEPARATOR_BYTE_ARRAY, schemaName == null ? ByteUtil.EMPTY_BYTE_ARRAY : schemaName, QueryConstants.SEPARATOR_BYTE_ARRAY, tableName, QueryConstants.SEPARATOR_BYTE_ARRAY, QueryConstants.SEPARATOR_BYTE_ARRAY, indexName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
new file mode 100644
index 0000000..ece1104
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.util;
+
+import java.math.BigDecimal;
+
+import org.apache.phoenix.schema.PDataType;
+
+/**
+ * Utility methods for numbers like decimal, long, etc.
+ *
+ * @author elevine
+ * @since 0.1
+ */
+public class NumberUtil {
+    
+    public static final String DEFAULT_NUMBER_FORMAT = "#,##0.###";
+
+    /**
+     * Strip all trailing zeros to ensure that no digit will be zero and
+     * round using our default context to ensure precision doesn't exceed max allowed.
+     * @return new {@link BigDecimal} instance
+     */
+    public static BigDecimal normalize(BigDecimal bigDecimal) {
+        return bigDecimal.stripTrailingZeros().round(PDataType.DEFAULT_MATH_CONTEXT);
+    }
+
+    public static BigDecimal setDecimalWidthAndScale(BigDecimal decimal, int precision, int scale) {
+        // If we could not fit all the digits before decimal point into the new desired precision and
+        // scale, return null and the caller method should handle the error.
+        if (((precision - scale) < (decimal.precision() - decimal.scale()))){
+            return null;
+        }
+        decimal = decimal.setScale(scale, BigDecimal.ROUND_DOWN);
+        return decimal;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java b/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
new file mode 100644
index 0000000..102a0f3
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
@@ -0,0 +1,308 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.util.Pair;
+
+import com.google.common.collect.Lists;
+import org.apache.phoenix.jdbc.PhoenixConnection;
+
+/**
+ * 
+ * Collection of non JDBC compliant utility methods
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public class PhoenixRuntime {
+    /**
+     * Use this connection property to control HBase timestamps
+     * by specifying your own long timestamp value at connection time. All
+     * queries will use this as the upper bound of the time range for scans
+     * and DDL, and DML will use this as t he timestamp for key values.
+     */
+    public static final String CURRENT_SCN_ATTRIB = "CurrentSCN";
+
+    /**
+     * Root for the JDBC URL that the Phoenix accepts accepts.
+     */
+    public final static String JDBC_PROTOCOL = "jdbc:phoenix";
+    public final static char JDBC_PROTOCOL_TERMINATOR = ';';
+    public final static char JDBC_PROTOCOL_SEPARATOR = ':';
+    
+    @Deprecated
+    public final static String EMBEDDED_JDBC_PROTOCOL = PhoenixRuntime.JDBC_PROTOCOL + PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR;
+    
+    /**
+     * Use this connection property to control the number of rows that are
+     * batched together on an UPSERT INTO table1... SELECT ... FROM table2.
+     * It's only used when autoCommit is true and your source table is
+     * different than your target table or your SELECT statement has a 
+     * GROUP BY clause.
+     */
+    public final static String UPSERT_BATCH_SIZE_ATTRIB = "UpsertBatchSize";
+    
+    /**
+     * Use this connection property to help with fairness of resource allocation
+     * for the client and server. The value of the attribute determines the
+     * bucket used to rollup resource usage for a particular tenant/organization. Each tenant
+     * may only use a percentage of total resources, governed by the {@link org.apache.phoenix.query.QueryServices}
+     * configuration properties
+     */
+    public static final String TENANT_ID_ATTRIB = "TenantId";
+
+    /**
+     * Use this as the zookeeper quorum name to have a connection-less connection. This enables
+     * Phoenix-compatible HFiles to be created in a map/reduce job by creating tables,
+     * upserting data into them, and getting the uncommitted state through {@link #getUncommittedData(Connection)}
+     */
+    public final static String CONNECTIONLESS = "none";
+    
+    private static final String TABLE_OPTION = "-t";
+    private static final String HEADER_OPTION = "-h";
+    private static final String STRICT_OPTION = "-s";
+    private static final String CSV_OPTION = "-d";
+    private static final String HEADER_IN_LINE = "in-line";
+    private static final String SQL_FILE_EXT = ".sql";
+    private static final String CSV_FILE_EXT = ".csv";
+    
+    private static void usageError() {
+        System.err.println("Usage: psql [-t table-name] [-h comma-separated-column-names | in-line] [-d field-delimiter-char quote-char escape-char]<zookeeper>  <path-to-sql-or-csv-file>...\n" +
+                "  By default, the name of the CSV file is used to determine the Phoenix table into which the CSV data is loaded\n" +
+                "  and the ordinal value of the columns determines the mapping.\n" +
+                "  -t overrides the table into which the CSV data is loaded\n" +
+                "  -h overrides the column names to which the CSV data maps\n" +
+                "     A special value of in-line indicating that the first line of the CSV file\n" +
+                "     determines the column to which the data maps.\n" +
+                "  -s uses strict mode by throwing an exception if a column name doesn't match during CSV loading.\n" +
+                "  -d uses custom delimiters for CSV loader, need to specify single char for field delimiter, phrase delimiter, and escape char.\n" +
+                "     number is NOT usually a delimiter and shall be taken as 1 -> ctrl A, 2 -> ctrl B ... 9 -> ctrl I. \n" +
+                "Examples:\n" +
+                "  psql localhost my_ddl.sql\n" +
+                "  psql localhost my_ddl.sql my_table.csv\n" +
+                "  psql -t my_table my_cluster:1825 my_table2012-Q3.csv\n" +
+                "  psql -t my_table -h col1,col2,col3 my_cluster:1825 my_table2012-Q3.csv\n" +
+                "  psql -t my_table -h col1,col2,col3 -d 1 2 3 my_cluster:1825 my_table2012-Q3.csv\n"
+        );
+        System.exit(-1);
+    }
+    /**
+     * Provides a mechanism to run SQL scripts against, where the arguments are:
+     * 1) connection URL string
+     * 2) one or more paths to either SQL scripts or CSV files
+     * If a CurrentSCN property is set on the connection URL, then it is incremented
+     * between processing, with each file being processed by a new connection at the
+     * increment timestamp value.
+     */
+    public static void main(String [] args) {
+        if (args.length < 2) {
+            usageError();
+        }
+        PhoenixConnection conn = null;
+        try {
+            String tableName = null;
+            List<String> columns = null;
+            boolean isStrict = false;
+            List<String> delimiter = new ArrayList<String>();
+
+            int i = 0;
+            for (; i < args.length; i++) {
+                if (TABLE_OPTION.equals(args[i])) {
+                    if (++i == args.length || tableName != null) {
+                        usageError();
+                    }
+                    tableName = args[i];
+                } else if (HEADER_OPTION.equals(args[i])) {
+                    if (++i >= args.length || columns != null) {
+                        usageError();
+                    }
+                    String header = args[i];
+                    if (HEADER_IN_LINE.equals(header)) {
+                        columns = Collections.emptyList();
+                    } else {
+                        columns = Lists.newArrayList();
+                        StringTokenizer tokenizer = new StringTokenizer(header,",");
+                        while(tokenizer.hasMoreTokens()) {
+                            columns.add(tokenizer.nextToken());
+                        }
+                    }
+                } else if (STRICT_OPTION.equals(args[i])) {
+                    isStrict = true;
+                } else if (CSV_OPTION.equals(args[i])) {
+                    for(int j=0; j < 3; j++) {
+                        if(args[++i].length()==1){
+                            delimiter.add(args[i]);
+                        } else {
+                            usageError();
+                        }
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (i == args.length) {
+                usageError();
+            }
+            
+            Properties props = new Properties();
+            String connectionUrl = JDBC_PROTOCOL + JDBC_PROTOCOL_SEPARATOR + args[i++];
+            conn = DriverManager.getConnection(connectionUrl, props).unwrap(PhoenixConnection.class);
+            
+            for (; i < args.length; i++) {
+                String fileName = args[i];
+                if (fileName.endsWith(SQL_FILE_EXT)) {
+               		PhoenixRuntime.executeStatements(conn, new FileReader(args[i]), Collections.emptyList());
+                } else if (fileName.endsWith(CSV_FILE_EXT)) {
+                    if (tableName == null) {
+                        tableName = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1, fileName.length()-CSV_FILE_EXT.length());
+                    }
+                    CSVLoader csvLoader = new CSVLoader(conn, tableName, columns, isStrict, delimiter);
+                    csvLoader.upsert(fileName);
+                } else {
+                    usageError();
+                }
+                Long scn = conn.getSCN();
+                // If specifying SCN, increment it between processing files to allow
+                // for later files to see earlier files tables.
+                if (scn != null) {
+                    scn++;
+                    props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, scn.toString());
+                    conn.close();
+                    conn = DriverManager.getConnection(connectionUrl, props).unwrap(PhoenixConnection.class);
+                }
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+        } finally {
+            if(conn != null) {
+                try {
+                    conn.close();
+                } catch (SQLException e) {
+                    //going to shut jvm down anyway. So might as well feast on it.
+                }
+            }
+            System.exit(0);
+        }
+    }
+
+    private PhoenixRuntime() {
+    }
+    
+    /**
+     * Runs a series of semicolon-terminated SQL statements using the connection provided, returning
+     * the number of SQL statements executed. Note that if the connection has specified an SCN through
+     * the {@link org.apache.phoenix.util.PhoenixRuntime#CURRENT_SCN_ATTRIB} connection property, then the timestamp
+     * is bumped up by one after each statement execution.
+     * @param conn an open JDBC connection
+     * @param reader a reader for semicolumn separated SQL statements
+     * @param binds the binds for all statements
+     * @return the number of SQL statements that were executed
+     * @throws IOException
+     * @throws SQLException
+     */
+    public static int executeStatements(Connection conn, Reader reader, List<Object> binds) throws IOException,SQLException {
+        PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+        // Turn auto commit to true when running scripts in case there's DML
+        pconn.setAutoCommit(true);
+        return pconn.executeStatements(reader, binds, System.out);
+    }
+    
+    /**
+     * Get the list of uncommitted KeyValues for the connection. Currently used to write an
+     * Phoenix-compliant HFile from a map/reduce job.
+     * @param conn an open JDBC connection
+     * @return the list of HBase mutations for uncommitted data
+     * @throws SQLException 
+     */
+    @Deprecated
+    public static List<KeyValue> getUncommittedData(Connection conn) throws SQLException {
+        Iterator<Pair<byte[],List<KeyValue>>> iterator = getUncommittedDataIterator(conn);
+        if (iterator.hasNext()) {
+            return iterator.next().getSecond();
+        }
+        return Collections.emptyList();
+    }
+    
+    /**
+     * Get the list of uncommitted KeyValues for the connection. Currently used to write an
+     * Phoenix-compliant HFile from a map/reduce job.
+     * @param conn an open JDBC connection
+     * @return the list of HBase mutations for uncommitted data
+     * @throws SQLException 
+     */
+    public static Iterator<Pair<byte[],List<KeyValue>>> getUncommittedDataIterator(Connection conn) throws SQLException {
+        return getUncommittedDataIterator(conn, false);
+    }
+    
+    /**
+     * Get the list of uncommitted KeyValues for the connection. Currently used to write an
+     * Phoenix-compliant HFile from a map/reduce job.
+     * @param conn an open JDBC connection
+     * @return the list of HBase mutations for uncommitted data
+     * @throws SQLException 
+     */
+    public static Iterator<Pair<byte[],List<KeyValue>>> getUncommittedDataIterator(Connection conn, boolean includeMutableIndexes) throws SQLException {
+        final Iterator<Pair<byte[],List<Mutation>>> iterator = conn.unwrap(PhoenixConnection.class).getMutationState().toMutations(includeMutableIndexes);
+        return new Iterator<Pair<byte[],List<KeyValue>>>() {
+
+            @Override
+            public boolean hasNext() {
+                return iterator.hasNext();
+            }
+
+            @Override
+            public Pair<byte[], List<KeyValue>> next() {
+                Pair<byte[],List<Mutation>> pair = iterator.next();
+                List<KeyValue> keyValues = Lists.newArrayListWithExpectedSize(pair.getSecond().size() * 5); // Guess-timate 5 key values per row
+                for (Mutation mutation : pair.getSecond()) {
+                    for (List<KeyValue> keyValueList : mutation.getFamilyMap().values()) {
+                        for (KeyValue keyValue : keyValueList) {
+                            keyValues.add(keyValue);
+                        }
+                    }
+                }
+                Collections.sort(keyValues, KeyValue.COMPARATOR);
+                return new Pair<byte[], List<KeyValue>>(pair.getFirst(),keyValues);
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+            
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/QueryUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/QueryUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/QueryUtil.java
new file mode 100644
index 0000000..bb0c9cc
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/QueryUtil.java
@@ -0,0 +1,75 @@
+package org.apache.phoenix.util;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class QueryUtil {
+	
+    /**
+     *  Column family name index within ResultSet resulting from {@link DatabaseMetaData#getColumns(String, String, String, String)}
+     */
+    public static final int COLUMN_FAMILY_POSITION = 1;
+
+ 	/**
+	 *  Column name index within ResultSet resulting from {@link DatabaseMetaData#getColumns(String, String, String, String)}
+	 */
+	public static final int COLUMN_NAME_POSITION = 4;
+	/**
+	 * Data type index within ResultSet resulting from {@link DatabaseMetaData#getColumns(String, String, String, String)}
+	 */
+	public static final int DATA_TYPE_POSITION = 5;
+
+	/**
+	 * Generates the upsert statement based on number of ColumnInfo. If
+	 * ColumnInfo is unavailable, it produces a generic UPSERT query without
+	 * columns information using number of columns.
+	 * 
+	 * @return Upsert Statement
+	 */
+	public static String constructUpsertStatement(ColumnInfo[] columnTypes,
+			String tableName, int numColumns) {
+		if(numColumns <= 0) {
+			throw new RuntimeException("Number of columns in HBase table cannot be less than 1");
+		}
+		StringBuilder sb = new StringBuilder();
+		sb.append("UPSERT INTO ");
+		sb.append(tableName);
+		if (columnTypes != null) {
+			sb.append("(");
+			for (ColumnInfo columnType : columnTypes) {
+				if (columnType != null) {
+					sb.append(columnType.getColumnName());
+					sb.append(",");
+				}
+			}
+			// Remove the trailing comma
+			sb.setLength(sb.length() - 1);
+			sb.append(") ");
+		}
+		sb.append("\n");
+		sb.append("VALUES (");
+		for (short i = 0; i < numColumns - 1; i++) {
+			sb.append("?,");
+		}
+		sb.append("?)");
+
+		return sb.toString();
+	}
+
+	public static String getUrl(String server) {
+		return PhoenixRuntime.JDBC_PROTOCOL + PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR + server;
+	}
+
+    public static String getExplainPlan(ResultSet rs) throws SQLException {
+        StringBuilder buf = new StringBuilder();
+        while (rs.next()) {
+            buf.append(rs.getString(1));
+            buf.append('\n');
+        }
+        if (buf.length() > 0) {
+            buf.setLength(buf.length()-1);
+        }
+        return buf.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/ReadOnlyProps.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ReadOnlyProps.java b/phoenix-core/src/main/java/org/apache/phoenix/util/ReadOnlyProps.java
new file mode 100644
index 0000000..6fcbc3d
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ReadOnlyProps.java
@@ -0,0 +1,239 @@
+package org.apache.phoenix.util;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.collect.*;
+
+/**
+ * 
+ * Read-only properties that avoids unnecessary synchronization in
+ * java.util.Properties.
+ *
+ * @author jtaylor
+ * @since 1.2.2
+ */
+public class ReadOnlyProps implements Iterable<Entry<String, String>> {
+    public static final ReadOnlyProps EMPTY_PROPS = new ReadOnlyProps(Iterators.<Entry<String, String>>emptyIterator());
+    private final Map<String, String> props;
+    
+    public ReadOnlyProps(Iterator<Entry<String, String>> iterator) {
+        Map<String, String> map = Maps.newHashMap();
+        while (iterator.hasNext()) {
+            Entry<String,String> entry = iterator.next();
+            map.put(entry.getKey(), entry.getValue());
+        }
+        this.props = ImmutableMap.copyOf(map);
+    }
+
+    public ReadOnlyProps(Map<String, String> props) {
+        this.props = ImmutableMap.copyOf(props);
+    }
+
+    private static Pattern varPat = Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");
+    private static int MAX_SUBST = 20;
+
+    private String substituteVars(String expr) {
+        if (expr == null) {
+          return null;
+        }
+        Matcher match = varPat.matcher("");
+        String eval = expr;
+        for(int s=0; s<MAX_SUBST; s++) {
+          match.reset(eval);
+          if (!match.find()) {
+            return eval;
+          }
+          String var = match.group();
+          var = var.substring(2, var.length()-1); // remove ${ .. }
+          String val = null;
+          try {
+            val = System.getProperty(var);
+          } catch(SecurityException se) {
+          }
+          if (val == null) {
+            val = getRaw(var);
+          }
+          if (val == null) {
+            return eval; // return literal ${var}: var is unbound
+          }
+          // substitute
+          eval = eval.substring(0, match.start())+val+eval.substring(match.end());
+        }
+        throw new IllegalStateException("Variable substitution depth too large: " 
+                                        + MAX_SUBST + " " + expr);
+      }
+      
+    /**
+     * Get the value of the <code>name</code> property, without doing
+     * <a href="#VariableExpansion">variable expansion</a>.
+     * 
+     * @param name the property name.
+     * @return the value of the <code>name</code> property, 
+     *         or null if no such property exists.
+     */
+    public String getRaw(String name) {
+      return props.get(name);
+    }
+
+    public String getRaw(String name, String defaultValue) {
+        String value = getRaw(name);
+        if (value == null) {
+            return defaultValue;
+        }
+        return value;
+      }
+
+    /** 
+     * Get the value of the <code>name</code> property. If no such property 
+     * exists, then <code>defaultValue</code> is returned.
+     * 
+     * @param name property name.
+     * @param defaultValue default value.
+     * @return property value, or <code>defaultValue</code> if the property 
+     *         doesn't exist.                    
+     */
+    public String get(String name, String defaultValue) {
+      return substituteVars(getRaw(name, defaultValue));
+    }
+      
+    /**
+     * Get the value of the <code>name</code> property, <code>null</code> if
+     * no such property exists.
+     * 
+     * Values are processed for <a href="#VariableExpansion">variable expansion</a> 
+     * before being returned. 
+     * 
+     * @param name the property name.
+     * @return the value of the <code>name</code> property, 
+     *         or null if no such property exists.
+     */
+    public String get(String name) {
+      return substituteVars(getRaw(name));
+    }
+
+    private String getHexDigits(String value) {
+        boolean negative = false;
+        String str = value;
+        String hexString = null;
+        if (value.startsWith("-")) {
+          negative = true;
+          str = value.substring(1);
+        }
+        if (str.startsWith("0x") || str.startsWith("0X")) {
+          hexString = str.substring(2);
+          if (negative) {
+            hexString = "-" + hexString;
+          }
+          return hexString;
+        }
+        return null;
+      }
+      
+    /** 
+     * Get the value of the <code>name</code> property as a <code>boolean</code>.  
+     * If no such property is specified, or if the specified value is not a valid
+     * <code>boolean</code>, then <code>defaultValue</code> is returned.
+     * 
+     * @param name property name.
+     * @param defaultValue default value.
+     * @return property value as a <code>boolean</code>, 
+     *         or <code>defaultValue</code>. 
+     */
+    public boolean getBoolean(String name, boolean defaultValue) {
+      String valueString = get(name);
+      if ("true".equals(valueString))
+        return true;
+      else if ("false".equals(valueString))
+        return false;
+      else return defaultValue;
+    }
+
+    /** 
+     * Get the value of the <code>name</code> property as an <code>int</code>.
+     *   
+     * If no such property exists, or if the specified value is not a valid
+     * <code>int</code>, then <code>defaultValue</code> is returned.
+     * 
+     * @param name property name.
+     * @param defaultValue default value.
+     * @return property value as an <code>int</code>, 
+     *         or <code>defaultValue</code>. 
+     */
+    public int getInt(String name, int defaultValue) {
+      String valueString = get(name);
+      if (valueString == null)
+        return defaultValue;
+      try {
+        String hexString = getHexDigits(valueString);
+        if (hexString != null) {
+          return Integer.parseInt(hexString, 16);
+        }
+        return Integer.parseInt(valueString);
+      } catch (NumberFormatException e) {
+        return defaultValue;
+      }
+    }
+    
+    /** 
+     * Get the value of the <code>name</code> property as a <code>long</code>.  
+     * If no such property is specified, or if the specified value is not a valid
+     * <code>long</code>, then <code>defaultValue</code> is returned.
+     * 
+     * @param name property name.
+     * @param defaultValue default value.
+     * @return property value as a <code>long</code>, 
+     *         or <code>defaultValue</code>. 
+     */
+    public long getLong(String name, long defaultValue) {
+      String valueString = get(name);
+      if (valueString == null)
+        return defaultValue;
+      try {
+        String hexString = getHexDigits(valueString);
+        if (hexString != null) {
+          return Long.parseLong(hexString, 16);
+        }
+        return Long.parseLong(valueString);
+      } catch (NumberFormatException e) {
+        return defaultValue;
+      }
+    }
+
+    /** 
+     * Get the value of the <code>name</code> property as a <code>float</code>.  
+     * If no such property is specified, or if the specified value is not a valid
+     * <code>float</code>, then <code>defaultValue</code> is returned.
+     * 
+     * @param name property name.
+     * @param defaultValue default value.
+     * @return property value as a <code>float</code>, 
+     *         or <code>defaultValue</code>. 
+     */
+    public float getFloat(String name, float defaultValue) {
+      String valueString = get(name);
+      if (valueString == null)
+        return defaultValue;
+      try {
+        return Float.parseFloat(valueString);
+      } catch (NumberFormatException e) {
+        return defaultValue;
+      }
+    }
+
+    /**
+     * Get the properties as a <code>Map<String,String></code>
+     * 
+     * @return Map<String,String> 
+     */
+    public Map<String,String> asMap() {
+        return props;
+    }
+    
+    @Override
+    public Iterator<Entry<String, String>> iterator() {
+        return props.entrySet().iterator();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/ResultUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ResultUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/ResultUtil.java
new file mode 100644
index 0000000..8339cc0
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ResultUtil.java
@@ -0,0 +1,177 @@
+/*
+ * 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.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Static class for various methods that would be nice to have added to {@link org.apache.hadoop.hbase.client.Result}.
+ * These methods work off of the raw bytes preventing the explosion of Result into object form.
+ * 
+ * @author jtaylor
+ * @since 0.1
+ */
+public class ResultUtil {
+    public static final Result EMPTY_RESULT = new Result() {
+        @Override
+        public final boolean isEmpty() { return true; }
+    };
+    
+    private ResultUtil() {
+    }
+    
+    /**
+     * Return a pointer into a potentially much bigger byte buffer that points to the key of a Result.
+     * @param r
+     */
+    public static ImmutableBytesWritable getKey(Result r) {
+        return getKey(r, 0);
+    }
+    
+    public static void getKey(Result r, ImmutableBytesWritable key) {
+        key.set(r.getRow());
+        //key.set(getRawBytes(r), getKeyOffset(r), getKeyLength(r));
+    }
+    
+    public static void getKey(KeyValue value, ImmutableBytesWritable key) {
+        key.set(value.getBuffer(), value.getRowOffset(), value.getRowLength());
+    }
+    
+    /**
+     * Return a pointer into a potentially much bigger byte buffer that points to the key of a Result.
+     * Use offset to return a subset of the key bytes, for example to skip the organization ID embedded
+     * in all of our keys.
+     * @param r
+     * @param offset offset added to start of key and subtracted from key length (to select subset of key bytes)
+     */
+    public static ImmutableBytesWritable getKey(Result r, int offset) {
+        return new ImmutableBytesWritable(getRawBytes(r), getKeyOffset(r) + offset, getKeyLength(r) - offset);
+    }
+
+    public static void getKey(Result r, int offset, int length, ImmutableBytesWritable key) {
+        key.set(getRawBytes(r), getKeyOffset(r) + offset, length);
+    }
+
+    /**
+     * Comparator for comparing the keys from two Results in-place, without allocating new byte arrays
+     */
+    public static final Comparator<Result> KEY_COMPARATOR = new Comparator<Result>() {
+
+        @Override
+        public int compare(Result r1, Result r2) {
+            byte[] r1Bytes = getRawBytes(r1);
+            byte[] r2Bytes = getRawBytes(r2);
+            return Bytes.compareTo(r1Bytes, getKeyOffset(r1), getKeyLength(r1), r2Bytes, getKeyOffset(r2), getKeyLength(r2));
+        }
+        
+    };
+    
+    /**
+     * Get the offset into the Result byte array to the key.
+     * @param r
+     * @return
+     */
+    static int getKeyOffset(Result r) {
+        // Special case for when Result was instantiated via KeyValue array (no bytes in that case) versus returned from a scanner
+        return (r.getBytes() == null ? r.raw()[0].getOffset() : (r.getBytes().getOffset() + Bytes.SIZEOF_INT /* KV length in Result */)) + KeyValue.ROW_OFFSET /* key offset in KV */ + Bytes.SIZEOF_SHORT /* key length */;
+    }
+    
+    static int getKeyLength(Result r) {
+        // Key length stored right before key as a short
+        return Bytes.toShort(getRawBytes(r), getKeyOffset(r) - Bytes.SIZEOF_SHORT);
+    }
+    
+    static byte[] getRawBytes(Result r) {
+        // Handle special case for when Result was instantiated via KeyValue array (no bytes in that case) versus returned from a scanner
+        ImmutableBytesWritable rPtr = r.getBytes();
+        if (rPtr != null)
+            return rPtr.get();
+        return r.raw()[0].getBuffer();
+    }
+
+    public static int compareKeys(Result r1, Result r2) {
+        return Bytes.compareTo(getRawBytes(r1), getKeyOffset(r1), getKeyLength(r1), getRawBytes(r2), getKeyOffset(r2), getKeyLength(r2));
+    }
+
+    /**
+     * Binary search for latest column value without allocating memory in the process
+     */
+    public static KeyValue getColumnLatest(Result r, byte[] family, byte[] qualifier) {
+        byte[] rbytes = getRawBytes(r);
+        int roffset = getKeyOffset(r);
+        int rlength = getKeyLength(r);
+        return getColumnLatest(r, rbytes, roffset, rlength, family, 0, family.length, qualifier, 0, qualifier.length);
+    }
+
+    public static KeyValue getSearchTerm(Result r, byte[] family, byte[] qualifier) {
+        byte[] rbytes = getRawBytes(r);
+        int roffset = getKeyOffset(r);
+        int rlength = getKeyLength(r);
+        return KeyValue.createFirstOnRow(rbytes, roffset, rlength, family, 0, family.length, qualifier, 0, qualifier.length);
+    }
+    /**
+     * Binary search for latest column value without allocating memory in the process
+     */
+    public static KeyValue getColumnLatest(Result r, byte[] row, int roffset, int rlength, byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength) {
+        KeyValue searchTerm = KeyValue.createFirstOnRow(row, roffset, rlength, family, foffset, flength, qualifier, qoffset, qlength);
+        return getColumnLatest(r,searchTerm);
+        
+    }
+
+     /**
+     * Binary search for latest column value without allocating memory in the process
+     * @param r
+     * @param searchTerm
+     */
+    public static KeyValue getColumnLatest(Result r, KeyValue searchTerm) {
+        KeyValue [] kvs = r.raw(); // side effect possibly.
+        if (kvs == null || kvs.length == 0) {
+          return null;
+        }
+        
+        // pos === ( -(insertion point) - 1)
+        int pos = Arrays.binarySearch(kvs, searchTerm, KeyValue.COMPARATOR);
+        // never will exact match
+        if (pos < 0) {
+          pos = (pos+1) * -1;
+          // pos is now insertion point
+        }
+        if (pos == kvs.length) {
+          return null; // doesn't exist
+        }
+
+        KeyValue kv = kvs[pos];
+        if (Bytes.compareTo(kv.getBuffer(), kv.getFamilyOffset(), kv.getFamilyLength(),
+                searchTerm.getBuffer(), searchTerm.getFamilyOffset(), searchTerm.getFamilyLength()) != 0) {
+            return null;
+        }
+        if (Bytes.compareTo(kv.getBuffer(), kv.getQualifierOffset(), kv.getQualifierLength(),
+                searchTerm.getBuffer(), searchTerm.getQualifierOffset(), searchTerm.getQualifierLength()) != 0) {
+            return null;
+        }
+        return kv;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseable.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseable.java b/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseable.java
new file mode 100644
index 0000000..11cf02f
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util;
+
+import java.sql.SQLException;
+
+/**
+ * 
+ * Interface for a SQL resource that should be closed
+ * after it is no longer in use.
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public interface SQLCloseable {
+    void close() throws SQLException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseables.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseables.java b/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseables.java
new file mode 100644
index 0000000..c255c1d
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/SQLCloseables.java
@@ -0,0 +1,133 @@
+/*
+ * 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.util;
+
+import java.sql.SQLException;
+import java.util.*;
+
+import com.google.common.collect.Iterables;
+
+
+/**
+ * Utilities for operating on {@link SQLCloseable}s.
+ * 
+ * @author jtaylor
+ * @since 0.1
+ */
+public class SQLCloseables {
+    /** Not constructed */
+    private SQLCloseables() { }
+    
+    /**
+     * Allows you to close as many of the {@link SQLCloseable}s as possible.
+     * 
+     * If any of the close's fail with an IOException, those exception(s) will
+     * be thrown after attempting to close all of the inputs.
+     */
+    public static void closeAll(Iterable<? extends SQLCloseable> iterable) throws SQLException {
+        SQLException ex = closeAllQuietly(iterable);
+        if (ex != null) throw ex;
+    }
+ 
+    public static SQLException closeAllQuietly(Iterable<? extends SQLCloseable> iterable) {
+        if (iterable == null) return null;
+        
+        LinkedList<SQLException> exceptions = null;
+        for (SQLCloseable closeable : iterable) {
+            try {
+                closeable.close();
+            } catch (SQLException x) {
+                if (exceptions == null) exceptions = new LinkedList<SQLException>();
+                exceptions.add(x);
+            }
+        }
+        
+        SQLException ex = MultipleCausesSQLException.fromSQLExceptions(exceptions);
+        return ex;
+    }
+
+    /**
+     * A subclass of {@link SQLException} that allows you to chain multiple 
+     * causes together.
+     * 
+     * @author jtaylor
+     * @since 0.1
+     * @see SQLCloseables
+     */
+    static private class MultipleCausesSQLException extends SQLException {
+		private static final long serialVersionUID = 1L;
+
+		static SQLException fromSQLExceptions(Collection<? extends SQLException> exceptions) {
+            if (exceptions == null || exceptions.isEmpty()) return null;
+            if (exceptions.size() == 1) return Iterables.getOnlyElement(exceptions);
+            
+            return new MultipleCausesSQLException(exceptions);
+        }
+        
+        private final Collection<? extends SQLException> exceptions;
+        private boolean hasSetStackTrace;
+        
+        /**
+         * Use the {@link #fromIOExceptions(Collection) factory}.
+         */
+        private MultipleCausesSQLException(Collection<? extends SQLException> exceptions) {
+            this.exceptions = exceptions;
+        }
+
+        @Override
+        public String getMessage() {
+            StringBuilder sb = new StringBuilder(this.exceptions.size() * 50);
+            int exceptionNum = 0;
+            for (SQLException ex : this.exceptions) {
+                sb.append("Cause Number " + exceptionNum + ": " + ex.getMessage() + "\n");
+                exceptionNum++;
+            }
+            return sb.toString();
+        }
+        
+        @Override
+        public StackTraceElement[] getStackTrace() {
+            if (!this.hasSetStackTrace) {
+                ArrayList<StackTraceElement> frames = new ArrayList<StackTraceElement>(this.exceptions.size() * 20);
+                
+                int exceptionNum = 0;
+                for (SQLException exception : this.exceptions) {
+                    StackTraceElement header = new StackTraceElement(MultipleCausesSQLException.class.getName(), 
+                            "Exception Number " + exceptionNum, 
+                            "<no file>",
+                            0);
+                    
+                    frames.add(header);
+                    for (StackTraceElement ste : exception.getStackTrace()) {
+                        frames.add(ste);
+                    }
+                    exceptionNum++;
+                }
+                
+                setStackTrace(frames.toArray(new StackTraceElement[frames.size()]));
+                this.hasSetStackTrace = true;
+            }        
+            
+            return super.getStackTrace();
+        }
+
+    }
+    
+}