/* * Copyright (c) 2010 Mysema Ltd. * All rights reserved. * */ package com.mysema.query.sql; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mysema.commons.lang.CloseableIterator; import com.mysema.commons.lang.IteratorAdapter; import com.mysema.query.DefaultQueryMetadata; import com.mysema.query.QueryException; import com.mysema.query.QueryMetadata; import com.mysema.query.QueryModifiers; import com.mysema.query.SearchResults; import com.mysema.query.support.ProjectableQuery; import com.mysema.query.support.QueryMixin; import com.mysema.query.types.Expr; import com.mysema.query.types.OrderSpecifier; import com.mysema.query.types.SubQuery; import com.mysema.query.types.expr.EBoolean; import com.mysema.query.types.expr.EConstructor; import com.mysema.query.types.path.PEntity; import com.mysema.query.types.query.ListSubQuery; import com.mysema.util.JDBCUtil; import com.mysema.util.ResultSetAdapter; /** * AbstractSQLQuery is the base type for SQL query implementations * * @author tiwe * @version $Id$ */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING") public abstract class AbstractSQLQuery> extends ProjectableQuery { public class UnionBuilder implements Union { @Override @SuppressWarnings("unchecked") public List list() throws SQLException { if (sq[0].getMetadata().getProjection().size() == 1) { return (List) IteratorAdapter.asList(AbstractSQLQuery.this.iterateSingle(null)); } else { return (List) AbstractSQLQuery.this.iterateMultiple(); } } @Override public UnionBuilder orderBy(OrderSpecifier... o) { AbstractSQLQuery.this.orderBy(o); return this; } } private static final Logger logger = LoggerFactory .getLogger(AbstractSQLQuery.class); @Nullable private final Connection conn; @Nullable private List constants; @Nullable private SubQuery[] sq; private final SQLTemplates templates; public AbstractSQLQuery(@Nullable Connection conn, SQLTemplates templates) { this(conn, templates, new DefaultQueryMetadata()); } @SuppressWarnings("unchecked") public AbstractSQLQuery(@Nullable Connection conn, SQLTemplates templates, QueryMetadata metadata) { super(new QueryMixin(metadata)); this.queryMixin.setSelf((Q) this); this.conn = conn; this.templates = templates; } protected String buildQueryString(boolean forCountRow) { SQLSerializer serializer = createSerializer(); if (sq != null) { serializer.serializeUnion(sq, queryMixin.getMetadata().getOrderBy()); } else { serializer.serialize(queryMixin.getMetadata(), forCountRow); } constants = serializer.getConstants(); return serializer.toString(); } @Override public long count() { try { return unsafeCount(); } catch (SQLException e) { String error = "Caught " + e.getClass().getName(); logger.error(error, e); throw new QueryException(e.getMessage(), e); } } protected SQLSerializer createSerializer() { return new SQLSerializer(templates); } public Q from(Expr... args) { return queryMixin.from(args); } public Q fullJoin(PEntity target) { return queryMixin.fullJoin(target); } @SuppressWarnings("unchecked") private T get(ResultSet rs, int i, Class type) { String methodName = "get" + type.getSimpleName(); if (methodName.equals("getInteger")) { methodName = "getInt"; } // TODO : cache methods try { return (T) ResultSet.class.getMethod(methodName, int.class).invoke(rs, i); } catch (SecurityException e) { throw new QueryException(e); } catch (IllegalAccessException e) { throw new QueryException(e); } catch (InvocationTargetException e) { throw new QueryException(e); } catch (NoSuchMethodException e) { throw new QueryException(e); } } public QueryMetadata getMetadata() { return queryMixin.getMetadata(); } public ResultSet getResults(Expr... exprs) { queryMixin.addToProjection(exprs); String queryString = buildQueryString(false); logger.debug("query : {}", queryString); try { final PreparedStatement stmt = conn.prepareStatement(queryString); JDBCUtil.setParameters(stmt, constants); ResultSet rs = stmt.executeQuery(); return new ResultSetAdapter(rs) { @Override public void close() throws SQLException { try { super.close(); } finally { stmt.close(); } } }; } catch (SQLException e) { throw new QueryException(e); } finally { reset(); } } protected SQLTemplates getTemplates() { return templates; } public Q innerJoin(PEntity target) { return queryMixin.innerJoin(target); } private UnionBuilder innerUnion(SubQuery... sq) { if (!queryMixin.getMetadata().getJoins().isEmpty()) { throw new IllegalArgumentException("Don't mix union and from"); } this.sq = sq; return new UnionBuilder(); } @Override public CloseableIterator iterate(Expr[] args) { queryMixin.addToProjection(args); return iterateMultiple(); } @SuppressWarnings("unchecked") @Override public CloseableIterator iterate(Expr expr) { queryMixin.addToProjection(expr); if (expr.getType().isArray()) { return (CloseableIterator) iterateMultiple(); } else { return iterateSingle(expr); } } private CloseableIterator iterateMultiple() { String queryString = buildQueryString(false); logger.debug("query : {}", queryString); try { PreparedStatement stmt = conn.prepareStatement(queryString); final List> projection = getMetadata().getProjection(); JDBCUtil.setParameters(stmt, constants); ResultSet rs = stmt.executeQuery(); return new SQLResultIterator(stmt, rs) { @SuppressWarnings("unchecked") @Override protected Object[] produceNext(ResultSet rs) { try { List objects = new ArrayList(projection.size()); int index = 0; for (int i = 0; i < projection.size(); i++){ Expr expr = projection.get(i); if (expr instanceof EConstructor){ objects.add(newInstance((EConstructor)expr, rs, index)); index += ((EConstructor)expr).getArgs().size(); }else if (expr.getType().isArray()){ for (int j = index; j < rs.getMetaData().getColumnCount(); j++){ objects.add(get(rs, index++ + 1, Object.class)); } i = objects.size(); }else{ objects.add(get(rs, index++ + 1, expr.getType())); } } return objects.toArray(); } catch (InstantiationException e) { close(); throw new QueryException(e); } catch (IllegalAccessException e) { close(); throw new QueryException(e); } catch (InvocationTargetException e) { close(); throw new QueryException(e); } catch (SQLException e) { close(); throw new QueryException(e); } } }; } catch (SQLException e) { throw new QueryException(e); } finally { reset(); } } @SuppressWarnings("unchecked") private CloseableIterator iterateSingle(@Nullable final Expr expr) { String queryString = buildQueryString(false); logger.debug("query : {}", queryString); try { PreparedStatement stmt = conn.prepareStatement(queryString); JDBCUtil.setParameters(stmt, constants); ResultSet rs = stmt.executeQuery(); return new SQLResultIterator(stmt, rs) { @Override public RT produceNext(ResultSet rs) { try { if (expr == null){ return (RT) rs.getObject(1); }else if (expr instanceof EConstructor) { return newInstance((EConstructor) expr, rs, 0); }else if (expr.getType().isArray()){ Object[] rv = new Object[rs.getMetaData().getColumnCount()]; for (int i = 0; i < rv.length; i++){ rv[i] = rs.getObject(i+1); } return (RT) rv; } else{ return (RT) get(rs, 1, expr.getType()); } } catch (IllegalAccessException e) { close(); throw new QueryException(e); } catch (InvocationTargetException e) { close(); throw new QueryException(e); } catch (InstantiationException e) { close(); throw new QueryException(e); } catch (SQLException e) { close(); throw new QueryException(e); } } }; } catch (SQLException e) { throw new QueryException(e); } finally { reset(); } } public Q join(PEntity target) { return queryMixin.join(target); } public Q leftJoin(PEntity target) { return queryMixin.leftJoin(target); } @Override public List list(Expr[] args) { return IteratorAdapter.asList(iterate(args)); } @Override public List list(Expr expr) { return IteratorAdapter.asList(iterate(expr)); } @Override public SearchResults listResults(Expr expr) { queryMixin.addToProjection(expr); long total = count(); try { if (total > 0) { QueryModifiers modifiers = queryMixin.getMetadata().getModifiers(); return new SearchResults(list(expr), modifiers, total); } else { return SearchResults.emptyResults(); } } finally { reset(); } } private RT newInstance(EConstructor c, ResultSet rs, int offset) throws InstantiationException, IllegalAccessException, InvocationTargetException{ Object[] args = new Object[c.getArgs().size()]; for (int i = 0; i < args.length; i++) { args[i] = get(rs, offset + i + 1, c.getArg(i).getType()); } return c.getJavaConstructor().newInstance(args); } public Q on(EBoolean... conditions) { return queryMixin.on(conditions); } private void reset() { queryMixin.getMetadata().reset(); constants = null; } @Override public String toString() { return buildQueryString(false).trim(); } public UnionBuilder union(ListSubQuery... sq) { return innerUnion(sq); } public UnionBuilder union(SubQuery... sq) { return innerUnion(sq); } @Override public RT uniqueResult(Expr expr) { List list = list(expr); return !list.isEmpty() ? list.get(0) : null; } private long unsafeCount() throws SQLException { String queryString = buildQueryString(true); logger.debug("query : {}", queryString); PreparedStatement stmt = null; ResultSet rs = null; try { stmt = conn.prepareStatement(queryString); JDBCUtil.setParameters(stmt, constants); rs = stmt.executeQuery(); rs.next(); return rs.getLong(1); } catch (SQLException e) { throw new QueryException(e.getMessage(), e); } finally { try { if (rs != null) { rs.close(); } } finally { if (stmt != null) { stmt.close(); } } } } }