From ea23f97a2d18455fba38d496e0ee348aee41b0c0 Mon Sep 17 00:00:00 2001 From: Kieran Kelleher Date: Thu, 26 Jul 2012 11:16:39 -0400 Subject: [PATCH] Ensure in-memory evaluation of QualifierOperatorContains selector has the same result as generated SQL fetch. When using EOQualifier.QualifierOperatorContains selector with a keyPath that has multiple manyToMany and/or toMany keys the in-Memory evaluation will incorrectly return false in the cases where the result of the keyPath is nested arrays rather than a flattened array that represents the effective SQL evaluation for the same qualifier. Thus in-Memory evaluation does not match the behavior of SQL evaluation in this scenario. This fix flattens the array object resulting from the valueForKeyPath so that the qualifier will correctly evaluate to true when checking if the flattened array contains the object being evaluated. --- .../qualifiers/ERXKeyValueQualifier.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/qualifiers/ERXKeyValueQualifier.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/qualifiers/ERXKeyValueQualifier.java index dd1a0838ab5..039c23900b6 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/qualifiers/ERXKeyValueQualifier.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/qualifiers/ERXKeyValueQualifier.java @@ -2,11 +2,18 @@ import com.webobjects.eocontrol.EOKeyValueQualifier; import com.webobjects.eocontrol.EOQualifier; +import com.webobjects.eocontrol.EOQualifierVariable; +import com.webobjects.eocontrol.EOQualifier.ComparisonSupport; import com.webobjects.foundation.NSArray; +import com.webobjects.foundation.NSKeyValueCoding; +import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSSelector; +import com.webobjects.foundation._NSStringUtilities; import er.extensions.eof.ERXQ; +import er.extensions.foundation.ERXArrayUtilities; +import er.extensions.foundation.ERXProperties; /** * ERXKeyValueQualifier is a chainable extension of EOKeyValueQualifier. @@ -20,6 +27,11 @@ public class ERXKeyValueQualifier extends EOKeyValueQualifier implements IERXCha * Java Object Serialization Spec */ private static final long serialVersionUID = 1L; + + // Lazy static initialization + private static class PROPERTIES { + static boolean shouldFlattenValueObject = ERXProperties.booleanForKeyWithDefault("er.extensions.ERXKeyValueQualifier.Contains.flatten", true); + } public ERXKeyValueQualifier(String key, NSSelector selector, Object value) { super(key, selector, value); @@ -62,4 +74,32 @@ public T one(NSArray array) { public T requiredOne(NSArray array) { return ERXQ.requiredOne(array, this); } + + /** + * Overridden to handle case of in-memory evaluation of QualifierOperatorContains selector and a keyPath that has multiple toMany and/or manyToMany-flattened relationships resulting in arrays of arrays rather than + * an array of discrete objects. In that case the object is evaluated against a flattened array which gives the same result as SQL evaluation. + * + * Since legacy code may depend on workarounds to the incorrect behavior, this patch can be disabled by setting the property er.extensions.ERXKeyValueQualifier.Contains.flatten to false + */ + @Override + public boolean evaluateWithObject(Object object) { + Object objectValue = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, _key); + + if (_value instanceof EOQualifierVariable) { + throw new IllegalStateException("Error evaluating qualifier with key " + _key + ", selector " + _selector + ", value " + _value + " - value must be substitued for variable before evaluating"); + } + + if (_selector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike)) { + if (_lowercaseCache == null) { + _lowercaseCache = (_value != NSKeyValueCoding.NullValue) ? (_value.toString()).toLowerCase() : ""; + } + return _NSStringUtilities.stringMatchesPattern(((objectValue != null) && (objectValue != NSKeyValueCoding.NullValue)) ? objectValue.toString() : "", _lowercaseCache, true); + } + + // Flatten in case we have array of arrays + if (_selector.equals(EOQualifier.QualifierOperatorContains) && PROPERTIES.shouldFlattenValueObject && objectValue != null && objectValue instanceof NSArray) { + objectValue = ERXArrayUtilities.flatten((NSArray) objectValue); + } + return ComparisonSupport.compareValues((objectValue != null) ? objectValue : NSKeyValueCoding.NullValue, _value, _selector); + } }