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();
+ }
+
+ }
+
+}