/**********************************************************************
Copyright (c) 2002 Mike Martin (TJDO) and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
2003 Andy Jefferson - coding standards
2003 Andy Jefferson - renamed from CandidateSetExpression
...
**********************************************************************/
package org.jpox.store.mapped.expression;
import org.jpox.ClassLoaderResolver;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.query.StatementText;
import org.jpox.store.mapped.scostore.CollectionStoreQueryable;
import org.jpox.store.scostore.CollectionStore;
/**
* An expression that represents some collection field in a query candidate class,
* or a collection field in an object linked from the candidate class by navigation.
* <p>
* When navigated through using contains(expr), the elements of the collection
* are relationally joined onto the query statement.
* </p>
* <p>
* As this is a Collection Expression it works equally for Sets and Lists, and we use CollectionStore
* as the backing store interface, so that ListStore and SetStore are equally applicable.
*
* @version $Revision: 1.33 $
*/
public class CollectionExpression extends ScalarExpression
{
private final CollectionStore collStore;
private final String fieldName;
/**
* Constructor.
* @param qs The Query Statement
* @param ownerMapping The mapping to the owner of this collection
* @param te The Table Expression
* @param collStore the backing store.
* @param fieldName Name of the field for the collection.
**/
public CollectionExpression(QueryExpression qs,
JavaTypeMapping ownerMapping,
LogicSetExpression te,
CollectionStore collStore,
String fieldName)
{
super(qs);
this.mapping = ownerMapping;
this.collStore = collStore;
this.fieldName = fieldName;
this.te = te;
}
/**
* Executed when the size() method is found in a query filter.
* @return The NumericExpression resulting from the size() method.
*/
public NumericExpression sizeMethod()
{
IdentifierFactory idFactory = qs.getStoreManager().getIdentifierFactory();
String ctIdentifier = idFactory.newIdentifier(te.getAlias(), fieldName).getIdentifier();
DatastoreIdentifier ctRangeVar = idFactory.newIdentifier(IdentifierFactory.TABLE, ctIdentifier);
return new ContainerSizeExpression(qs, getBackingStoreQueryable().getSizeSubquery(qs, mapping, te, ctRangeVar));
}
/**
* Executed when the contains() method is found in a query filter.
* @param expr The ScalarExpression passed as a parameter to contains().
* @return The BooleanExpression resulting from the contains() method.
*/
public BooleanExpression containsMethod(ScalarExpression expr)
{
IdentifierFactory idFactory = qs.getStoreManager().getIdentifierFactory();
ClassLoaderResolver clr = qs.getClassLoaderResolver();
if (expr instanceof NullLiteral)
{
// JPOX doesn't currently support querying for nulls in Collections so just return "1 = 0"
// TODO Add support for querying for nulls in collections
return new BooleanLiteral(qs, mapping, false).eq(new BooleanLiteral(qs, mapping, true));
}
else if (expr instanceof UnboundVariable)
{
UnboundVariable var = (UnboundVariable)expr;
if (var.getVariableType() == null)
{
// Set the variable type to be the element type for this collection
// implicit variable type. We now set the type to the collection type
var.setVariableType(clr.classForName(collStore.getElementType()));
}
// Get the exists query of the collection table
String existsTableId = idFactory.newIdentifier(
idFactory.newIdentifier(te.getAlias(), fieldName), var.getVariableName()).getIdentifier();
DatastoreIdentifier existsTableAlias = idFactory.newIdentifier(IdentifierFactory.TABLE, existsTableId);
QueryExpression qexpr = getBackingStoreQueryable().getExistsSubquery(qs, mapping, te, existsTableAlias);
// Join from the collection table to the element table
DatastoreIdentifier elementTableAlias = null;
if (expr.te == null)
{
String elementTableId = "UNBOUND" + '.' + var.getVariableName();
elementTableAlias = idFactory.newIdentifier(IdentifierFactory.TABLE, elementTableId);
}
else
{
elementTableAlias = expr.te.getAlias();
}
ScalarExpression joinExpr = getBackingStoreQueryable().joinElementsTo(qexpr, qs, mapping, te,
existsTableAlias, var.getVariableType(), expr, elementTableAlias, true);
var.bindTo(joinExpr);
//START see JDOQLContainerTest.testContainsResultVariable
LogicSetExpression elementTblExpr = qs.getTableExpression(elementTableAlias);
if (qs.hasCrossJoin(elementTblExpr))
{
// Perhaps some description about what this is supposed to be doing and WHY ????
qexpr.andCondition(joinExpr.eq(expr.mapping.newScalarExpression(qs, elementTblExpr)));
}
//END see JDOQLContainerTest.
return new ExistsExpression(qs, qexpr, true);
}
else
{
// "contains(Literal)", "contains(Expression)"
String existsTableId = idFactory.newIdentifier(te.getAlias(), fieldName).getIdentifier();
DatastoreIdentifier existsTableAlias = idFactory.newIdentifier(IdentifierFactory.TABLE, existsTableId);
DatastoreIdentifier elementTableAlias;
if (expr.te == null) // literals
{
int n = 0;
do
{
String elementTableId = existsTableId + '.' + (++n);
elementTableAlias = idFactory.newIdentifier(IdentifierFactory.TABLE, elementTableId);
} while (qs.getTableExpression(elementTableAlias) != null);
}
else // expressions
{
elementTableAlias = expr.te.getAlias();
}
if (expr instanceof Literal)
{
// EXISTS (SELECT 1 FROM COLLECTION_TBL WHERE ...)
QueryExpression qexpr = getBackingStoreQueryable().getExistsSubquery(qs, mapping, te, existsTableAlias);
ScalarExpression joinExpr = getBackingStoreQueryable().joinElementsTo(qexpr, qs, mapping, te,
existsTableAlias, clr.classForName(expr.getMapping().getType()), expr, elementTableAlias, true);
// TODO This sometimes adds TBL1.COL1 = TBL1.COL1 - check that the equals() removes such things
if (!expr.equals(joinExpr))
{
// Join to literal value (?)
qexpr.andCondition(expr.eq(joinExpr));
}
return new ExistsExpression(qs, qexpr, true);
}
else
{
boolean existsAlways = false;
Object ext = expr.qs.getValueForExtension("org.jpox.rdbms.query.containsUsesExistsAlways");
if (ext != null && ((String)ext).equals("true"))
{
existsAlways = true;
}
if (existsAlways)
{
// EXISTS (SELECT 1 FROM COLLECTION_TBL WHERE ...)
QueryExpression qexpr = getBackingStoreQueryable().getExistsSubquery(qs, mapping, te, existsTableAlias);
ScalarExpression joinExpr = getBackingStoreQueryable().joinElementsTo(qexpr, qs, mapping, te,
existsTableAlias, clr.classForName(expr.getMapping().getType()), expr, elementTableAlias, true);
// TODO This sometimes adds TBL1.COL1 = TBL1.COL1 - check that the equals() removes such things
if (!expr.equals(joinExpr))
{
// Join to literal value (?)
qexpr.andCondition(expr.eq(joinExpr));
}
return new ExistsExpression(qs, qexpr, true);
}
else
{
// Join to element
// TODO This is WRONG - see RDBMS-94. All contains() should do "EXISTS (SELECT ... FROM ...)"
// The problem is that when using UnboundVariables that are referenced in other legs of SQL
// we need to do cross joins up at the parent query, and no mechanism is readily available yet
ScalarExpression joinExpr = getBackingStoreQueryable().joinElementsTo(
expr.getQueryExpression(), qs, mapping, te,
existsTableAlias, clr.classForName(collStore.getElementType()), expr, elementTableAlias, false);
return joinExpr.eq(expr);
}
}
}
}
/**
* Return the BooleanExpression for a query filter in the form "collection.isEmpty()".
* @return The BooleanExpression for a query filter in the form "collection.isEmpty()".
*/
public BooleanExpression isEmptyMethod()
{
IdentifierFactory idFactory = qs.getStoreManager().getIdentifierFactory();
String existsTableId = idFactory.newIdentifier(te.getAlias(), fieldName).getIdentifier();
DatastoreIdentifier existsTableAlias = idFactory.newIdentifier(IdentifierFactory.TABLE, existsTableId);
QueryExpression qexpr = getBackingStoreQueryable().getExistsSubquery(qs, mapping, te, existsTableAlias);
return new ExistsExpression(qs, qexpr, false);
}
/**
* Method to return the expression for comparing a collection with a value.
* JPOX only supports comparisons with null currently.
* @param expr The value to compare with.
* @return The expression of equality
*/
public BooleanExpression eq(ScalarExpression expr)
{
if (expr instanceof NullLiteral)
{
return isEmptyMethod();
}
else
{
throw new JPOXUserException(LOCALISER.msg("037004"));
}
}
/**
* Method to return the statement text.
* @param mode (0=PROJECTION;1=FILTER)
* @return The statement
* @throws JPOXUserException since this object is inaccessible directly.
**/
public StatementText toStatementText(int mode)
{
throw new JPOXUserException("Cannot reference collection object directly: field name = " + fieldName);
}
private CollectionStoreQueryable getBackingStoreQueryable()
{
return (CollectionStoreQueryable)collStore;
}
}