This is an automated email from the ASF dual-hosted git repository. stoty pushed a commit to branch 5.1 in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/5.1 by this push: new 87f8216fe0 PHOENIX-6881 Implement the applicable Date/Time features from JDBC 4.2 87f8216fe0 is described below commit 87f8216fe00058825b34848c0d0ddc94bf5bcdc6 Author: Istvan Toth <st...@apache.org> AuthorDate: Tue Feb 28 15:55:00 2023 +0100 PHOENIX-6881 Implement the applicable Date/Time features from JDBC 4.2 --- .../java/org/apache/phoenix/end2end/Array1IT.java | 50 +++++ .../org/apache/phoenix/end2end/DateTimeIT.java | 215 ++++++++++++++++++++- .../org/apache/phoenix/end2end/GetSetObjectIT.java | 183 ++++++++++++++++++ .../apache/phoenix/compile/ColumnProjector.java | 14 +- .../phoenix/compile/ExpressionProjector.java | 23 +++ .../phoenix/jdbc/PhoenixPreparedStatement.java | 85 +++++--- .../org/apache/phoenix/jdbc/PhoenixResultSet.java | 32 ++- .../phoenix/schema/types/PArrayDataType.java | 15 +- .../apache/phoenix/schema/types/PBinaryBase.java | 14 ++ .../org/apache/phoenix/schema/types/PBoolean.java | 14 ++ .../org/apache/phoenix/schema/types/PChar.java | 13 ++ .../org/apache/phoenix/schema/types/PDataType.java | 34 +++- .../phoenix/schema/types/PDataTypeFactory.java | 47 +++++ .../org/apache/phoenix/schema/types/PDate.java | 42 ++++ .../apache/phoenix/schema/types/PNumericType.java | 14 ++ .../org/apache/phoenix/schema/types/PTime.java | 43 ++++- .../apache/phoenix/schema/types/PTimestamp.java | 40 +++- .../apache/phoenix/schema/types/PUnsignedDate.java | 13 +- .../apache/phoenix/schema/types/PUnsignedTime.java | 12 +- .../org/apache/phoenix/schema/types/PVarchar.java | 12 ++ .../java/org/apache/phoenix/util/DateUtil.java | 4 +- 21 files changed, 867 insertions(+), 52 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Array1IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Array1IT.java index 85958f86cd..f5e9587783 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Array1IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Array1IT.java @@ -23,6 +23,7 @@ import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.sql.Array; import java.sql.Connection; @@ -31,6 +32,7 @@ import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.Properties; import org.apache.phoenix.query.BaseTest; @@ -1002,4 +1004,52 @@ public class Array1IT extends ArrayIT { conn.close(); } + @Test + public void testGetSetObject() throws SQLException { + String table = generateUniqueName(); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + String[] s = new String[] { "1", "2" }; + try (Connection conn = DriverManager.getConnection(getUrl(), props); + Statement stmt = conn.createStatement(); + PreparedStatement insertStmt = + conn.prepareStatement("upsert into " + table + "(k, a) values (?,?)")) { + stmt.executeUpdate( + "CREATE TABLE " + table + " ( k VARCHAR PRIMARY KEY, a VARCHAR(5) ARRAY)"); + PhoenixArray phoenixArray = (PhoenixArray) conn.createArrayOf("VARCHAR", s); + + insertStmt.setString(1, "a"); + insertStmt.setArray(2, phoenixArray); + insertStmt.executeUpdate(); + insertStmt.setString(1, "b"); + insertStmt.setObject(2, phoenixArray); + insertStmt.executeUpdate(); + conn.commit(); + try { + insertStmt.setString(1, "c"); + insertStmt.setObject(2, s); + insertStmt.executeUpdate(); + conn.commit(); + fail("must only accept java.sql.Array objects"); + } catch (Exception e) { + // FIXME we should be throwing an SQLException + // Success + } + + ResultSet rs = stmt.executeQuery("select * from " + table); + int rows = 0; + while (rs.next()) { + rows++; + assertEquals(phoenixArray, rs.getObject(2)); + assertEquals(phoenixArray, rs.getObject(2, java.sql.Array.class)); + assertEquals(phoenixArray, rs.getArray(2)); + try { + rs.getObject(2, String[].class); + fail("must only accept java.sql.Array subclasses"); + } catch (SQLException e) { + // Success + } + } + assertEquals(2, rows); + } + } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java index 61378fbc3d..af6594afd5 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DateTimeIT.java @@ -53,13 +53,14 @@ import java.sql.Timestamp; import java.sql.Types; import java.text.Format; import java.time.LocalDate; +import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.List; import java.util.Properties; import java.util.TimeZone; -import java.util.List; -import java.util.Arrays; import org.apache.commons.lang3.time.FastDateFormat; import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; @@ -69,6 +70,7 @@ import org.apache.phoenix.compile.StatementContext; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.query.QueryConstants; +import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.types.PDataType; import org.apache.phoenix.schema.types.PDate; import org.apache.phoenix.schema.types.PTime; @@ -2157,7 +2159,7 @@ public class DateTimeIT extends ParallelStatsDisabledIT { Connection conn1 = DriverManager.getConnection(getUrl(), props); String tableName = generateUniqueName(); - String ddl = "CREATE TABLE IF NOT EXISTS " + tableName + + String ddl = "CREATE TABLE " + tableName + " (k1 INTEGER PRIMARY KEY," + " v_date DATE," + " v_time TIME," + @@ -2212,4 +2214,211 @@ public class DateTimeIT extends ParallelStatsDisabledIT { assertTrue(formatter instanceof FastDateFormat); assertEquals(((FastDateFormat)formatter).getTimeZone().getID(), timeZoneId); } + + @Test + public void testJdbc42ApiWithoutDisplacement() throws SQLException { + testJdbc42Api(false); + } + + @Test + public void testJdbc42ApiWithDisplacement() throws SQLException { + testJdbc42Api(true); + } + + private void testJdbc42Api(boolean withDisplacement) throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + ZoneId testZoneId; + if (withDisplacement) { + testZoneId = ZoneOffset.systemDefault(); + props.setProperty(QueryServices.APPLY_TIME_ZONE_DISPLACMENT_ATTRIB, + Boolean.TRUE.toString()); + } else { + testZoneId = ZoneId.of("UTC"); + } + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + + String tableName = generateUniqueName(); + String ddl = + "CREATE TABLE " + tableName + + " (ID INTEGER PRIMARY KEY, D DATE, T TIME, S TIMESTAMP, " + + "UD UNSIGNED_DATE, UT UNSIGNED_TIME, US UNSIGNED_TIMESTAMP)"; + java.time.LocalDateTime localDateTime = java.time.LocalDateTime.now(); + java.time.LocalDateTime localDateTimeMs = + localDateTime.withNano((localDateTime.getNano() / 1000000) * 1000000); + java.time.LocalDate localDate = localDateTime.toLocalDate(); + java.time.LocalTime localTime = localDateTime.toLocalTime(); + java.time.LocalTime localTimeMs = + localTime.withNano((localDateTime.getNano() / 1000000) * 1000000); + + java.sql.Timestamp javaSqlTimestampFull = + java.sql.Timestamp.from(localDateTime.atZone(testZoneId).toInstant()); + java.sql.Timestamp javaSqlTimestampFullMs = + new java.sql.Timestamp(javaSqlTimestampFull.getTime()); + java.sql.Timestamp javaSqlTimestampDatePart = + java.sql.Timestamp + .from(localDate.atStartOfDay().atZone(testZoneId).toInstant()); + java.sql.Timestamp javaSqlTimestampTimePart = + java.sql.Timestamp.from( + localTime.atDate(DateUtil.LD_EPOCH).atZone(testZoneId).toInstant()); + java.sql.Timestamp javaSqlTimestampTimePartMs = + new java.sql.Timestamp(javaSqlTimestampTimePart.getTime()); + + java.util.Date javaUtilDateFull = new java.util.Date(javaSqlTimestampFull.getTime()); + java.util.Date javaUtilDateDatePart = + new java.util.Date(javaSqlTimestampDatePart.getTime()); + java.util.Date javaUtilDateTimePart = + new java.util.Date(javaSqlTimestampTimePart.getTime()); + + java.sql.Date javaSqlDateFull = new java.sql.Date(javaSqlTimestampFull.getTime()); + java.sql.Time javaSqlTimeFull = new java.sql.Time(javaSqlTimestampFull.getTime()); + + // Differ by date + String dml = "UPSERT INTO " + tableName + " VALUES (?, ?, ?, ?, ?, ?, ?)"; + try (Statement stmt = conn.createStatement(); + PreparedStatement ps = conn.prepareStatement(dml)) { + stmt.executeUpdate(ddl); + + ps.setInt(1, 1); + ps.setObject(2, localDateTime); + ps.setObject(3, localDateTime); + ps.setObject(4, localDateTime); + ps.setObject(5, localDateTime); + ps.setObject(6, localDateTime); + ps.setObject(7, localDateTime); + assertEquals(1, ps.executeUpdate()); + + ps.setInt(1, 2); + ps.setObject(2, localDate); + ps.setObject(3, localDate); + ps.setObject(4, localDate); + ps.setObject(5, localDate); + ps.setObject(6, localDate); + ps.setObject(7, localDate); + assertEquals(1, ps.executeUpdate()); + + ps.setInt(1, 3); + ps.setObject(2, localTime); + ps.setObject(3, localTime); + ps.setObject(4, localTime); + ps.setObject(5, localTime); + ps.setObject(6, localTime); + ps.setObject(7, localTime); + assertEquals(1, ps.executeUpdate()); + + ps.setInt(1, 4); + ps.setObject(2, javaSqlTimestampFull); + ps.setObject(3, javaSqlTimestampFull); + ps.setObject(4, javaSqlTimestampFull); + ps.setObject(5, javaSqlTimestampFull); + ps.setObject(6, javaSqlTimestampFull); + ps.setObject(7, javaSqlTimestampFull); + assertEquals(1, ps.executeUpdate()); + + ps.setInt(1, 5); + ps.setObject(2, javaSqlDateFull); + ps.setObject(3, javaSqlDateFull); + ps.setObject(4, javaSqlDateFull); + ps.setObject(5, javaSqlDateFull); + ps.setObject(6, javaSqlDateFull); + ps.setObject(7, javaSqlDateFull); + assertEquals(1, ps.executeUpdate()); + + ps.setInt(1, 6); + ps.setObject(2, javaSqlTimeFull); + ps.setObject(3, javaSqlTimeFull); + ps.setObject(4, javaSqlTimeFull); + ps.setObject(5, javaSqlTimeFull); + ps.setObject(6, javaSqlTimeFull); + ps.setObject(7, javaSqlTimeFull); + assertEquals(1, ps.executeUpdate()); + + conn.commit(); + ResultSet rs = stmt.executeQuery("select * from " + tableName + " order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + for (int i = 2; i <= 7; i++) { + if (i == 4 || i == 7) { + // Nanos + assertEquals(javaSqlTimestampFull, rs.getTimestamp(i)); + assertEquals(localDateTime, rs.getObject(i, java.time.LocalDateTime.class)); + // Non-standard extensions + assertEquals(localTime, rs.getObject(i, java.time.LocalTime.class)); + } else { + // Millis + assertEquals(javaSqlDateFull, rs.getTimestamp(i)); + assertEquals(localDateTimeMs, + rs.getObject(i, java.time.LocalDateTime.class)); + // Non-standard extension + assertEquals(localTimeMs, rs.getObject(i, java.time.LocalTime.class)); + + } + // Non-standard extension + assertEquals(localDate, rs.getObject(i, java.time.LocalDate.class)); + assertEquals(javaUtilDateFull, rs.getObject(i, java.util.Date.class)); + } + // Make sure name based getObject() works + assertEquals(localDateTimeMs, rs.getObject("D", java.time.LocalDateTime.class)); + assertEquals(localDateTimeMs, rs.getObject("T", java.time.LocalDateTime.class)); + assertEquals(localDateTime, rs.getObject("S", java.time.LocalDateTime.class)); + assertEquals(localDateTimeMs, rs.getObject("UD", java.time.LocalDateTime.class)); + assertEquals(localDateTimeMs, rs.getObject("UT", java.time.LocalDateTime.class)); + assertEquals(localDateTime, rs.getObject("US", java.time.LocalDateTime.class)); + + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + for (int i = 2; i <= 7; i++) { + assertEquals(javaSqlTimestampDatePart, rs.getTimestamp(i)); + assertEquals(javaUtilDateDatePart, rs.getObject(i, java.util.Date.class)); + } + assertEquals(localDate, rs.getObject(2, java.time.LocalDate.class)); + assertEquals(localDate, rs.getObject(5, java.time.LocalDate.class)); + + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + for (int i = 2; i <= 7; i++) { + if (i == 4 || i == 7) { + assertEquals(javaSqlTimestampTimePart, rs.getTimestamp(i)); + // Can read nanos from timestamp field + assertEquals(localTime, rs.getObject(i, java.time.LocalTime.class)); + } else { + // Millis + assertEquals(javaSqlTimestampTimePartMs, rs.getTimestamp(i)); + } + assertEquals(javaUtilDateTimePart, rs.getObject(i, java.util.Date.class)); + } + assertEquals(localTimeMs, rs.getObject(3, java.time.LocalTime.class)); + assertEquals(localTimeMs, rs.getObject(6, java.time.LocalTime.class)); + + assertTrue(rs.next()); + assertEquals(4, rs.getInt(1)); + for (int i = 2; i <= 7; i++) { + if (i == 4 || i == 7) { + // Nanos + assertEquals(javaSqlTimestampFull, + rs.getObject(i, java.sql.Timestamp.class)); + } else { + // Millis + assertEquals(javaSqlTimestampFullMs, + rs.getObject(i, java.sql.Timestamp.class)); + } + } + + assertTrue(rs.next()); + assertEquals(5, rs.getInt(1)); + for (int i = 2; i <= 7; i++) { + assertEquals(javaSqlDateFull, rs.getObject(i, java.sql.Date.class)); + } + + assertTrue(rs.next()); + assertEquals(6, rs.getInt(1)); + for (int i = 2; i <= 7; i++) { + // java.sql.time doesn't consider fractional seconds when comparing or in + // toString, so we compare the millis values. + assertEquals(javaSqlTimeFull.getTime(), + rs.getObject(i, java.sql.Time.class).getTime()); + } + } + } + } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/GetSetObjectIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/GetSetObjectIT.java new file mode 100644 index 0000000000..4d3a9dc80e --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/GetSetObjectIT.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.phoenix.end2end; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.expression.function.SqrtFunction; +import org.apache.phoenix.util.PropertiesUtil; +import org.apache.phoenix.util.TestUtil; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * End to end tests for {@link SqrtFunction} + */ +@Category(ParallelStatsDisabledTest.class) +public class GetSetObjectIT extends ParallelStatsDisabledIT { + + // Temporals are tested in DateTimeIT + // Arrays are tested in Array1IT + + @Test + public void testNonNumeric() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES); + String tableName = generateUniqueName(); + + byte[] bytes = { 1, 2 }; + String characters = "11"; + + try (Connection conn = DriverManager.getConnection(getUrl(), props); + Statement stmt = conn.createStatement(); + PreparedStatement insertStmt = + conn.prepareStatement("upsert into " + tableName + + "(id, binary_col, varbinary_col, boolean_col, char_col, varchar_col) values (?,?,?,?,?,?)")) { + stmt.executeUpdate("CREATE TABLE " + tableName + + " (ID INTEGER PRIMARY KEY, BINARY_COL BINARY(2), VARBINARY_COL VARBINARY, BOOLEAN_COL BOOLEAN, CHAR_COL CHAR(2), VARCHAR_COL VARCHAR)"); + insertStmt.setInt(1, 1); + // The type specific setters also delegate to these + insertStmt.setObject(2, bytes); + insertStmt.setObject(3, bytes); + insertStmt.setObject(4, Boolean.TRUE); + insertStmt.setObject(5, characters); + insertStmt.setObject(6, characters); + insertStmt.executeUpdate(); + conn.commit(); + + ResultSet rs = stmt.executeQuery("select * from " + tableName); + while (rs.next()) { + assertArrayEquals(bytes, rs.getBytes(2)); + assertArrayEquals(bytes, (byte[]) rs.getObject(2)); + assertArrayEquals(bytes, rs.getObject(2, byte[].class)); + try { + rs.getObject(2, Long.class); + fail("We only implement byte[]"); + } catch (SQLException e) { + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + assertArrayEquals(bytes, rs.getBytes(3)); + assertArrayEquals(bytes, (byte[]) rs.getObject(3)); + assertArrayEquals(bytes, rs.getObject(3, byte[].class)); + try { + rs.getObject(3, Long.class); + fail("We only implement byte[]"); + } catch (SQLException e) { + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + assertEquals(Boolean.TRUE, rs.getBoolean(4)); + assertEquals(Boolean.TRUE, rs.getObject(4)); + assertEquals(Boolean.TRUE, rs.getObject(4, Boolean.class)); + try { + assertEquals(Boolean.TRUE, rs.getObject(3, Long.class)); + fail("We only implement Boolean"); + } catch (SQLException e) { + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + assertEquals(characters, rs.getString(5)); + assertEquals(characters, rs.getObject(5)); + assertEquals(characters, rs.getObject(5, String.class)); + try { + assertEquals(Boolean.TRUE, rs.getObject(5, Boolean.class)); + fail("We only implement String"); + } catch (SQLException e) { + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + assertEquals(characters, rs.getString(6)); + assertEquals(characters, rs.getObject(6)); + assertEquals(characters, rs.getObject(6, String.class)); + try { + assertEquals(Boolean.TRUE, rs.getObject(6, Boolean.class)); + fail("We only implement String"); + } catch (SQLException e) { + assertEquals(SQLExceptionCode.TYPE_MISMATCH.getErrorCode(), e.getErrorCode()); + } + } + } + } + + @Test + public void testNumeric() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES); + String tableName = generateUniqueName(); + + try (Connection conn = DriverManager.getConnection(getUrl(), props); + Statement stmt = conn.createStatement(); + PreparedStatement insertStmt = + conn.prepareStatement("upsert into " + tableName + + "(id, TINY_COL, SMALL_COL, INT_COL, BIG_COL, FLOAT_COL, DOUBLE_COL, DECIMAL_COL) values (?,?,?,?,?,?,?,?)")) { + stmt.executeUpdate("CREATE TABLE " + tableName + + " (ID INTEGER PRIMARY KEY, TINY_COL TINYINT, SMALL_COL SMALLINT, INT_COL INTEGER, BIG_COL BIGINT, FLOAT_COL FLOAT, DOUBLE_COL DOUBLE, DECIMAL_COL DECIMAL)"); + insertStmt.setInt(1, 1); + // The type specific setters also delegate to these + insertStmt.setObject(2, Byte.valueOf("1")); + insertStmt.setObject(3, Short.valueOf("1")); + insertStmt.setObject(4, Integer.valueOf("1")); + insertStmt.setObject(5, Long.valueOf("1")); + insertStmt.setObject(6, Float.valueOf("1.0")); + insertStmt.setObject(7, Double.valueOf("1.0")); + insertStmt.setObject(8, BigDecimal.ONE); + insertStmt.executeUpdate(); + conn.commit(); + + ResultSet rs = stmt.executeQuery("select * from " + tableName); + assertTrue(rs.next()); + assertEquals(Byte.valueOf("1"), rs.getObject(2)); + assertEquals(Short.valueOf("1"), rs.getObject(3)); + assertEquals(Integer.valueOf("1"), rs.getObject(4)); + assertEquals(Long.valueOf("1"), rs.getObject(5)); + assertEquals(Float.valueOf("1.0"), rs.getObject(6)); + assertEquals(Double.valueOf("1.0"), rs.getObject(7)); + assertEquals(BigDecimal.ONE, rs.getObject(8)); + for (int i = 2; i <= 8; i++) { + assertEquals(Byte.valueOf("1"), Byte.valueOf(rs.getByte(i))); + assertEquals(Byte.valueOf("1"), rs.getObject(i, Byte.class)); + assertEquals(Short.valueOf("1"), Short.valueOf(rs.getShort(i))); + assertEquals(Short.valueOf("1"), rs.getObject(i, Short.class)); + assertEquals(Integer.valueOf("1"), Integer.valueOf(rs.getInt(i))); + assertEquals(Integer.valueOf("1"), rs.getObject(i, Integer.class)); + assertEquals(Long.valueOf("1"), Long.valueOf(rs.getLong(i))); + assertEquals(Long.valueOf("1"), rs.getObject(i, Long.class)); + assertEquals(Float.valueOf("1.0"), Float.valueOf(rs.getFloat(i))); + assertEquals(Float.valueOf("1.0"), rs.getObject(i, Float.class)); + assertEquals(Double.valueOf("1.0"), Double.valueOf(rs.getDouble(i))); + assertEquals(Double.valueOf("1.0"), rs.getObject(i, Double.class)); + assertTrue(BigDecimal.ONE.abs().compareTo(rs.getBigDecimal(i)) == 0); + assertTrue(BigDecimal.ONE.abs().compareTo(rs.getObject(i, BigDecimal.class)) == 0); + try { + rs.getObject(i, Boolean.class); + fail("Non-numeric types are not supported"); + } catch (SQLException e) { + // Expected + } + } + } + } +} diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ColumnProjector.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ColumnProjector.java index 934d73c90d..88c795685c 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ColumnProjector.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ColumnProjector.java @@ -63,6 +63,18 @@ public interface ColumnProjector { * @throws SQLException */ Object getValue(Tuple tuple, PDataType type, ImmutableBytesWritable ptr) throws SQLException; - + + /** + * Get the value of the column, coercing it if necessary to the specified type + * @param tuple the row containing the column + * @param type the type to which to coerce the binary value + * @param ptr used to retrieve the value + * @param jdbcType The java type to convert to, for rs.getObject() + * @return the object representation of the column value. + * @throws SQLException + */ + Object getValue(Tuple tuple, PDataType type, ImmutableBytesWritable ptr, Class jdbcType) + throws SQLException; + boolean isCaseSensitive(); } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java index b333ac65f2..39349b0fbe 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java @@ -83,6 +83,29 @@ public class ExpressionProjector implements ColumnProjector { } } + @Override + public final Object getValue(Tuple tuple, PDataType type, ImmutableBytesWritable ptr, + Class jdbcType) throws SQLException { + try { + Expression expression = getExpression(); + if (!expression.evaluate(tuple, ptr)) { + return null; + } + if (ptr.getLength() == 0) { + return null; + } + return type.toObject(ptr, expression.getDataType(), expression.getSortOrder(), + expression.getMaxLength(), expression.getScale(), jdbcType); + } catch (RuntimeException e) { + // FIXME: Expression.evaluate does not throw SQLException + // so this will unwrap throws from that. + if (e.getCause() instanceof SQLException) { + throw (SQLException) e.getCause(); + } + throw e; + } + } + @Override public boolean isCaseSensitive() { return isCaseSensitive; diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixPreparedStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixPreparedStatement.java index d1a775d9b5..d955777cd8 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixPreparedStatement.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixPreparedStatement.java @@ -39,6 +39,7 @@ import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -369,15 +370,19 @@ public class PhoenixPreparedStatement extends PhoenixStatement implements Prepar @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + setParameter(parameterIndex, processDate(x, cal)); + } + + private java.sql.Date processDate(Date x, Calendar cal) { if (x != null) { // Since Date is mutable, make a copy if (connection.isApplyTimeZoneDisplacement()) { - x = DateUtil.applyInputDisplacement(x, cal.getTimeZone()); + return DateUtil.applyInputDisplacement(x, cal.getTimeZone()); } else { // Since Date is mutable, make a copy - x = new Date(x.getTime()); + return new Date(x.getTime()); } } - setParameter(parameterIndex, x); + return x; } @Override @@ -442,30 +447,12 @@ public class PhoenixPreparedStatement extends PhoenixStatement implements Prepar @Override public void setObject(int parameterIndex, Object o) throws SQLException { - if (o instanceof java.util.Date) { - // TODO add java.time when implemented - if (connection.isApplyTimeZoneDisplacement()) { - if (o instanceof java.sql.Timestamp) { - o = - DateUtil.applyInputDisplacement((java.sql.Timestamp) o, - localCalendar.getTimeZone()); - } else if (o instanceof java.sql.Time) { - o = - DateUtil.applyInputDisplacement((java.sql.Time) o, - localCalendar.getTimeZone()); - } else if (o instanceof java.sql.Date) { - o = - DateUtil.applyInputDisplacement((java.sql.Date) o, - localCalendar.getTimeZone()); - } - } - // FIXME if we make a copy in setDate() from temporals, why not here ? - } - setParameter(parameterIndex, o); + setParameter(parameterIndex, processObject(o)); } @Override public void setObject(int parameterIndex, Object o, int targetSqlType) throws SQLException { + o = processObject(o); PDataType targetType = PDataType.fromTypeId(targetSqlType); if (o != null) { PDataType sourceType = PDataType.fromLiteral(o); @@ -511,15 +498,19 @@ public class PhoenixPreparedStatement extends PhoenixStatement implements Prepar @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + setParameter(parameterIndex, processTime(x, cal)); + } + + private java.sql.Time processTime(Time x, Calendar cal) { if (x != null) { if (connection.isApplyTimeZoneDisplacement()) { - x = DateUtil.applyInputDisplacement(x, cal.getTimeZone()); + return DateUtil.applyInputDisplacement(x, cal.getTimeZone()); } else { - // Since Date is mutable, make a copy - x = new Time(x.getTime()); + // Since Time is mutable, make a copy + return new Time(x.getTime()); } } - setParameter(parameterIndex, x); + return x; } @Override @@ -529,16 +520,20 @@ public class PhoenixPreparedStatement extends PhoenixStatement implements Prepar @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + setParameter(parameterIndex, processTimestamp(x, cal)); + } + + private java.sql.Timestamp processTimestamp(Timestamp x, Calendar cal) { if (x != null) { if (connection.isApplyTimeZoneDisplacement()) { - x = DateUtil.applyInputDisplacement(x, cal.getTimeZone()); + return DateUtil.applyInputDisplacement(x, cal.getTimeZone()); } else { int nanos = x.getNanos(); x = new Timestamp(x.getTime()); x.setNanos(nanos); } } - setParameter(parameterIndex, x); + return x; } @Override @@ -550,4 +545,36 @@ public class PhoenixPreparedStatement extends PhoenixStatement implements Prepar public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { throw new SQLFeatureNotSupportedException(); } + + // Convert objects to their canonical forms as expected by the Phoenix type system and apply + // TZ displacement + private Object processObject(Object o) { + // We cannot use the direct conversions, as those work in the local TZ. + if (o instanceof java.time.temporal.Temporal) { + if (o instanceof java.time.LocalDateTime) { + return java.sql.Timestamp.from(((java.time.LocalDateTime) o).toInstant(ZoneOffset.UTC)); + } else if (o instanceof java.time.LocalDate) { + // java.sql.Date.from() is inherited from j.u.Date.from() and returns j.u.Date + return new java.sql.Date(((java.time.LocalDate) o).atStartOfDay() + .toInstant(ZoneOffset.UTC).toEpochMilli()); + } else if (o instanceof java.time.LocalTime) { + // preserve nanos if writing to timestamp + return java.sql.Timestamp.from( + ((java.time.LocalTime) o).atDate(DateUtil.LD_EPOCH).toInstant(ZoneOffset.UTC)); + } + } else if (o instanceof java.util.Date) { + if (o instanceof java.sql.Date) { + return processDate((java.sql.Date) o, localCalendar); + } else if (o instanceof java.sql.Timestamp) { + return processTimestamp((java.sql.Timestamp) o, localCalendar); + } else if (o instanceof java.sql.Time) { + return processTime((java.sql.Time) o, localCalendar); + } else { + // We could use Timestamp, we don't have millis, and don't differentiate anyway + return processDate(new java.sql.Date(((java.util.Date) o).getTime()), localCalendar) + .getTime(); + } + } + return o; + } } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java index 55a6de250b..b2275b3d94 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java @@ -759,7 +759,8 @@ public class PhoenixResultSet implements ResultSet, SQLCloseable { return DateUtil.applyOutputDisplacement(value, cal.getTimeZone()); } else { return value; - } } + } + } @Override public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { @@ -1390,15 +1391,36 @@ public class PhoenixResultSet implements ResultSet, SQLCloseable { @SuppressWarnings("unchecked") @Override public <T> T getObject(int columnIndex, Class<T> type) throws SQLException { - return (T) getObject(columnIndex); // Just ignore type since we only support built-in types + if (type.equals(String.class)) { + // Special case, the connection specific formatter is not available in the Type system + return (T) getString(columnIndex); + } else if (java.util.Date.class.isAssignableFrom(type)) { + // The displacement handling code is in the specific getters + if (java.sql.Timestamp.class.isAssignableFrom(type)) { + return (T) getTimestamp(columnIndex); + } else if (java.sql.Date.class.isAssignableFrom(type)) { + return (T) getDate(columnIndex); + } else if (java.sql.Time.class.isAssignableFrom(type)) { + return (T) getTime(columnIndex); + } else if (java.util.Date.class.equals(type)) { + return (T) new java.util.Date(getDate(columnIndex).getTime()); + } + } + checkCursorState(); + ColumnProjector projector = getRowProjector().getColumnProjector(columnIndex - 1); + + Object value = + projector.getValue(currentRow, projector.getExpression().getDataType(), ptr, type); + + wasNull = (value == null); + return (T) value; } - @SuppressWarnings("unchecked") @Override public <T> T getObject(String columnLabel, Class<T> type) throws SQLException { - return (T) getObject(columnLabel); // Just ignore type since we only support built-in types + return getObject(findColumn(columnLabel), type); } - + @VisibleForTesting public ResultIterator getUnderlyingIterator() { return scanner; diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java index 497c90fdae..9646ee68ea 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java @@ -33,15 +33,14 @@ import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.schema.ConstraintViolationException; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.ValueSchema; +import org.apache.phoenix.thirdparty.com.google.common.base.Objects; +import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions; import org.apache.phoenix.util.ByteUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TrustedByteArrayOutputStream; import edu.umd.cs.findbugs.annotations.SuppressWarnings; -import org.apache.phoenix.thirdparty.com.google.common.base.Objects; -import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions; - /** * The datatype for PColummns that are Arrays. Any variable length array would follow the below order. Every element * would be seperated by a seperator byte '0'. Null elements are counted and once a first non null element appears we @@ -388,6 +387,16 @@ public abstract class PArrayDataType<T> extends PDataType<T> { return Math.abs(getSerializedOffset(bytes, arrayIndex, useShort, indexOffset, serializationVersion)); } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + if (java.sql.Array.class.isAssignableFrom(jdbcType)) { + return toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + } + throw newMismatchException(actualType, jdbcType); + } + static int getSerializedOffset(byte[] bytes, int arrayIndex, boolean useShort, int indexOffset, byte serializationVersion) { int offset; if (useShort) { diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinaryBase.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinaryBase.java index 562875da5e..178e331eaa 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinaryBase.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinaryBase.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.schema.types; +import java.sql.SQLException; + import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.phoenix.schema.SortOrder; @@ -112,4 +114,16 @@ public abstract class PBinaryBase extends PDataType<byte[]> { } return true; } + + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + if (jdbcType == byte[].class) { + return toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + } else { + // TODO we could duplicate getString() for String type + throw newMismatchException(actualType, jdbcType); + } + } } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java index f827f36514..35e38486b0 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBoolean.java @@ -18,6 +18,7 @@ package org.apache.phoenix.schema.types; import java.math.BigDecimal; +import java.sql.SQLException; import java.sql.Types; import org.apache.phoenix.schema.SortOrder; @@ -85,6 +86,19 @@ public class PBoolean extends PDataType<Boolean> { return null; } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + // FIXME according to the JDBC spec, we should support all these types: + // TINYINT, SMALLINT, INTEGER, BIGINT, REAL, FLOAT, DOUBLE, DECIMAL, NUMERIC, BIT, + // BOOLEAN, CHAR, VARCHAR, LONGVARCHAR + if (Boolean.class.isAssignableFrom(jdbcType)) { + return toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + } + throw newMismatchException(actualType, jdbcType); + } + @Override public boolean isCoercibleTo(PDataType targetType) { return super.isCoercibleTo(targetType) || targetType.equals(PBinary.INSTANCE); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PChar.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PChar.java index 4f63003b33..b36768d5c1 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PChar.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PChar.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.schema.types; +import java.sql.SQLException; import java.sql.Types; import java.text.Format; import java.util.Arrays; @@ -123,6 +124,17 @@ public class PChar extends PDataType<String> { return s; } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + if (String.class.isAssignableFrom(jdbcType)) { + //We don't actually get here, we shortcut the String case in ResultSet + return toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + } + throw newMismatchException(actualType, jdbcType); + } + @Override public Object toObject(Object object, PDataType actualType) { if (equalsAny(actualType, PVarchar.INSTANCE, this)) { @@ -230,4 +242,5 @@ public class PChar extends PDataType<String> { public Object getSampleValue(Integer maxLength, Integer arrayLength) { return PVarchar.INSTANCE.getSampleValue(maxLength, arrayLength); } + } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java index 5fa706a24f..cfa8a56141 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataType.java @@ -289,6 +289,11 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< .setMessage(source + " cannot be coerced to " + target).build().buildException()); } + protected static SQLException newMismatchException(PDataType source, Class target) { + return new SQLExceptionInfo.Builder(SQLExceptionCode.TYPE_MISMATCH) + .setMessage(source + " cannot be retrieved as " + target).build().buildException(); + } + protected static RuntimeException newIllegalDataException() { return new IllegalDataException(new SQLExceptionInfo.Builder(SQLExceptionCode.ILLEGAL_DATA).build() .buildException()); @@ -857,6 +862,7 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< return (date == null || date.getTime() >= 0); } + //FIXME this is misnamed protected static void throwIfNonNegativeDate(java.util.Date date) { if (!isNonNegativeDate(date)) { throw newIllegalDataException("Value may not be negative(" + date + ")"); } } @@ -865,6 +871,7 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< return v == null || v.longValue() >= 0; } + //FIXME this is misnamed protected static void throwIfNonNegativeNumber(Number v) { if (!isNonNegativeNumber(v)) { throw newIllegalDataException("Value may not be negative(" + v + ")"); } } @@ -962,6 +969,10 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< public abstract Object toObject(byte[] bytes, int offset, int length, PDataType actualType, SortOrder sortOrder, Integer maxLength, Integer scale); + public abstract Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException; + @SuppressWarnings("unchecked") @Override public T decode(PositionedByteRange pbr) { @@ -1005,6 +1016,13 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< return this.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), actualType, sortOrder, maxLength, scale); } + public final Object toObject(ImmutableBytesWritable ptr, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + return this.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), actualType, sortOrder, + maxLength, scale, jdbcType); + } + public final Object toObject(ImmutableBytesWritable ptr, SortOrder sortOrder, Integer maxLength, Integer scale) { return this.toObject(ptr.get(), ptr.getOffset(), ptr.getLength(), this, sortOrder, maxLength, scale); } @@ -1147,15 +1165,20 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< return KeyRange.getKeyRange(lowerRange, lowerInclusive, upperRange, upperInclusive); } + //TODO this could be improved by some lookup tables instead of iterating over all types public static PDataType fromLiteral(Object value) { - if (value == null) { return null; } + if (value == null) { + return null; + } for (PDataType type : PDataType.values()) { if (type.isArrayType()) { if (value instanceof PhoenixArray) { PhoenixArray arr = (PhoenixArray)value; if ((type.getSqlType() == arr.baseType.sqlType + PDataType.ARRAY_TYPE_BASE) - && type.getJavaClass().isInstance(value)) { return type; } - } else { + && type.getJavaClass().isInstance(value)) { + return type; + } + } else if (value instanceof Array) { Array arr = (Array) value; try { // Does the array's component type make sense for what we were told it is @@ -1165,7 +1188,9 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< } catch (SQLException e) { /* Passthrough to fail */ } } } else { - if (type.getJavaClass().isInstance(value)) { return type; } + if (type.getJavaClass().isInstance(value)) { + return type; + } } } throw new UnsupportedOperationException("Unsupported literal value [" + value + "] of type " @@ -1191,4 +1216,5 @@ public abstract class PDataType<T> implements DataType<T>, Comparable<PDataType< Preconditions.checkArgument(arrayType.isArrayType(), "Not a phoenix array type"); return fromTypeId(arrayType.getSqlType() - ARRAY_TYPE_BASE); } + } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java index 45a96573ee..3da8b2ef21 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDataTypeFactory.java @@ -33,6 +33,9 @@ public class PDataTypeFactory { private final PDataType[] orderedTypes; private final SortedSet<PDataType> types; private final Map<Class<? extends PDataType>, PDataType> classToInstance; + private final Map<Class, PDataType> javaClassToInstance; + private final Map<Class, PDataType> javaClassToUnsignedInstance; + private final SortedSet<PDataType> unsignedtypes; public static PDataTypeFactory getInstance() { if (INSTANCE == null) { @@ -48,6 +51,12 @@ public class PDataTypeFactory { return Integer.compare(o1.ordinal(), o2.ordinal()); } }); // TODO: replace with ServiceLoader or some other plugin system + unsignedtypes = new TreeSet<>(new Comparator<PDataType>() { + @Override + public int compare(PDataType o1, PDataType o2) { + return Integer.compare(o1.ordinal(), o2.ordinal()); + } + }); // TODO: replace with ServiceLoader or some other plugin system types.add(PBinary.INSTANCE); types.add(PBinaryArray.INSTANCE); types.add(PChar.INSTANCE); @@ -75,23 +84,41 @@ public class PDataTypeFactory { types.add(PTinyint.INSTANCE); types.add(PTinyintArray.INSTANCE); types.add(PUnsignedDate.INSTANCE); + unsignedtypes.add(PUnsignedDate.INSTANCE); types.add(PUnsignedDateArray.INSTANCE); + unsignedtypes.add(PUnsignedDateArray.INSTANCE); types.add(PUnsignedDouble.INSTANCE); + unsignedtypes.add(PUnsignedDouble.INSTANCE); types.add(PUnsignedDoubleArray.INSTANCE); + unsignedtypes.add(PUnsignedDoubleArray.INSTANCE); types.add(PUnsignedFloat.INSTANCE); + unsignedtypes.add(PUnsignedFloat.INSTANCE); types.add(PUnsignedFloatArray.INSTANCE); + unsignedtypes.add(PUnsignedFloatArray.INSTANCE); types.add(PUnsignedInt.INSTANCE); + unsignedtypes.add(PUnsignedInt.INSTANCE); types.add(PUnsignedIntArray.INSTANCE); + unsignedtypes.add(PUnsignedIntArray.INSTANCE); types.add(PUnsignedLong.INSTANCE); + unsignedtypes.add(PUnsignedLong.INSTANCE); types.add(PUnsignedLongArray.INSTANCE); + unsignedtypes.add(PUnsignedLongArray.INSTANCE); types.add(PUnsignedSmallint.INSTANCE); + unsignedtypes.add(PUnsignedSmallint.INSTANCE); types.add(PUnsignedSmallintArray.INSTANCE); + unsignedtypes.add(PUnsignedSmallintArray.INSTANCE); types.add(PUnsignedTime.INSTANCE); + unsignedtypes.add(PUnsignedTime.INSTANCE); types.add(PUnsignedTimeArray.INSTANCE); + unsignedtypes.add(PUnsignedTimeArray.INSTANCE); types.add(PUnsignedTimestamp.INSTANCE); + unsignedtypes.add(PUnsignedTimestamp.INSTANCE); types.add(PUnsignedTimestampArray.INSTANCE); + unsignedtypes.add(PUnsignedTimestampArray.INSTANCE); types.add(PUnsignedTinyint.INSTANCE); + unsignedtypes.add(PUnsignedTinyint.INSTANCE); types.add(PUnsignedTinyintArray.INSTANCE); + unsignedtypes.add(PUnsignedTinyintArray.INSTANCE); types.add(PVarbinary.INSTANCE); types.add(PVarbinaryArray.INSTANCE); types.add(PVarchar.INSTANCE); @@ -101,6 +128,18 @@ public class PDataTypeFactory { for (PDataType t : types) { classToInstance.put(t.getClass(), t); } + javaClassToInstance = new HashMap<>(types.size()); + for (PDataType t : types) { + Class javaClass = t.getJavaClass(); + // The first match + javaClassToInstance.putIfAbsent(javaClass, t); + } + javaClassToUnsignedInstance = new HashMap<>(types.size()); + for (PDataType t : unsignedtypes) { + Class javaClass = t.getJavaClass(); + // The first match + javaClassToInstance.putIfAbsent(javaClass, t); + } orderedTypes = types.toArray(new PDataType[types.size()]); } @@ -115,4 +154,12 @@ public class PDataTypeFactory { public PDataType instanceFromClass(Class<? extends PDataType> clazz) { return classToInstance.get(clazz); } + + public PDataType instanceFromJavaClass(Class clazz, PDataType actualType) { + if (unsignedtypes.contains(actualType)) { + return javaClassToUnsignedInstance.get(clazz); + } else { + return javaClassToInstance.get(clazz); + } + } } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDate.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDate.java index 3dd6c6903d..c98a8eb4af 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDate.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PDate.java @@ -19,8 +19,10 @@ package org.apache.phoenix.schema.types; import java.math.BigDecimal; import java.sql.Date; +import java.sql.SQLException; import java.sql.Types; import java.text.Format; +import java.time.ZoneOffset; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.util.Bytes; @@ -94,6 +96,46 @@ public class PDate extends PDataType<Date> { return null; } + // Keep this in sync with PUnsignedDate + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + java.sql.Date sqlDate = + toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + return dateToClass(sqlDate, actualType, jdbcType); + } + + Object dateToClass(java.sql.Date sqlDate, PDataType actualType, Class jdbcType) + throws SQLException { + // FIXME java.time.Local conversions use ISO chronology, unlike the rest of Phoenix. + if (jdbcType == java.time.LocalDate.class) { + // FIXME this does a lot of unnecessary computation. + return java.time.LocalDateTime + .ofInstant(java.time.Instant.ofEpochMilli(sqlDate.getTime()), ZoneOffset.UTC) + .toLocalDate(); + } else if (jdbcType == java.time.LocalDateTime.class) { + // This is NOT JDBC compliant, but is useful because Dates are really Timestamps. + // We cannot use toInstant(), as that nulls the time fields. + return java.time.LocalDateTime + .ofInstant(java.time.Instant.ofEpochMilli(sqlDate.getTime()), ZoneOffset.UTC); + } else if (jdbcType == java.time.LocalTime.class) { + // This is NOT JDBC compliant, but is useful because Dates are really Timestamps. + return java.time.LocalDateTime + .ofInstant(java.time.Instant.ofEpochMilli(sqlDate.getTime()), ZoneOffset.UTC) + .toLocalTime(); + } else if (jdbcType == java.sql.Date.class) { + return sqlDate; + } else if (jdbcType == java.sql.Time.class) { + return new java.sql.Time(sqlDate.getTime()); + } else if (jdbcType == java.sql.Timestamp.class) { + return new java.sql.Timestamp(sqlDate.getTime()); + } else if (jdbcType == java.util.Date.class) { + return new java.util.Date(sqlDate.getTime()); + } + throw newMismatchException(actualType, jdbcType); + } + @Override public boolean isCastableTo(PDataType targetType) { return super.isCastableTo(targetType) || diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PNumericType.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PNumericType.java index 826d9add39..3e18d2d761 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PNumericType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PNumericType.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.schema.types; +import java.sql.SQLException; + import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.phoenix.schema.SortOrder; @@ -49,4 +51,16 @@ public abstract class PNumericType<T> extends PDataType<T> { ImmutableBytesWritable outPtr) { abs(ptr.get(), ptr.getOffset(), ptr.getLength(), sortOrder, outPtr); } + + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + PDataType pType = PDataTypeFactory.getInstance().instanceFromJavaClass(jdbcType, this); + if (pType == null || !PNumericType.class.isAssignableFrom(pType.getClass())) { + throw newMismatchException(actualType, jdbcType); + } else { + return pType.toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + } + } } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTime.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTime.java index 03afae9fc1..e0bcd9232a 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTime.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTime.java @@ -18,9 +18,11 @@ package org.apache.phoenix.schema.types; import java.math.BigDecimal; +import java.sql.SQLException; import java.sql.Time; import java.sql.Types; import java.text.Format; +import java.time.ZoneOffset; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.schema.SortOrder; @@ -53,7 +55,7 @@ public class PTime extends PDataType<Time> { if (equalsAny(actualType, PTimestamp.INSTANCE, PUnsignedTimestamp.INSTANCE, PDate.INSTANCE, PUnsignedDate.INSTANCE, PTime.INSTANCE, PUnsignedTime.INSTANCE, PLong.INSTANCE, PUnsignedLong.INSTANCE)) { - return new java.sql.Time(actualType.getCodec().decodeLong(b, o, sortOrder)); + return new java.sql.Time(DateUtil.getCodecFor(actualType).decodeLong(b, o, sortOrder)); } else if (actualType == PDecimal.INSTANCE) { BigDecimal bd = (BigDecimal) actualType.toObject(b, o, l, actualType, sortOrder); return new java.sql.Time(bd.longValueExact()); @@ -83,6 +85,45 @@ public class PTime extends PDataType<Time> { return throwConstraintViolationException(actualType, this); } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + java.sql.Time sqlTime = + toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + return timeToClass(sqlTime, actualType, jdbcType); + } + + Object timeToClass(java.sql.Time sqlTime, PDataType actualType, Class jdbcType) + throws SQLException { + if (jdbcType == java.time.LocalTime.class) { + // FIXME this does a lot of unnecessary computation. + return java.time.LocalDateTime + .ofInstant(java.time.Instant.ofEpochMilli(sqlTime.getTime()), ZoneOffset.UTC) + .toLocalTime(); + } else if (jdbcType == java.time.LocalDateTime.class) { + // This is NOT JDBC compliant + // We cannot use toInstant(), as that nulls the time fields. + return java.time.LocalDateTime + .ofInstant(java.time.Instant.ofEpochMilli(sqlTime.getTime()), ZoneOffset.UTC); + } else if (jdbcType == java.time.LocalDate.class) { + // This is NOT JDBC compliant + // FIXME this does a lot of unnecessary computation. + return java.time.LocalDateTime + .ofInstant(java.time.Instant.ofEpochMilli(sqlTime.getTime()), ZoneOffset.UTC) + .toLocalDate(); + } else if (jdbcType == java.sql.Time.class) { + return sqlTime; + } else if (jdbcType == java.sql.Date.class) { + return new java.sql.Date(sqlTime.getTime()); + } else if (jdbcType == java.sql.Timestamp.class) { + return new java.sql.Timestamp(sqlTime.getTime()); + } else if (jdbcType == java.util.Date.class) { + return new java.util.Date(sqlTime.getTime()); + } + throw newMismatchException(actualType, jdbcType); + } + @Override public boolean isCastableTo(PDataType targetType) { return PDate.INSTANCE.isCastableTo(targetType); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java index a0a5e27a69..4d26807a9e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PTimestamp.java @@ -18,9 +18,11 @@ package org.apache.phoenix.schema.types; import java.math.BigDecimal; +import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.text.Format; +import java.time.ZoneOffset; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.util.Bytes; @@ -28,11 +30,10 @@ import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.schema.IllegalDataException; import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions; import org.apache.phoenix.util.ByteUtil; import org.apache.phoenix.util.DateUtil; -import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions; - public class PTimestamp extends PDataType<Timestamp> { public static final int MAX_NANOS_VALUE_EXCLUSIVE = 1000000; public static final PTimestamp INSTANCE = new PTimestamp(); @@ -166,6 +167,41 @@ public class PTimestamp extends PDataType<Timestamp> { return null; } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + java.sql.Timestamp sqlTs = + toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + return dateToClass(sqlTs, actualType, jdbcType); + } + + Object dateToClass(java.sql.Timestamp sqlTs, PDataType actualType, Class jdbcType) + throws SQLException { + // FIXME java.time.Local conversions use ISO chronology, unlike the rest of Phoenix. + if (jdbcType == java.time.LocalDateTime.class) { + return java.time.LocalDateTime.ofInstant(sqlTs.toInstant(), ZoneOffset.UTC); + } else if (jdbcType == java.time.LocalTime.class) { + // This is NOT JDBC compliant + // This preserves nanos + return java.time.LocalDateTime.ofInstant(sqlTs.toInstant(), ZoneOffset.UTC) + .toLocalTime(); + } else if (jdbcType == java.time.LocalDate.class) { + // This is NOT JDBC compliant + return java.time.LocalDateTime.ofInstant(sqlTs.toInstant(), ZoneOffset.UTC) + .toLocalDate(); + } else if (jdbcType == java.sql.Timestamp.class) { + return sqlTs; + } else if (jdbcType == java.sql.Date.class) { + return new java.sql.Date(sqlTs.getTime()); + } else if (jdbcType == java.util.Date.class) { + return new java.util.Date(sqlTs.getTime()); + } else if (jdbcType == java.sql.Time.class) { + return new java.sql.Time(sqlTs.getTime()); + } + throw newMismatchException(actualType, jdbcType); + } + @Override public boolean isCastableTo(PDataType targetType) { return PDate.INSTANCE.isCastableTo(targetType); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedDate.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedDate.java index 863b0d18d0..ee21fa832e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedDate.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedDate.java @@ -18,6 +18,7 @@ package org.apache.phoenix.schema.types; import java.sql.Date; +import java.sql.SQLException; import java.sql.Types; import java.text.Format; @@ -61,12 +62,22 @@ public class PUnsignedDate extends PDataType<Date> { } @Override - public Object toObject(byte[] b, int o, int l, PDataType actualType, SortOrder sortOrder, Integer maxLength, Integer scale) { + public Date toObject(byte[] b, int o, int l, PDataType actualType, SortOrder sortOrder, + Integer maxLength, Integer scale) { Date d = (Date) PDate.INSTANCE.toObject(b, o, l, actualType, sortOrder); throwIfNonNegativeDate(d); return d; } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + java.sql.Date sqlDate = + toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + return PDate.INSTANCE.dateToClass(sqlDate, actualType, jdbcType); + } + @Override public boolean isCastableTo(PDataType targetType) { return PDate.INSTANCE.isCastableTo(targetType); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedTime.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedTime.java index db0ba0caa8..fd07ec485e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedTime.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PUnsignedTime.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.schema.types; +import java.sql.SQLException; import java.sql.Time; import java.sql.Types; import java.text.Format; @@ -43,13 +44,22 @@ public class PUnsignedTime extends PDataType<Time> { } @Override - public Object toObject(byte[] b, int o, int l, PDataType actualType, SortOrder sortOrder, + public Time toObject(byte[] b, int o, int l, PDataType actualType, SortOrder sortOrder, Integer maxLength, Integer scale) { java.sql.Time t = (java.sql.Time) PTime.INSTANCE.toObject(b, o, l, actualType, sortOrder); throwIfNonNegativeDate(t); return t; } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + java.sql.Time sqlTime = + toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + return PTime.INSTANCE.timeToClass(sqlTime, actualType, jdbcType); + } + @Override public Object toObject(Object object, PDataType actualType) { java.sql.Time t = (java.sql.Time) PTime.INSTANCE.toObject(object, actualType); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java index 7c74281260..ce357104df 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarchar.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.schema.types; +import java.sql.SQLException; import java.sql.Types; import java.text.Format; @@ -72,6 +73,17 @@ public class PVarchar extends PDataType<String> { return Bytes.toString(bytes, offset, length); } + @Override + public Object toObject(byte[] bytes, int offset, int length, PDataType actualType, + SortOrder sortOrder, Integer maxLength, Integer scale, Class jdbcType) + throws SQLException { + if (String.class.isAssignableFrom(jdbcType)) { + //We don't actually get here, we shortcut the String case in ResultSet + return toObject(bytes, offset, length, actualType, sortOrder, maxLength, scale); + } + throw newMismatchException(actualType, jdbcType); + } + @Override public Object toObject(Object object, PDataType actualType) { if (equalsAny(actualType, this, PChar.INSTANCE)) { diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java index fefdc8d3ad..5bd7027202 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/util/DateUtil.java @@ -72,8 +72,7 @@ public class DateUtil { public static final String DEFAULT_TIMESTAMP_FORMAT = DEFAULT_MS_DATE_FORMAT; public static final Format DEFAULT_TIMESTAMP_FORMATTER = DEFAULT_MS_DATE_FORMATTER; - //Caching for performance. We don't expect the default TZ to changed after startup - //private static final java.util.TimeZone LOCAL_TIME_ZONE = TimeZone.getDefault(); + public static final java.time.LocalDate LD_EPOCH = java.time.LocalDate.of(1970, 1, 1); private static final DateTimeFormatter JULIAN_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() .append(ISODateTimeFormat.dateParser()) @@ -87,6 +86,7 @@ public class DateUtil { } @NonNull + // FIXME why don't we just set these codecs in the Types ? public static PDataCodec getCodecFor(PDataType type) { PDataCodec codec = type.getCodec(); if (codec != null) {