/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu;
import java.lang.reflect.Field;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import org.h2.jaqu.bytecode.ClassReader;
import org.h2.jaqu.util.StatementLogger;
import org.h2.jaqu.util.ClassUtils;
import org.h2.util.JdbcUtils;
import org.h2.util.New;
/**
* This class represents a query.
*
* @param <T> the return type
*/
public class Query<T> {
private Db db;
private SelectTable<T> from;
private ArrayList<Token> conditions = New.arrayList();
private ArrayList<UpdateColumn> updateColumnDeclarations = New.arrayList();
private ArrayList<SelectTable<?>> joins = New.arrayList();
private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = ClassUtils.newIdentityHashMap();
private ArrayList<OrderExpression<T>> orderByList = New.arrayList();
private Object[] groupByExpressions;
private long limit;
private long offset;
Query(Db db) {
this.db = db;
}
@SuppressWarnings("unchecked")
static <T> Query<T> from(Db db, T alias) {
Query<T> query = new Query<T>(db);
TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
query.from = new SelectTable<T>(db, query, alias, false);
def.initSelectObject(query.from, alias, query.aliasMap);
return query;
}
public long selectCount() {
SQLStatement stat = getSelectStatement(false);
stat.appendSQL("COUNT(*) ");
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
Statement s = null;
try {
s = rs.getStatement();
rs.next();
long value = rs.getLong(1);
return value;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(s);
}
}
public List<T> select() {
return select(false);
}
public T selectFirst() {
return select(false).get(0);
}
public List<T> selectDistinct() {
return select(true);
}
@SuppressWarnings("unchecked")
public <X, Z> X selectFirst(Z x) {
List<X> list = (List<X>) select(x);
return list.isEmpty() ? null : list.get(0);
}
public String getSQL() {
SQLStatement stat = getSelectStatement(false);
stat.appendSQL("*");
appendFromWhere(stat);
return stat.getSQL().trim();
}
private List<T> select(boolean distinct) {
List<T> result = New.arrayList();
TableDefinition<T> def = from.getAliasDefinition();
SQLStatement stat = getSelectStatement(distinct);
def.appendSelectList(stat);
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
Statement s = null;
try {
s = rs.getStatement();
while (rs.next()) {
T item = from.newObject();
from.getAliasDefinition().readRow(item, rs);
result.add(item);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(s);
}
return result;
}
public int delete() {
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("DELETE FROM ");
from.appendSQL(stat);
appendWhere(stat);
StatementLogger.delete(stat.getSQL());
return stat.executeUpdate();
}
public <A> UpdateColumnSet<T, A> set(A field) {
return new UpdateColumnSet<T, A>(this, field);
}
public <A> UpdateColumnIncrement<T, A> increment(A field) {
return new UpdateColumnIncrement<T, A>(this, field);
}
public int update() {
if (updateColumnDeclarations.size() == 0) {
throw new RuntimeException("Missing set or increment call.");
}
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("UPDATE ");
from.appendSQL(stat);
stat.appendSQL(" SET ");
int i = 0;
for (UpdateColumn declaration : updateColumnDeclarations) {
if (i++ > 0) {
stat.appendSQL(", ");
}
declaration.appendSQL(stat);
}
appendWhere(stat);
StatementLogger.update(stat.getSQL());
return stat.executeUpdate();
}
public <X, Z> List<X> selectDistinct(Z x) {
return select(x, true);
}
public <X, Z> List<X> select(Z x) {
return select(x, false);
}
@SuppressWarnings("unchecked")
private <X, Z> List<X> select(Z x, boolean distinct) {
Class<?> clazz = x.getClass();
if (ClassUtils.isSimpleType(clazz)) {
return selectSimple((X) x, distinct);
}
clazz = clazz.getSuperclass();
return select((Class<X>) clazz, (X) x, distinct);
}
private <X> List<X> select(Class<X> clazz, X x, boolean distinct) {
List<X> result = New.arrayList();
TableDefinition<X> def = db.define(clazz);
SQLStatement stat = getSelectStatement(distinct);
def.appendSelectList(stat, this, x);
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
Statement s = null;
try {
s = rs.getStatement();
while (rs.next()) {
X row = ClassUtils.newObject(clazz);
def.readRow(row, rs);
result.add(row);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(s);
}
return result;
}
@SuppressWarnings("unchecked")
private <X> List<X> selectSimple(X x, boolean distinct) {
SQLStatement stat = getSelectStatement(distinct);
appendSQL(stat, x);
appendFromWhere(stat);
ResultSet rs = stat.executeQuery();
List<X> result = New.arrayList();
Statement s = null;
try {
s = rs.getStatement();
while (rs.next()) {
try {
X value;
Object o = rs.getObject(1);
int convertHereIsProbablyWrong;
if (Clob.class.isAssignableFrom(o.getClass())) {
value = (X) ClassUtils.convert(o, String.class);
} else {
value = (X) o;
}
result.add(value);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(s);
}
return result;
}
private SQLStatement getSelectStatement(boolean distinct) {
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("SELECT ");
if (distinct) {
stat.appendSQL("DISTINCT ");
}
return stat;
}
public <A> QueryCondition<T, A> where(A x) {
return new QueryCondition<T, A>(this, x);
}
public <A> QueryWhere<T> where(Filter filter) {
HashMap<String, Object> fieldMap = New.hashMap();
for (Field f : filter.getClass().getDeclaredFields()) {
f.setAccessible(true);
try {
Object obj = f.get(filter);
if (obj == from.getAlias()) {
List<TableDefinition.FieldDefinition> fields = from.getAliasDefinition().getFields();
String name = f.getName();
for (TableDefinition.FieldDefinition field : fields) {
String n = name + "." + field.field.getName();
Object o = field.field.get(obj);
fieldMap.put(n, o);
}
}
fieldMap.put(f.getName(), f.get(filter));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Token filterCode = new ClassReader().decompile(filter, fieldMap, "where");
// String filterQuery = filterCode.toString();
conditions.add(filterCode);
return new QueryWhere<T>(this);
}
public QueryWhere<T> whereTrue(Boolean condition) {
Token token = new Function("", condition);
addConditionToken(token);
return new QueryWhere<T>(this);
}
/**
* Sets the Limit and Offset of a query.
*
* @return the query
*/
public Query<T> limit(long limit) {
this.limit = limit;
return this;
}
public Query<T> offset(long offset) {
this.offset = offset;
return this;
}
/**
* Order by a number of columns.
*
* @param expressions the columns
* @return the query
*/
public Query<T> orderBy(Object... expressions) {
for (Object expr : expressions) {
OrderExpression<T> e =
new OrderExpression<T>(this, expr, false, false, false);
addOrderBy(e);
}
return this;
}
public Query<T> orderByDesc(Object expr) {
OrderExpression<T> e =
new OrderExpression<T>(this, expr, true, false, false);
addOrderBy(e);
return this;
}
public Query<T> groupBy(Object... groupBy) {
this.groupByExpressions = groupBy;
return this;
}
/**
* INTERNAL
*
* @param stat the statement
* @param x the alias object
*/
public void appendSQL(SQLStatement stat, Object x) {
if (x == Function.count()) {
stat.appendSQL("COUNT(*)");
return;
}
Token token = Db.getToken(x);
if (token != null) {
token.appendSQL(stat, this);
return;
}
SelectColumn<T> col = aliasMap.get(x);
if (col != null) {
col.appendSQL(stat);
return;
}
stat.appendSQL("?");
stat.addParameter(x);
}
void addConditionToken(Token condition) {
conditions.add(condition);
}
void addUpdateColumnDeclaration(UpdateColumn declaration) {
updateColumnDeclarations.add(declaration);
}
void appendWhere(SQLStatement stat) {
if (!conditions.isEmpty()) {
stat.appendSQL(" WHERE ");
for (Token token : conditions) {
token.appendSQL(stat, this);
stat.appendSQL(" ");
}
}
}
@SuppressWarnings("unchecked")
void appendFromWhere(SQLStatement stat) {
stat.appendSQL(" FROM ");
from.appendSQL(stat);
for (SelectTable join : joins) {
join.appendSQLAsJoin(stat, this);
}
appendWhere(stat);
if (groupByExpressions != null) {
stat.appendSQL(" GROUP BY ");
int i = 0;
for (Object obj : groupByExpressions) {
if (i++ > 0) {
stat.appendSQL(", ");
}
appendSQL(stat, obj);
stat.appendSQL(" ");
}
}
if (!orderByList.isEmpty()) {
stat.appendSQL(" ORDER BY ");
int i = 0;
for (OrderExpression o : orderByList) {
if (i++ > 0) {
stat.appendSQL(", ");
}
o.appendSQL(stat);
stat.appendSQL(" ");
}
}
if (limit > 0) {
db.getDialect().appendLimit(stat, limit);
}
if (offset > 0) {
db.getDialect().appendOffset(stat, offset);
}
StatementLogger.select(stat.getSQL());
}
/**
* Join another table.
*
* @param alias an alias for the table to join
* @return the joined query
*/
@SuppressWarnings("unchecked")
public <U> QueryJoin innerJoin(U alias) {
TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
SelectTable<T> join = new SelectTable(db, this, alias, false);
def.initSelectObject(join, alias, aliasMap);
joins.add(join);
return new QueryJoin(this, join);
}
Db getDb() {
return db;
}
boolean isJoin() {
return !joins.isEmpty();
}
SelectColumn<T> getSelectColumn(Object obj) {
return aliasMap.get(obj);
}
void addOrderBy(OrderExpression<T> expr) {
orderByList.add(expr);
}
}