You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by el...@apache.org on 2016/03/07 19:28:10 UTC
[32/59] [partial] calcite git commit: [CALCITE-1078] Detach avatica
from the core calcite Maven project
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/ByteString.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/ByteString.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ByteString.java
new file mode 100644
index 0000000..4e606b7
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ByteString.java
@@ -0,0 +1,347 @@
+/*
+ * 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.calcite.avatica.util;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * Collection of bytes.
+ *
+ * <p>ByteString is to bytes what {@link String} is to chars: It is immutable,
+ * implements equality ({@link #hashCode} and {@link #equals}),
+ * comparison ({@link #compareTo}) and
+ * {@link Serializable serialization} correctly.</p>
+ */
+public class ByteString implements Comparable<ByteString>, Serializable {
+ private final byte[] bytes;
+
+ /** An empty byte string. */
+ public static final ByteString EMPTY = new ByteString(new byte[0], false);
+
+ private static final char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ /**
+ * Creates a ByteString.
+ *
+ * @param bytes Bytes
+ */
+ public ByteString(byte[] bytes) {
+ this(bytes.clone(), false);
+ }
+
+ // private constructor that does not copy
+ private ByteString(byte[] bytes, boolean dummy) {
+ this.bytes = bytes;
+ }
+
+ @Override public int hashCode() {
+ return Arrays.hashCode(bytes);
+ }
+
+ @Override public boolean equals(Object obj) {
+ return this == obj
+ || obj instanceof ByteString
+ && Arrays.equals(bytes, ((ByteString) obj).bytes);
+ }
+
+ public int compareTo(ByteString that) {
+ final byte[] v1 = bytes;
+ final byte[] v2 = that.bytes;
+ final int n = Math.min(v1.length, v2.length);
+ for (int i = 0; i < n; i++) {
+ int c1 = v1[i] & 0xff;
+ int c2 = v2[i] & 0xff;
+ if (c1 != c2) {
+ return c1 - c2;
+ }
+ }
+ return v1.length - v2.length;
+ }
+
+ /**
+ * Returns this byte string in hexadecimal format.
+ *
+ * @return Hexadecimal string
+ */
+ @Override public String toString() {
+ return toString(16);
+ }
+
+ /**
+ * Returns this byte string in a given base.
+ *
+ * @return String in given base
+ */
+ public String toString(int base) {
+ return toString(bytes, base);
+ }
+
+ /**
+ * Returns the given byte array in hexadecimal format.
+ *
+ * <p>For example, <tt>toString(new byte[] {0xDE, 0xAD})</tt>
+ * returns {@code "DEAD"}.</p>
+ *
+ * @param bytes Array of bytes
+ * @param base Base (2 or 16)
+ * @return String
+ */
+ public static String toString(byte[] bytes, int base) {
+ char[] chars;
+ int j = 0;
+ switch (base) {
+ case 2:
+ chars = new char[bytes.length * 8];
+ for (byte b : bytes) {
+ chars[j++] = DIGITS[(b & 0x80) >> 7];
+ chars[j++] = DIGITS[(b & 0x40) >> 6];
+ chars[j++] = DIGITS[(b & 0x20) >> 5];
+ chars[j++] = DIGITS[(b & 0x10) >> 4];
+ chars[j++] = DIGITS[(b & 0x08) >> 3];
+ chars[j++] = DIGITS[(b & 0x04) >> 2];
+ chars[j++] = DIGITS[(b & 0x02) >> 1];
+ chars[j++] = DIGITS[b & 0x01];
+ }
+ break;
+ case 16:
+ chars = new char[bytes.length * 2];
+ for (byte b : bytes) {
+ chars[j++] = DIGITS[(b & 0xF0) >> 4];
+ chars[j++] = DIGITS[b & 0x0F];
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("bad base " + base);
+ }
+ return new String(chars, 0, j);
+ }
+
+ /**
+ * Returns this byte string in Base64 format.
+ *
+ * @return Base64 string
+ */
+ public String toBase64String() {
+ return Base64.encodeBytes(bytes);
+ }
+
+ /**
+ * Creates a byte string from a hexadecimal or binary string.
+ *
+ * <p>For example, <tt>of("DEAD", 16)</tt>
+ * returns the same as {@code ByteString(new byte[] {0xDE, 0xAD})}.
+ *
+ * @param string Array of bytes
+ * @param base Base (2 or 16)
+ * @return String
+ */
+ public static ByteString of(String string, int base) {
+ final byte[] bytes = parse(string, base);
+ return new ByteString(bytes, false);
+ }
+
+ /**
+ * Parses a hexadecimal or binary string to a byte array.
+ *
+ * @param string Hexadecimal or binary string
+ * @param base Base (2 or 16)
+ * @return Byte array
+ */
+ public static byte[] parse(String string, int base) {
+ char[] chars = string.toCharArray();
+ byte[] bytes;
+ int j = 0;
+ byte b = 0;
+ switch (base) {
+ case 2:
+ bytes = new byte[chars.length / 8];
+ for (char c : chars) {
+ b <<= 1;
+ if (c == '1') {
+ b |= 0x1;
+ }
+ if (j % 8 == 7) {
+ bytes[j / 8] = b;
+ b = 0;
+ }
+ ++j;
+ }
+ break;
+ case 16:
+ if (chars.length % 2 != 0) {
+ throw new IllegalArgumentException("hex string has odd length");
+ }
+ bytes = new byte[chars.length / 2];
+ for (char c : chars) {
+ b <<= 4;
+ byte i = decodeHex(c);
+ b |= i & 0x0F;
+ if (j % 2 == 1) {
+ bytes[j / 2] = b;
+ b = 0;
+ }
+ ++j;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("bad base " + base);
+ }
+ return bytes;
+ }
+
+ private static byte decodeHex(char c) {
+ if (c >= '0' && c <= '9') {
+ return (byte) (c - '0');
+ }
+ if (c >= 'a' && c <= 'f') {
+ return (byte) (c - 'a' + 10);
+ }
+ if (c >= 'A' && c <= 'F') {
+ return (byte) (c - 'A' + 10);
+ }
+ throw new IllegalArgumentException("invalid hex character: " + c);
+ }
+
+ /**
+ * Creates a byte string from a Base64 string.
+ *
+ * @param string Base64 string
+ * @return Byte string
+ */
+ public static ByteString ofBase64(String string) {
+ final byte[] bytes = parseBase64(string);
+ return new ByteString(bytes, false);
+ }
+
+ /**
+ * Parses a Base64 to a byte array.
+ *
+ * @param string Base64 string
+ * @return Byte array
+ */
+ public static byte[] parseBase64(String string) {
+ try {
+ return Base64.decode(string);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("bad base64 string", e);
+ }
+ }
+
+ @SuppressWarnings({
+ "CloneDoesntCallSuperClone",
+ "CloneDoesntDeclareCloneNotSupportedException"
+ })
+ @Override public Object clone() {
+ return this;
+ }
+
+ /**
+ * Returns the number of bytes in this byte string.
+ *
+ * @return Length of this byte string
+ */
+ public int length() {
+ return bytes.length;
+ }
+
+ /**
+ * Returns the byte at a given position in the byte string.
+ *
+ * @param i Index
+ * @throws IndexOutOfBoundsException
+ * if the <tt>index</tt> argument is negative or not less than
+ * <tt>length()</tt>
+ * @return Byte at given position
+ */
+ public byte byteAt(int i) {
+ return bytes[i];
+ }
+
+ /**
+ * Returns a ByteString that consists of a given range.
+ *
+ * @param start Start of range
+ * @param end Position after end of range
+ * @return Substring
+ */
+ public ByteString substring(int start, int end) {
+ byte[] bytes = Arrays.copyOfRange(this.bytes, start, end);
+ return new ByteString(bytes, false);
+ }
+
+ /**
+ * Returns a ByteString that starts at a given position.
+ *
+ * @param start Start of range
+ * @return Substring
+ */
+ public ByteString substring(int start) {
+ return substring(start, length());
+ }
+
+ /**
+ * Returns a copy of the byte array.
+ */
+ @JsonValue
+ public byte[] getBytes() {
+ return bytes.clone();
+ }
+
+ /**
+ * Returns a ByteString consisting of the concatenation of this and another
+ * string.
+ *
+ * @param other Byte string to concatenate
+ * @return Combined byte string
+ */
+ public ByteString concat(ByteString other) {
+ int otherLen = other.length();
+ if (otherLen == 0) {
+ return this;
+ }
+ int len = bytes.length;
+ byte[] buf = Arrays.copyOf(bytes, len + otherLen);
+ System.arraycopy(other.bytes, 0, buf, len, other.bytes.length);
+ return new ByteString(buf, false);
+ }
+
+ /** Returns the position at which {@code seek} first occurs in this byte
+ * string, or -1 if it does not occur. */
+ public int indexOf(ByteString seek) {
+ iLoop:
+ for (int i = 0; i < bytes.length - seek.bytes.length + 1; i++) {
+ for (int j = 0;; j++) {
+ if (j == seek.bytes.length) {
+ return i;
+ }
+ if (bytes[i + j] != seek.bytes[j]) {
+ continue iLoop;
+ }
+ }
+ }
+ return -1;
+ }
+}
+
+// End ByteString.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/Casing.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/Casing.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Casing.java
new file mode 100644
index 0000000..5f13214
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Casing.java
@@ -0,0 +1,35 @@
+/*
+ * 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.calcite.avatica.util;
+
+/** Policy for converting case of identifiers before storing them.
+ *
+ * <p>A database often has policies for quoted versus unquoted identifiers.
+ * For example, Oracle converts unquoted identifiers to upper-case, but
+ * quoted identifiers are unchanged.</p> */
+public enum Casing {
+ /** The case of identifiers is not changed. */
+ UNCHANGED,
+
+ /** Identifiers are converted to upper-case. */
+ TO_UPPER,
+
+ /** Identifiers are converted to lower-case. */
+ TO_LOWER
+}
+
+// End Casing.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/Cursor.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/Cursor.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Cursor.java
new file mode 100644
index 0000000..8eab72f
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Cursor.java
@@ -0,0 +1,145 @@
+/*
+ * 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.calcite.avatica.util;
+
+import org.apache.calcite.avatica.ColumnMetaData;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.Ref;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Interface to an iteration that is similar to, and can easily support,
+ * a JDBC {@link java.sql.ResultSet}, but is simpler to implement.
+ */
+public interface Cursor extends AutoCloseable {
+ /**
+ * Creates a list of accessors, one per column.
+ *
+ *
+ * @param types List of column types, per {@link java.sql.Types}.
+ * @param localCalendar Calendar in local time zone
+ * @param factory Factory that creates sub-ResultSets when needed
+ * @return List of column accessors
+ */
+ List<Accessor> createAccessors(List<ColumnMetaData> types,
+ Calendar localCalendar, ArrayImpl.Factory factory);
+
+ /**
+ * Moves to the next row.
+ *
+ * @return Whether moved
+ *
+ * @throws SQLException on database error
+ */
+ boolean next() throws SQLException;
+
+ /**
+ * Closes this cursor and releases resources.
+ */
+ void close();
+
+ /**
+ * Returns whether the last value returned was null.
+ *
+ * @throws SQLException on database error
+ */
+ boolean wasNull() throws SQLException;
+
+ /**
+ * Accessor of a column value.
+ */
+ public interface Accessor {
+ boolean wasNull() throws SQLException;
+
+ String getString() throws SQLException;
+
+ boolean getBoolean() throws SQLException;
+
+ byte getByte() throws SQLException;
+
+ short getShort() throws SQLException;
+
+ int getInt() throws SQLException;
+
+ long getLong() throws SQLException;
+
+ float getFloat() throws SQLException;
+
+ double getDouble() throws SQLException;
+
+ BigDecimal getBigDecimal() throws SQLException;
+
+ BigDecimal getBigDecimal(int scale) throws SQLException;
+
+ byte[] getBytes() throws SQLException;
+
+ InputStream getAsciiStream() throws SQLException;
+
+ InputStream getUnicodeStream() throws SQLException;
+
+ InputStream getBinaryStream() throws SQLException;
+
+ Object getObject() throws SQLException;
+
+ Reader getCharacterStream() throws SQLException;
+
+ Object getObject(Map<String, Class<?>> map) throws SQLException;
+
+ Ref getRef() throws SQLException;
+
+ Blob getBlob() throws SQLException;
+
+ Clob getClob() throws SQLException;
+
+ Array getArray() throws SQLException;
+
+ Date getDate(Calendar calendar) throws SQLException;
+
+ Time getTime(Calendar calendar) throws SQLException;
+
+ Timestamp getTimestamp(Calendar calendar) throws SQLException;
+
+ URL getURL() throws SQLException;
+
+ NClob getNClob() throws SQLException;
+
+ SQLXML getSQLXML() throws SQLException;
+
+ String getNString() throws SQLException;
+
+ Reader getNCharacterStream() throws SQLException;
+
+ <T> T getObject(Class<T> type) throws SQLException;
+ }
+}
+
+// End Cursor.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
new file mode 100644
index 0000000..67e1245
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/DateTimeUtils.java
@@ -0,0 +1,842 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Utility functions for datetime types: date, time, timestamp.
+ *
+ * <p>Used by the JDBC driver.
+ *
+ * <p>TODO: review methods for performance. Due to allocations required, it may
+ * be preferable to introduce a "formatter" with the required state.
+ */
+public class DateTimeUtils {
+ /** The julian date of the epoch, 1970-01-01. */
+ public static final int EPOCH_JULIAN = 2440588;
+
+ private DateTimeUtils() {}
+
+ //~ Static fields/initializers ---------------------------------------------
+
+ /** The SimpleDateFormat string for ISO dates, "yyyy-MM-dd". */
+ public static final String DATE_FORMAT_STRING = "yyyy-MM-dd";
+
+ /** The SimpleDateFormat string for ISO times, "HH:mm:ss". */
+ public static final String TIME_FORMAT_STRING = "HH:mm:ss";
+
+ /** The SimpleDateFormat string for ISO timestamps, "yyyy-MM-dd HH:mm:ss". */
+ public static final String TIMESTAMP_FORMAT_STRING =
+ DATE_FORMAT_STRING + " " + TIME_FORMAT_STRING;
+
+ /** The GMT time zone. */
+ public static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+ /** The Java default time zone. */
+ public static final TimeZone DEFAULT_ZONE = TimeZone.getDefault();
+
+ /**
+ * The number of milliseconds in a second.
+ */
+ public static final long MILLIS_PER_SECOND = 1000L;
+
+ /**
+ * The number of milliseconds in a minute.
+ */
+ public static final long MILLIS_PER_MINUTE = 60000L;
+
+ /**
+ * The number of milliseconds in an hour.
+ */
+ public static final long MILLIS_PER_HOUR = 3600000L; // = 60 * 60 * 1000
+
+ /**
+ * The number of milliseconds in a day.
+ *
+ * <p>This is the modulo 'mask' used when converting
+ * TIMESTAMP values to DATE and TIME values.
+ */
+ public static final long MILLIS_PER_DAY = 86400000; // = 24 * 60 * 60 * 1000
+
+ /**
+ * Calendar set to the epoch (1970-01-01 00:00:00 UTC). Useful for
+ * initializing other values. Calendars are not immutable, so be careful not
+ * to screw up this object for everyone else.
+ */
+ public static final Calendar ZERO_CALENDAR;
+
+ static {
+ ZERO_CALENDAR = Calendar.getInstance(DateTimeUtils.GMT_ZONE);
+ ZERO_CALENDAR.setTimeInMillis(0);
+ }
+
+ /**
+ * Calendar set to local time.
+ */
+ private static final Calendar LOCAL_CALENDAR = Calendar.getInstance();
+
+ //~ Methods ----------------------------------------------------------------
+
+ /**
+ * Parses a string using {@link SimpleDateFormat} and a given pattern. This
+ * method parses a string at the specified parse position and if successful,
+ * updates the parse position to the index after the last character used.
+ * The parsing is strict and requires months to be less than 12, days to be
+ * less than 31, etc.
+ *
+ * @param s string to be parsed
+ * @param pattern {@link SimpleDateFormat} pattern (not null)
+ * @param tz time zone in which to interpret string. Defaults to the Java
+ * default time zone
+ * @param pp position to start parsing from
+ * @return a Calendar initialized with the parsed value, or null if parsing
+ * failed. If returned, the Calendar is configured to the GMT time zone.
+ */
+ private static Calendar parseDateFormat(
+ String s,
+ String pattern,
+ TimeZone tz,
+ ParsePosition pp) {
+ assert pattern != null;
+ SimpleDateFormat df = new SimpleDateFormat(pattern);
+ if (tz == null) {
+ tz = DEFAULT_ZONE;
+ }
+ Calendar ret = Calendar.getInstance(tz);
+ df.setCalendar(ret);
+ df.setLenient(false);
+
+ java.util.Date d = df.parse(s, pp);
+ if (null == d) {
+ return null;
+ }
+ ret.setTime(d);
+ ret.setTimeZone(GMT_ZONE);
+ return ret;
+ }
+
+ /**
+ * Parses a string using {@link SimpleDateFormat} and a given pattern. The
+ * entire string must match the pattern specified.
+ *
+ * @param s string to be parsed
+ * @param pattern {@link SimpleDateFormat} pattern
+ * @param tz time zone in which to interpret string. Defaults to the Java
+ * default time zone
+ * @return a Calendar initialized with the parsed value, or null if parsing
+ * failed. If returned, the Calendar is configured to the GMT time zone.
+ */
+ public static Calendar parseDateFormat(
+ String s,
+ String pattern,
+ TimeZone tz) {
+ assert pattern != null;
+ ParsePosition pp = new ParsePosition(0);
+ Calendar ret = parseDateFormat(s, pattern, tz, pp);
+ if (pp.getIndex() != s.length()) {
+ // Didn't consume entire string - not good
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Parses a string using {@link SimpleDateFormat} and a given pattern, and
+ * if present, parses a fractional seconds component. The fractional seconds
+ * component must begin with a decimal point ('.') followed by numeric
+ * digits. The precision is rounded to a maximum of 3 digits of fractional
+ * seconds precision (to obtain milliseconds).
+ *
+ * @param s string to be parsed
+ * @param pattern {@link SimpleDateFormat} pattern
+ * @param tz time zone in which to interpret string. Defaults to the
+ * local time zone
+ * @return a {@link DateTimeUtils.PrecisionTime PrecisionTime} initialized
+ * with the parsed value, or null if parsing failed. The PrecisionTime
+ * contains a GMT Calendar and a precision.
+ */
+ public static PrecisionTime parsePrecisionDateTimeLiteral(
+ String s,
+ String pattern,
+ TimeZone tz) {
+ assert pattern != null;
+ ParsePosition pp = new ParsePosition(0);
+ Calendar cal = parseDateFormat(s, pattern, tz, pp);
+ if (cal == null) {
+ return null; // Invalid date/time format
+ }
+
+ // Note: the Java SimpleDateFormat 'S' treats any number after
+ // the decimal as milliseconds. That means 12:00:00.9 has 9
+ // milliseconds and 12:00:00.9999 has 9999 milliseconds.
+ int p = 0;
+ if (pp.getIndex() < s.length()) {
+ // Check to see if rest is decimal portion
+ if (s.charAt(pp.getIndex()) != '.') {
+ return null;
+ }
+
+ // Skip decimal sign
+ pp.setIndex(pp.getIndex() + 1);
+
+ // Parse decimal portion
+ if (pp.getIndex() < s.length()) {
+ String secFraction = s.substring(pp.getIndex());
+ if (!secFraction.matches("\\d+")) {
+ return null;
+ }
+ NumberFormat nf = NumberFormat.getIntegerInstance();
+ Number num = nf.parse(s, pp);
+ if ((num == null) || (pp.getIndex() != s.length())) {
+ // Invalid decimal portion
+ return null;
+ }
+
+ // Determine precision - only support prec 3 or lower
+ // (milliseconds) Higher precisions are quietly rounded away
+ p = Math.min(
+ 3,
+ secFraction.length());
+
+ // Calculate milliseconds
+ int ms =
+ (int) Math.round(
+ num.longValue()
+ * Math.pow(10, 3 - secFraction.length()));
+ cal.add(Calendar.MILLISECOND, ms);
+ }
+ }
+
+ assert pp.getIndex() == s.length();
+ PrecisionTime ret = new PrecisionTime(cal, p);
+ return ret;
+ }
+
+ /**
+ * Gets the active time zone based on a Calendar argument
+ */
+ public static TimeZone getTimeZone(Calendar cal) {
+ if (cal == null) {
+ return DEFAULT_ZONE;
+ }
+ return cal.getTimeZone();
+ }
+
+ /**
+ * Checks if the date/time format is valid
+ *
+ * @param pattern {@link SimpleDateFormat} pattern
+ * @throws IllegalArgumentException if the given pattern is invalid
+ */
+ public static void checkDateFormat(String pattern) {
+ new SimpleDateFormat(pattern);
+ }
+
+ /**
+ * Creates a new date formatter with Farrago specific options. Farrago
+ * parsing is strict and does not allow values such as day 0, month 13, etc.
+ *
+ * @param format {@link SimpleDateFormat} pattern
+ */
+ public static SimpleDateFormat newDateFormat(String format) {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ sdf.setLenient(false);
+ return sdf;
+ }
+
+ /** Helper for CAST({timestamp} AS VARCHAR(n)). */
+ public static String unixTimestampToString(long timestamp) {
+ final StringBuilder buf = new StringBuilder(17);
+ int date = (int) (timestamp / MILLIS_PER_DAY);
+ int time = (int) (timestamp % MILLIS_PER_DAY);
+ if (time < 0) {
+ --date;
+ time += MILLIS_PER_DAY;
+ }
+ unixDateToString(buf, date);
+ buf.append(' ');
+ unixTimeToString(buf, time);
+ return buf.toString();
+ }
+
+ /** Helper for CAST({timestamp} AS VARCHAR(n)). */
+ public static String unixTimeToString(int time) {
+ final StringBuilder buf = new StringBuilder(8);
+ unixTimeToString(buf, time);
+ return buf.toString();
+ }
+
+ private static void unixTimeToString(StringBuilder buf, int time) {
+ int h = time / 3600000;
+ int time2 = time % 3600000;
+ int m = time2 / 60000;
+ int time3 = time2 % 60000;
+ int s = time3 / 1000;
+ int ms = time3 % 1000;
+ int2(buf, h);
+ buf.append(':');
+ int2(buf, m);
+ buf.append(':');
+ int2(buf, s);
+ }
+
+ private static void int2(StringBuilder buf, int i) {
+ buf.append((char) ('0' + (i / 10) % 10));
+ buf.append((char) ('0' + i % 10));
+ }
+
+ private static void int4(StringBuilder buf, int i) {
+ buf.append((char) ('0' + (i / 1000) % 10));
+ buf.append((char) ('0' + (i / 100) % 10));
+ buf.append((char) ('0' + (i / 10) % 10));
+ buf.append((char) ('0' + i % 10));
+ }
+
+ /** Helper for CAST({date} AS VARCHAR(n)). */
+ public static String unixDateToString(int date) {
+ final StringBuilder buf = new StringBuilder(10);
+ unixDateToString(buf, date);
+ return buf.toString();
+ }
+
+ private static void unixDateToString(StringBuilder buf, int date) {
+ julianToString(buf, date + EPOCH_JULIAN);
+ }
+
+ private static void julianToString(StringBuilder buf, int julian) {
+ // this shifts the epoch back to astronomical year -4800 instead of the
+ // start of the Christian era in year AD 1 of the proleptic Gregorian
+ // calendar.
+ int j = julian + 32044;
+ int g = j / 146097;
+ int dg = j % 146097;
+ int c = (dg / 36524 + 1) * 3 / 4;
+ int dc = dg - c * 36524;
+ int b = dc / 1461;
+ int db = dc % 1461;
+ int a = (db / 365 + 1) * 3 / 4;
+ int da = db - a * 365;
+
+ // integer number of full years elapsed since March 1, 4801 BC
+ int y = g * 400 + c * 100 + b * 4 + a;
+ // integer number of full months elapsed since the last March 1
+ int m = (da * 5 + 308) / 153 - 2;
+ // number of days elapsed since day 1 of the month
+ int d = da - (m + 4) * 153 / 5 + 122;
+ int year = y - 4800 + (m + 2) / 12;
+ int month = (m + 2) % 12 + 1;
+ int day = d + 1;
+ int4(buf, year);
+ buf.append('-');
+ int2(buf, month);
+ buf.append('-');
+ int2(buf, day);
+ }
+
+ public static String intervalYearMonthToString(int v, TimeUnitRange range) {
+ final StringBuilder buf = new StringBuilder();
+ if (v >= 0) {
+ buf.append('+');
+ } else {
+ buf.append('-');
+ v = -v;
+ }
+ final int y;
+ final int m;
+ switch (range) {
+ case YEAR:
+ v = roundUp(v, 12);
+ y = v / 12;
+ buf.append(y);
+ break;
+ case YEAR_TO_MONTH:
+ y = v / 12;
+ buf.append(y);
+ buf.append('-');
+ m = v % 12;
+ number(buf, m, 2);
+ break;
+ case MONTH:
+ m = v;
+ buf.append(m);
+ break;
+ default:
+ throw new AssertionError(range);
+ }
+ return buf.toString();
+ }
+
+ public static StringBuilder number(StringBuilder buf, int v, int n) {
+ for (int k = digitCount(v); k < n; k++) {
+ buf.append('0');
+ }
+ return buf.append(v);
+ }
+
+ public static int digitCount(int v) {
+ for (int n = 1;; n++) {
+ v /= 10;
+ if (v == 0) {
+ return n;
+ }
+ }
+ }
+
+ private static int roundUp(int dividend, int divisor) {
+ int remainder = dividend % divisor;
+ dividend -= remainder;
+ if (remainder * 2 > divisor) {
+ dividend += divisor;
+ }
+ return dividend;
+ }
+
+ /** Cheap, unsafe, long power. power(2, 3) returns 8. */
+ public static long powerX(long a, long b) {
+ long x = 1;
+ while (b > 0) {
+ x *= a;
+ --b;
+ }
+ return x;
+ }
+
+ public static String intervalDayTimeToString(long v, TimeUnitRange range,
+ int scale) {
+ final StringBuilder buf = new StringBuilder();
+ if (v >= 0) {
+ buf.append('+');
+ } else {
+ buf.append('-');
+ v = -v;
+ }
+ final long ms;
+ final long s;
+ final long m;
+ final long h;
+ final long d;
+ switch (range) {
+ case DAY_TO_SECOND:
+ v = roundUp(v, powerX(10, 3 - scale));
+ ms = v % 1000;
+ v /= 1000;
+ s = v % 60;
+ v /= 60;
+ m = v % 60;
+ v /= 60;
+ h = v % 24;
+ v /= 24;
+ d = v;
+ buf.append((int) d);
+ buf.append(' ');
+ number(buf, (int) h, 2);
+ buf.append(':');
+ number(buf, (int) m, 2);
+ buf.append(':');
+ number(buf, (int) s, 2);
+ fraction(buf, scale, ms);
+ break;
+ case DAY_TO_MINUTE:
+ v = roundUp(v, 1000 * 60);
+ v /= 1000;
+ v /= 60;
+ m = v % 60;
+ v /= 60;
+ h = v % 24;
+ v /= 24;
+ d = v;
+ buf.append((int) d);
+ buf.append(' ');
+ number(buf, (int) h, 2);
+ buf.append(':');
+ number(buf, (int) m, 2);
+ break;
+ case DAY_TO_HOUR:
+ v = roundUp(v, 1000 * 60 * 60);
+ v /= 1000;
+ v /= 60;
+ v /= 60;
+ h = v % 24;
+ v /= 24;
+ d = v;
+ buf.append((int) d);
+ buf.append(' ');
+ number(buf, (int) h, 2);
+ break;
+ case DAY:
+ v = roundUp(v, 1000 * 60 * 60 * 24);
+ d = v / (1000 * 60 * 60 * 24);
+ buf.append((int) d);
+ break;
+ case HOUR:
+ v = roundUp(v, 1000 * 60 * 60);
+ v /= 1000;
+ v /= 60;
+ v /= 60;
+ h = v;
+ buf.append((int) h);
+ break;
+ case HOUR_TO_MINUTE:
+ v = roundUp(v, 1000 * 60);
+ v /= 1000;
+ v /= 60;
+ m = v % 60;
+ v /= 60;
+ h = v;
+ buf.append((int) h);
+ buf.append(':');
+ number(buf, (int) m, 2);
+ break;
+ case HOUR_TO_SECOND:
+ v = roundUp(v, powerX(10, 3 - scale));
+ ms = v % 1000;
+ v /= 1000;
+ s = v % 60;
+ v /= 60;
+ m = v % 60;
+ v /= 60;
+ h = v;
+ buf.append((int) h);
+ buf.append(':');
+ number(buf, (int) m, 2);
+ buf.append(':');
+ number(buf, (int) s, 2);
+ fraction(buf, scale, ms);
+ break;
+ case MINUTE_TO_SECOND:
+ v = roundUp(v, powerX(10, 3 - scale));
+ ms = v % 1000;
+ v /= 1000;
+ s = v % 60;
+ v /= 60;
+ m = v;
+ buf.append((int) m);
+ buf.append(':');
+ number(buf, (int) s, 2);
+ fraction(buf, scale, ms);
+ break;
+ case MINUTE:
+ v = roundUp(v, 1000 * 60);
+ v /= 1000;
+ v /= 60;
+ m = v;
+ buf.append((int) m);
+ break;
+ case SECOND:
+ v = roundUp(v, powerX(10, 3 - scale));
+ ms = v % 1000;
+ v /= 1000;
+ s = v;
+ buf.append((int) s);
+ fraction(buf, scale, ms);
+ break;
+ default:
+ throw new AssertionError(range);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Rounds a dividend to the nearest divisor.
+ * For example roundUp(31, 10) yields 30; roundUp(37, 10) yields 40.
+ * @param dividend Number to be divided
+ * @param divisor Number to divide by
+ * @return Rounded dividend
+ */
+ private static long roundUp(long dividend, long divisor) {
+ long remainder = dividend % divisor;
+ dividend -= remainder;
+ if (remainder * 2 > divisor) {
+ dividend += divisor;
+ }
+ return dividend;
+ }
+
+ private static void fraction(StringBuilder buf, int scale, long ms) {
+ if (scale > 0) {
+ buf.append('.');
+ long v1 = scale == 3 ? ms
+ : scale == 2 ? ms / 10
+ : scale == 1 ? ms / 100
+ : 0;
+ number(buf, (int) v1, scale);
+ }
+ }
+
+ public static int dateStringToUnixDate(String s) {
+ int hyphen1 = s.indexOf('-');
+ int y;
+ int m;
+ int d;
+ if (hyphen1 < 0) {
+ y = Integer.parseInt(s.trim());
+ m = 1;
+ d = 1;
+ } else {
+ y = Integer.parseInt(s.substring(0, hyphen1).trim());
+ final int hyphen2 = s.indexOf('-', hyphen1 + 1);
+ if (hyphen2 < 0) {
+ m = Integer.parseInt(s.substring(hyphen1 + 1).trim());
+ d = 1;
+ } else {
+ m = Integer.parseInt(s.substring(hyphen1 + 1, hyphen2).trim());
+ d = Integer.parseInt(s.substring(hyphen2 + 1).trim());
+ }
+ }
+ return ymdToUnixDate(y, m, d);
+ }
+
+ public static int timeStringToUnixDate(String v) {
+ return timeStringToUnixDate(v, 0);
+ }
+
+ public static int timeStringToUnixDate(String v, int start) {
+ final int colon1 = v.indexOf(':', start);
+ int hour;
+ int minute;
+ int second;
+ int milli;
+ if (colon1 < 0) {
+ hour = Integer.parseInt(v.trim());
+ minute = 1;
+ second = 1;
+ milli = 0;
+ } else {
+ hour = Integer.parseInt(v.substring(start, colon1).trim());
+ final int colon2 = v.indexOf(':', colon1 + 1);
+ if (colon2 < 0) {
+ minute = Integer.parseInt(v.substring(colon1 + 1).trim());
+ second = 1;
+ milli = 0;
+ } else {
+ minute = Integer.parseInt(v.substring(colon1 + 1, colon2).trim());
+ int dot = v.indexOf('.', colon2);
+ if (dot < 0) {
+ second = Integer.parseInt(v.substring(colon2 + 1).trim());
+ milli = 0;
+ } else {
+ second = Integer.parseInt(v.substring(colon2 + 1, dot).trim());
+ milli = Integer.parseInt(v.substring(dot + 1).trim());
+ }
+ }
+ }
+ return hour * (int) MILLIS_PER_HOUR
+ + minute * (int) MILLIS_PER_MINUTE
+ + second * (int) MILLIS_PER_SECOND
+ + milli;
+ }
+
+ public static long timestampStringToUnixDate(String s) {
+ final long d;
+ final long t;
+ s = s.trim();
+ int space = s.indexOf(' ');
+ if (space >= 0) {
+ d = dateStringToUnixDate(s.substring(0, space));
+ t = timeStringToUnixDate(s, space + 1);
+ } else {
+ d = dateStringToUnixDate(s);
+ t = 0;
+ }
+ return d * MILLIS_PER_DAY + t;
+ }
+
+ public static long unixDateExtract(TimeUnitRange range, long date) {
+ return julianExtract(range, (int) date + EPOCH_JULIAN);
+ }
+
+ private static int julianExtract(TimeUnitRange range, int julian) {
+ // this shifts the epoch back to astronomical year -4800 instead of the
+ // start of the Christian era in year AD 1 of the proleptic Gregorian
+ // calendar.
+ int j = julian + 32044;
+ int g = j / 146097;
+ int dg = j % 146097;
+ int c = (dg / 36524 + 1) * 3 / 4;
+ int dc = dg - c * 36524;
+ int b = dc / 1461;
+ int db = dc % 1461;
+ int a = (db / 365 + 1) * 3 / 4;
+ int da = db - a * 365;
+
+ // integer number of full years elapsed since March 1, 4801 BC
+ int y = g * 400 + c * 100 + b * 4 + a;
+ // integer number of full months elapsed since the last March 1
+ int m = (da * 5 + 308) / 153 - 2;
+ // number of days elapsed since day 1 of the month
+ int d = da - (m + 4) * 153 / 5 + 122;
+ int year = y - 4800 + (m + 2) / 12;
+ int month = (m + 2) % 12 + 1;
+ int day = d + 1;
+ switch (range) {
+ case YEAR:
+ return year;
+ case MONTH:
+ return month;
+ case DAY:
+ return day;
+ default:
+ throw new AssertionError(range);
+ }
+ }
+
+ /** Resets to zero the "time" part of a timestamp. */
+ public static long resetTime(long timestamp) {
+ int date = (int) (timestamp / MILLIS_PER_DAY);
+ return (long) date * MILLIS_PER_DAY;
+ }
+
+ /** Resets to epoch (1970-01-01) the "date" part of a timestamp. */
+ public static long resetDate(long timestamp) {
+ return floorMod(timestamp, MILLIS_PER_DAY);
+ }
+
+ public static long unixTimestampFloor(TimeUnitRange range, long timestamp) {
+ int date = (int) (timestamp / MILLIS_PER_DAY);
+ final int f = julianDateFloor(range, date + EPOCH_JULIAN, true);
+ return (long) f * MILLIS_PER_DAY;
+ }
+
+ public static long unixDateFloor(TimeUnitRange range, long date) {
+ return julianDateFloor(range, (int) date + EPOCH_JULIAN, true);
+ }
+
+ public static long unixTimestampCeil(TimeUnitRange range, long timestamp) {
+ int date = (int) (timestamp / MILLIS_PER_DAY);
+ final int f = julianDateFloor(range, date + EPOCH_JULIAN, false);
+ return (long) f * MILLIS_PER_DAY;
+ }
+
+ public static long unixDateCeil(TimeUnitRange range, long date) {
+ return julianDateFloor(range, (int) date + EPOCH_JULIAN, true);
+ }
+
+ private static int julianDateFloor(TimeUnitRange range, int julian,
+ boolean floor) {
+ // this shifts the epoch back to astronomical year -4800 instead of the
+ // start of the Christian era in year AD 1 of the proleptic Gregorian
+ // calendar.
+ int j = julian + 32044;
+ int g = j / 146097;
+ int dg = j % 146097;
+ int c = (dg / 36524 + 1) * 3 / 4;
+ int dc = dg - c * 36524;
+ int b = dc / 1461;
+ int db = dc % 1461;
+ int a = (db / 365 + 1) * 3 / 4;
+ int da = db - a * 365;
+
+ // integer number of full years elapsed since March 1, 4801 BC
+ int y = g * 400 + c * 100 + b * 4 + a;
+ // integer number of full months elapsed since the last March 1
+ int m = (da * 5 + 308) / 153 - 2;
+ // number of days elapsed since day 1 of the month
+ int d = da - (m + 4) * 153 / 5 + 122;
+ int year = y - 4800 + (m + 2) / 12;
+ int month = (m + 2) % 12 + 1;
+ int day = d + 1;
+ switch (range) {
+ case YEAR:
+ if (!floor && (month > 1 || day > 1)) {
+ ++year;
+ }
+ return ymdToUnixDate(year, 1, 1);
+ case MONTH:
+ if (!floor && day > 1) {
+ ++month;
+ }
+ return ymdToUnixDate(year, month, 1);
+ default:
+ throw new AssertionError(range);
+ }
+ }
+
+ public static int ymdToUnixDate(int year, int month, int day) {
+ final int julian = ymdToJulian(year, month, day);
+ return julian - EPOCH_JULIAN;
+ }
+
+ public static int ymdToJulian(int year, int month, int day) {
+ int a = (14 - month) / 12;
+ int y = year + 4800 - a;
+ int m = month + 12 * a - 3;
+ int j = day + (153 * m + 2) / 5
+ + 365 * y
+ + y / 4
+ - y / 100
+ + y / 400
+ - 32045;
+ if (j < 2299161) {
+ j = day + (153 * m + 2) / 5 + 365 * y + y / 4 - 32083;
+ }
+ return j;
+ }
+
+ public static long unixTimestamp(int year, int month, int day, int hour,
+ int minute, int second) {
+ final int date = ymdToUnixDate(year, month, day);
+ return (long) date * MILLIS_PER_DAY
+ + (long) hour * MILLIS_PER_HOUR
+ + (long) minute * MILLIS_PER_MINUTE
+ + (long) second * MILLIS_PER_SECOND;
+ }
+
+ /** Divide, rounding towards negative infinity. */
+ public static long floorDiv(long x, long y) {
+ long r = x / y;
+ // if the signs are different and modulo not zero, round down
+ if ((x ^ y) < 0 && (r * y != x)) {
+ r--;
+ }
+ return r;
+ }
+
+ /** Modulo, always returning a non-negative result. */
+ public static long floorMod(long x, long y) {
+ return x - floorDiv(x, y) * y;
+ }
+
+ //~ Inner Classes ----------------------------------------------------------
+
+ /**
+ * Helper class for {@link DateTimeUtils#parsePrecisionDateTimeLiteral}
+ */
+ public static class PrecisionTime {
+ private final Calendar cal;
+ private final int precision;
+
+ public PrecisionTime(Calendar cal, int precision) {
+ this.cal = cal;
+ this.precision = precision;
+ }
+
+ public Calendar getCalendar() {
+ return cal;
+ }
+
+ public int getPrecision() {
+ return precision;
+ }
+ }
+}
+
+// End DateTimeUtils.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/IteratorCursor.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/IteratorCursor.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/IteratorCursor.java
new file mode 100644
index 0000000..c09373b
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/IteratorCursor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Implementation of {@link org.apache.calcite.avatica.util.Cursor}
+ * on top of an {@link Iterator} that
+ * returns a record for each row. The returned record is cached to avoid
+ * multiple computations of current row.
+ *
+ * @param <E> Element type
+ */
+public abstract class IteratorCursor<E> extends PositionedCursor<E> {
+ private Position position = Position.BEFORE_START;
+ private final Iterator<E> iterator;
+ private E current = null;
+
+ /**
+ * Creates an {@code IteratorCursor}.
+ *
+ * @param iterator input iterator
+ */
+ protected IteratorCursor(Iterator<E> iterator) {
+ this.iterator = iterator;
+ }
+
+ public boolean next() {
+ if (iterator.hasNext()) {
+ current = iterator.next();
+ position = Position.OK;
+ return true;
+ }
+ current = null;
+ position = Position.AFTER_END;
+ return false;
+ }
+
+ public void close() {
+ current = null;
+ position = Position.CLOSED;
+ if (iterator instanceof AutoCloseable) {
+ try {
+ ((AutoCloseable) iterator).close();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ protected E current() {
+ if (position != Position.OK) {
+ throw new NoSuchElementException();
+ }
+ return current;
+ }
+
+ /** Are we positioned on a valid row? */
+ private enum Position {
+ CLOSED,
+ BEFORE_START,
+ OK,
+ AFTER_END
+ }
+}
+
+// End IteratorCursor.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/ListIteratorCursor.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/ListIteratorCursor.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ListIteratorCursor.java
new file mode 100644
index 0000000..e2801ec
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/ListIteratorCursor.java
@@ -0,0 +1,43 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of {@link Cursor} on top of an
+ * {@link java.util.Iterator} that
+ * returns a {@link List} for each row.
+ */
+public class ListIteratorCursor extends IteratorCursor<List<Object>> {
+
+ /**
+ * Creates a RecordEnumeratorCursor.
+ *
+ * @param iterator Iterator
+ */
+ public ListIteratorCursor(Iterator<List<Object>> iterator) {
+ super(iterator);
+ }
+
+ protected Getter createGetter(int ordinal) {
+ return new ListGetter(ordinal);
+ }
+}
+
+// End ListIteratorCursor.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/MapIteratorCursor.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/MapIteratorCursor.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/MapIteratorCursor.java
new file mode 100644
index 0000000..9ab6c9c
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/MapIteratorCursor.java
@@ -0,0 +1,51 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link Cursor} on top of an
+ * {@link java.util.Iterator} that
+ * returns a {@link Map} for each row.
+ *
+ * <p>The Map contains (field, value) pairs.
+ */
+public class MapIteratorCursor extends IteratorCursor<Map<String, Object>> {
+ private final List<String> fieldNames;
+
+ /**
+ * Creates a MapIteratorCursor.
+ *
+ * @param iterator Iterator
+ * @param fieldNames Field names to project
+ */
+ public MapIteratorCursor(Iterator<Map<String, Object>> iterator,
+ List<String> fieldNames) {
+ super(iterator);
+ assert fieldNames != null;
+ this.fieldNames = fieldNames;
+ }
+
+ protected Getter createGetter(int ordinal) {
+ return new MapGetter<String>(fieldNames.get(ordinal));
+ }
+}
+
+// End MapIteratorCursor.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/PackageMarker.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/PackageMarker.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/PackageMarker.java
new file mode 100644
index 0000000..3a6a9c6
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/PackageMarker.java
@@ -0,0 +1,37 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This is a dummy annotation that forces javac to produce output for
+ * otherwise empty package-info.java.
+ *
+ * <p>The result is maven-compiler-plugin can properly identify the scope of
+ * changed files
+ *
+ * <p>See more details in
+ * <a href="https://jira.codehaus.org/browse/MCOMPILER-205">
+ * maven-compiler-plugin: incremental compilation broken</a>
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface PackageMarker {
+}
+
+// End PackageMarker.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/PositionedCursor.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/PositionedCursor.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/PositionedCursor.java
new file mode 100644
index 0000000..f60f47d
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/PositionedCursor.java
@@ -0,0 +1,134 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract implementation of {@link org.apache.calcite.avatica.util.Cursor}
+ * that caches its current row.
+ *
+ * @param <T> Element type
+ */
+public abstract class PositionedCursor<T> extends AbstractCursor {
+ /**
+ * Returns the current row.
+ *
+ * @return current row
+ *
+ * @throws java.util.NoSuchElementException if the iteration has no more
+ * elements
+ */
+ protected abstract T current();
+
+ /** Implementation of
+ * {@link org.apache.calcite.avatica.util.AbstractCursor.Getter}
+ * that reads from records that are arrays. */
+ protected class ArrayGetter extends AbstractGetter {
+ protected final int field;
+
+ public ArrayGetter(int field) {
+ this.field = field;
+ }
+
+ public Object getObject() {
+ Object o = ((Object[]) current())[field];
+ wasNull[0] = o == null;
+ return o;
+ }
+ }
+
+ /** Implementation of
+ * {@link org.apache.calcite.avatica.util.AbstractCursor.Getter}
+ * that reads items from a list. */
+ protected class ListGetter extends AbstractGetter {
+ protected final int index;
+
+ public ListGetter(int index) {
+ this.index = index;
+ }
+
+ public Object getObject() {
+ Object o = ((List) current()).get(index);
+ wasNull[0] = o == null;
+ return o;
+ }
+ }
+
+ /** Implementation of
+ * {@link org.apache.calcite.avatica.util.AbstractCursor.Getter}
+ * for records that consist of a single field.
+ *
+ * <p>Each record is represented as an object, and the value of the sole
+ * field is that object. */
+ protected class ObjectGetter extends AbstractGetter {
+ public ObjectGetter(int field) {
+ assert field == 0;
+ }
+
+ public Object getObject() {
+ Object o = current();
+ wasNull[0] = o == null;
+ return o;
+ }
+ }
+
+ /** Implementation of
+ * {@link org.apache.calcite.avatica.util.AbstractCursor.Getter}
+ * that reads fields via reflection. */
+ protected class FieldGetter extends AbstractGetter {
+ protected final Field field;
+
+ public FieldGetter(Field field) {
+ this.field = field;
+ }
+
+ public Object getObject() {
+ Object o;
+ try {
+ o = field.get(current());
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ wasNull[0] = o == null;
+ return o;
+ }
+ }
+
+ /** Implementation of
+ * {@link org.apache.calcite.avatica.util.AbstractCursor.Getter}
+ * that reads entries from a {@link java.util.Map}. */
+ protected class MapGetter<K> extends AbstractGetter {
+ protected final K key;
+
+ public MapGetter(K key) {
+ this.key = key;
+ }
+
+ public Object getObject() {
+ @SuppressWarnings("unchecked") final Map<K, Object> map =
+ (Map<K, Object>) current();
+ Object o = map.get(key);
+ wasNull[0] = o == null;
+ return o;
+ }
+ }
+}
+
+// End PositionedCursor.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/Quoting.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/Quoting.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Quoting.java
new file mode 100644
index 0000000..855e4a6
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Quoting.java
@@ -0,0 +1,37 @@
+/*
+ * 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.calcite.avatica.util;
+
+/** Syntax for quoting identifiers in SQL statements. */
+public enum Quoting {
+ /** Quote identifiers in double-quotes. For example, {@code "my id"}. */
+ DOUBLE_QUOTE("\""),
+
+ /** Quote identifiers in back-quotes. For example, {@code `my id`}. */
+ BACK_TICK("`"),
+
+ /** Quote identifiers in brackets. For example, {@code [my id]}. */
+ BRACKET("[");
+
+ public String string;
+
+ Quoting(String string) {
+ this.string = string;
+ }
+}
+
+// End Quoting.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/RecordIteratorCursor.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/RecordIteratorCursor.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/RecordIteratorCursor.java
new file mode 100644
index 0000000..717247d
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/RecordIteratorCursor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of {@link org.apache.calcite.avatica.util.Cursor} on top of an
+ * {@link java.util.Iterator} that
+ * returns a record for each row. The record is a synthetic class whose fields
+ * are all public.
+ *
+ * @param <E> Element type
+ */
+public class RecordIteratorCursor<E> extends IteratorCursor<E> {
+ private final List<Field> fields;
+
+ /**
+ * Creates a RecordIteratorCursor.
+ *
+ * @param iterator Iterator
+ * @param clazz Element type
+ */
+ public RecordIteratorCursor(Iterator<E> iterator, Class<E> clazz) {
+ this(iterator, clazz, Arrays.asList(clazz.getFields()));
+ }
+
+ /**
+ * Creates a RecordIteratorCursor that projects particular fields.
+ *
+ * @param iterator Iterator
+ * @param clazz Element type
+ * @param fields Fields to project
+ */
+ public RecordIteratorCursor(Iterator<E> iterator, Class<E> clazz,
+ List<Field> fields) {
+ super(iterator);
+ this.fields = fields;
+ }
+
+ protected Getter createGetter(int ordinal) {
+ return new FieldGetter(fields.get(ordinal));
+ }
+}
+
+// End RecordIteratorCursor.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spacer.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spacer.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spacer.java
new file mode 100644
index 0000000..cc0a097
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spacer.java
@@ -0,0 +1,80 @@
+/*
+ * 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.calcite.avatica.util;
+
+/**
+ * Efficiently writes strings of spaces.
+ */
+public class Spacer {
+ private int n;
+
+ /** Creates a Spacer with zero spaces. */
+ public Spacer() {
+ this(0);
+ }
+
+ /** Creates a Spacer with a given number of spaces. */
+ public Spacer(int n) {
+ set(n);
+ }
+
+ /** Sets the current number of spaces. */
+ public Spacer set(int n) {
+ this.n = n;
+ return this;
+ }
+
+ /** Returns the current number of spaces. */
+ public int get() {
+ return n;
+ }
+
+ /** Increases the current number of spaces by {@code n}. */
+ public Spacer add(int n) {
+ return set(this.n + n);
+ }
+
+ /** Reduces the current number of spaces by {@code n}. */
+ public Spacer subtract(int n) {
+ return set(this.n - n);
+ }
+
+ /** Returns a string of the current number of spaces. */
+ public String toString() {
+ return Spaces.of(n);
+ }
+
+ /** Appends current number of spaces to a {@link StringBuilder}. */
+ public StringBuilder spaces(StringBuilder buf) {
+ return Spaces.append(buf, n);
+ }
+
+ /** Returns a string that is padded on the right with spaces to the current
+ * length. */
+ public String padRight(String string) {
+ Spaces.padRight(string, n);
+ final int x = n - string.length();
+ if (x <= 0) {
+ return string;
+ }
+ // Replacing StringBuffer with String would hurt performance.
+ //noinspection StringBufferReplaceableByString
+ return Spaces.append(new StringBuilder(string), x).toString();
+ }
+}
+
+// End Spacer.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spaces.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spaces.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spaces.java
new file mode 100644
index 0000000..6469400
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/Spaces.java
@@ -0,0 +1,185 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Utilities for creating strings of spaces. */
+public class Spaces {
+ /** It doesn't look like this list is ever updated. But it is - when a call to
+ * to {@link SpaceList#get} causes an {@link IndexOutOfBoundsException}. */
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+ private static final List<String> SPACE_LIST = new SpaceList();
+
+ /** The longest possible string of spaces. Fine as long as you don't try
+ * to print it.
+ *
+ * <p>Use with {@link StringBuilder#append(CharSequence, int, int)} to
+ * append spaces without doing memory allocation.</p>
+ */
+ public static final CharSequence MAX = sequence(Integer.MAX_VALUE);
+
+ // Utility class. Do not instantiate.
+ private Spaces() {}
+
+ /** Creates a sequence of {@code n} spaces. */
+ public static CharSequence sequence(int n) {
+ return new SpaceString(n);
+ }
+
+ /** Returns a string of {@code n} spaces. */
+ public static String of(int n) {
+ return SPACE_LIST.get(n);
+ }
+
+ /** Appends {@code n} spaces to an {@link Appendable}. */
+ public static Appendable append(Appendable buf, int n) throws IOException {
+ buf.append(MAX, 0, n);
+ return buf;
+ }
+
+ /** Appends {@code n} spaces to a {@link PrintWriter}. */
+ public static PrintWriter append(PrintWriter pw, int n) {
+ pw.append(MAX, 0, n);
+ return pw;
+ }
+
+ /** Appends {@code n} spaces to a {@link StringWriter}. */
+ public static StringWriter append(StringWriter pw, int n) {
+ pw.append(MAX, 0, n);
+ return pw;
+ }
+
+ /** Appends {@code n} spaces to a {@link StringBuilder}. */
+ public static StringBuilder append(StringBuilder buf, int n) {
+ buf.append(MAX, 0, n);
+ return buf;
+ }
+
+ /** Appends {@code n} spaces to a {@link StringBuffer}. */
+ public static StringBuffer append(StringBuffer buf, int n) {
+ buf.append(MAX, 0, n);
+ return buf;
+ }
+
+ /** Returns a string that is padded on the right with spaces to the given
+ * length. */
+ public static String padRight(String string, int n) {
+ final int x = n - string.length();
+ if (x <= 0) {
+ return string;
+ }
+ // Replacing StringBuffer with String would hurt performance.
+ //noinspection StringBufferReplaceableByString
+ return append(new StringBuilder(string), x).toString();
+ }
+
+ /** Returns a string that is padded on the left with spaces to the given
+ * length. */
+ public static String padLeft(String string, int n) {
+ final int x = n - string.length();
+ if (x <= 0) {
+ return string;
+ }
+ // Replacing StringBuffer with String would hurt performance.
+ //noinspection StringBufferReplaceableByString
+ return append(new StringBuilder(), x).append(string).toString();
+ }
+
+ /** A string of spaces. */
+ private static class SpaceString implements CharSequence {
+ private final int length;
+
+ private SpaceString(int length) {
+ this.length = length;
+ }
+
+ // Do not override equals and hashCode to be like String. CharSequence does
+ // not require it.
+
+ @SuppressWarnings("NullableProblems")
+ @Override public String toString() {
+ return of(length);
+ }
+
+ public int length() {
+ return length;
+ }
+
+ public char charAt(int index) {
+ return ' ';
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return new SpaceString(end - start);
+ }
+ }
+
+ /** List whose {@code i}th entry is a string consisting of {@code i} spaces.
+ * It populates itself the first time you ask for a particular string, and
+ * caches the result. */
+ private static class SpaceList extends CopyOnWriteArrayList<String> {
+ @Override public String get(int index) {
+ for (;;) {
+ try {
+ return super.get(index);
+ } catch (IndexOutOfBoundsException e) {
+ if (index < 0) {
+ throw e;
+ }
+ populate(Math.max(16, index + 1));
+ }
+ }
+ }
+
+ /**
+ * Populates this list with all prefix strings of a given string. All
+ * of the prefix strings share the same backing array of chars.
+ */
+ private synchronized void populate(int newSize) {
+ final int size = size();
+ if (newSize <= size) {
+ return;
+ }
+ final char[] chars = new char[newSize];
+ Arrays.fill(chars, ' ');
+ final int length = newSize - size;
+ final int offset = size;
+
+ // addAll is much more efficient than repeated add for
+ // CopyOnWriteArrayList
+ addAll(
+ new AbstractList<String>() {
+ public String get(int index) {
+ return new String(chars, 0, offset + index);
+ }
+
+ public int size() {
+ return length;
+ }
+ });
+ }
+ }
+}
+
+// End Spaces.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/StructImpl.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/StructImpl.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/StructImpl.java
new file mode 100644
index 0000000..b25fce6
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/StructImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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.calcite.avatica.util;
+
+import org.apache.calcite.avatica.ColumnMetaData;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Struct;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/** Implementation of JDBC {@link Struct}. */
+public class StructImpl implements Struct {
+ private final List list;
+
+ public StructImpl(List list) {
+ this.list = list;
+ }
+
+ @Override public String toString() {
+ final Iterator iterator = list.iterator();
+ if (!iterator.hasNext()) {
+ return "{}";
+ }
+ final StringBuilder buf = new StringBuilder("{");
+ for (;;) {
+ append(buf, iterator.next());
+ if (!iterator.hasNext()) {
+ return buf.append("}").toString();
+ }
+ buf.append(", ");
+ }
+ }
+
+ @Override public String getSQLTypeName() throws SQLException {
+ return "ROW";
+ }
+
+ @Override public Object[] getAttributes() throws SQLException {
+ return list.toArray();
+ }
+
+ @Override public Object[] getAttributes(Map<String, Class<?>> map)
+ throws SQLException {
+ throw new UnsupportedOperationException(); // TODO
+ }
+
+ private void append(StringBuilder buf, Object o) {
+ if (o == null) {
+ buf.append("null");
+ } else {
+ buf.append(o);
+ }
+ }
+
+ /** Factory that can create a result set based on a list of values. */
+ public interface Factory {
+ ResultSet create(ColumnMetaData.AvaticaType elementType,
+ Iterable<Object> iterable);
+ }
+}
+
+// End StructImpl.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
new file mode 100644
index 0000000..b249232
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnit.java
@@ -0,0 +1,75 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.math.BigDecimal;
+
+/**
+ * Enumeration of time units used to construct an interval.
+ */
+public enum TimeUnit {
+ YEAR(true, ' ', 12 /* months */, null),
+ MONTH(true, '-', 1 /* months */, BigDecimal.valueOf(12)),
+ DAY(false, '-', DateTimeUtils.MILLIS_PER_DAY, null),
+ HOUR(false, ' ', DateTimeUtils.MILLIS_PER_HOUR, BigDecimal.valueOf(24)),
+ MINUTE(false, ':', DateTimeUtils.MILLIS_PER_MINUTE, BigDecimal.valueOf(60)),
+ SECOND(false, ':', DateTimeUtils.MILLIS_PER_SECOND, BigDecimal.valueOf(60)),
+
+ /** Unlike the other units, MILLISECOND may not be the unit of a SQL interval.
+ * Still, it is convenient to use it internally, when converting to and from
+ * UNIX timestamps. */
+ MILLISECOND(false, '.', 1, BigDecimal.valueOf(1));
+
+ public final boolean yearMonth;
+ public final char separator;
+ public final long multiplier;
+ private final BigDecimal limit;
+
+ private static final TimeUnit[] CACHED_VALUES = values();
+
+ TimeUnit(boolean yearMonth, char separator, long multiplier,
+ BigDecimal limit) {
+ this.yearMonth = yearMonth;
+ this.separator = separator;
+ this.multiplier = multiplier;
+ this.limit = limit;
+ }
+
+ /**
+ * Returns the TimeUnit associated with an ordinal. The value returned
+ * is null if the ordinal is not a member of the TimeUnit enumeration.
+ */
+ public static TimeUnit getValue(int ordinal) {
+ return ordinal < 0 || ordinal >= CACHED_VALUES.length
+ ? null
+ : CACHED_VALUES[ordinal];
+ }
+
+ /**
+ * Returns whether a given value is valid for a field of this time unit.
+ *
+ * @param field Field value
+ * @return Whether value
+ */
+ public boolean isValidValue(BigDecimal field) {
+ return field.compareTo(BigDecimal.ZERO) >= 0
+ && (limit == null
+ || field.compareTo(limit) < 0);
+ }
+}
+
+// End TimeUnit.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
new file mode 100644
index 0000000..4d9b322
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/TimeUnitRange.java
@@ -0,0 +1,107 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/** A range of time units. The first is more significant than the
+ * other (e.g. year-to-day) or the same as the other (e.g. month). */
+public enum TimeUnitRange {
+ YEAR(TimeUnit.YEAR, null),
+ YEAR_TO_MONTH(TimeUnit.YEAR, TimeUnit.MONTH),
+ MONTH(TimeUnit.MONTH, null),
+ DAY(TimeUnit.DAY, null),
+ DAY_TO_HOUR(TimeUnit.DAY, TimeUnit.HOUR),
+ DAY_TO_MINUTE(TimeUnit.DAY, TimeUnit.MINUTE),
+ DAY_TO_SECOND(TimeUnit.DAY, TimeUnit.SECOND),
+ HOUR(TimeUnit.HOUR, null),
+ HOUR_TO_MINUTE(TimeUnit.HOUR, TimeUnit.MINUTE),
+ HOUR_TO_SECOND(TimeUnit.HOUR, TimeUnit.SECOND),
+ MINUTE(TimeUnit.MINUTE, null),
+ MINUTE_TO_SECOND(TimeUnit.MINUTE, TimeUnit.SECOND),
+ SECOND(TimeUnit.SECOND, null);
+
+ public final TimeUnit startUnit;
+ public final TimeUnit endUnit;
+
+ private static final Map<Pair<TimeUnit>, TimeUnitRange> MAP = createMap();
+
+ /**
+ * Creates a TimeUnitRange.
+ *
+ * @param startUnit Start time unit
+ * @param endUnit End time unit
+ */
+ TimeUnitRange(TimeUnit startUnit, TimeUnit endUnit) {
+ assert startUnit != null;
+ this.startUnit = startUnit;
+ this.endUnit = endUnit;
+ }
+
+ /**
+ * Returns a {@code TimeUnitRange} with a given start and end unit.
+ *
+ * @param startUnit Start unit
+ * @param endUnit End unit
+ * @return Time unit range, or null if not valid
+ */
+ public static TimeUnitRange of(TimeUnit startUnit, TimeUnit endUnit) {
+ return MAP.get(new Pair<>(startUnit, endUnit));
+ }
+
+ private static Map<Pair<TimeUnit>, TimeUnitRange> createMap() {
+ Map<Pair<TimeUnit>, TimeUnitRange> map = new HashMap<>();
+ for (TimeUnitRange value : values()) {
+ map.put(new Pair<>(value.startUnit, value.endUnit), value);
+ }
+ return Collections.unmodifiableMap(map);
+ }
+
+ /** Whether this is in the YEAR-TO-MONTH family of intervals. */
+ public boolean monthly() {
+ return ordinal() <= MONTH.ordinal();
+ }
+
+ /** Immutable pair of values of the same type. */
+ private static class Pair<E> {
+ final E left;
+ final E right;
+
+ private Pair(E left, E right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override public int hashCode() {
+ int k = (left == null) ? 0 : left.hashCode();
+ int k1 = (right == null) ? 0 : right.hashCode();
+ return ((k << 4) | k) ^ k1;
+ }
+
+ @Override public boolean equals(Object obj) {
+ return obj == this
+ || obj instanceof Pair
+ && Objects.equals(left, ((Pair) obj).left)
+ && Objects.equals(right, ((Pair) obj).right);
+ }
+ }
+}
+
+// End TimeUnitRange.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/UnsynchronizedBuffer.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/UnsynchronizedBuffer.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/UnsynchronizedBuffer.java
new file mode 100644
index 0000000..8daee60
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/UnsynchronizedBuffer.java
@@ -0,0 +1,152 @@
+/*
+ * 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.calcite.avatica.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A utility class for reading and writing bytes to byte buffers without synchronization. A
+ * reduced variant taken from Apache Accumulo. This class is <b>not</b> thread-safe by design.
+ * It is up to the caller to guarantee mutual exclusion as necessary.
+ */
+public class UnsynchronizedBuffer extends OutputStream {
+ // Anything larger than 64K, reap the backing buffer
+ private static final int LARGE_BUFFER_SIZE = 1024 * 64;
+
+ final int initialCapacity;
+ int offset = 0;
+ byte[] data;
+
+ /**
+ * Creates a new writer.
+ */
+ public UnsynchronizedBuffer() {
+ this(4096);
+ }
+
+ /**
+ * Creates a new writer.
+ *
+ * @param initialCapacity initial byte capacity
+ */
+ public UnsynchronizedBuffer(int initialCapacity) {
+ this.initialCapacity = initialCapacity;
+ data = new byte[initialCapacity];
+ }
+
+ private void reserve(int l) {
+ if (offset + l > data.length) {
+ int newSize = UnsynchronizedBuffer.nextArraySize(offset + l);
+
+ byte[] newData = new byte[newSize];
+ System.arraycopy(data, 0, newData, 0, offset);
+ data = newData;
+ }
+
+ }
+
+ /**
+ * Adds bytes to this writer's buffer.
+ *
+ * @param bytes byte array
+ * @param off offset into array to start copying bytes
+ * @param length number of bytes to add
+ * @throws IndexOutOfBoundsException if off or length are invalid
+ */
+ public void write(byte[] bytes, int off, int length) {
+ reserve(length);
+ System.arraycopy(bytes, off, data, offset, length);
+ offset += length;
+ }
+
+ @Override public void write(int b) throws IOException {
+ reserve(1);
+ data[offset] = (byte) b;
+ offset++;
+ }
+
+ /**
+ * Gets (a copy of) the contents of this writer's buffer.
+ *
+ * @return byte buffer contents
+ */
+ public byte[] toArray() {
+ byte[] ret = new byte[offset];
+ System.arraycopy(data, 0, ret, 0, offset);
+ return ret;
+ }
+
+ /**
+ * Resets the internal pointer into the buffer.
+ */
+ public void reset() {
+ offset = 0;
+ if (data.length >= LARGE_BUFFER_SIZE) {
+ data = new byte[this.initialCapacity];
+ }
+ }
+
+ /**
+ * @return The current offset into the backing array.
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
+ * @return The current length of the backing array.
+ */
+ public long getSize() {
+ return data.length;
+ }
+
+ /**
+ * Determines what next array size should be by rounding up to next power of two.
+ *
+ * @param i current array size
+ * @return next array size
+ * @throws IllegalArgumentException if i is negative
+ */
+ public static int nextArraySize(int i) {
+ if (i < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (i > (1 << 30)) {
+ return Integer.MAX_VALUE; // this is the next power of 2 minus one... a special case
+ }
+
+ if (i == 0) {
+ return 1;
+ }
+
+ // round up to next power of two
+ int ret = i;
+ ret--;
+ ret |= ret >> 1;
+ ret |= ret >> 2;
+ ret |= ret >> 4;
+ ret |= ret >> 8;
+ ret |= ret >> 16;
+ ret++;
+
+ return ret;
+ }
+}
+
+// End UnsynchronizedBuffer.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/5cee486f/avatica/core/src/main/java/org/apache/calcite/avatica/util/package-info.java
----------------------------------------------------------------------
diff --git a/avatica/core/src/main/java/org/apache/calcite/avatica/util/package-info.java b/avatica/core/src/main/java/org/apache/calcite/avatica/util/package-info.java
new file mode 100644
index 0000000..eab457c
--- /dev/null
+++ b/avatica/core/src/main/java/org/apache/calcite/avatica/util/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * Avatica utilities.
+ */
+@PackageMarker
+package org.apache.calcite.avatica.util;
+
+// End package-info.java