/*
* 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 java.util.ArrayList;
import java.util.Map;
import com.caucho.amber.expr.AmberExpr;
import com.caucho.amber.expr.JoinExpr;
import com.caucho.amber.expr.KeyColumnExpr;
import com.caucho.amber.expr.LoadEntityExpr;
import com.caucho.amber.table.AmberColumn;
import com.caucho.amber.type.AmberType;
import com.caucho.amber.type.EntityType;
import com.caucho.amber.type.SubEntityType;
import com.caucho.jdbc.JdbcMetaData;
import com.caucho.util.CharBuffer;
/**
* Represents an Amber select query
*/
public class AmberSelectQuery extends AbstractQuery {
private AbstractQuery _parentQuery;
private boolean _isDistinct;
private ArrayList<AmberExpr> _resultList;
private ArrayList<AmberExpr> _orderList;
private ArrayList<Boolean> _ascList;
private ArrayList<AmberExpr> _groupList;
private int _offset = -1;
private int _limit = -1;
private Map<AmberExpr, String> _joinFetchMap;
private String _sql;
// SELECT NEW
private Class _constructorClass;
private boolean _isTableReadOnly = false;
private long _cacheTimeout = -1;
private boolean _hasFrom = true;
AmberSelectQuery(String query, JdbcMetaData metaData)
{
super(query, metaData);
}
/**
* Gets the (join) fetch map.
*/
Map<AmberExpr, String> getJoinFetchMap()
{
return _joinFetchMap;
}
/**
* Sets the constructor class for SELECT NEW.
*/
void setConstructorClass(Class cl)
{
_constructorClass = cl;
}
/**
* Gets the constructor class for SELECT NEW.
*/
public Class getConstructorClass()
{
return _constructorClass;
}
/**
* Sets whether the query has a FROM clause or not.
*/
void setHasFrom(boolean hasFrom)
{
// The spec. is not clear about the FROM clause for
// Current_Date/Time/Timestamp functions.
_hasFrom = hasFrom;
}
/**
* Sets the parent query.
*/
void setParentQuery(AbstractQuery parent)
{
_parentQuery = parent;
// jpa/0g40
if (parent != null) {
// jpa/1231
parent.setHasSubQuery(true);
}
}
/**
* Gets the parent query.
*/
public AbstractQuery getParentQuery()
{
return _parentQuery;
}
/**
* Sets true if distinct.
*/
void setDistinct(boolean isDistinct)
{
_isDistinct = isDistinct;
}
/**
* Sets the result list.
*/
void setResultList(ArrayList<AmberExpr> resultList)
{
_resultList = resultList;
}
/**
* Returns the result list.
*/
public ArrayList<AmberExpr> getResultList()
{
return _resultList;
}
/**
* Returns the result type.
*/
int getResultCount()
{
return _resultList.size();
}
/**
* Returns the result type.
*/
AmberType getResultType(int index)
{
AmberExpr expr = _resultList.get(index);
return expr.getType();
}
/**
* Sets the having expression
*/
void setHaving(AmberExpr expr)
{
_having = expr;
}
/**
* Sets the where expression
*/
void setWhere(AmberExpr expr)
{
_where = expr;
}
/**
* Sets the group by list.
*/
void setGroupList(ArrayList<AmberExpr> groupList)
{
_groupList = groupList;
}
/**
* Sets the (join) fetch map.
*/
void setJoinFetchMap(Map<AmberExpr, String> joinFetchMap)
{
_joinFetchMap = joinFetchMap;
}
/**
* Sets the order by list.
*/
void setOrderList(ArrayList<AmberExpr> orderList,
ArrayList<Boolean> ascList)
{
_orderList = orderList;
_ascList = ascList;
}
/**
* Returns the id load sql
*/
public String getSQL()
{
return _sql;
}
/**
* Returns the expire time.
*/
public long getCacheMaxAge()
{
return _cacheTimeout;
}
/**
* Returns true for cacheable queries.
*/
public boolean isCacheable()
{
return 100L <= _cacheTimeout;
}
/**
* Are the tables read-only
*/
public boolean isTableReadOnly()
{
return _isTableReadOnly;
}
/**
* Sets the OFFSET value.
*/
public void setOffset(int offset)
{
_offset = offset;
}
/**
* Gets the OFFSET value.
*/
public int getOffset()
{
return _offset;
}
/**
* Sets the LIMIT value.
*/
public void setLimit(int limit)
{
_limit = limit;
}
/**
* Gets the LIMIT value.
*/
public int getLimit()
{
return _limit;
}
/**
* initializes the query.
*/
void init()
throws QueryParseException
{
super.init();
_cacheTimeout = Long.MAX_VALUE / 2;
_isTableReadOnly = true;
for (FromItem item : _fromList) {
EntityType type = item.getTableType();
if (type != null) {
long timeout = type.getCacheTimeout();
if (timeout < _cacheTimeout)
_cacheTimeout = timeout;
if (! type.isReadOnly())
_isTableReadOnly = false;
}
else {
// XXX: kills the cache?
_isTableReadOnly = false;
}
}
_sql = generateLoadSQL();
}
/**
* Returns true if the item must have at least one entry in the database.
*/
public boolean exists(FromItem item)
{
// jpa/0h1b vs jpa/114g
if (_where != null && _where.exists(item)) {
return true;
}
if (_orderList != null) {
for (AmberExpr orderBy : _orderList) {
// jpa/1110
if (orderBy instanceof KeyColumnExpr
&& orderBy.usesFrom(item, AmberExpr.IS_INNER_JOIN, false))
return true;
}
}
if (_groupList != null) {
for (AmberExpr groupBy : _groupList) {
if (groupBy instanceof KeyColumnExpr
&& groupBy.usesFrom(item, AmberExpr.IS_INNER_JOIN, false))
return true;
}
}
if (_having != null && _having.exists(item))
return true;
return false;
}
/**
* Returns true if the from item is used by the query.
*/
public boolean usesFrom(FromItem item, int type)
{
for (int j = 0; j < _resultList.size(); j++) {
AmberExpr result = _resultList.get(j);
if (result.usesFrom(item, type)) {
return true;
}
}
if (_where != null && _where.usesFrom(item, type)) {
return true;
}
if (_orderList != null) {
for (int j = 0; j < _orderList.size(); j++) {
AmberExpr order = _orderList.get(j);
if (order.usesFrom(item, type)) {
return true;
}
}
}
// jpa/1123
if (_groupList != null) {
for (int j = 0; j < _groupList.size(); j++) {
AmberExpr group = _groupList.get(j);
// jpa/1123 if (group.usesFrom(item, type)) {
if (group.usesFrom(item, AmberExpr.IS_INNER_JOIN)) {
return true;
}
}
if (_having != null && _having.usesFrom(item, type))
return true;
}
return false;
}
void replaceJoin(JoinExpr join)
{
for (int i = 0; i < _resultList.size(); i++) {
AmberExpr result = _resultList.get(i);
_resultList.set(i, result.replaceJoin(join));
}
if (_where != null) {
_where = _where.replaceJoin(join);
}
if (_orderList != null) {
for (int i = 0; i < _orderList.size(); i++) {
AmberExpr order = _orderList.get(i);
_orderList.set(i, order.replaceJoin(join));
}
}
}
public String generateLoadSQL()
{
return generateLoadSQL(true);
}
/**
* Generates the load SQL.
*
* @param fullSelect true if the load entity expressions
* should be fully loaded for all entity
* fields. Otherwise, only the entity id
* will be loaded: select o.id from ...
* It is implemented to optimize the SQL
* and allow for databases that only
* support single columns in subqueries.
* Derby is an example. An additional
* condition to generate only the o.id
* is the absence of group by. If there
* is a group by the full select will
* always be generated.
*
* See also com.caucho.amber.expr.ExistsExpr
*
* @return the load SQL.
*/
public String generateLoadSQL(boolean fullSelect)
{
CharBuffer cb = CharBuffer.allocate();
cb.append("select ");
if (_isDistinct)
cb.append(" distinct ");
for (int i = 0; i < _resultList.size(); i++) {
if (i != 0)
cb.append(", ");
AmberExpr expr = _resultList.get(i);
if (_groupList == null && expr instanceof LoadEntityExpr)
((LoadEntityExpr) expr).generateSelect(cb, fullSelect);
else
expr.generateSelect(cb);
}
if (_hasFrom)
cb.append(" from ");
// jpa/114f: reorder from list for left outer join
for (int i = 1; i < _fromList.size(); i++) {
FromItem item = _fromList.get(i);
if (item.isOuterJoin()) {
JoinExpr join = item.getJoinExpr();
if (join == null)
continue;
FromItem parent = join.getJoinParent();
int index = _fromList.indexOf(parent);
if (index < 0)
continue;
_fromList.remove(i);
if (index < i)
index++;
_fromList.add(index, item);
}
}
boolean hasJoinExpr = false;
boolean isFirst = true;
for (int i = 0; i < _fromList.size(); i++) {
FromItem item = _fromList.get(i);
// jpa/1178
if (getParentQuery() != null) {
ArrayList<FromItem> fromList = getParentQuery().getFromList();
if (fromList != null) {
if (fromList.contains(item)) {
hasJoinExpr = true;
continue;
}
}
}
if (isFirst) {
isFirst = false;
}
else {
if (item.isOuterJoin())
cb.append(" left outer join ");
else {
cb.append(", ");
if (item.getJoinExpr() != null)
hasJoinExpr = true;
}
}
cb.append(item.getTable().getName());
cb.append(" ");
cb.append(item.getName());
if (item.getJoinExpr() != null && item.isOuterJoin()) {
cb.append(" on ");
item.getJoinExpr().generateJoin(cb);
}
EntityType entityType = item.getEntityType();
// jpa/0l44, jpa/0l12
/* XXX: jpa/0l47 move this to LoadExpr.generateSelect
if (entityType != null) {
AmberColumn discriminator = entityType.getDiscriminator();
if (entityType instanceof SubEntityType &&
discriminator != null) {
// jpa/0l4b
// XXX: needs to use parser.createTableName()
FromItem discriminatorItem
= new FromItem((EntityType) entityType,
discriminator.getTable(),
item.getName() + "_disc",
++i);
discriminatorItem.setQuery(this);
_fromList.add(i, discriminatorItem);
cb.append(", ");
cb.append(discriminator.getTable().getName());
cb.append(' ');
cb.append(discriminatorItem.getName());
}
}
*/
}
// jpa/0l12
// if (hasJoinExpr || _where != null) {
boolean hasExpr = false;
for (int i = 0; i < _fromList.size(); i++) {
FromItem item = _fromList.get(i);
AmberExpr expr = item.getJoinExpr();
if (expr != null && ! item.isOuterJoin()) {
if (hasExpr)
cb.append(" and ");
else {
cb.append(" where ");
hasExpr = true;
}
expr.generateJoin(cb);
}
EntityType entityType = item.getEntityType();
// jpa/0l44
if (entityType != null) {
AmberColumn discriminator = entityType.getDiscriminator();
// jpa/0l43
if (entityType instanceof SubEntityType &&
discriminator != null) {
// jpa/0l12, jpa/0l4b
if (item.getTable() == discriminator.getTable()) {
if (hasExpr)
cb.append(" and ");
else {
cb.append(" where ");
hasExpr = true;
}
cb.append("(" + item.getName() + "." + discriminator.getName() + " = ");
cb.append("'" + entityType.getDiscriminatorValue() + "')");
}
}
}
}
if (_where != null) {
if (hasExpr)
cb.append(" and ");
else {
cb.append(" where ");
hasExpr = true;
}
_where.generateWhere(cb);
}
if (_groupList != null) {
cb.append(" group by ");
for (int i = 0; i < _groupList.size(); i++) {
if (i != 0)
cb.append(", ");
_groupList.get(i).generateSelect(cb);
}
}
if (_having != null) {
hasExpr = false;
cb.append(" having ");
/*
for (int i = 0; i < _fromList.size(); i++) {
FromItem item = _fromList.get(i);
AmberExpr expr = item.getJoinExpr();
if (expr != null && ! item.isOuterJoin()) {
if (hasExpr)
cb.append(" and ");
hasExpr = true;
expr.generateJoin(cb);
}
}
*/
if (_having != null) {
if (hasExpr)
cb.append(" and ");
hasExpr = true;
_having.generateHaving(cb);
}
}
if (_orderList != null) {
cb.append(" order by ");
for (int i = 0; i < _orderList.size(); i++) {
if (i != 0)
cb.append(", ");
_orderList.get(i).generateSelect(cb);
if (Boolean.FALSE.equals(_ascList.get(i)))
cb.append(" desc");
}
}
return cb.toString();
}
/**
* Returns true if modifying the given table modifies a cached query.
*/
public boolean invalidateTable(String table)
{
for (int i = _fromList.size() - 1; i >= 0; i--) {
FromItem from = _fromList.get(i);
if (table.equals(from.getTable().getName()))
return true;
}
return false;
}
/**
* Debug view.
*/
public String toString()
{
return "SelectQuery[" + getQueryString() + "]";
}
}