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
