| /* |
| * 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; |
| |
| import org.apache.calcite.avatica.remote.TypedValue; |
| import org.apache.calcite.avatica.util.Cursor; |
| |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| 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.RowId; |
| import java.sql.SQLException; |
| import java.sql.SQLXML; |
| import java.sql.Time; |
| import java.sql.Timestamp; |
| import java.sql.Types; |
| import java.util.Calendar; |
| |
| /** |
| * A location that a value can be written to or read from. |
| */ |
| public class AvaticaSite { |
| final AvaticaParameter parameter; |
| |
| /** Calendar is not thread-safe. But calendar is only used from within one |
| * thread, and we have to trust that clients are not modifying calendars |
| * that they pass to us in a method such as |
| * {@link java.sql.PreparedStatement#setTime(int, Time, Calendar)}, so we do |
| * not need to synchronize access. */ |
| final Calendar calendar; |
| private final int index; |
| final TypedValue[] slots; |
| |
| /** Value that means the parameter has been set to null. |
| * If value is null, parameter has not been set. */ |
| public static final Object DUMMY_VALUE = Dummy.INSTANCE; |
| |
| public AvaticaSite(AvaticaParameter parameter, Calendar calendar, int index, |
| TypedValue[] slots) { |
| assert calendar != null; |
| assert parameter != null; |
| assert slots != null; |
| this.parameter = parameter; |
| this.calendar = calendar; |
| this.index = index; |
| this.slots = slots; |
| } |
| |
| private TypedValue wrap(ColumnMetaData.Rep rep, Object o, |
| Calendar calendar) { |
| return TypedValue.ofJdbc(rep, o, calendar); |
| } |
| |
| private TypedValue wrap(ColumnMetaData.Rep rep, Object o) { |
| return TypedValue.ofJdbc(rep, o, calendar); |
| } |
| |
| public boolean isSet(int index) { |
| return slots[index] != null; |
| } |
| |
| public void setByte(byte o) { |
| slots[index] = wrap(ColumnMetaData.Rep.BYTE, o); |
| } |
| |
| public void setChar(char o) { |
| slots[index] = wrap(ColumnMetaData.Rep.CHARACTER, o); |
| } |
| |
| public void setShort(short o) { |
| slots[index] = wrap(ColumnMetaData.Rep.SHORT, o); |
| } |
| |
| public void setInt(int o) { |
| slots[index] = wrap(ColumnMetaData.Rep.INTEGER, o); |
| } |
| |
| public void setLong(long o) { |
| slots[index] = wrap(ColumnMetaData.Rep.LONG, o); |
| } |
| |
| public void setBoolean(boolean o) { |
| slots[index] = wrap(ColumnMetaData.Rep.BOOLEAN, o); |
| } |
| |
| public void setRowId(RowId x) { |
| slots[index] = wrap(ColumnMetaData.Rep.OBJECT, x); |
| } |
| |
| public void setNString(String o) { |
| slots[index] = wrap(ColumnMetaData.Rep.STRING, o); |
| } |
| |
| public void setNCharacterStream(Reader value, long length) { |
| } |
| |
| public void setNClob(NClob value) { |
| slots[index] = wrap(ColumnMetaData.Rep.OBJECT, value); |
| } |
| |
| public void setClob(Reader reader, long length) { |
| } |
| |
| public void setBlob(InputStream inputStream, long length) { |
| } |
| |
| public void setNClob(Reader reader, long length) { |
| } |
| |
| public void setSQLXML(SQLXML xmlObject) { |
| slots[index] = wrap(ColumnMetaData.Rep.OBJECT, xmlObject); |
| } |
| |
| public void setAsciiStream(InputStream x, long length) { |
| } |
| |
| public void setBinaryStream(InputStream x, long length) { |
| } |
| |
| public void setCharacterStream(Reader reader, long length) { |
| } |
| |
| public void setAsciiStream(InputStream x) { |
| } |
| |
| public void setBinaryStream(InputStream x) { |
| } |
| |
| public void setCharacterStream(Reader reader) { |
| } |
| |
| public void setNCharacterStream(Reader value) { |
| } |
| |
| public void setClob(Reader reader) { |
| } |
| |
| public void setBlob(InputStream inputStream) { |
| } |
| |
| public void setNClob(Reader reader) { |
| } |
| |
| public void setUnicodeStream(InputStream x, int length) { |
| } |
| |
| public void setFloat(float x) { |
| slots[index] = wrap(ColumnMetaData.Rep.FLOAT, x); |
| } |
| |
| public void setDouble(double x) { |
| slots[index] = wrap(ColumnMetaData.Rep.DOUBLE, x); |
| } |
| |
| public void setBigDecimal(BigDecimal x) { |
| slots[index] = wrap(ColumnMetaData.Rep.NUMBER, x); |
| } |
| |
| public void setString(String x) { |
| slots[index] = wrap(ColumnMetaData.Rep.STRING, x); |
| } |
| |
| public void setBytes(byte[] x) { |
| slots[index] = wrap(ColumnMetaData.Rep.BYTE_STRING, x); |
| } |
| |
| public void setTimestamp(Timestamp x, Calendar calendar) { |
| slots[index] = wrap(ColumnMetaData.Rep.JAVA_SQL_TIMESTAMP, x, calendar); |
| } |
| |
| public void setTime(Time x, Calendar calendar) { |
| slots[index] = wrap(ColumnMetaData.Rep.JAVA_SQL_TIME, x, calendar); |
| } |
| |
| public void setDate(Date x, Calendar calendar) { |
| slots[index] = wrap(ColumnMetaData.Rep.JAVA_SQL_DATE, x, calendar); |
| } |
| |
| public void setObject(Object x, int targetSqlType) { |
| if (x == null || Types.NULL == targetSqlType) { |
| setNull(targetSqlType); |
| return; |
| } |
| switch (targetSqlType) { |
| case Types.CLOB: |
| case Types.DATALINK: |
| case Types.NCLOB: |
| case Types.OTHER: |
| case Types.REF: |
| case Types.SQLXML: |
| case Types.STRUCT: |
| throw notImplemented(); |
| case Types.ARRAY: |
| setArray(toArray(x)); |
| break; |
| case Types.BIGINT: |
| setLong(toLong(x)); |
| break; |
| case Types.BINARY: |
| case Types.LONGVARBINARY: |
| case Types.VARBINARY: |
| setBytes(toBytes(x)); |
| break; |
| case Types.BIT: |
| case Types.BOOLEAN: |
| setBoolean(toBoolean(x)); |
| break; |
| case Types.BLOB: |
| if (x instanceof Blob) { |
| setBlob((Blob) x); |
| break; |
| } else if (x instanceof InputStream) { |
| setBlob((InputStream) x); |
| } |
| throw unsupportedCast(x.getClass(), Blob.class); |
| case Types.DATE: |
| setDate(toDate(x), calendar); |
| break; |
| case Types.DECIMAL: |
| case Types.NUMERIC: |
| setBigDecimal(toBigDecimal(x)); |
| break; |
| case Types.DISTINCT: |
| throw notImplemented(); |
| case Types.DOUBLE: |
| case Types.FLOAT: // yes really; SQL FLOAT is up to 8 bytes |
| setDouble(toDouble(x)); |
| break; |
| case Types.INTEGER: |
| setInt(toInt(x)); |
| break; |
| case Types.JAVA_OBJECT: |
| setObject(x); |
| break; |
| case Types.LONGNVARCHAR: |
| case Types.LONGVARCHAR: |
| case Types.NVARCHAR: |
| case Types.VARCHAR: |
| case Types.CHAR: |
| case Types.NCHAR: |
| setString(toString(x)); |
| break; |
| case Types.REAL: |
| setFloat(toFloat(x)); |
| break; |
| case Types.ROWID: |
| if (x instanceof RowId) { |
| setRowId((RowId) x); |
| break; |
| } |
| throw unsupportedCast(x.getClass(), RowId.class); |
| case Types.SMALLINT: |
| setShort(toShort(x)); |
| break; |
| case Types.TIME: |
| setTime(toTime(x), calendar); |
| break; |
| case Types.TIMESTAMP: |
| setTimestamp(toTimestamp(x), calendar); |
| break; |
| case Types.TINYINT: |
| setByte(toByte(x)); |
| break; |
| default: |
| throw notImplemented(); |
| } |
| } |
| |
| /** Similar logic to {@link #setObject}. */ |
| public static Object get(Cursor.Accessor accessor, int targetSqlType, |
| Calendar localCalendar) throws SQLException { |
| switch (targetSqlType) { |
| case Types.CLOB: |
| case Types.DATALINK: |
| case Types.NCLOB: |
| case Types.REF: |
| case Types.SQLXML: |
| throw notImplemented(); |
| case Types.STRUCT: |
| return accessor.getStruct(); |
| case Types.ARRAY: |
| return accessor.getArray(); |
| case Types.BIGINT: |
| final long aLong = accessor.getLong(); |
| if (aLong == 0 && accessor.wasNull()) { |
| return null; |
| } |
| return aLong; |
| case Types.BINARY: |
| case Types.LONGVARBINARY: |
| case Types.VARBINARY: |
| return accessor.getBytes(); |
| case Types.BIT: |
| case Types.BOOLEAN: |
| final boolean aBoolean = accessor.getBoolean(); |
| if (!aBoolean && accessor.wasNull()) { |
| return null; |
| } |
| return aBoolean; |
| case Types.BLOB: |
| return accessor.getBlob(); |
| case Types.DATE: |
| return accessor.getDate(localCalendar); |
| case Types.DECIMAL: |
| case Types.NUMERIC: |
| return accessor.getBigDecimal(); |
| case Types.DISTINCT: |
| throw notImplemented(); |
| case Types.DOUBLE: |
| case Types.FLOAT: // yes really; SQL FLOAT is up to 8 bytes |
| final double aDouble = accessor.getDouble(); |
| if (aDouble == 0 && accessor.wasNull()) { |
| return null; |
| } |
| return aDouble; |
| case Types.INTEGER: |
| final int anInt = accessor.getInt(); |
| if (anInt == 0 && accessor.wasNull()) { |
| return null; |
| } |
| return anInt; |
| case Types.JAVA_OBJECT: |
| case Types.NULL: |
| case Types.OTHER: |
| return accessor.getObject(); |
| case Types.LONGNVARCHAR: |
| case Types.LONGVARCHAR: |
| case Types.NVARCHAR: |
| case Types.VARCHAR: |
| case Types.CHAR: |
| case Types.NCHAR: |
| return accessor.getString(); |
| case Types.REAL: |
| final float aFloat = accessor.getFloat(); |
| if (aFloat == 0 && accessor.wasNull()) { |
| return null; |
| } |
| return aFloat; |
| case Types.ROWID: |
| throw notImplemented(); |
| case Types.SMALLINT: |
| final short aShort = accessor.getShort(); |
| if (aShort == 0 && accessor.wasNull()) { |
| return null; |
| } |
| return aShort; |
| case Types.TIME: |
| return accessor.getTime(localCalendar); |
| case Types.TIMESTAMP: |
| return accessor.getTimestamp(localCalendar); |
| case Types.TINYINT: |
| final byte aByte = accessor.getByte(); |
| if (aByte == 0 && accessor.wasNull()) { |
| return null; |
| } |
| return aByte; |
| default: |
| throw notImplemented(); |
| } |
| } |
| |
| public void setObject(Object x) { |
| slots[index] = TypedValue.ofJdbc(x, calendar); |
| } |
| |
| public void setNull(int sqlType) { |
| slots[index] = wrap(ColumnMetaData.Rep.OBJECT, null); |
| } |
| |
| public void setRef(Ref x) { |
| } |
| |
| public void setBlob(Blob x) { |
| } |
| |
| public void setClob(Clob x) { |
| } |
| |
| public void setArray(Array x) { |
| slots[index] = wrap(ColumnMetaData.Rep.ARRAY, x); |
| } |
| |
| public void setNull(int sqlType, String typeName) { |
| } |
| |
| public void setURL(URL x) { |
| } |
| |
| public void setObject(Object x, int targetSqlType, |
| int scaleOrLength) { |
| } |
| |
| private static RuntimeException unsupportedCast(Class<?> from, Class<?> to) { |
| return new UnsupportedOperationException("Cannot convert from " |
| + from.getCanonicalName() + " to " + to.getCanonicalName()); |
| } |
| |
| private static RuntimeException notImplemented() { |
| return new RuntimeException("not implemented"); |
| } |
| |
| private static Array toArray(Object x) { |
| if (x instanceof Array) { |
| return (Array) x; |
| } |
| throw unsupportedCast(x.getClass(), Array.class); |
| } |
| |
| public static BigDecimal toBigDecimal(Object x) { |
| if (x instanceof BigDecimal) { |
| return (BigDecimal) x; |
| } else if (x instanceof BigInteger) { |
| return new BigDecimal((BigInteger) x); |
| } else if (x instanceof Number) { |
| if (x instanceof Double || x instanceof Float) { |
| return new BigDecimal(((Number) x).doubleValue()); |
| } else { |
| return new BigDecimal(((Number) x).longValue()); |
| } |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? BigDecimal.ONE : BigDecimal.ZERO; |
| } else if (x instanceof String) { |
| return new BigDecimal((String) x); |
| } |
| throw unsupportedCast(x.getClass(), BigDecimal.class); |
| } |
| |
| private static boolean toBoolean(Object x) { |
| if (x instanceof Boolean) { |
| return (Boolean) x; |
| } else if (x instanceof Number) { |
| return ((Number) x).intValue() != 0; |
| } else if (x instanceof String) { |
| String s = (String) x; |
| if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("yes")) { |
| return true; |
| } else if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("no")) { |
| return false; |
| } |
| } |
| throw unsupportedCast(x.getClass(), Boolean.TYPE); |
| } |
| |
| private static byte toByte(Object x) { |
| if (x instanceof Number) { |
| return ((Number) x).byteValue(); |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? (byte) 1 : (byte) 0; |
| } else if (x instanceof String) { |
| return Byte.parseByte((String) x); |
| } else { |
| throw unsupportedCast(x.getClass(), Byte.TYPE); |
| } |
| } |
| |
| private static byte[] toBytes(Object x) { |
| if (x instanceof byte[]) { |
| return (byte[]) x; |
| } |
| if (x instanceof String) { |
| return ((String) x).getBytes(StandardCharsets.UTF_8); |
| } |
| throw unsupportedCast(x.getClass(), byte[].class); |
| } |
| |
| private static Date toDate(Object x) { |
| if (x instanceof String) { |
| return Date.valueOf((String) x); |
| } |
| return new Date(toLong(x)); |
| } |
| |
| private static Time toTime(Object x) { |
| if (x instanceof String) { |
| return Time.valueOf((String) x); |
| } |
| return new Time(toLong(x)); |
| } |
| |
| private static Timestamp toTimestamp(Object x) { |
| if (x instanceof String) { |
| return Timestamp.valueOf((String) x); |
| } |
| return new Timestamp(toLong(x)); |
| } |
| |
| private static double toDouble(Object x) { |
| if (x instanceof Number) { |
| return ((Number) x).doubleValue(); |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? 1D : 0D; |
| } else if (x instanceof String) { |
| return Double.parseDouble((String) x); |
| } else { |
| throw unsupportedCast(x.getClass(), Double.TYPE); |
| } |
| } |
| |
| private static float toFloat(Object x) { |
| if (x instanceof Number) { |
| return ((Number) x).floatValue(); |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? 1F : 0F; |
| } else if (x instanceof String) { |
| return Float.parseFloat((String) x); |
| } else { |
| throw unsupportedCast(x.getClass(), Float.TYPE); |
| } |
| } |
| |
| private static int toInt(Object x) { |
| if (x instanceof Number) { |
| return ((Number) x).intValue(); |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? 1 : 0; |
| } else if (x instanceof String) { |
| return Integer.parseInt((String) x); |
| } else { |
| throw unsupportedCast(x.getClass(), Integer.TYPE); |
| } |
| } |
| |
| private static long toLong(Object x) { |
| if (x instanceof Number) { |
| return ((Number) x).longValue(); |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? 1L : 0L; |
| } else if (x instanceof String) { |
| return Long.parseLong((String) x); |
| } else { |
| throw unsupportedCast(x.getClass(), Long.TYPE); |
| } |
| } |
| |
| private static short toShort(Object x) { |
| if (x instanceof Number) { |
| return ((Number) x).shortValue(); |
| } else if (x instanceof Boolean) { |
| return (Boolean) x ? (short) 1 : (short) 0; |
| } else if (x instanceof String) { |
| return Short.parseShort((String) x); |
| } else { |
| throw unsupportedCast(x.getClass(), Short.TYPE); |
| } |
| } |
| |
| private static String toString(Object x) { |
| if (x instanceof String) { |
| return (String) x; |
| } else if (x instanceof Character |
| || x instanceof Boolean) { |
| return x.toString(); |
| } |
| throw unsupportedCast(x.getClass(), String.class); |
| } |
| |
| /** Singleton value to denote parameters that have been set to null (as |
| * opposed to not set). |
| * |
| * <p>Not a valid value for a parameter. |
| * |
| * <p>As an enum, it is serializable by Jackson. */ |
| private enum Dummy { |
| INSTANCE |
| } |
| } |
| |
| // End AvaticaSite.java |