package com.w11k.lsql.query;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.w11k.lsql.Column;
import com.w11k.lsql.LSql;
import com.w11k.lsql.ResultSetColumn;
import com.w11k.lsql.ResultSetWithColumns;
import com.w11k.lsql.converter.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import rx.subjects.Subject;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;
public abstract class AbstractQuery<T> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final LSql lSql;
private final PreparedStatement preparedStatement;
private Map<String, Converter> converters = Maps.newHashMap();
private boolean ignoreDuplicateColumns = false;
AbstractQuery(LSql lSql, PreparedStatement preparedStatement) {
this.lSql = lSql;
this.preparedStatement = preparedStatement;
}
public LSql getlSql() {
return lSql;
}
public AbstractQuery<T> ignoreDuplicateColumns() {
ignoreDuplicateColumns = true;
return this;
}
public PreparedStatement getPreparedStatement() {
return preparedStatement;
}
public Map<String, Converter> getConverters() {
return converters;
}
public AbstractQuery<T> setConverters(Map<String, Converter> converters) {
this.converters = converters;
return this;
}
public AbstractQuery<T> addConverter(String columnName, Converter converter) {
this.converters.put(columnName, converter);
return this;
}
public List<T> toList() {
return rx().toList().toBlocking().first();
}
public <R> List<R> toList(Func1<T, R> mapper) {
return rx().map(mapper).toList().toBlocking().first();
}
public abstract List<T> toTree();
/**
* Executes the query and returns the first row in the result set. Return absent() if the result set is empty.
*/
public Optional<T> first() {
List<T> list = rx().take(1).toList().toBlocking().first();
if (list.isEmpty()) {
return Optional.absent();
} else {
return of(list.get(0));
}
}
public <R> Optional<R> first(final Func1<T, R> mapper) {
return this.first().transform(new Function<T, R>() {
@Override
public R apply(T input) {
return mapper.call(input);
}
});
}
/**
* Turns this query into an Observable. Each subscription will trigger the underlying database operation.
*
* @return the Observable
*/
public Observable<T> rx() {
return rxResultSet().map(new Func1<ResultSetWithColumns, T>() {
@Override
public T call(ResultSetWithColumns resultSetWithColumns) {
return extractEntity(resultSetWithColumns);
}
});
}
/**
* Turns this query into an Observable. Each subscription will trigger the underlying database operation.
* <p/>
* This is a low-level API to directly work with the JDBC ResultSet.
*
* @return the Observable
*/
public Observable<ResultSetWithColumns> rxResultSet() {
return Subject.create(new Observable.OnSubscribe<ResultSetWithColumns>() {
@Override
public void call(Subscriber<? super ResultSetWithColumns> subscriber) {
try {
ResultSet resultSet = AbstractQuery.this.preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
// used to find duplicates
// or unused converter
Set<String> processedColumnLabels = Sets.newLinkedHashSet();
List<ResultSetColumn> resultSetColumns = Lists.newLinkedList();
LinkedHashMap<String, Converter> converters = Maps.newLinkedHashMap();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnLabel = AbstractQuery.this.lSql.identifierSqlToJava(metaData.getColumnLabel(i));
// check duplicates
if (!AbstractQuery.this.ignoreDuplicateColumns && processedColumnLabels.contains(columnLabel)) {
throw new IllegalStateException("Duplicate column '" + columnLabel + "' in query.");
}
processedColumnLabels.add(columnLabel);
Optional<Converter> converter = getConverterForResultSetColumn(metaData, i, columnLabel, false);
if (converter.isPresent()) {
resultSetColumns.add(new ResultSetColumn(i, columnLabel, converter.get()));
converters.put(columnLabel, converter.get());
}
}
// Check for unused converters
for (String converterFor : AbstractQuery.this.converters.keySet()) {
if (!processedColumnLabels.contains(converterFor)) {
throw new IllegalArgumentException(
"unused converter for column '" + converterFor + "'");
}
}
ResultSetWithColumns resultSetWithColumns =
new ResultSetWithColumns(resultSet, metaData, resultSetColumns);
checkConformity(converters);
while (resultSet.next() && !subscriber.isUnsubscribed()) {
subscriber.onNext(resultSetWithColumns);
}
resultSet.close();
subscriber.onCompleted();
} catch (SQLException e) {
subscriber.onError(e);
}
}
});
}
public Optional<Converter> getConverterForResultSetColumn(ResultSetMetaData metaData,
int position,
String columnLabel,
boolean getConverterBySqlType)
throws SQLException {
// Check for user provided Converter
if (converters.containsKey(columnLabel)) {
Converter converter = converters.get(columnLabel);
if (converter != null) {
return of(converter);
}
}
// Determine source table and column from ResultSet
String tableName = lSql.getDialect().getSchemaAndTableNameFromResultSetMetaData(metaData, position);
String columnName = lSql.getDialect().getColumnNameFromResultSetMetaData(metaData, position);
if (tableName != null
&& tableName.length() > 0
&& columnName != null
&& columnName.length() > 0) {
Column column = lSql.table(tableName).column(columnName);
if (column != null) {
if (!column.isIgnored()) {
return of(column.getConverter());
} else {
return absent();
}
}
}
// Check if the user registered null as a Converter to use a type-based Converter
// or if the default converter was allowed
if (getConverterBySqlType
|| (converters.containsKey(columnLabel) && converters.get(columnLabel) == null)) {
return of(getConverterByColumnType(metaData, position));
}
// Error/Warn
String msg = "Unable to determine a Converter instance for column '" + columnLabel + "'. ";
msg += "Register a converter with Query#addConverter() / Query#setConverters().";
if (lSql.getConfig().isUseColumnTypeForConverterLookupInQueries()) {
this.logger.warn(msg);
return of(getConverterByColumnType(metaData, position));
} else {
throw new IllegalStateException(msg);
}
}
protected abstract T createEntity();
protected abstract void checkConformity(Map<String, Converter> converters);
protected abstract void setValue(T entity, String name, Object value);
private Converter getConverterByColumnType(ResultSetMetaData metaData, int position) throws SQLException {
int columnSqlType = metaData.getColumnType(position);
return lSql.getDialect().getConverterRegistry().getConverterForSqlType(columnSqlType);
}
private T extractEntity(ResultSetWithColumns resultSetWithColumns) {
ResultSet resultSet = resultSetWithColumns.getResultSet();
Collection<ResultSetColumn> columnList = resultSetWithColumns.getColumnsByLabel().values();
T entity = createEntity();
for (ResultSetColumn column : columnList) {
try {
setValue(
entity,
column.getName(),
column.getConverter().getValueFromResultSet(lSql, resultSet, column.getPosition()));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return entity;
}
}