/**
* Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
* All rights reserved. Use is subject to license terms. See LICENSE.TXT
*/
package org.diirt.service.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.diirt.service.ServiceMethod;
import org.diirt.util.array.CircularBufferDouble;
import org.diirt.vtype.VNumber;
import org.diirt.vtype.VString;
import org.diirt.vtype.VTable;
import org.diirt.vtype.ValueFactory;
/**
* The implementation of a JDBC service method.
*
* @author carcassi
*/
class JDBCServiceMethod extends ServiceMethod {
private final DataSource dataSource;
private final String query;
private final List<String> parameterNames;
/**
* Creates a new JDBC service method, for querying a JDBC datasource.
*
* @param serviceMethodDescription the description of the JDBC service
* method; can't be null
* @param serviceDescription the description of the JDBC service; can't be
* null
*/
JDBCServiceMethod(JDBCServiceMethodDescription serviceMethodDescription, JDBCServiceDescription serviceDescription) {
super(serviceMethodDescription, serviceDescription);
this.dataSource = serviceDescription.dataSource;
this.query = serviceMethodDescription.query;
this.parameterNames = serviceMethodDescription.orderedParameterNames;
}
private DataSource getDataSource() {
return dataSource;
}
/**
* Gets the query command to execute on the JDBC datasource.
*
* @return query command
*/
protected String getQuery() {
return query;
}
private boolean isResultQuery() {
return !getResults().isEmpty();
}
/**
* Gets the list of ordered parameter names.
*
* @return parameter names
*/
protected List<String> getParameterNames() {
return parameterNames;
}
@Override
public Map<String, Object> syncExecImpl(Map<String, Object> parameters) throws Exception {
try (Connection connection = getDataSource().getConnection()) {
try (PreparedStatement preparedStatement = connection.prepareStatement(getQuery())) {
int i = 0;
for (String parameterName : getParameterNames()) {
Object value = parameters.get(parameterName);
if (value instanceof VString) {
preparedStatement.setString(i+1, ((VString) value).getValue());
} else if (value instanceof VNumber) {
preparedStatement.setDouble(i+1, ((VNumber) value).getValue().doubleValue());
} else {
throw new RuntimeException("JDBC mapping support for " + value.getClass().getSimpleName() + " not implemented");
}
i++;
}
if (isResultQuery()) {
ResultSet resultSet = preparedStatement.executeQuery();
VTable table = resultSetToVTable(resultSet);
return Collections.<String, Object>singletonMap(getResults().get(0).getName(), table);
} else {
preparedStatement.execute();
return new HashMap<>();
}
}
} catch (Exception ex) {
throw ex;
}
}
/**
* Maps a result set to a VTable.
*/
static VTable resultSetToVTable(ResultSet resultSet) throws SQLException {
ResultSetMetaData metaData = resultSet.getMetaData();
int nColumns = metaData.getColumnCount();
List<Class<?>> types = new ArrayList<>(nColumns);
List<Object> data = new ArrayList<>(nColumns);
List<String> names = new ArrayList<>(nColumns);
for (int j = 1; j <= nColumns; j++) {
names.add(metaData.getColumnName(j));
switch (metaData.getColumnType(j)) {
case Types.DOUBLE:
case Types.FLOAT:
// XXX: NUMERIC should be BigInteger
case Types.NUMERIC:
// XXX: Integers should be Long/Int
case Types.INTEGER:
case Types.TINYINT:
case Types.BIGINT:
case Types.SMALLINT:
types.add(double.class);
data.add(new CircularBufferDouble(Integer.MAX_VALUE));
break;
case Types.LONGNVARCHAR:
case Types.CHAR:
case Types.VARCHAR:
// XXX: should be a booloean
case Types.BOOLEAN:
case Types.BIT:
types.add(String.class);
data.add(new ArrayList<>());
break;
case Types.TIMESTAMP:
types.add(Instant.class);
data.add(new ArrayList<>());
break;
default:
if ("java.lang.String".equals(metaData.getColumnClassName(j))) {
types.add(String.class);
data.add(new ArrayList<>());
} else {
throw new IllegalArgumentException("Unsupported type " + metaData.getColumnTypeName(j));
}
}
}
while (resultSet.next()) {
for (int i = 0; i < nColumns; i++) {
Class<?> type = types.get(i);
if (type.equals(String.class)) {
@SuppressWarnings("unchecked")
List<String> strings = (List<String>) data.get(i);
strings.add(resultSet.getString(i+1));
} else if (type.equals(Instant.class)) {
@SuppressWarnings("unchecked")
List<Instant> timestamps = (List<Instant>) data.get(i);
java.sql.Timestamp sqlTimestamp = resultSet.getTimestamp(i+1);
if (sqlTimestamp == null) {
timestamps.add(null);
} else {
timestamps.add((new Date(sqlTimestamp.getTime())).toInstant());
}
} else if (type.equals(double.class)) {
((CircularBufferDouble) data.get(i)).addDouble(resultSet.getDouble(i+1));
}
}
}
return ValueFactory.newVTable(types, names, data);
}
}