/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed 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.jkiss.dbeaver.model.impl.jdbc.data.handlers;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBValueFormatting;
import org.jkiss.dbeaver.model.data.DBDDataFormatter;
import org.jkiss.dbeaver.model.data.DBDDataFormatterProfile;
import org.jkiss.dbeaver.model.data.DBDDisplayFormat;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.impl.data.formatters.DefaultDataFormatter;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import org.jkiss.utils.CommonUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.sql.Types;
/**
* JDBC number value handler
*/
public class JDBCNumberValueHandler extends JDBCAbstractValueHandler {
private static final Log log = Log.getLog(JDBCNumberValueHandler.class);
private DBDDataFormatter formatter;
public JDBCNumberValueHandler(DBDDataFormatterProfile formatterProfile)
{
try {
formatter = formatterProfile.createFormatter(DBDDataFormatter.TYPE_NAME_NUMBER);
} catch (Exception e) {
log.error("Can't create formatter for number value handler", e); //$NON-NLS-1$
formatter = DefaultDataFormatter.INSTANCE;
}
}
/**
* NumberFormat is not thread safe thus this method is synchronized.
*/
@NotNull
@Override
public synchronized String getValueDisplayString(@NotNull DBSTypedObject column, @Nullable Object value, @NotNull DBDDisplayFormat format)
{
if (value == null) {
return DBValueFormatting.getDefaultValueDisplayString(null, format);
} else if (value instanceof String) {
// Binary string
return "b'" + value + "'";
} else if (value instanceof Double) {
double dbl = ((Double) value).doubleValue();
if (dbl != dbl) {
return "NaN";
} else if (dbl == Double.POSITIVE_INFINITY) {
return "+Infinity";
} else if (dbl == Double.NEGATIVE_INFINITY) {
return "-Infinity";
}
}
if (value instanceof Number && (format == DBDDisplayFormat.NATIVE || format == DBDDisplayFormat.EDIT)) {
return DBValueFormatting.convertNumberToNativeString((Number) value);
}
return formatter.formatValue(value);
}
@Nullable
@Override
protected Object fetchColumnValue(
DBCSession session,
JDBCResultSet resultSet,
DBSTypedObject type,
int index)
throws DBCException, SQLException
{
Number value;
switch (type.getTypeID()) {
case Types.DOUBLE:
case Types.REAL:
value = resultSet.getDouble(index);
break;
case Types.FLOAT:
try {
// Read value with maximum precision. Some drivers reports FLOAT but means double [JDBC:SQLite]
value = resultSet.getDouble(index);
} catch (SQLException e) {
value = resultSet.getFloat(index);
}
break;
case Types.INTEGER:
try {
// Read value with maximum precision. Some drivers reports INTEGER but means long [JDBC:SQLite]
value = resultSet.getLong(index);
} catch (SQLException e) {
value = resultSet.getInt(index);
}
break;
case Types.SMALLINT:
// Read int in case of unsigned shorts
value = resultSet.getInt(index);
break;
case Types.TINYINT:
// Read short in case of unsigned byte
value = resultSet.getShort(index);
break;
case Types.BIT:
if (type.getPrecision() <= 1) {
try {
// single bit
value = resultSet.getByte(index);
} catch (NumberFormatException e) {
// Maybe it is boolean? (#1604)
try {
boolean bValue = resultSet.getBoolean(index);
value = bValue ? (byte)1 : (byte)0;
} catch (Throwable e1) {
// No, it is not - rethrow original error
throw e;
}
}
} else {
// bit string
return CommonUtils.toBinaryString(resultSet.getLong(index), type.getPrecision());
}
break;
default:
// Here may be any numeric value. BigDecimal or BigInteger for example
boolean gotValue = false;
value = null;
try {
Object objectValue = resultSet.getObject(index);
if (objectValue == null || objectValue instanceof Number) {
value = (Number) objectValue;
gotValue = true;
}
} catch (SQLException e) {
log.debug(e);
}
if (value == null && !gotValue) {
if (type.getScale() > 0) {
value = resultSet.getDouble(index);
} else {
value = resultSet.getLong(index);
}
}
break;
}
if (resultSet.wasNull()) {
return null;
} else {
return value;
}
}
@Override
protected void bindParameter(JDBCSession session, JDBCPreparedStatement statement, DBSTypedObject paramType,
int paramIndex, Object value) throws SQLException
{
if (value instanceof String) {
if (paramType.getTypeID() == Types.BIT) {
// Bit string
try {
value = Long.parseLong((String) value, 2);
} catch (NumberFormatException e) {
throw new SQLException("Can't convert value '" + value + "' into bit string", e);
}
} else {
// Some number. Actually we shouldn't be here
value = DBValueFormatting.convertStringToNumber((String) value, getNumberType(paramType), formatter);
}
}
if (value == null) {
statement.setNull(paramIndex, paramType.getTypeID());
} else if (value instanceof Number) {
Number number = (Number) value;
switch (paramType.getTypeID()) {
case Types.BIGINT:
if (number instanceof BigInteger) {
statement.setBigDecimal(paramIndex, new BigDecimal((BigInteger) number));
} else {
statement.setLong(paramIndex, number.longValue());
}
statement.setLong(paramIndex, number.longValue());
break;
case Types.FLOAT:
if (number instanceof Double) {
statement.setDouble(paramIndex, number.doubleValue());
} else {
statement.setFloat(paramIndex, number.floatValue());
}
break;
case Types.DOUBLE:
case Types.REAL:
statement.setDouble(paramIndex, number.doubleValue());
break;
case Types.INTEGER:
if (number instanceof Long) {
statement.setLong(paramIndex, number.longValue());
} else {
statement.setInt(paramIndex, number.intValue());
}
break;
case Types.SMALLINT:
case Types.TINYINT:
if (number instanceof Integer) {
statement.setInt(paramIndex, number.intValue());
} else if (number instanceof Long) {
statement.setLong(paramIndex, number.longValue());
} else {
statement.setShort(paramIndex, number.shortValue());
}
break;
case Types.BIT:
if (paramType.getPrecision() <= 1) {
statement.setByte(paramIndex, number.byteValue());
} else {
statement.setLong(paramIndex, number.longValue());
}
break;
case Types.NUMERIC:
if (number instanceof Long) {
statement.setLong(paramIndex, number.longValue());
} else if (number instanceof Integer) {
statement.setInt(paramIndex, number.intValue());
} else if (number instanceof Short) {
statement.setShort(paramIndex, number.shortValue());
} else if (number instanceof Byte) {
statement.setByte(paramIndex, number.byteValue());
} else if (number instanceof Float) {
statement.setFloat(paramIndex, number.floatValue());
} else if (number instanceof BigDecimal) {
statement.setBigDecimal(paramIndex, (BigDecimal) number);
} else if (number instanceof BigInteger) {
statement.setBigDecimal(paramIndex, new BigDecimal((BigInteger) number));
} else {
statement.setDouble(paramIndex, number.doubleValue());
}
break;
default:
if (paramType.getScale() >= 0) {
statement.setDouble(paramIndex, number.doubleValue());
} else {
statement.setLong(paramIndex, number.longValue());
}
break;
}
} else {
throw new SQLException("Numeric value type '" + value.getClass().getName() + "' is not supported");
}
}
@NotNull
@Override
public Class<? extends Number> getValueObjectType(@NotNull DBSTypedObject attribute)
{
return getNumberType(attribute);
}
@Nullable
@Override
public Object getValueFromObject(@NotNull DBCSession session, @NotNull DBSTypedObject type, Object object, boolean copy) throws DBCException
{
if (object == null) {
return null;
} else if (object instanceof Number) {
return object;
} else if (object instanceof String) {
return DBValueFormatting.convertStringToNumber((String) object, getNumberType(type), formatter);
} else {
log.warn("Unrecognized type '" + object.getClass().getName() + "' - can't convert to numeric");
return null;
}
}
public Class<? extends Number> getNumberType(DBSTypedObject type) {
switch (type.getTypeID()) {
case Types.BIGINT:
return Long.class;
case Types.DECIMAL:
case Types.DOUBLE:
case Types.REAL:
return Double.class;
case Types.FLOAT:
return Float.class;
case Types.INTEGER:
return Integer.class;
case Types.SMALLINT:
case Types.TINYINT:
return Short.class;
case Types.BIT:
if (type.getPrecision() <= 1) {
return Byte.class;
} else {
// bit string (hopefully long is enough)
return Long.class;
}
case Types.NUMERIC:
return BigDecimal.class;
default:
if (type.getScale() > 0) {
return Double.class;
} else {
return Long.class;
}
}
}
}