summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/amazon/carbonado/Storage.java2
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java12
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/FilteredCursorGenerator.java253
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/PropertyOrdinalMapVisitor.java58
-rw-r--r--src/main/java/com/amazon/carbonado/cursor/ShortCircuitOptimizer.java11
-rw-r--r--src/main/java/com/amazon/carbonado/filter/AndFilter.java12
-rw-r--r--src/main/java/com/amazon/carbonado/filter/BinaryOpFilter.java2
-rw-r--r--src/main/java/com/amazon/carbonado/filter/Binder.java23
-rw-r--r--src/main/java/com/amazon/carbonado/filter/ClosedFilter.java12
-rw-r--r--src/main/java/com/amazon/carbonado/filter/Distributer.java14
-rw-r--r--src/main/java/com/amazon/carbonado/filter/ExistsFilter.java264
-rw-r--r--src/main/java/com/amazon/carbonado/filter/Filter.java180
-rw-r--r--src/main/java/com/amazon/carbonado/filter/FilterParser.java59
-rw-r--r--src/main/java/com/amazon/carbonado/filter/Group.java8
-rw-r--r--src/main/java/com/amazon/carbonado/filter/OpenFilter.java12
-rw-r--r--src/main/java/com/amazon/carbonado/filter/OrFilter.java18
-rw-r--r--src/main/java/com/amazon/carbonado/filter/PropertyFilter.java14
-rw-r--r--src/main/java/com/amazon/carbonado/filter/PropertyFilterList.java17
-rw-r--r--src/main/java/com/amazon/carbonado/filter/Reducer.java11
-rw-r--r--src/main/java/com/amazon/carbonado/filter/Visitor.java7
-rw-r--r--src/main/java/com/amazon/carbonado/info/ChainedProperty.java10
-rw-r--r--src/main/java/com/amazon/carbonado/qe/EmptyQuery.java5
-rw-r--r--src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java10
-rw-r--r--src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java3
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryEngine.java17
-rw-r--r--src/main/java/com/amazon/carbonado/qe/QueryFactory.java7
-rw-r--r--src/main/java/com/amazon/carbonado/qe/StandardQuery.java89
-rw-r--r--src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java30
-rw-r--r--src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java12
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java194
30 files changed, 1138 insertions, 228 deletions
diff --git a/src/main/java/com/amazon/carbonado/Storage.java b/src/main/java/com/amazon/carbonado/Storage.java
index bb4b8b7..551e1eb 100644
--- a/src/main/java/com/amazon/carbonado/Storage.java
+++ b/src/main/java/com/amazon/carbonado/Storage.java
@@ -86,10 +86,12 @@ public interface Storage<S extends Storable> {
* AndFilter = NotFilter { "&" NotFilter }
* NotFilter = [ "!" ] EntityFilter
* EntityFilter = PropertyFilter
+ * = ChainedFilter
* | "(" Filter ")"
* PropertyFilter = ChainedProperty RelOp "?"
* RelOp = "=" | "!=" | "&lt;" | "&gt;=" | "&gt;" | "&lt;="
* ChainedProperty = Identifier { "." Identifier }
+ * ChainedFilter = ChainedProperty "(" [ Filter ] ")"
* </pre>
*
* @param filter query filter expression
diff --git a/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java b/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java
index 51d69e7..59cb941 100644
--- a/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java
+++ b/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java
@@ -25,10 +25,8 @@ import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.FetchInterruptedException;
import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.filter.ClosedFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
-import com.amazon.carbonado.filter.OpenFilter;
/**
* Wraps another cursor and applies custom filtering to reduce the set of
@@ -66,7 +64,7 @@ public abstract class FilteredCursor<S> extends AbstractCursor<S> {
* IllegalStateException will result otherwise.
*
* @param filter filter to apply
- * @param filterValues values for filter
+ * @param filterValues values for filter, which may be null if filter has no parameters
* @param cursor cursor to wrap
* @return wrapped cursor which filters results
* @throws IllegalStateException if any values are not specified
@@ -76,18 +74,18 @@ public abstract class FilteredCursor<S> extends AbstractCursor<S> {
FilterValues<S> filterValues,
Cursor<S> cursor)
{
- if (filter instanceof OpenFilter) {
+ if (filter.isOpen()) {
return cursor;
}
- if (filter instanceof ClosedFilter) {
+ if (filter.isClosed()) {
throw new IllegalArgumentException();
}
// Make sure the filter is the same one that filterValues should be using.
filter = filter.bind();
- return FilteredCursorGenerator.getFactory(filter)
- .newFilteredCursor(cursor, filterValues.getValuesFor(filter));
+ Object[] values = filterValues == null ? null : filterValues.getValuesFor(filter);
+ return FilteredCursorGenerator.getFactory(filter).newFilteredCursor(cursor, values);
}
private final Cursor<S> mCursor;
diff --git a/src/main/java/com/amazon/carbonado/cursor/FilteredCursorGenerator.java b/src/main/java/com/amazon/carbonado/cursor/FilteredCursorGenerator.java
index 9ca0b79..745ad7a 100644
--- a/src/main/java/com/amazon/carbonado/cursor/FilteredCursorGenerator.java
+++ b/src/main/java/com/amazon/carbonado/cursor/FilteredCursorGenerator.java
@@ -18,8 +18,13 @@
package com.amazon.carbonado.cursor;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Stack;
@@ -38,9 +43,11 @@ import org.cojen.util.ClassInjector;
import org.cojen.util.WeakIdentityMap;
import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.Query;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.filter.AndFilter;
+import com.amazon.carbonado.filter.ExistsFilter;
import com.amazon.carbonado.filter.OrFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.PropertyFilter;
@@ -50,8 +57,11 @@ import com.amazon.carbonado.filter.Visitor;
import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.util.ThrowUnchecked;
import com.amazon.carbonado.util.QuickConstructorGenerator;
+import com.amazon.carbonado.gen.CodeBuilderUtil;
+
/**
* Generates Cursor implementations that wrap another Cursor, applying a
@@ -61,6 +71,8 @@ import com.amazon.carbonado.util.QuickConstructorGenerator;
* @see FilteredCursor
*/
class FilteredCursorGenerator {
+ private static final String SUB_FILTER_INIT_METHOD = "subFilterInit$";
+
private static Map cCache = new WeakIdentityMap();
/**
@@ -72,11 +84,6 @@ class FilteredCursorGenerator {
*/
@SuppressWarnings("unchecked")
static <S extends Storable> Factory<S> getFactory(Filter<S> filter) {
- return getFactory(filter, false);
- }
-
- @SuppressWarnings("unchecked")
- private static <S extends Storable> Factory<S> getFactory(Filter<S> filter, boolean optimize) {
if (filter == null) {
throw new IllegalArgumentException();
}
@@ -85,16 +92,8 @@ class FilteredCursorGenerator {
if (factory != null) {
return factory;
}
-
- Filter<S> optimized;
- if (optimize && (optimized = ShortCircuitOptimizer.optimize(filter)) != filter) {
- // Use factory for filter optimized for short-circuit logic.
- factory = getFactory(optimized, false);
- } else {
- Class<Cursor<S>> clazz = generateClass(filter);
- factory = QuickConstructorGenerator.getInstance(clazz, Factory.class);
- }
-
+ Class<Cursor<S>> clazz = generateClass(filter);
+ factory = QuickConstructorGenerator.getInstance(clazz, Factory.class);
cCache.put(filter, factory);
return factory;
}
@@ -145,7 +144,7 @@ class FilteredCursorGenerator {
CodeBuilder isAllowedBuilder;
LocalVariable storableVar;
{
- TypeDesc[] params = {TypeDesc.OBJECT};
+ TypeDesc[] params = {OBJECT};
MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, "isAllowed", BOOLEAN, params);
isAllowedBuilder = new CodeBuilder(mi);
@@ -167,12 +166,38 @@ class FilteredCursorGenerator {
isAllowedBuilder.storeLocal(storableVar);
}
- filter.accept(new CodeGen<S>(cf, ctorBuilder, isAllowedBuilder, storableVar), null);
+ // Capture property filter ordinals before optimization scrambles them.
+ Map<PropertyFilter, Integer> propertyOrdinalMap;
+ {
+ PropertyOrdinalMapVisitor<S> visitor = new PropertyOrdinalMapVisitor<S>();
+ filter.accept(visitor, null);
+ propertyOrdinalMap = visitor.getPropertyOrdinalMap();
+ }
+
+ filter = ShortCircuitOptimizer.optimize(filter);
+
+ CodeGen<S> cg = new CodeGen<S>
+ (propertyOrdinalMap, cf, ctorBuilder, isAllowedBuilder, storableVar);
+ filter.accept(cg, null);
+
+ List<Filter> subFilters = cg.finishSubFilterInit();
// Finish constructor.
ctorBuilder.returnVoid();
- return (Class<Cursor<S>>) ci.defineClass(cf);
+ Class generated = ci.defineClass(cf);
+
+ // Pass sub-filter instances to be stored in static fields.
+ if (subFilters != null && subFilters.size() > 0) {
+ try {
+ Method init = generated.getMethod(SUB_FILTER_INIT_METHOD, Filter[].class);
+ init.invoke(null, (Object) subFilters.toArray(new Filter[subFilters.size()]));
+ } catch (Exception e) {
+ ThrowUnchecked.fireDeclaredRootCause(e);
+ }
+ }
+
+ return (Class<Cursor<S>>) generated;
}
public static interface Factory<S extends Storable> {
@@ -235,8 +260,10 @@ class FilteredCursorGenerator {
}
private static class CodeGen<S extends Storable> extends Visitor<S, Object, Object> {
- private static String FIELD_PREFIX = "value$";
+ private static final String FIELD_PREFIX = "value$";
+ private static final String FILTER_FIELD_PREFIX = "filter$";
+ private final Map<PropertyFilter, Integer> mPropertyOrdinalMap;
private final ClassFile mClassFile;
private final CodeBuilder mCtorBuilder;
private final CodeBuilder mIsAllowedBuilder;
@@ -244,11 +271,16 @@ class FilteredCursorGenerator {
private final Stack<Scope> mScopeStack;
- private int mPropertyOrdinal;
+ private List<Filter> mSubFilters;
+ private CodeBuilder mSubFilterInitBuilder;
- CodeGen(ClassFile cf,
+ CodeGen(Map<PropertyFilter, Integer> propertyOrdinalMap,
+ ClassFile cf,
CodeBuilder ctorBuilder,
- CodeBuilder isAllowedBuilder, LocalVariable storableVar) {
+ CodeBuilder isAllowedBuilder,
+ LocalVariable storableVar)
+ {
+ mPropertyOrdinalMap = propertyOrdinalMap;
mClassFile = cf;
mCtorBuilder = ctorBuilder;
mIsAllowedBuilder = isAllowedBuilder;
@@ -257,6 +289,16 @@ class FilteredCursorGenerator {
mScopeStack.push(new Scope(null, null));
}
+ public List<Filter> finishSubFilterInit() {
+ if (mSubFilterInitBuilder != null) {
+ mSubFilterInitBuilder.returnVoid();
+ }
+ if (mSubFilters == null) {
+ return Collections.emptyList();
+ }
+ return mSubFilters;
+ }
+
public Object visit(OrFilter<S> filter, Object param) {
Label failLocation = mIsAllowedBuilder.createLabel();
// Inherit success location to short-circuit if 'or' test succeeds.
@@ -280,26 +322,144 @@ class FilteredCursorGenerator {
}
public Object visit(PropertyFilter<S> filter, Object param) {
- ChainedProperty<S> chained = filter.getChainedProperty();
- TypeDesc type = TypeDesc.forClass(chained.getType());
- TypeDesc fieldType = actualFieldType(type);
- String fieldName = FIELD_PREFIX + mPropertyOrdinal;
+ final int propertyOrdinal = mPropertyOrdinalMap.get(filter);
+ final TypeDesc type = TypeDesc.forClass(filter.getChainedProperty().getType());
+ final TypeDesc fieldType = actualFieldType(type);
+ final String fieldName = FIELD_PREFIX + propertyOrdinal;
// Define storage field.
mClassFile.addField(Modifiers.PRIVATE.toFinal(true), fieldName, fieldType);
// Add code to constructor to store value into field.
- CodeBuilder b = mCtorBuilder;
- b.loadThis();
- b.loadLocal(b.getParameter(1));
- b.loadConstant(mPropertyOrdinal);
- b.loadFromArray(OBJECT);
- b.checkCast(type.toObjectType());
- convertProperty(b, type.toObjectType(), fieldType);
- b.storeField(fieldName, fieldType);
-
- // Add code to load property value to stack.
- b = mIsAllowedBuilder;
+ {
+ CodeBuilder b = mCtorBuilder;
+ b.loadThis();
+ b.loadLocal(b.getParameter(1));
+ b.loadConstant(propertyOrdinal);
+ b.loadFromArray(OBJECT);
+ b.checkCast(type.toObjectType());
+ convertProperty(b, type.toObjectType(), fieldType);
+ b.storeField(fieldName, fieldType);
+ }
+
+ loadChainedProperty(mIsAllowedBuilder, filter.getChainedProperty());
+ addPropertyFilter(mIsAllowedBuilder, propertyOrdinal, type, filter.getOperator());
+
+ return null;
+ }
+
+ public Object visit(ExistsFilter<S> filter, Object param) {
+ // Recursively gather all the properties to be passed to sub-filter.
+ final List<PropertyFilter> subPropFilters = new ArrayList<PropertyFilter>();
+
+ filter.getSubFilter().accept(new Visitor() {
+ @Override
+ public Object visit(PropertyFilter filter, Object param) {
+ subPropFilters.add(filter);
+ return null;
+ }
+ @Override
+ public Object visit(ExistsFilter filter, Object param) {
+ return filter.getSubFilter().accept(this, param);
+ }
+ }, null);
+
+ // Load join property value to stack. It is expected to be a Query.
+ CodeBuilder b = mIsAllowedBuilder;
+ loadChainedProperty(b, filter.getChainedProperty());
+
+ final TypeDesc queryType = TypeDesc.forClass(Query.class);
+
+ // Refine Query filter, if sub-filter isn't open.
+ if (!filter.getSubFilter().isOpen()) {
+ String subFilterFieldName = addStaticFilterField(filter.getSubFilter());
+
+ TypeDesc filterType = TypeDesc.forClass(Filter.class);
+
+ b.loadStaticField(subFilterFieldName, filterType);
+ b.invokeInterface(queryType, "and", queryType, new TypeDesc[] {filterType});
+
+ for (PropertyFilter subPropFilter : subPropFilters) {
+ final int propertyOrdinal = mPropertyOrdinalMap.get(subPropFilter);
+ final ChainedProperty<S> chained = subPropFilter.getChainedProperty();
+ final String fieldName = FIELD_PREFIX + propertyOrdinal;
+
+ // Define storage for sub-filter.
+ mClassFile.addField(Modifiers.PRIVATE.toFinal(true), fieldName, OBJECT);
+
+ // Assign value passed from constructor.
+ mCtorBuilder.loadThis();
+ mCtorBuilder.loadLocal(mCtorBuilder.getParameter(1));
+ mCtorBuilder.loadConstant(propertyOrdinal);
+ mCtorBuilder.loadFromArray(OBJECT);
+ mCtorBuilder.storeField(fieldName, OBJECT);
+
+ // Pass value to Query.
+ b.loadThis();
+ b.loadField(fieldName, OBJECT);
+ b.invokeInterface(queryType, "with", queryType, new TypeDesc[] {OBJECT});
+ }
+ }
+
+ // Call the all-important Query.exists method.
+ b.invokeInterface(queryType, "exists", BOOLEAN, null);
+
+ // Success if boolean value is true (non-zero), opposite for "not exists".
+ RelOp op = filter.isNotExists() ? RelOp.EQ : RelOp.NE;
+ getScope().successIfZeroComparisonElseFail(b, op);
+
+ return null;
+ }
+
+ private String addStaticFilterField(Filter filter) {
+ if (mSubFilters == null) {
+ mSubFilters = new ArrayList<Filter>();
+ }
+
+ final int filterOrdinal = mSubFilters.size();
+ final String fieldName = FILTER_FIELD_PREFIX + filterOrdinal;
+ final TypeDesc filterType = TypeDesc.forClass(Filter.class);
+
+ mClassFile.addField(Modifiers.PRIVATE.toStatic(true), fieldName, filterType);
+
+ mSubFilters.add(filter);
+
+ if (mSubFilterInitBuilder == null) {
+ TypeDesc filterArrayType = filterType.toArrayType();
+ mSubFilterInitBuilder = new CodeBuilder
+ (mClassFile.addMethod
+ (Modifiers.PUBLIC.toStatic(true),
+ SUB_FILTER_INIT_METHOD, null, new TypeDesc[] {filterArrayType}));
+
+ // This method must be public, so add a check to ensure it is
+ // called at most once. Just check one filter to see if it is non-null.
+
+ mSubFilterInitBuilder.loadStaticField(fieldName, filterType);
+ Label isNull = mSubFilterInitBuilder.createLabel();
+ mSubFilterInitBuilder.ifNullBranch(isNull, true);
+ CodeBuilderUtil.throwException
+ (mSubFilterInitBuilder, IllegalStateException.class, null);
+ isNull.setLocation();
+ }
+
+ // Now add code to init field later.
+ mSubFilterInitBuilder.loadLocal(mSubFilterInitBuilder.getParameter(0));
+ mSubFilterInitBuilder.loadConstant(filterOrdinal);
+ mSubFilterInitBuilder.loadFromArray(filterType);
+ mSubFilterInitBuilder.storeStaticField(fieldName, filterType);
+
+ return fieldName;
+ }
+
+ private Scope getScope() {
+ return mScopeStack.peek();
+ }
+
+ /**
+ * Generated code checks if chained properties resolve to null, and if
+ * so, branches to the current scope's fail location.
+ */
+ private void loadChainedProperty(CodeBuilder b, ChainedProperty<?> chained) {
b.loadLocal(mStorableVar);
loadProperty(b, chained.getPrimeProperty());
for (int i=0; i<chained.getChainCount(); i++) {
@@ -314,15 +474,6 @@ class FilteredCursorGenerator {
// Now load next property in chain.
loadProperty(b, chained.getChainedProperty(i));
}
-
- addPropertyFilter(b, type, filter.getOperator());
-
- mPropertyOrdinal++;
- return null;
- }
-
- private Scope getScope() {
- return mScopeStack.peek();
}
private void loadProperty(CodeBuilder b, StorableProperty<?> property) {
@@ -341,9 +492,9 @@ class FilteredCursorGenerator {
b.invoke(readMethod);
}
- private void addPropertyFilter(CodeBuilder b, TypeDesc type, RelOp relOp) {
+ private void addPropertyFilter(CodeBuilder b, int ordinal, TypeDesc type, RelOp relOp) {
TypeDesc fieldType = actualFieldType(type);
- String fieldName = FIELD_PREFIX + mPropertyOrdinal;
+ String fieldName = FIELD_PREFIX + ordinal;
if (type.getTypeCode() == OBJECT_CODE) {
// Check if actual property being examined is null.
@@ -524,7 +675,7 @@ class FilteredCursorGenerator {
private void convertProperty(CodeBuilder b, TypeDesc fromType, TypeDesc toType) {
TypeDesc fromPrimType = fromType.toPrimitiveType();
- if (fromPrimType != TypeDesc.FLOAT && fromPrimType != TypeDesc.DOUBLE) {
+ if (fromPrimType != FLOAT && fromPrimType != DOUBLE) {
// Not converting floating point, so just convert as normal.
b.convert(fromType, toType);
return;
@@ -532,7 +683,7 @@ class FilteredCursorGenerator {
TypeDesc toPrimType = toType.toPrimitiveType();
- if (toPrimType != TypeDesc.INT && toPrimType != TypeDesc.LONG) {
+ if (toPrimType != INT && toPrimType != LONG) {
// Floating point not being converted to bits, so just convert as normal.
b.convert(fromType, toType);
return;
@@ -557,7 +708,7 @@ class FilteredCursorGenerator {
// Floating point bits need to be flipped for negative values.
- if (toPrimType == TypeDesc.INT) {
+ if (toPrimType == INT) {
b.dup();
b.ifZeroComparisonBranch(box, ">=");
b.loadConstant(0x7fffffff);
diff --git a/src/main/java/com/amazon/carbonado/cursor/PropertyOrdinalMapVisitor.java b/src/main/java/com/amazon/carbonado/cursor/PropertyOrdinalMapVisitor.java
new file mode 100644
index 0000000..8bcfa52
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/cursor/PropertyOrdinalMapVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. 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.
+ */
+
+package com.amazon.carbonado.cursor;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.filter.ExistsFilter;
+import com.amazon.carbonado.filter.PropertyFilter;
+import com.amazon.carbonado.filter.Visitor;
+
+/**
+ * Visits a filter and maps all property filters to their zero-based ordinal
+ * position.
+ *
+ * @author Brian S O'Neill
+ */
+class PropertyOrdinalMapVisitor<S extends Storable> extends Visitor<S, Object, Object> {
+ private int mOrdinal;
+ private Map<PropertyFilter, Integer> mOrdinalMap;
+
+ PropertyOrdinalMapVisitor() {
+ mOrdinalMap = new IdentityHashMap<PropertyFilter, Integer>();
+ }
+
+ public Map<PropertyFilter, Integer> getPropertyOrdinalMap() {
+ return mOrdinalMap;
+ }
+
+ @Override
+ public Object visit(PropertyFilter filter, Object param) {
+ mOrdinalMap.put(filter, mOrdinal++);
+ return null;
+ }
+
+ @Override
+ public Object visit(ExistsFilter filter, Object param) {
+ return filter.getSubFilter().accept(this, param);
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/cursor/ShortCircuitOptimizer.java b/src/main/java/com/amazon/carbonado/cursor/ShortCircuitOptimizer.java
index 1a46d1e..46b03b1 100644
--- a/src/main/java/com/amazon/carbonado/cursor/ShortCircuitOptimizer.java
+++ b/src/main/java/com/amazon/carbonado/cursor/ShortCircuitOptimizer.java
@@ -25,6 +25,7 @@ import com.amazon.carbonado.info.StorableProperty;
import com.amazon.carbonado.filter.AndFilter;
import com.amazon.carbonado.filter.ClosedFilter;
+import com.amazon.carbonado.filter.ExistsFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.OpenFilter;
import com.amazon.carbonado.filter.OrFilter;
@@ -132,6 +133,7 @@ class ShortCircuitOptimizer {
}
private static class Walker<S extends Storable> extends Visitor<S, FilterAndCost<S>, Object> {
+ @Override
public FilterAndCost<S> visit(OrFilter<S> filter, Object param) {
FilterAndCost<S> leftCost = filter.getLeftFilter().accept(this, param);
FilterAndCost<S> rightCost = filter.getRightFilter().accept(this, param);
@@ -154,6 +156,7 @@ class ShortCircuitOptimizer {
return new FilterAndCost<S>(newFilter, expensiveProperty);
}
+ @Override
public FilterAndCost<S> visit(AndFilter<S> filter, Object param) {
FilterAndCost<S> leftCost = filter.getLeftFilter().accept(this, param);
FilterAndCost<S> rightCost = filter.getRightFilter().accept(this, param);
@@ -176,14 +179,22 @@ class ShortCircuitOptimizer {
return new FilterAndCost<S>(newFilter, expensiveProperty);
}
+ @Override
public FilterAndCost<S> visit(PropertyFilter<S> filter, Object param) {
return new FilterAndCost<S>(filter, filter.getChainedProperty());
}
+ @Override
+ public FilterAndCost<S> visit(ExistsFilter<S> filter, Object param) {
+ return new FilterAndCost<S>(filter, filter.getChainedProperty());
+ }
+
+ @Override
public FilterAndCost<S> visit(OpenFilter<S> filter, Object param) {
return new FilterAndCost<S>(filter, null);
}
+ @Override
public FilterAndCost<S> visit(ClosedFilter<S> filter, Object param) {
return new FilterAndCost<S>(filter, null);
}
diff --git a/src/main/java/com/amazon/carbonado/filter/AndFilter.java b/src/main/java/com/amazon/carbonado/filter/AndFilter.java
index be38800..a260b8c 100644
--- a/src/main/java/com/amazon/carbonado/filter/AndFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/AndFilter.java
@@ -62,16 +62,14 @@ public class AndFilter<S extends Storable> extends BinaryOpFilter<S> {
return mLeft.unbind().and(mRight.unbind());
}
- public <T extends Storable> Filter<T> asJoinedFrom(ChainedProperty<T> joinProperty) {
- return mLeft.asJoinedFrom(joinProperty).and(mRight.asJoinedFrom(joinProperty));
+ <T extends Storable> Filter<T> asJoinedFromAny(ChainedProperty<T> joinProperty) {
+ return mLeft.asJoinedFromAny(joinProperty).and(mRight.asJoinedFromAny(joinProperty));
}
@Override
- NotJoined notJoinedFrom(ChainedProperty<S> joinProperty,
- Class<? extends Storable> joinPropertyType)
- {
- NotJoined left = mLeft.notJoinedFrom(joinProperty, joinPropertyType);
- NotJoined right = mRight.notJoinedFrom(joinProperty, joinPropertyType);
+ NotJoined notJoinedFromCNF(ChainedProperty<S> joinProperty) {
+ NotJoined left = mLeft.notJoinedFromCNF(joinProperty);
+ NotJoined right = mRight.notJoinedFromCNF(joinProperty);
// Remove wildcards to shut the compiler up.
Filter leftNotJoined = left.getNotJoinedFilter();
diff --git a/src/main/java/com/amazon/carbonado/filter/BinaryOpFilter.java b/src/main/java/com/amazon/carbonado/filter/BinaryOpFilter.java
index d17d466..640c0ae 100644
--- a/src/main/java/com/amazon/carbonado/filter/BinaryOpFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/BinaryOpFilter.java
@@ -42,7 +42,7 @@ public abstract class BinaryOpFilter<S extends Storable> extends Filter<S> {
BinaryOpFilter(Filter<S> left, Filter<S> right) {
super(left == null ? null : left.getStorableType());
if (left == null || right == null) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Left or right filter is null");
}
if (left.getStorableType() != right.getStorableType()) {
throw new IllegalArgumentException("Type mismatch");
diff --git a/src/main/java/com/amazon/carbonado/filter/Binder.java b/src/main/java/com/amazon/carbonado/filter/Binder.java
index 7a854f1..7b074c2 100644
--- a/src/main/java/com/amazon/carbonado/filter/Binder.java
+++ b/src/main/java/com/amazon/carbonado/filter/Binder.java
@@ -39,6 +39,10 @@ class Binder<S extends Storable> extends Visitor<S, Filter<S>, Object> {
mBindMap = new IdentityHashMap<PropertyFilter<S>, PropertyFilter<S>>();
}
+ private Binder(Map<PropertyFilter<S>, PropertyFilter<S>> bindMap) {
+ mBindMap = bindMap;
+ }
+
@Override
public Filter<S> visit(OrFilter<S> filter, Object param) {
Filter<S> left = filter.getLeftFilter();
@@ -77,7 +81,7 @@ class Binder<S extends Storable> extends Visitor<S, Filter<S>, Object> {
@Override
public Filter<S> visit(PropertyFilter<S> filter, Object param) {
- if (filter.getBindID() != 0) {
+ if (filter.isBound()) {
return filter;
}
filter = PropertyFilter.getCanonical(filter, 1);
@@ -90,4 +94,21 @@ class Binder<S extends Storable> extends Visitor<S, Filter<S>, Object> {
mBindMap.put(filter, highest);
return highest;
}
+
+ @Override
+ public Filter<S> visit(ExistsFilter<S> filter, Object param) {
+ if (filter.isBound()) {
+ return filter;
+ }
+ Filter<S> boundJoinedSubFilter =
+ filter.getJoinedSubFilter().accept(new Binder<S>(mBindMap), null);
+ Filter<S>.NotJoined nj =
+ boundJoinedSubFilter.notJoinedFromAny(filter.getChainedProperty());
+ if (nj.getRemainderFilter() != null && !(nj.getRemainderFilter().isOpen())) {
+ // This should not happen.
+ throw new IllegalStateException(nj.toString());
+ }
+ return ExistsFilter.getCanonical
+ (filter.getChainedProperty(), nj.getNotJoinedFilter(), filter.isNotExists());
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/filter/ClosedFilter.java b/src/main/java/com/amazon/carbonado/filter/ClosedFilter.java
index 598f149..0630bab 100644
--- a/src/main/java/com/amazon/carbonado/filter/ClosedFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/ClosedFilter.java
@@ -36,6 +36,16 @@ public class ClosedFilter<S extends Storable> extends Filter<S> {
super(type);
}
+ /**
+ * Always returns true.
+ *
+ * @since 1.2
+ */
+ @Override
+ public final boolean isClosed() {
+ return true;
+ }
+
public ClosedFilter<S> and(Filter<S> filter) {
return this;
}
@@ -92,7 +102,7 @@ public class ClosedFilter<S extends Storable> extends Filter<S> {
return true;
}
- public <T extends Storable> ClosedFilter<T> asJoinedFrom(ChainedProperty<T> joinProperty) {
+ <T extends Storable> ClosedFilter<T> asJoinedFromAny(ChainedProperty<T> joinProperty) {
return getClosedFilter(joinProperty.getPrimeProperty().getEnclosingType());
}
diff --git a/src/main/java/com/amazon/carbonado/filter/Distributer.java b/src/main/java/com/amazon/carbonado/filter/Distributer.java
index e47e842..2aa9a2c 100644
--- a/src/main/java/com/amazon/carbonado/filter/Distributer.java
+++ b/src/main/java/com/amazon/carbonado/filter/Distributer.java
@@ -75,4 +75,18 @@ class Distributer<S extends Storable> extends Visitor<S, Filter<S>, Filter<S>> {
return mDoAnd ? distribute.and(filter) : distribute.or(filter);
}
}
+
+ /**
+ * @param filter candidate node to potentially replace
+ * @param distribute node to distribute into candidate node
+ * @return original candidate or replacement
+ */
+ @Override
+ public Filter<S> visit(ExistsFilter<S> filter, Filter<S> distribute) {
+ if (mDoRight) {
+ return mDoAnd ? filter.and(distribute) : filter.or(distribute);
+ } else {
+ return mDoAnd ? distribute.and(filter) : distribute.or(filter);
+ }
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/filter/ExistsFilter.java b/src/main/java/com/amazon/carbonado/filter/ExistsFilter.java
new file mode 100644
index 0000000..b75de28
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/filter/ExistsFilter.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. 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.
+ */
+
+package com.amazon.carbonado.filter;
+
+import java.io.IOException;
+
+import com.amazon.carbonado.Query;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.StorableProperty;
+
+/**
+ * Filter tree node that performs an existence or non-existence test against a
+ * one-to-many join.
+ *
+ * @author Brian S O'Neill
+ * @since 1.2
+ */
+public class ExistsFilter<S extends Storable> extends Filter<S> {
+ /**
+ * Returns a canonical instance, creating a new one if there isn't one
+ * already in the cache.
+ */
+ @SuppressWarnings("unchecked")
+ static <S extends Storable> ExistsFilter<S> getCanonical(ChainedProperty<S> property,
+ Filter<?> subFilter,
+ boolean not)
+ {
+ return (ExistsFilter<S>) cCanonical.put(new ExistsFilter<S>(property, subFilter, not));
+ }
+
+ private final ChainedProperty<S> mProperty;
+ private final Filter<?> mSubFilter;
+ private final boolean mNot;
+
+ private transient volatile Filter<S> mJoinedSubFilter;
+ private transient volatile boolean mNoParameters;
+
+ ExistsFilter(ChainedProperty<S> property, Filter<?> subFilter, boolean not) {
+ super(property == null ? null : property.getPrimeProperty().getEnclosingType());
+
+ StorableProperty<?> joinProperty = property.getLastProperty();
+ if (!joinProperty.isQuery()) {
+ throw new IllegalArgumentException("Not a one-to-many join property: " + property);
+ }
+ if (subFilter == null) {
+ subFilter = Filter.getOpenFilter(joinProperty.getJoinedType());
+ } else if (subFilter.isClosed()) {
+ throw new IllegalArgumentException("Exists sub-filter cannot be closed: " + subFilter);
+ } else if (joinProperty.getJoinedType() != subFilter.getStorableType()) {
+ throw new IllegalArgumentException
+ ("Filter not compatible with join property type: " +
+ property + " joins to a " + joinProperty.getJoinedType().getName() +
+ ", but filter is for a " + subFilter.getStorableType().getName());
+ }
+
+ mProperty = property;
+ mSubFilter = subFilter;
+ mNot = not;
+ }
+
+ /**
+ * @return chained property whose last property is a one-to-many join
+ */
+ public ChainedProperty<S> getChainedProperty() {
+ return mProperty;
+ }
+
+ /**
+ * @return filter which is applied to last property of chain, which might be open
+ */
+ public Filter<?> getSubFilter() {
+ return mSubFilter;
+ }
+
+ Filter<S> getJoinedSubFilter() {
+ Filter<S> joined = mJoinedSubFilter;
+ if (joined == null) {
+ mJoinedSubFilter = joined = mSubFilter.asJoinedFromAny(mProperty);
+ }
+ return joined;
+ }
+
+ /**
+ * @return true if this filter is testing for "not exists"
+ */
+ public boolean isNotExists() {
+ return mNot;
+ }
+
+ public Filter<S> not() {
+ return getCanonical(mProperty, mSubFilter, !mNot);
+ }
+
+ @Override
+ public FilterValues<S> initialFilterValues() {
+ if (mNoParameters) {
+ return null;
+ }
+ FilterValues<S> filterValues = super.initialFilterValues();
+ if (filterValues == null) {
+ // Avoid cost of discovering this the next time.
+ mNoParameters = true;
+ }
+ return filterValues;
+ }
+
+ @Override
+ PropertyFilterList<S> getTailPropertyFilterList() {
+ if (mNoParameters) {
+ return null;
+ }
+ PropertyFilterList<S> tail = super.getTailPropertyFilterList();
+ if (tail == null) {
+ // Avoid cost of discovering this the next time.
+ mNoParameters = true;
+ }
+ return tail;
+ }
+
+ public <R, P> R accept(Visitor<S, R, P> visitor, P param) {
+ return visitor.visit(this, param);
+ }
+
+ public ExistsFilter<S> bind() {
+ Filter<?> boundSubFilter = mSubFilter.bind();
+ if (boundSubFilter == mSubFilter) {
+ return this;
+ }
+ return getCanonical(mProperty, boundSubFilter, mNot);
+ }
+
+ public ExistsFilter<S> unbind() {
+ Filter<?> unboundSubFilter = mSubFilter.unbind();
+ if (unboundSubFilter == mSubFilter) {
+ return this;
+ }
+ return getCanonical(mProperty, unboundSubFilter, mNot);
+ }
+
+ public boolean isBound() {
+ return mSubFilter.isBound();
+ }
+
+ void markBound() {
+ }
+
+ <T extends Storable> ExistsFilter<T> asJoinedFromAny(ChainedProperty<T> joinProperty) {
+ ChainedProperty<T> newProperty = joinProperty.append(getChainedProperty());
+ return getCanonical(newProperty, mSubFilter, mNot);
+ }
+
+ @Override
+ NotJoined notJoinedFromCNF(ChainedProperty<S> joinProperty) {
+ ChainedProperty<?> notJoinedProp = getChainedProperty();
+ ChainedProperty<?> jp = joinProperty;
+
+ while (notJoinedProp.getPrimeProperty().equals(jp.getPrimeProperty())) {
+ notJoinedProp = notJoinedProp.tail();
+ if (jp.getChainCount() == 0) {
+ jp = null;
+ break;
+ }
+ jp = jp.tail();
+ }
+
+ if (jp != null || notJoinedProp.equals(getChainedProperty())) {
+ return super.notJoinedFromCNF(joinProperty);
+ }
+
+ ExistsFilter<?> notJoinedFilter = getCanonical(notJoinedProp, mSubFilter, mNot);
+
+ return new NotJoined(notJoinedFilter, getOpenFilter(getStorableType()));
+ }
+
+ Filter<S> buildDisjunctiveNormalForm() {
+ return this;
+ }
+
+ Filter<S> buildConjunctiveNormalForm() {
+ return this;
+ }
+
+ boolean isDisjunctiveNormalForm() {
+ return true;
+ }
+
+ boolean isConjunctiveNormalForm() {
+ return true;
+ }
+
+ boolean isReduced() {
+ return true;
+ }
+
+ void markReduced() {
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = mProperty.hashCode() * 31 + mSubFilter.hashCode();
+ return mNot ? ~hash : hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ExistsFilter) {
+ ExistsFilter<?> other = (ExistsFilter<?>) obj;
+ return getStorableType() == other.getStorableType()
+ && mSubFilter == other.mSubFilter
+ && mNot == other.mNot
+ && mProperty.equals(other.mProperty);
+ }
+ return false;
+ }
+
+ public void appendTo(Appendable app, FilterValues<S> values) throws IOException {
+ if (mNot) {
+ app.append('!');
+ }
+ mProperty.appendTo(app);
+ app.append('(');
+
+ Filter<?> subFilter = mSubFilter;
+ if (subFilter != null && !(subFilter.isOpen())) {
+ FilterValues subValues;
+ if (values == null) {
+ subValues = null;
+ } else {
+ FilterValues subInitialValues = mSubFilter.initialFilterValues();
+ if (subInitialValues == null) {
+ subValues = null;
+ } else {
+ subValues = subInitialValues
+ .withValues(values.getSuppliedValuesFor(getJoinedSubFilter()));
+ subFilter = subValues.getFilter();
+ }
+ }
+ subFilter.appendTo(app, subValues);
+ }
+
+ app.append(')');
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/filter/Filter.java b/src/main/java/com/amazon/carbonado/filter/Filter.java
index cbc37dc..11f36d9 100644
--- a/src/main/java/com/amazon/carbonado/filter/Filter.java
+++ b/src/main/java/com/amazon/carbonado/filter/Filter.java
@@ -51,10 +51,12 @@ import com.amazon.carbonado.util.Appender;
* AndFilter = NotFilter { "&" NotFilter }
* NotFilter = [ "!" ] EntityFilter
* EntityFilter = PropertyFilter
+ * = ChainedFilter
* | "(" Filter ")"
* PropertyFilter = ChainedProperty RelOp "?"
* RelOp = "=" | "!=" | "&lt;" | "&gt;=" | "&gt;" | "&lt;="
* ChainedProperty = Identifier { "." Identifier }
+ * ChainedFilter = ChainedProperty "(" [ Filter ] ")"
* </pre>
*
* @author Brian S O'Neill
@@ -176,17 +178,12 @@ public abstract class Filter<S extends Storable> implements Appender {
* difference is caused by the filter property values being {@link #bind bound}.
*/
public FilterValues<S> initialFilterValues() {
- if (mFilterValues == null) {
- Filter<S> boundFilter = bind();
-
- if (boundFilter != this) {
- return boundFilter.initialFilterValues();
- }
-
+ FilterValues<S> filterValues = mFilterValues;
+ if (filterValues == null) {
buildFilterValues();
+ filterValues = mFilterValues;
}
-
- return mFilterValues;
+ return filterValues;
}
/**
@@ -196,27 +193,33 @@ public abstract class Filter<S extends Storable> implements Appender {
* @return tail of PropertyFilterList, or null if no parameters
*/
PropertyFilterList<S> getTailPropertyFilterList() {
- if (mTailPropertyFilterList == null) {
+ PropertyFilterList<S> tail = mTailPropertyFilterList;
+ if (tail == null) {
buildFilterValues();
+ tail = mTailPropertyFilterList;
}
-
- return mTailPropertyFilterList;
+ return tail;
}
private void buildFilterValues() {
- PropertyFilterList<S> list = accept(new PropertyFilterList.Builder<S>(), null);
+ Filter<S> boundFilter = bind();
+
+ if (boundFilter != this) {
+ mFilterValues = boundFilter.initialFilterValues();
+ mTailPropertyFilterList = boundFilter.getTailPropertyFilterList();
+ return;
+ }
- // List should never be null since only OpenFilter and ClosedFilter
- // have no properties, and they override initialFilterValues and
- // getTailPropertyFilterList.
- assert(list != null);
+ PropertyFilterList<S> list = accept(new PropertyFilterList.Builder<S>(), null);
- // Since FilterValues instances are immutable, save this for re-use.
- mFilterValues = FilterValues.create(this, list);
+ if (list != null) {
+ // Since FilterValues instances are immutable, save this for re-use.
+ mFilterValues = FilterValues.create(this, list);
- // PropertyFilterList can be saved for re-use because it too is
- // immutable (after PropertyFilterListBuilder has run).
- mTailPropertyFilterList = list.get(-1);
+ // PropertyFilterList can be saved for re-use because it too is
+ // immutable (after PropertyFilterListBuilder has run).
+ mTailPropertyFilterList = list.get(-1);
+ }
}
/**
@@ -239,10 +242,10 @@ public abstract class Filter<S extends Storable> implements Appender {
* @throws IllegalArgumentException if filter is null
*/
public Filter<S> and(Filter<S> filter) {
- if (filter instanceof OpenFilter) {
+ if (filter.isOpen()) {
return this;
}
- if (filter instanceof ClosedFilter) {
+ if (filter.isClosed()) {
return filter;
}
return AndFilter.getCanonical(this, filter);
@@ -278,6 +281,38 @@ public abstract class Filter<S extends Storable> implements Appender {
}
/**
+ * Returns a combined filter instance that accepts records which are only
+ * accepted by this filter and the "exists" test applied to a one-to-many join.
+ *
+ * @param propertyName one-to-many join property name, which may be a chained property
+ * @param subFilter sub-filter to apply to one-to-many join, which may be
+ * null to test for any existing
+ * @return canonical Filter instance
+ * @throws IllegalArgumentException if property is not found
+ * @since 1.2
+ */
+ public final Filter<S> andExists(String propertyName, Filter<?> subFilter) {
+ ChainedProperty<S> prop = new FilterParser<S>(mType, propertyName).parseChainedProperty();
+ return and(ExistsFilter.getCanonical(prop, subFilter, false));
+ }
+
+ /**
+ * Returns a combined filter instance that accepts records which are only
+ * accepted by this filter and the "not exists" test applied to a one-to-many join.
+ *
+ * @param propertyName one-to-many join property name, which may be a chained property
+ * @param subFilter sub-filter to apply to one-to-many join, which may be
+ * null to test for any not existing
+ * @return canonical Filter instance
+ * @throws IllegalArgumentException if property is not found
+ * @since 1.2
+ */
+ public final Filter<S> andNotExists(String propertyName, Filter<?> subFilter) {
+ ChainedProperty<S> prop = new FilterParser<S>(mType, propertyName).parseChainedProperty();
+ return and(ExistsFilter.getCanonical(prop, subFilter, true));
+ }
+
+ /**
* Returns a combined filter instance that accepts records which are
* accepted either by this filter or the one given.
*
@@ -297,10 +332,10 @@ public abstract class Filter<S extends Storable> implements Appender {
* @throws IllegalArgumentException if filter is null
*/
public Filter<S> or(Filter<S> filter) {
- if (filter instanceof OpenFilter) {
+ if (filter.isOpen()) {
return filter;
}
- if (filter instanceof ClosedFilter) {
+ if (filter.isClosed()) {
return this;
}
return OrFilter.getCanonical(this, filter);
@@ -336,6 +371,40 @@ public abstract class Filter<S extends Storable> implements Appender {
}
/**
+ * Returns a combined filter instance that accepts records which are
+ * accepted either by this filter or the "exists" test applied to a
+ * one-to-many join.
+ *
+ * @param propertyName one-to-many join property name, which may be a chained property
+ * @param subFilter sub-filter to apply to one-to-many join, which may be
+ * null to test for any existing
+ * @return canonical Filter instance
+ * @throws IllegalArgumentException if property is not found
+ * @since 1.2
+ */
+ public final Filter<S> orExists(String propertyName, Filter<?> subFilter) {
+ ChainedProperty<S> prop = new FilterParser<S>(mType, propertyName).parseChainedProperty();
+ return or(ExistsFilter.getCanonical(prop, subFilter, false));
+ }
+
+ /**
+ * Returns a combined filter instance that accepts records which are
+ * accepted either by this filter or the "not exists" test applied to a
+ * one-to-many join.
+ *
+ * @param propertyName one-to-many join property name, which may be a chained property
+ * @param subFilter sub-filter to apply to one-to-many join, which may be
+ * null to test for any not existing
+ * @return canonical Filter instance
+ * @throws IllegalArgumentException if property is not found
+ * @since 1.2
+ */
+ public final Filter<S> orNotExists(String propertyName, Filter<?> subFilter) {
+ ChainedProperty<S> prop = new FilterParser<S>(mType, propertyName).parseChainedProperty();
+ return or(ExistsFilter.getCanonical(prop, subFilter, true));
+ }
+
+ /**
* Returns the logical negation of this filter.
*
* @return canonical Filter instance
@@ -385,6 +454,11 @@ public abstract class Filter<S extends Storable> implements Appender {
list.add(filter);
return null;
}
+
+ public Object visit(ExistsFilter<S> filter, Object param) {
+ list.add(filter);
+ return null;
+ }
}, null);
return Collections.unmodifiableList(list);
@@ -441,6 +515,11 @@ public abstract class Filter<S extends Storable> implements Appender {
list.add(filter);
return null;
}
+
+ public Object visit(ExistsFilter<S> filter, Object param) {
+ list.add(filter);
+ return null;
+ }
}, null);
return Collections.unmodifiableList(list);
@@ -534,7 +613,19 @@ public abstract class Filter<S extends Storable> implements Appender {
* @return filter for type T
* @throws IllegalArgumentException if property is not a join to type S
*/
- public abstract <T extends Storable> Filter<T> asJoinedFrom(ChainedProperty<T> joinProperty);
+ public final <T extends Storable> Filter<T> asJoinedFrom(ChainedProperty<T> joinProperty) {
+ if (joinProperty.getType() != getStorableType()) {
+ throw new IllegalArgumentException
+ ("Property is not of type \"" + getStorableType().getName() + "\": " +
+ joinProperty);
+ }
+ return asJoinedFromAny(joinProperty);
+ }
+
+ /**
+ * Allows join from any property type, including one-to-many joins.
+ */
+ abstract <T extends Storable> Filter<T> asJoinedFromAny(ChainedProperty<T> joinProperty);
/**
* Removes a join property prefix from all applicable properties of this
@@ -577,16 +668,17 @@ public abstract class Filter<S extends Storable> implements Appender {
* @throws IllegalArgumentException if property does not refer to a Storable
*/
public final NotJoined notJoinedFrom(ChainedProperty<S> joinProperty) {
- Class<?> type = joinProperty.getType();
- if (!Storable.class.isAssignableFrom(type)) {
+ if (!Storable.class.isAssignableFrom(joinProperty.getType())) {
throw new IllegalArgumentException
("Join property type is not a Storable: " + joinProperty);
}
+ return notJoinedFromAny(joinProperty);
+ }
- Filter<S> cnf = conjunctiveNormalForm();
- NotJoined nj = cnf.notJoinedFrom(joinProperty, (Class<Storable>) type);
+ final NotJoined notJoinedFromAny(ChainedProperty<S> joinProperty) {
+ NotJoined nj = conjunctiveNormalForm().notJoinedFromCNF(joinProperty);
- if (nj.getNotJoinedFilter() instanceof OpenFilter) {
+ if (nj.getNotJoinedFilter().isOpen()) {
// Remainder filter should be same as original, but it might have
// expanded with conjunctive normal form. If so, restore to
// original, but still bind it to ensure consistent side-effects.
@@ -615,10 +707,26 @@ public abstract class Filter<S extends Storable> implements Appender {
/**
* Should only be called on a filter in conjunctive normal form.
*/
- NotJoined notJoinedFrom(ChainedProperty<S> joinProperty,
- Class<? extends Storable> joinPropertyType)
- {
- return new NotJoined(getOpenFilter(joinPropertyType), this);
+ NotJoined notJoinedFromCNF(ChainedProperty<S> joinProperty) {
+ return new NotJoined(getOpenFilter(joinProperty.getLastProperty().getJoinedType()), this);
+ }
+
+ /**
+ * Returns true if filter allows all results to pass through.
+ *
+ * @since 1.2
+ */
+ public boolean isOpen() {
+ return false;
+ }
+
+ /**
+ * Returns true if filter prevents any results from passing through.
+ *
+ * @since 1.2
+ */
+ public boolean isClosed() {
+ return false;
}
abstract Filter<S> buildDisjunctiveNormalForm();
diff --git a/src/main/java/com/amazon/carbonado/filter/FilterParser.java b/src/main/java/com/amazon/carbonado/filter/FilterParser.java
index a0a178d..bb7dac8 100644
--- a/src/main/java/com/amazon/carbonado/filter/FilterParser.java
+++ b/src/main/java/com/amazon/carbonado/filter/FilterParser.java
@@ -41,6 +41,10 @@ class FilterParser<S extends Storable> {
private int mPos;
FilterParser(Class<S> type, String filter) {
+ this(type, filter, 0);
+ }
+
+ private FilterParser(Class<S> type, String filter, int pos) {
if (type == null) {
throw new IllegalArgumentException();
}
@@ -49,6 +53,7 @@ class FilterParser<S extends Storable> {
}
mType = type;
mFilter = filter;
+ mPos = pos;
}
// Design note: This parser is actually a scanner, parser, and type checker
@@ -120,12 +125,46 @@ class FilterParser<S extends Storable> {
return test;
} else {
mPos--;
- return parsePropertyFilter();
+ ChainedProperty<S> chained = parseChainedProperty();
+ c = nextCharIgnoreWhitespace();
+ if (c != '(') {
+ mPos--;
+ return parsePropertyFilter(chained);
+ }
+
+ boolean isExistsFilter = chained.getLastProperty().isQuery();
+
+ Filter<S> chainedFilter;
+ c = nextCharIgnoreWhitespace();
+ if (c == ')') {
+ if (isExistsFilter) {
+ chainedFilter = ExistsFilter.getCanonical(chained, null, false);
+ } else {
+ // FIXME: support exists filter for this case
+ mPos--;
+ throw error("Property \"" + chained +
+ "\" is a many-to-one join and requires property filters");
+ }
+ } else {
+ mPos--;
+ Filter<?> cf = parseChainedFilter(chained);
+ if (isExistsFilter) {
+ chainedFilter = ExistsFilter.getCanonical(chained, cf, false);
+ } else {
+ chainedFilter = cf.asJoinedFrom(chained);
+ }
+ c = nextCharIgnoreWhitespace();
+ if (c != ')') {
+ mPos--;
+ throw error("Right paren expected");
+ }
+ }
+
+ return chainedFilter;
}
}
-
- private PropertyFilter<S> parsePropertyFilter() {
- ChainedProperty<S> chained = parseChainedProperty();
+
+ private PropertyFilter<S> parsePropertyFilter(ChainedProperty<S> chained) {
int c = nextCharIgnoreWhitespace();
RelOp op;
@@ -204,6 +243,15 @@ class FilterParser<S extends Storable> {
}
@SuppressWarnings("unchecked")
+ private Filter<?> parseChainedFilter(ChainedProperty<S> chained) {
+ FilterParser<?> chainedParser = new FilterParser
+ (chained.getLastProperty().getJoinedType(), mFilter, mPos);
+ Filter<?> chainedFilter = chainedParser.parseFilter();
+ mPos = chainedParser.mPos;
+ return chainedFilter;
+ }
+
+ @SuppressWarnings("unchecked")
ChainedProperty<S> parseChainedProperty() {
String ident = parseIdentifier();
StorableProperty<S> prime =
@@ -246,7 +294,8 @@ class FilterParser<S extends Storable> {
}
}
- return ChainedProperty.get(prime, (StorableProperty<?>[]) chain.toArray(new StorableProperty[chain.size()]));
+ return ChainedProperty
+ .get(prime, (StorableProperty<?>[]) chain.toArray(new StorableProperty[chain.size()]));
}
private String parseIdentifier() {
diff --git a/src/main/java/com/amazon/carbonado/filter/Group.java b/src/main/java/com/amazon/carbonado/filter/Group.java
index 6805bf4..1138567 100644
--- a/src/main/java/com/amazon/carbonado/filter/Group.java
+++ b/src/main/java/com/amazon/carbonado/filter/Group.java
@@ -130,5 +130,13 @@ class Group<S extends Storable> {
public Boolean visit(PropertyFilter<S> filter, Filter<S> child) {
return filter == child;
}
+
+ /**
+ * @return TRUE if overlap was found
+ */
+ @Override
+ public Boolean visit(ExistsFilter<S> filter, Filter<S> child) {
+ return filter == child;
+ }
}
}
diff --git a/src/main/java/com/amazon/carbonado/filter/OpenFilter.java b/src/main/java/com/amazon/carbonado/filter/OpenFilter.java
index 7604a16..35659e6 100644
--- a/src/main/java/com/amazon/carbonado/filter/OpenFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/OpenFilter.java
@@ -36,6 +36,16 @@ public class OpenFilter<S extends Storable> extends Filter<S> {
super(type);
}
+ /**
+ * Always returns true.
+ *
+ * @since 1.2
+ */
+ @Override
+ public final boolean isOpen() {
+ return true;
+ }
+
public Filter<S> and(Filter<S> filter) {
return filter;
}
@@ -92,7 +102,7 @@ public class OpenFilter<S extends Storable> extends Filter<S> {
return true;
}
- public <T extends Storable> OpenFilter<T> asJoinedFrom(ChainedProperty<T> joinProperty) {
+ <T extends Storable> OpenFilter<T> asJoinedFromAny(ChainedProperty<T> joinProperty) {
return getOpenFilter(joinProperty.getPrimeProperty().getEnclosingType());
}
diff --git a/src/main/java/com/amazon/carbonado/filter/OrFilter.java b/src/main/java/com/amazon/carbonado/filter/OrFilter.java
index d5748aa..a06912a 100644
--- a/src/main/java/com/amazon/carbonado/filter/OrFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/OrFilter.java
@@ -62,16 +62,14 @@ public class OrFilter<S extends Storable> extends BinaryOpFilter<S> {
return mLeft.unbind().or(mRight.unbind());
}
- public <T extends Storable> Filter<T> asJoinedFrom(ChainedProperty<T> joinProperty) {
- return mLeft.asJoinedFrom(joinProperty).or(mRight.asJoinedFrom(joinProperty));
+ <T extends Storable> Filter<T> asJoinedFromAny(ChainedProperty<T> joinProperty) {
+ return mLeft.asJoinedFromAny(joinProperty).or(mRight.asJoinedFromAny(joinProperty));
}
@Override
- NotJoined notJoinedFrom(ChainedProperty<S> joinProperty,
- Class<? extends Storable> joinPropertyType)
- {
- NotJoined left = mLeft.notJoinedFrom(joinProperty, joinPropertyType);
- NotJoined right = mRight.notJoinedFrom(joinProperty, joinPropertyType);
+ NotJoined notJoinedFromCNF(ChainedProperty<S> joinProperty) {
+ NotJoined left = mLeft.notJoinedFromCNF(joinProperty);
+ NotJoined right = mRight.notJoinedFromCNF(joinProperty);
// Assert that our child nodes are only OrFilter or PropertyFilter.
if (!isConjunctiveNormalForm()) {
@@ -85,10 +83,8 @@ public class OrFilter<S extends Storable> extends BinaryOpFilter<S> {
// and remainder filters would need to logically or'd together to
// reform the original filter, breaking the notJoinedFrom contract.
- if (!(left.getRemainderFilter() instanceof OpenFilter) ||
- !(right.getRemainderFilter() instanceof OpenFilter))
- {
- return super.notJoinedFrom(joinProperty, joinPropertyType);
+ if (!(left.getRemainderFilter().isOpen()) || !(right.getRemainderFilter().isOpen())) {
+ return super.notJoinedFromCNF(joinProperty);
}
// Remove wildcards to shut the compiler up.
diff --git a/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java b/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java
index 9f12095..97d508a 100644
--- a/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java
+++ b/src/main/java/com/amazon/carbonado/filter/PropertyFilter.java
@@ -188,13 +188,7 @@ public class PropertyFilter<S extends Storable> extends Filter<S> {
return mBindID != 0;
}
- public <T extends Storable> PropertyFilter<T> asJoinedFrom(ChainedProperty<T> joinProperty) {
- if (joinProperty.getType() != getStorableType()) {
- throw new IllegalArgumentException
- ("Property is not of type \"" + getStorableType().getName() + "\": " +
- joinProperty);
- }
-
+ <T extends Storable> PropertyFilter<T> asJoinedFromAny(ChainedProperty<T> joinProperty) {
ChainedProperty<T> newProperty = joinProperty.append(getChainedProperty());
if (isConstant()) {
@@ -205,9 +199,7 @@ public class PropertyFilter<S extends Storable> extends Filter<S> {
}
@Override
- NotJoined notJoinedFrom(ChainedProperty<S> joinProperty,
- Class<? extends Storable> joinPropertyType)
- {
+ NotJoined notJoinedFromCNF(ChainedProperty<S> joinProperty) {
ChainedProperty<?> notJoinedProp = getChainedProperty();
ChainedProperty<?> jp = joinProperty;
@@ -221,7 +213,7 @@ public class PropertyFilter<S extends Storable> extends Filter<S> {
}
if (jp != null || notJoinedProp.equals(getChainedProperty())) {
- return super.notJoinedFrom(joinProperty, joinPropertyType);
+ return super.notJoinedFromCNF(joinProperty);
}
PropertyFilter<?> notJoinedFilter;
diff --git a/src/main/java/com/amazon/carbonado/filter/PropertyFilterList.java b/src/main/java/com/amazon/carbonado/filter/PropertyFilterList.java
index 447edef..1027c67 100644
--- a/src/main/java/com/amazon/carbonado/filter/PropertyFilterList.java
+++ b/src/main/java/com/amazon/carbonado/filter/PropertyFilterList.java
@@ -182,5 +182,22 @@ class PropertyFilterList<S extends Storable> {
public PropertyFilterList<S> visit(PropertyFilter<S> filter, PropertyFilterList<S> list) {
return list == null ? new PropertyFilterList<S>(filter, null) : list.prepend(filter);
}
+
+ public PropertyFilterList<S> visit(ExistsFilter<S> filter, PropertyFilterList<S> list) {
+ PropertyFilterList<S> subList =
+ filter.getJoinedSubFilter().getTailPropertyFilterList();
+
+ while (subList != null) {
+ PropertyFilter<S> joinedFilter = subList.getPropertyFilter();
+ if (list == null) {
+ list = new PropertyFilterList<S>(joinedFilter, null);
+ } else {
+ list = list.prepend(joinedFilter);
+ }
+ subList = subList.getPrevious();
+ }
+
+ return list;
+ }
}
}
diff --git a/src/main/java/com/amazon/carbonado/filter/Reducer.java b/src/main/java/com/amazon/carbonado/filter/Reducer.java
index 6e2acc1..1c3d400 100644
--- a/src/main/java/com/amazon/carbonado/filter/Reducer.java
+++ b/src/main/java/com/amazon/carbonado/filter/Reducer.java
@@ -100,4 +100,15 @@ class Reducer<S extends Storable> extends Visitor<S, Filter<S>, Group<S>> {
group.add(filter);
return null;
}
+
+ /**
+ * @param filter candidate node to potentially replace
+ * @param group gathered children
+ * @return original candidate or replacement
+ */
+ @Override
+ public Filter<S> visit(ExistsFilter<S> filter, Group<S> group) {
+ group.add(filter);
+ return null;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/filter/Visitor.java b/src/main/java/com/amazon/carbonado/filter/Visitor.java
index 77ca07f..2c9e318 100644
--- a/src/main/java/com/amazon/carbonado/filter/Visitor.java
+++ b/src/main/java/com/amazon/carbonado/filter/Visitor.java
@@ -45,6 +45,13 @@ public abstract class Visitor<S extends Storable, R, P> {
return null;
}
+ /**
+ * @since 1.2
+ */
+ public R visit(ExistsFilter<S> filter, P param) {
+ return null;
+ }
+
public R visit(OpenFilter<S> filter, P param) {
return null;
}
diff --git a/src/main/java/com/amazon/carbonado/info/ChainedProperty.java b/src/main/java/com/amazon/carbonado/info/ChainedProperty.java
index 7d21a49..575bcbf 100644
--- a/src/main/java/com/amazon/carbonado/info/ChainedProperty.java
+++ b/src/main/java/com/amazon/carbonado/info/ChainedProperty.java
@@ -336,8 +336,9 @@ public class ChainedProperty<S extends Storable> implements Appender {
}
/**
- * Returns the chained property in a parseable form. The format is
- * "name.subname.subsubname".
+ * Returns the chained property formatted as "name.subname.subsubname".
+ * This format is parseable only if the chain is composed of valid
+ * many-to-one joins.
*/
@Override
public String toString() {
@@ -354,8 +355,9 @@ public class ChainedProperty<S extends Storable> implements Appender {
}
/**
- * Appends the chained property in a parseable form. The format is
- * "name.subname.subsubname".
+ * Appends the chained property formatted as "name.subname.subsubname".
+ * This format is parseable only if the chain is composed of valid
+ * many-to-one joins.
*/
public void appendTo(Appendable app) throws IOException {
app.append(mPrime.getName());
diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
index 1a970e0..626b435 100644
--- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
@@ -194,15 +194,14 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
}
public Query<S> or(Filter<S> filter) throws FetchException {
- FilterValues<S> values = filter == null ? null : filter.initialFilterValues();
- return mFactory.query(values, mOrdering);
+ return mFactory.query(filter, null, mOrdering);
}
/**
* Returns a query that fetches everything, possibly in a specified order.
*/
public Query<S> not() throws FetchException {
- return mFactory.query(null, mOrdering);
+ return mFactory.query(null, null, mOrdering);
}
public Query<S> orderBy(String property) throws FetchException {
diff --git a/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java
index d431b3d..494d353 100644
--- a/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/FilteredQueryExecutor.java
@@ -26,10 +26,8 @@ import com.amazon.carbonado.Storable;
import com.amazon.carbonado.cursor.FilteredCursor;
-import com.amazon.carbonado.filter.ClosedFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
-import com.amazon.carbonado.filter.OpenFilter;
/**
* QueryExecutor which wraps another and filters results.
@@ -50,12 +48,16 @@ public class FilteredQueryExecutor<S extends Storable> extends AbstractQueryExec
if (executor == null) {
throw new IllegalArgumentException();
}
- if (filter == null || filter instanceof OpenFilter || filter instanceof ClosedFilter) {
+ if (filter == null || filter.isOpen() || filter.isClosed()) {
throw new IllegalArgumentException();
}
mExecutor = executor;
// Ensure filter is same as what will be provided by values.
- mFilter = filter.initialFilterValues().getFilter();
+ FilterValues<S> values = filter.initialFilterValues();
+ if (values != null) {
+ filter = values.getFilter();
+ }
+ mFilter = filter;
}
public Cursor<S> fetch(FilterValues<S> values) throws FetchException {
diff --git a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java
index b1d47e9..12238a6 100644
--- a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java
+++ b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java
@@ -42,7 +42,6 @@ import com.amazon.carbonado.cursor.MultiTransformedCursor;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
-import com.amazon.carbonado.filter.OpenFilter;
import com.amazon.carbonado.filter.RelOp;
import com.amazon.carbonado.info.ChainedProperty;
@@ -501,7 +500,7 @@ public class JoinedQueryExecutor<S extends Storable, T extends Storable>
throw new IllegalArgumentException("Outer loop executor filter must be bound");
}
- if (targetFilter instanceof OpenFilter) {
+ if (targetFilter.isOpen()) {
targetFilter = null;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
index 56f6145..8218b66 100644
--- a/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryEngine.java
@@ -49,13 +49,20 @@ public class QueryEngine<S extends Storable> extends StandardQueryFactory<S>
return mExecutorFactory.executor(filter, ordering);
}
- protected StandardQuery<S> createQuery(FilterValues<S> values, OrderingList<S> ordering) {
- return new Query(values, ordering, null);
+ protected StandardQuery<S> createQuery(Filter<S> filter,
+ FilterValues<S> values,
+ OrderingList<S> ordering)
+ {
+ return new Query(filter, values, ordering, null);
}
private class Query extends StandardQuery<S> {
- Query(FilterValues<S> values, OrderingList<S> ordering, QueryExecutor<S> executor) {
- super(values, ordering, executor);
+ Query(Filter<S> filter,
+ FilterValues<S> values,
+ OrderingList<S> ordering,
+ QueryExecutor<S> executor)
+ {
+ super(filter, values, ordering, executor);
}
protected Transaction enterTransaction(IsolationLevel level) {
@@ -74,7 +81,7 @@ public class QueryEngine<S extends Storable> extends StandardQueryFactory<S>
OrderingList<S> ordering,
QueryExecutor<S> executor)
{
- return new Query(values, ordering, executor);
+ return new Query(values.getFilter(), values, ordering, executor);
}
}
}
diff --git a/src/main/java/com/amazon/carbonado/qe/QueryFactory.java b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java
index b7ead1d..ed1bfb4 100644
--- a/src/main/java/com/amazon/carbonado/qe/QueryFactory.java
+++ b/src/main/java/com/amazon/carbonado/qe/QueryFactory.java
@@ -22,6 +22,7 @@ import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Query;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
/**
@@ -35,8 +36,10 @@ public interface QueryFactory<S extends Storable> {
/**
* Returns a query that handles the given query specification.
*
- * @param values optional values object, defaults to open filter if null
+ * @param filter optional filter object, defaults to open filter if null
+ * @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
*/
- Query<S> query(FilterValues<S> values, OrderingList<S> ordering) throws FetchException;
+ Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering)
+ throws FetchException;
}
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
index 2d7995e..2914bf8 100644
--- a/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/StandardQuery.java
@@ -32,10 +32,8 @@ import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.Query;
-import com.amazon.carbonado.filter.ClosedFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
-import com.amazon.carbonado.filter.OpenFilter;
import com.amazon.carbonado.filter.RelOp;
import com.amazon.carbonado.info.Direction;
@@ -51,6 +49,8 @@ import com.amazon.carbonado.util.Appender;
public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
implements Appender
{
+ // Filter for this query, which may be null.
+ private final Filter<S> mFilter;
// Values for this query, which may be null.
private final FilterValues<S> mValues;
// Properties that this query is ordered by.
@@ -59,15 +59,29 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
private volatile QueryExecutor<S> mExecutor;
/**
- * @param values optional values object, defaults to open filter if null
+ * @param filter optional filter object, defaults to open filter if null
+ * @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
* @param executor optional executor to use (by default lazily obtains and caches executor)
*/
- protected StandardQuery(FilterValues<S> values,
+ protected StandardQuery(Filter<S> filter,
+ FilterValues<S> values,
OrderingList<S> ordering,
QueryExecutor<S> executor)
{
+ if (filter != null && filter.isOpen()) {
+ filter = null;
+ }
+
+ if (values == null) {
+ if (filter != null) {
+ values = filter.initialFilterValues();
+ }
+ }
+
+ mFilter = filter;
mValues = values;
+
if (ordering == null) {
ordering = OrderingList.emptyList();
}
@@ -80,11 +94,11 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
public Filter<S> getFilter() {
- FilterValues<S> values = mValues;
- if (values != null) {
- return values.getFilter();
+ Filter<S> filter = mFilter;
+ if (filter == null) {
+ return Filter.getOpenFilter(getStorableType());
}
- return Filter.getOpenFilter(getStorableType());
+ return filter;
}
public FilterValues<S> getFilterValues() {
@@ -139,46 +153,57 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
}
public Query<S> and(Filter<S> filter) throws FetchException {
+ Filter<S> newFilter;
FilterValues<S> newValues;
- if (mValues == null) {
+ if (mFilter == null) {
+ newFilter = filter;
newValues = filter.initialFilterValues();
} else {
if (getBlankParameterCount() > 0) {
throw new IllegalStateException("Blank parameters exist in query: " + this);
}
- newValues = mValues.getFilter().and(filter)
- .initialFilterValues().withValues(mValues.getSuppliedValues());
+ newFilter = mFilter.and(filter);
+ newValues = newFilter.initialFilterValues();
+ if (mValues != null) {
+ newValues = newValues.withValues(mValues.getSuppliedValues());
+ }
}
- return createQuery(newValues, mOrdering);
+ return createQuery(newFilter, newValues, mOrdering);
}
public Query<S> or(Filter<S> filter) throws FetchException {
- if (mValues == null) {
+ if (mFilter == null) {
throw new IllegalStateException("Query is already guaranteed to fetch everything");
}
if (getBlankParameterCount() > 0) {
throw new IllegalStateException("Blank parameters exist in query: " + this);
}
- FilterValues<S> newValues = mValues.getFilter().or(filter)
- .initialFilterValues().withValues(mValues.getSuppliedValues());
- return createQuery(newValues, mOrdering);
+ Filter<S> newFilter = mFilter.or(filter);
+ FilterValues<S> newValues = newFilter.initialFilterValues();
+ if (mValues != null) {
+ newValues = newValues.withValues(mValues.getSuppliedValues());
+ }
+ return createQuery(newFilter, newValues, mOrdering);
}
public Query<S> not() throws FetchException {
- if (mValues == null) {
+ if (mFilter == null) {
return new EmptyQuery<S>(queryFactory(), mOrdering);
}
- FilterValues<S> newValues = mValues.getFilter().not()
- .initialFilterValues().withValues(mValues.getSuppliedValues());
- return createQuery(newValues, mOrdering);
+ Filter<S> newFilter = mFilter.not();
+ FilterValues<S> newValues = newFilter.initialFilterValues();
+ if (mValues != null) {
+ newValues = newValues.withValues(mValues.getSuppliedValues());
+ }
+ return createQuery(newFilter, newValues, mOrdering);
}
public Query<S> orderBy(String property) throws FetchException {
- return createQuery(mValues, OrderingList.get(getStorableType(), property));
+ return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), property));
}
public Query<S> orderBy(String... properties) throws FetchException {
- return createQuery(mValues, OrderingList.get(getStorableType(), properties));
+ return createQuery(mFilter, mValues, OrderingList.get(getStorableType(), properties));
}
public Cursor<S> fetch() throws FetchException {
@@ -323,6 +348,9 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
public int hashCode() {
int hash = queryFactory().hashCode();
hash = hash * 31 + executorFactory().hashCode();
+ if (mFilter != null) {
+ hash = hash * 31 + mFilter.hashCode();
+ }
if (mValues != null) {
hash = hash * 31 + mValues.hashCode();
}
@@ -339,6 +367,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
StandardQuery<?> other = (StandardQuery<?>) obj;
return queryFactory().equals(other.queryFactory())
&& executorFactory().equals(other.executorFactory())
+ && (mFilter == null ? (other.mFilter == null) : (mFilter.equals(other.mFilter)))
&& (mValues == null ? (other.mValues == null) : (mValues.equals(other.mValues)))
&& mOrdering.equals(other.mOrdering);
}
@@ -350,7 +379,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
app.append(getStorableType().getName());
app.append(", filter=");
Filter<S> filter = getFilter();
- if (filter instanceof OpenFilter || filter instanceof ClosedFilter) {
+ if (filter.isOpen() || filter.isClosed()) {
filter.appendTo(app);
} else {
app.append('"');
@@ -386,8 +415,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
protected QueryExecutor<S> executor() throws RepositoryException {
QueryExecutor<S> executor = mExecutor;
if (executor == null) {
- Filter<S> filter = mValues == null ? null : mValues.getFilter();
- mExecutor = executor = executorFactory().executor(filter, mOrdering);
+ mExecutor = executor = executorFactory().executor(mFilter, mOrdering);
}
return executor;
}
@@ -406,8 +434,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
*/
protected void resetExecutor() throws RepositoryException {
if (mExecutor != null) {
- Filter<S> filter = mValues == null ? null : mValues.getFilter();
- mExecutor = executorFactory().executor(filter, mOrdering);
+ mExecutor = executorFactory().executor(mFilter, mOrdering);
}
}
@@ -443,7 +470,7 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
* new filter values. The Filter in the FilterValues is the same as was
* passed in the constructor.
*
- * @param values optional values object, defaults to open filter if null
+ * @param values non-null values object
* @param ordering order-by properties, never null
*/
protected abstract StandardQuery<S> newInstance(FilterValues<S> values,
@@ -454,9 +481,11 @@ public abstract class StandardQuery<S extends Storable> extends AbstractQuery<S>
return newInstance(values, mOrdering, mExecutor);
}
- private Query<S> createQuery(FilterValues<S> values, OrderingList<S> ordering)
+ private Query<S> createQuery(Filter<S> filter,
+ FilterValues<S> values,
+ OrderingList<S> ordering)
throws FetchException
{
- return queryFactory().query(values, ordering);
+ return queryFactory().query(filter, values, ordering);
}
}
diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java
index 06767c5..1ce509b 100644
--- a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java
+++ b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java
@@ -28,7 +28,6 @@ import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Query;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.filter.ClosedFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
@@ -109,6 +108,8 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF
* @throws IllegalArgumentException if filter is null
*/
public Query<S> query(Filter<S> filter, OrderingList<S> ordering) throws FetchException {
+ filter = filter.bind();
+
Map<OrderingList<S>, Query<S>> map;
synchronized (mFilterToQuery) {
map = mFilterToQuery.get(filter);
@@ -126,10 +127,10 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF
query = map.get(ordering);
if (query == null) {
FilterValues<S> values = filter.initialFilterValues();
- if (values == null && filter instanceof ClosedFilter) {
+ if (values == null && filter.isClosed()) {
query = new EmptyQuery<S>(this, ordering);
} else {
- StandardQuery<S> standardQuery = createQuery(values, ordering);
+ StandardQuery<S> standardQuery = createQuery(filter, values, ordering);
if (!mLazySetExecutor) {
try {
standardQuery.setExecutor();
@@ -149,16 +150,19 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF
/**
* Returns a new or cached query for the given query specification.
*
- * @param values optional values object, defaults to open filter if null
+ * @param filter optional filter object, defaults to open filter if null
+ * @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
*/
- public Query<S> query(FilterValues<S> values, OrderingList<S> ordering) throws FetchException {
- Query<S> query;
- if (values == null) {
- query = query(Filter.getOpenFilter(mType), ordering);
- } else {
- query = query(values.getFilter(), ordering).withValues(values.getSuppliedValues());
+ public Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering)
+ throws FetchException
+ {
+ Query<S> query = query(filter != null ? filter : Filter.getOpenFilter(mType), ordering);
+
+ if (values != null) {
+ query = query.withValues(values.getSuppliedValues());
}
+
return query;
}
@@ -196,10 +200,12 @@ public abstract class StandardQueryFactory<S extends Storable> implements QueryF
/**
* Implement this method to return query implementations.
*
- * @param values optional values object, defaults to open filter if null
+ * @param filter optional filter object, defaults to open filter if null
+ * @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
*/
- protected abstract StandardQuery<S> createQuery(FilterValues<S> values,
+ protected abstract StandardQuery<S> createQuery(Filter<S> filter,
+ FilterValues<S> values,
OrderingList<S> ordering)
throws FetchException;
diff --git a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java
index fda5f29..1771c71 100644
--- a/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java
+++ b/src/main/java/com/amazon/carbonado/qe/UnionQueryAnalyzer.java
@@ -33,6 +33,7 @@ import com.amazon.carbonado.Storable;
import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.filter.AndFilter;
+import com.amazon.carbonado.filter.ExistsFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.OrFilter;
import com.amazon.carbonado.filter.PropertyFilter;
@@ -625,6 +626,17 @@ public class UnionQueryAnalyzer<S extends Storable> implements QueryExecutorFact
}
}
+ // This method should only be called if root filter has no logical operators.
+ @Override
+ public RepositoryException visit(ExistsFilter<S> filter, Object param) {
+ try {
+ subAnalyze(filter);
+ return null;
+ } catch (RepositoryException e) {
+ return e;
+ }
+ }
+
private void subAnalyze(Filter<S> subFilter) throws SupportException, RepositoryException {
IndexedQueryAnalyzer<S>.Result subResult =
mIndexAnalyzer.analyze(subFilter, mOrdering);
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
index 25b0209..c0f23a2 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
@@ -45,6 +45,7 @@ import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.Trigger;
import com.amazon.carbonado.capability.IndexInfo;
import com.amazon.carbonado.filter.AndFilter;
+import com.amazon.carbonado.filter.ExistsFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.filter.OrFilter;
@@ -287,8 +288,11 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
return mInfo;
}
- protected StandardQuery<S> createQuery(FilterValues<S> values, OrderingList<S> ordering) {
- return new JDBCQuery(values, ordering, null);
+ protected StandardQuery<S> createQuery(Filter<S> filter,
+ FilterValues<S> values,
+ OrderingList<S> ordering)
+ {
+ return new JDBCQuery(filter, values, ordering, null);
}
public S instantiate(ResultSet rs) throws SQLException {
@@ -313,24 +317,23 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
JoinNode jn;
try {
- JoinNodeBuilder jnb = new JoinNodeBuilder(aliasGenerator);
- if (filter == null) {
- jn = new JoinNode(getStorableInfo(), null);
- } else {
+ JoinNodeBuilder<S> jnb =
+ new JoinNodeBuilder<S>(mRepository, getStorableInfo(), aliasGenerator);
+ if (filter != null) {
filter.accept(jnb, null);
- jn = jnb.getRootJoinNode();
}
+ jn = jnb.getRootJoinNode();
jnb.captureOrderings(ordering);
} catch (UndeclaredThrowableException e) {
throw mRepository.toFetchException(e);
}
- StatementBuilder selectBuilder = new StatementBuilder();
+ StatementBuilder<S> selectBuilder = new StatementBuilder<S>(mRepository);
selectBuilder.append("SELECT ");
// Don't bother using a table alias for one table. With just one table,
// there's no need to disambiguate.
- String alias = jn.hasAnyJoins() ? jn.getAlias() : null;
+ String alias = jn.isAliasRequired() ? jn.getAlias() : null;
Map<String, JDBCStorableProperty<S>> properties = getStorableInfo().getAllProperties();
int ordinal = 0;
@@ -351,7 +354,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
selectBuilder.append(" FROM");
- StatementBuilder fromWhereBuilder = new StatementBuilder();
+ StatementBuilder<S> fromWhereBuilder = new StatementBuilder<S>(mRepository);
fromWhereBuilder.append(" FROM");
if (alias == null) {
@@ -366,7 +369,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
PropertyFilter<S>[] propertyFilters;
boolean[] propertyFilterNullable;
- if (filter == null) {
+ if (filter == null || filter.isOpen()) {
propertyFilters = null;
propertyFilterNullable = null;
} else {
@@ -374,7 +377,8 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
selectBuilder.append(" WHERE ");
fromWhereBuilder.append(" WHERE ");
- WhereBuilder wb = new WhereBuilder(selectBuilder, alias == null ? null : jn);
+ WhereBuilder<S> wb = new WhereBuilder<S>
+ (selectBuilder, alias == null ? null : jn, aliasGenerator);
FetchException e = filter.accept(wb, null);
if (e != null) {
throw e;
@@ -383,7 +387,8 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
propertyFilters = wb.getPropertyFilters();
propertyFilterNullable = wb.getPropertyFilterNullable();
- wb = new WhereBuilder(fromWhereBuilder, alias == null ? null : jn);
+ wb = new WhereBuilder<S>
+ (fromWhereBuilder, alias == null ? null : jn, aliasGenerator);
e = filter.accept(wb, null);
if (e != null) {
throw e;
@@ -686,8 +691,12 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
}
private class JDBCQuery extends StandardQuery<S> {
- JDBCQuery(FilterValues<S> values, OrderingList<S> ordering, QueryExecutor<S> executor) {
- super(values, ordering, executor);
+ JDBCQuery(Filter<S> filter,
+ FilterValues<S> values,
+ OrderingList<S> ordering,
+ QueryExecutor<S> executor)
+ {
+ super(filter, values, ordering, executor);
}
@Override
@@ -721,14 +730,14 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
OrderingList<S> ordering,
QueryExecutor<S> executor)
{
- return new JDBCQuery(values, ordering, executor);
+ return new JDBCQuery(values.getFilter(), values, ordering, executor);
}
}
/**
* Node in a tree structure describing how tables are joined together.
*/
- private class JoinNode {
+ private static class JoinNode {
// Joined property which led to this node. For root node, it is null.
private final JDBCStorableProperty<?> mProperty;
@@ -737,6 +746,8 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
private final Map<String, JoinNode> mSubNodes;
+ private boolean mAliasRequired;
+
/**
* @param alias table alias in SQL statement, i.e. "T1"
*/
@@ -782,8 +793,8 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
return null;
}
- public boolean hasAnyJoins() {
- return mSubNodes.size() > 0;
+ public boolean isAliasRequired() {
+ return mAliasRequired || mSubNodes.size() > 0;
}
/**
@@ -843,13 +854,16 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
}
}
- public void addJoin(ChainedProperty<?> chained, TableAliasGenerator aliasGenerator)
+ public void addJoin(JDBCRepository repository,
+ ChainedProperty<?> chained,
+ TableAliasGenerator aliasGenerator)
throws RepositoryException
{
- addJoin(chained, aliasGenerator, 0);
+ addJoin(repository, chained, aliasGenerator, 0);
}
- private void addJoin(ChainedProperty<?> chained,
+ private void addJoin(JDBCRepository repository,
+ ChainedProperty<?> chained,
TableAliasGenerator aliasGenerator,
int offset)
throws RepositoryException
@@ -867,12 +881,16 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
String name = property.getName();
JoinNode subNode = mSubNodes.get(name);
if (subNode == null) {
- JDBCStorableInfo<?> info = mRepository.examineStorable(property.getJoinedType());
- JDBCStorableProperty<?> jProperty = mRepository.getJDBCStorableProperty(property);
+ JDBCStorableInfo<?> info = repository.examineStorable(property.getJoinedType());
+ JDBCStorableProperty<?> jProperty = repository.getJDBCStorableProperty(property);
subNode = new JoinNode(jProperty, info, aliasGenerator.nextAlias());
mSubNodes.put(name, subNode);
}
- subNode.addJoin(chained, aliasGenerator, offset + 1);
+ subNode.addJoin(repository, chained, aliasGenerator, offset + 1);
+ }
+
+ public void aliasIsRequired() {
+ mAliasRequired = true;
}
public String toString() {
@@ -893,13 +911,18 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
/**
* Filter visitor that constructs a JoinNode tree.
*/
- private class JoinNodeBuilder extends Visitor<S, Object, Object> {
+ private static class JoinNodeBuilder<S extends Storable> extends Visitor<S, Object, Object> {
+ private final JDBCRepository mRepository;
private final TableAliasGenerator mAliasGenerator;
private final JoinNode mRootJoinNode;
- JoinNodeBuilder(TableAliasGenerator aliasGenerator) {
+ JoinNodeBuilder(JDBCRepository repository,
+ JDBCStorableInfo<S> info,
+ TableAliasGenerator aliasGenerator)
+ {
+ mRepository = repository;
mAliasGenerator = aliasGenerator;
- mRootJoinNode = new JoinNode(getStorableInfo(), aliasGenerator.nextAlias());
+ mRootJoinNode = new JoinNode(info, aliasGenerator.nextAlias());
}
public JoinNode getRootJoinNode() {
@@ -917,7 +940,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
if (ordering != null) {
for (OrderedProperty<?> orderedProperty : ordering) {
ChainedProperty<?> chained = orderedProperty.getChainedProperty();
- mRootJoinNode.addJoin(chained, mAliasGenerator);
+ mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
}
}
} catch (RepositoryException e) {
@@ -940,15 +963,37 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
private void visit(PropertyFilter<S> filter) throws RepositoryException {
ChainedProperty<S> chained = filter.getChainedProperty();
- mRootJoinNode.addJoin(chained, mAliasGenerator);
+ mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
+ }
+
+ /**
+ * @throws UndeclaredThrowableException wraps a RepositoryException
+ * since RepositoryException cannot be thrown directly
+ */
+ public Object visit(ExistsFilter<S> filter, Object param) {
+ try {
+ visit(filter);
+ return null;
+ } catch (RepositoryException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+
+ private void visit(ExistsFilter<S> filter) throws RepositoryException {
+ mRootJoinNode.aliasIsRequired();
+ ChainedProperty<S> chained = filter.getChainedProperty();
+ mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
}
}
- private class StatementBuilder {
+ private static class StatementBuilder<S extends Storable> {
+ private final JDBCRepository mRepository;
+
private List<SQLStatement<S>> mStatements;
private StringBuilder mLiteralBuilder;
- StatementBuilder() {
+ StatementBuilder(JDBCRepository repository) {
+ mRepository = repository;
mStatements = new ArrayList<SQLStatement<S>>();
mLiteralBuilder = new StringBuilder();
}
@@ -1013,18 +1058,31 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
}
mLiteralBuilder.append(jProperty.getColumnName());
}
+
+ JDBCRepository getRepository() {
+ return mRepository;
+ }
}
- private class WhereBuilder extends Visitor<S, FetchException, Object> {
+ private static class WhereBuilder<S extends Storable>
+ extends Visitor<S, FetchException, Object>
+ {
private final StatementBuilder mStatementBuilder;
private final JoinNode mJoinNode;
+ private final TableAliasGenerator mAliasGenerator;
private List<PropertyFilter<S>> mPropertyFilters;
private List<Boolean> mPropertyFilterNullable;
- WhereBuilder(StatementBuilder statementBuilder, JoinNode jn) {
+ /**
+ * @param aliasGenerator used for supporting "EXISTS" filter
+ */
+ WhereBuilder(StatementBuilder statementBuilder, JoinNode jn,
+ TableAliasGenerator aliasGenerator)
+ {
mStatementBuilder = statementBuilder;
mJoinNode = jn;
+ mAliasGenerator = aliasGenerator;
mPropertyFilters = new ArrayList<PropertyFilter<S>>();
mPropertyFilterNullable = new ArrayList<Boolean>();
}
@@ -1114,6 +1172,74 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
return null;
}
+ public FetchException visit(ExistsFilter<S> filter, Object param) {
+ if (filter.isNotExists()) {
+ mStatementBuilder.append("NOT ");
+ }
+ mStatementBuilder.append("EXISTS (SELECT * FROM");
+
+ ChainedProperty<S> chained = filter.getChainedProperty();
+
+ JDBCStorableInfo<?> oneToManyInfo;
+ JDBCStorableProperty<?> oneToMany;
+
+ final JDBCRepository repo = mStatementBuilder.getRepository();
+ try {
+ StorableProperty<?> lastProp = chained.getLastProperty();
+ oneToManyInfo = repo.examineStorable(lastProp.getJoinedType());
+ oneToMany = repo.getJDBCStorableProperty(lastProp);
+ } catch (RepositoryException e) {
+ return repo.toFetchException(e);
+ }
+
+ Filter<?> subFilter = filter.getSubFilter();
+
+ JoinNode oneToManyNode;
+ try {
+ JoinNodeBuilder jnb =
+ new JoinNodeBuilder(repo, oneToManyInfo, mAliasGenerator);
+ if (subFilter != null) {
+ subFilter.accept(jnb, null);
+ }
+ oneToManyNode = jnb.getRootJoinNode();
+ } catch (UndeclaredThrowableException e) {
+ return repo.toFetchException(e);
+ }
+
+ oneToManyNode.appendFullJoinTo(mStatementBuilder);
+
+ mStatementBuilder.append(" WHERE ");
+
+ int count = oneToMany.getJoinElementCount();
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ mStatementBuilder.append(" AND ");
+ }
+ mStatementBuilder.append(oneToManyNode.getAlias());
+ mStatementBuilder.append('.');
+ mStatementBuilder.append(oneToMany.getInternalJoinElement(i).getColumnName());
+ mStatementBuilder.append('=');
+ mStatementBuilder.append(mJoinNode.findAliasFor(chained));
+ mStatementBuilder.append('.');
+ mStatementBuilder.append(oneToMany.getExternalJoinElement(i).getColumnName());
+ }
+
+ if (subFilter != null && !subFilter.isOpen()) {
+ mStatementBuilder.append(" AND (");
+ WhereBuilder wb = new WhereBuilder
+ (mStatementBuilder, oneToManyNode, mAliasGenerator);
+ FetchException e = (FetchException) subFilter.accept(wb, null);
+ if (e != null) {
+ return e;
+ }
+ mStatementBuilder.append(')');
+ }
+
+ mStatementBuilder.append(')');
+
+ return null;
+ }
+
private void addBindParameter(PropertyFilter<S> filter) {
RelOp op = filter.getOperator();
StorableProperty<?> property = filter.getChainedProperty().getLastProperty();