/*
* 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 com.teradata.tempto.query;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Lists.newArrayList;
import static java.sql.JDBCType.INTEGER;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
/**
* Result of a query.
* <p>
* It stores all returned values, column names and their types as {@link java.sql.JDBCType}.
*/
public class QueryResult
{
private final List<JDBCType> columnTypes;
private final BiMap<String, Integer> columnNamesIndexes;
private final List<List<Object>> values;
private final Optional<ResultSet> jdbcResultSet;
private QueryResult(List<JDBCType> columnTypes, BiMap<String, Integer> columnNamesIndexes, List<List<Object>> values, Optional<ResultSet> jdbcResultSet)
{
this.columnTypes = columnTypes;
this.values = values;
this.columnNamesIndexes = columnNamesIndexes;
this.jdbcResultSet = jdbcResultSet;
}
public int getRowsCount()
{
return values.size();
}
public int getColumnsCount()
{
return columnTypes.size();
}
public List<JDBCType> getColumnTypes()
{
return columnTypes;
}
public JDBCType getColumnType(int columnIndex)
{
return columnTypes.get(fromSqlIndex(columnIndex));
}
public Optional<Integer> tryFindColumnIndex(String columnName)
{
return ofNullable(columnNamesIndexes.get(columnName));
}
public List<Object> row(int rowIndex)
{
return values.get(rowIndex);
}
public List<List<Object>> rows()
{
return values;
}
@SuppressWarnings("unchecked")
public <T> List<T> column(int sqlColumnIndex)
{
int internalColumnIndex = fromSqlIndex(sqlColumnIndex);
return (List) values.stream()
.map(row -> row.get(internalColumnIndex))
.collect(toList());
}
public QueryResult project(int... sqlColumnIndexes)
{
List<JDBCType> projectedColumnTypes = newArrayList();
List<String> projectedColumnNames = newArrayList();
for (int sqlColumnIndex : sqlColumnIndexes) {
projectedColumnTypes.add(columnTypes.get(fromSqlIndex(sqlColumnIndex)));
projectedColumnNames.add(columnNamesIndexes.inverse().get(sqlColumnIndex));
}
QueryResultBuilder queryResultBuilder = new QueryResultBuilder(projectedColumnTypes, projectedColumnNames);
for (List<Object> valueList : values) {
List<Object> projectedValueList = Lists.newArrayList();
for (int sqlColumnIndex : sqlColumnIndexes) {
projectedValueList.add(valueList.get(fromSqlIndex(sqlColumnIndex)));
}
queryResultBuilder.addRow(projectedValueList);
}
if (jdbcResultSet.isPresent()) {
queryResultBuilder.setJdbcResultSet(jdbcResultSet.get());
}
return queryResultBuilder.build();
}
public Optional<ResultSet> getJdbcResultSet()
{
return jdbcResultSet;
}
/**
* In SQL/JDBC column indexing starts form 1. This method returns SQL index for given Java index.
*
* @param index 0 based column index
* @return index + 1
*/
public static int toSqlIndex(int index)
{
return index + 1;
}
/**
* In SQL/JDBC column indexing starts form 1. This method returns Java index for given SQL index.
*
* @param index 1 based column index
* @return index - 1;
*/
public static int fromSqlIndex(int index)
{
return index - 1;
}
public static QueryResultBuilder builder(ResultSetMetaData metaData)
throws SQLException
{
return new QueryResultBuilder(metaData);
}
public static QueryResult forSingleIntegerValue(int value)
throws SQLException
{
return new QueryResult(ImmutableList.of(INTEGER), HashBiMap.create(), ImmutableList.of(ImmutableList.of(value)), Optional.empty());
}
public static <T> QueryResult forSingleValue(JDBCType type, T value)
throws SQLException
{
return new QueryResult(ImmutableList.of(type), HashBiMap.create(), ImmutableList.of(ImmutableList.of(value)), Optional.empty());
}
public static QueryResult empty()
{
return new QueryResult(ImmutableList.of(INTEGER), HashBiMap.create(), ImmutableList.of(), Optional.empty());
}
public static QueryResult forResultSet(ResultSet rs)
throws SQLException
{
return QueryResult.builder(rs.getMetaData())
.addRows(rs)
.setJdbcResultSet(rs)
.build();
}
public static class QueryResultBuilder
{
private final List<JDBCType> columnTypes = newArrayList();
private final BiMap<String, Integer> columnNamesIndexes = HashBiMap.create();
private final List<List<Object>> values = newArrayList();
private Optional<ResultSet> jdbcResultSet = Optional.empty();
QueryResultBuilder(ResultSetMetaData metaData)
throws SQLException
{
for (int sqlColumnIndex = 1; sqlColumnIndex <= metaData.getColumnCount(); ++sqlColumnIndex) {
columnTypes.add(JDBCType.valueOf(metaData.getColumnType(sqlColumnIndex)));
columnNamesIndexes.put(metaData.getColumnName(sqlColumnIndex), sqlColumnIndex);
}
}
public QueryResultBuilder(List<JDBCType> columnTypes, List<String> columnNames)
{
checkState(columnTypes.size() == columnNames.size(),
"inconsistent number of entries in columnTypes and columnNames lists %s != %s",
columnTypes.size(), columnNames.size());
this.columnTypes.addAll(columnTypes);
int sqlColumnIndex = 1;
for (String columnName : columnNames) {
columnNamesIndexes.put(columnName, sqlColumnIndex);
sqlColumnIndex++;
}
}
public QueryResultBuilder addRow(Object... rowValues)
{
return addRow(Arrays.asList(rowValues));
}
public QueryResultBuilder addRow(List<Object> rowValues)
{
Preconditions.checkState(rowValues.size() == columnTypes.size(), "expected %s objects", columnTypes.size());
values.add(newArrayList(rowValues));
return this;
}
public QueryResultBuilder addRows(ResultSet rs)
throws SQLException
{
int columnCount = columnTypes.size();
while (rs.next()) {
List<Object> row = newArrayList();
for (int sqlColumnIndex = 1; sqlColumnIndex <= columnCount; ++sqlColumnIndex) {
row.add(rs.getObject(sqlColumnIndex));
}
values.add(row);
}
return this;
}
public QueryResultBuilder setJdbcResultSet(ResultSet rs)
{
this.jdbcResultSet = Optional.of(rs);
return this;
}
public QueryResult build()
{
return new QueryResult(columnTypes, columnNamesIndexes, values, jdbcResultSet);
}
}
}