/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.amber.query; import com.caucho.amber.AmberQuery; import com.caucho.amber.expr.ArgExpr; import com.caucho.amber.manager.AmberConnection; import com.caucho.amber.type.*; import com.caucho.jdbc.JdbcMetaData; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Represents the application's view of the query. */ public class UserQuery implements AmberQuery { private static final int LIMIT_INF = Integer.MAX_VALUE / 2; private AmberConnection _aConn; private AbstractQuery _query; private ResultSetImpl _rs; private QueryCacheKey _cacheKey; private AmberType []_argTypes; private Object []_argValues; private int _argLength = 0; private int _firstResult = 0; private int _maxResults = -1; private long _cacheMaxAge; private boolean _copyOnLoad = true; private boolean _loadOnQuery; public UserQuery(AbstractQuery query) { _query = query; ArgExpr []argList = query.getArgList(); _argTypes = new AmberType[argList.length]; _argValues = new Object[argList.length]; _argLength = argList.length; if (query instanceof AmberSelectQuery) { AmberSelectQuery select = (AmberSelectQuery) query; if (select.getOffset() >= 0) _firstResult = select.getOffset(); if (select.getLimit() >= 0) _maxResults = select.getLimit(); } } /** * Returns the query string. */ public String getQueryString() { return _query.getQueryString(); } public void init(AmberConnection aConn) { setSession(aConn); } public void setSession(AmberConnection aConn) { _aConn = aConn; } public AmberConnection getSession() { return _aConn; } public AmberConnection getConnection() { return _aConn; } /** * Sets true for load-on-query. */ public void setLoadOnQuery(boolean loadOnQuery) { _loadOnQuery = loadOnQuery; } /** * Returns the compiled query. */ public AbstractQuery getQuery() { return _query; } /** * Returns the arg type array. */ AmberType []getArgTypes() { return _argTypes; } /** * Returns the arg values */ Object []getArgValues() { return _argValues; } /** * Returns the arg length */ int getArgLength() { return _argLength; } /** * Sets the argument with a string */ public void setString(int index, String v) { _argTypes[index - 1] = StringType.create(); _argValues[index - 1] = v; _argLength = index; } /** * Sets the argument with a byte */ public void setByte(int index, byte v) { _argTypes[index - 1] = ByteType.create(); _argValues[index - 1] = new Integer(v); _argLength = index; } /** * Sets the argument with a short */ public void setShort(int index, short v) { _argTypes[index - 1] = ShortType.create(); _argValues[index - 1] = new Integer(v); _argLength = index; } /** * Sets the argument with an int */ public void setInt(int index, int v) { _argTypes[index - 1] = IntegerType.create(); _argValues[index - 1] = new Integer(v); _argLength = index; } /** * Sets the argument with a string */ public void setLong(int index, long v) { _argTypes[index - 1] = LongType.create(); _argValues[index - 1] = new Long(v); _argLength = index; } /** * Sets the argument with a double */ public void setDouble(int index, double v) { _argTypes[index - 1] = DoubleType.create(); _argValues[index - 1] = new Double(v); _argLength = index; } /** * Sets the argument with a double */ public void setFloat(int index, float v) { _argTypes[index - 1] = FloatType.create(); _argValues[index - 1] = new Float(v); _argLength = index; } /** * Sets the argument with a timestamp */ public void setTimestamp(int index, java.sql.Timestamp v) { _argTypes[index - 1] = SqlTimestampType.create(); _argValues[index - 1] = v; _argLength = index; } /** * Sets the argument with a date */ public void setDate(int index, java.sql.Date v) { _argTypes[index - 1] = SqlDateType.create(); _argValues[index - 1] = v; _argLength = index; } /** * Sets the argument with an object. */ public void setObject(int index, Object v) { _argTypes[index - 1] = ObjectType.create(); _argValues[index - 1] = v; _argLength = index; } /** * Sets the argument with an object and its Amber type. */ public void setObject(int index, Object v, AmberType type) { _argTypes[index - 1] = type; _argValues[index - 1] = v; _argLength = index; } /** * Sets the argument with a null */ public void setNull(int index, int v) { _argTypes[index - 1] = StringType.create(); _argValues[index - 1] = null; _argLength = index; } /** * Sets the first result. */ public void setFirstResult(int index) { _firstResult = index; } /** * Sets the maximum number of results. */ public void setMaxResults(int index) { _maxResults = index; } /** * Returns the max results. */ public int getMaxResults() { return _maxResults; } /** * Executes the query returning a result set. */ public ResultSet executeQuery() throws SQLException { _aConn.flushNoChecks(); if (_rs == null) _rs = new ResultSetImpl(); AmberSelectQuery query = (AmberSelectQuery) _query; int firstResult = _firstResult; int maxResults = _maxResults; _rs.setQuery(query); _rs.setSession(_aConn); _rs.setFirstResult(firstResult); _rs.setMaxResults(maxResults); _rs.setRow(0); int chunkSize = _aConn.getCacheChunkSize(); boolean isCacheable; if (chunkSize <= _firstResult) isCacheable = false; else if (_aConn.isActiveTransaction() && ! query.isTableReadOnly()) isCacheable = false; else if (! query.isCacheable()) isCacheable = false; else isCacheable = true; ResultSetCacheChunk cacheChunk = null; ResultSetMetaData metaData = null; if (isCacheable) { int row = 0; cacheChunk = _aConn.getQueryCacheChunk(query.getSQL(), _argValues, row); metaData = _aConn.getQueryMetaData(); if (cacheChunk == null) { cacheChunk = fillCache(query); } // all data returned in chunk firstResult = cacheChunk.getRowCount(); if (cacheChunk.getRowCount() < chunkSize) maxResults = 0; else if (maxResults < 0) maxResults = LIMIT_INF; else maxResults -= firstResult - _firstResult; _rs.setCacheChunk(cacheChunk, metaData); _rs.setUserQuery(this); } else if (maxResults < 0) maxResults = LIMIT_INF; if (maxResults > 0) { ResultSet rs; rs = executeQuery(firstResult, maxResults); metaData = rs.getMetaData(); _rs.setResultSet(rs, metaData); _rs.setRow(_firstResult); } _rs.init(); return _rs; } private ResultSetCacheChunk fillCache(AmberSelectQuery query) throws SQLException { int chunkSize = _aConn.getCacheChunkSize(); ResultSet rs = executeQuery(0, chunkSize); ResultSetMetaData metaData = rs.getMetaData(); _rs.setResultSet(rs, metaData); ResultSetCacheChunk cacheChunk = new ResultSetCacheChunk(); cacheChunk.setQuery(query); _rs.fillCacheChunk(cacheChunk); _aConn.putQueryCacheChunk(query.getSQL(), _argValues, 0, cacheChunk, metaData); return cacheChunk; } /** * Executes the query. */ ResultSet executeQuery(int firstResults, int maxResults) throws SQLException { String sql = _query.getSQL(); int row = 0; if (maxResults > 0 && maxResults < LIMIT_INF) { JdbcMetaData metaData = _aConn.getAmberManager().getMetaData(); // XXX: should limit meta-data as well? // jps/1431 if (metaData.isLimitOffset()) { sql = metaData.limit(sql, firstResults, maxResults); row = firstResults; } else sql = metaData.limit(sql, 0, firstResults + maxResults); } PreparedStatement pstmt = _aConn.prepareStatement(sql); ArgExpr []args = _query.getArgList(); if (args.length > 0) pstmt.clearParameters(); for (int i = 0; i < args.length; i++) { args[i].setParameter(pstmt, i + 1, _argTypes, _argValues); } ResultSet rs = pstmt.executeQuery(); // jpa/1431 for (int i = row; i < firstResults && rs.next(); i++) { } return rs; } /** * Executes the query returning a result set. */ public int executeUpdate() throws SQLException { _aConn.flushNoChecks(); // XXX: sync String sql = _query.getSQL(); PreparedStatement pstmt = _aConn.prepareStatement(sql); ArgExpr []args = _query.getArgList(); if (args.length > 0) pstmt.clearParameters(); for (int i = 0; i < args.length; i++) { args[i].setParameter(pstmt, i + 1, _argTypes, _argValues); } _query.prepare(this, _aConn); int count = pstmt.executeUpdate(); if (count != 0) _query.complete(this, _aConn); return count; } /** * Sets the cache max age for the query. */ public void setCacheMaxAge(long ms) { _cacheMaxAge = ms; } /** * Executes the query, returning a list. */ public List<Object> list() throws SQLException { ArrayList<Object> list = new ArrayList<Object>(); list(list); return list; } /** * Executes the query returning the single result. */ public Object getSingleResult() throws SQLException { AmberSelectQuery query = (AmberSelectQuery) _query; ResultSet rs = null; _aConn.pushDepth(); try { rs = executeQuery(); if (rs.next()) return rs.getObject(1); return null; } catch (SQLException e) { throw e; } finally { _aConn.popDepth(); if (rs != null) rs.close(); } } /** * Executes the query, filling the list. */ public void list(List<Object> list) throws SQLException { AmberSelectQuery query = (AmberSelectQuery) _query; ResultSet rs = null; _aConn.pushDepth(); try { rs = executeQuery(); int tupleCount = query.getResultCount(); while (rs.next()) { if (tupleCount == 1) { Object value = rs.getObject(1); list.add(value); } else { Object []values = new Object[tupleCount]; for (int i = 0; i < tupleCount; i++) { values[i] = rs.getObject(i + 1); } list.add(values); } } } catch (SQLException e) { throw e; } finally { _aConn.popDepth(); if (rs != null) rs.close(); } } /** * Executes the query, filling the map. */ public void list(Map<Object,Object> map, Method methodGetMapKey) throws SQLException, IllegalAccessException, InvocationTargetException { AmberSelectQuery query = (AmberSelectQuery) _query; ResultSet rs = null; _aConn.pushDepth(); try { rs = executeQuery(); int tupleCount = query.getResultCount(); while (rs.next()) { if (tupleCount == 1) { Object value = rs.getObject(1); com.caucho.amber.entity.Entity entity; entity = (com.caucho.amber.entity.Entity) value; Object mapKey; if (methodGetMapKey == null) mapKey = entity.__caucho_getPrimaryKey(); else mapKey = methodGetMapKey.invoke(entity, null); map.put(mapKey, value); } else { Object []values = new Object[tupleCount]; for (int i = 0; i < tupleCount; i++) { values[i] = rs.getObject(i + 1); } // XXX: for now, assume 1st column is the key. Object mapKey = values[0]; map.put(mapKey, values); } } } catch (SQLException e) { throw e; } finally { _aConn.popDepth(); if (rs != null) rs.close(); } } public String toString() { return "UserQuery[" + _query.getQueryString() + "]"; } }