You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/08/30 16:04:42 UTC

(cayenne) 02/02: removing dead code - EOQuery

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit debcdc94dab0812372b6fbb04d7be9b1db17d079
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Aug 30 12:03:35 2026 -0400

    removing dead code - EOQuery
---
 .../wocompat/EOFetchSpecificationParser.java       | 302 +++++++++++++
 .../apache/cayenne/wocompat/EOModelProcessor.java  |   2 +-
 .../org/apache/cayenne/wocompat/EOObjEntity.java   |   4 +-
 .../java/org/apache/cayenne/wocompat/EOQuery.java  | 495 ---------------------
 ...st.java => EOFetchSpecificationParserTest.java} |  45 +-
 5 files changed, 331 insertions(+), 517 deletions(-)

diff --git a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOFetchSpecificationParser.java b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOFetchSpecificationParser.java
new file mode 100644
index 000000000..a52d0866f
--- /dev/null
+++ b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOFetchSpecificationParser.java
@@ -0,0 +1,302 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *    https://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 org.apache.cayenne.wocompat;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.ExpressionParameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * EOFetchSpecificationParser parses EOFetchSpecifications from a
+ * WebObjects-style EOModel. It recursively builds Cayenne Expression
+ * objects and assembles them into the final aggregate Expression.
+ */
+@SuppressWarnings("unchecked")
+class EOFetchSpecificationParser {
+
+	// Xcode/EOModeler expressions have a colon at the end of the selector
+	// name
+	// (just like standard Objective-C syntax). WOLips does not. Add both
+	// sets to the hash map to handle both types of models.
+
+	// Selector strings (Java-base).
+	static final String IS_EQUAL_TO = "isEqualTo";
+	static final String IS_NOT_EQUAL_TO = "isNotEqualTo";
+	static final String IS_LIKE = "isLike";
+	static final String CASE_INSENSITIVE_LIKE = "isCaseInsensitiveLike";
+	static final String IS_LESS_THAN = "isLessThan";
+	static final String IS_LESS_THAN_OR_EQUAL_TO = "isLessThanOrEqualTo";
+	static final String IS_GREATER_THAN = "isGreaterThan";
+	static final String IS_GREATER_THAN_OR_EQUAL_TO = "isGreaterThanOrEqualTo";
+
+	private static final String OBJ_C = ":"; // Objective-C syntax addition.
+
+	private static Map<String, Integer> selectorToExpressionBridge;
+	private static final Logger LOGGER = LoggerFactory.getLogger(EOFetchSpecificationParser.class);
+
+	/**
+	 * selectorToExpressionBridge is just a mapping of EOModeler's selector
+	 * types to Cayenne Expression types.
+	 * 
+	 * @return HashMap of Expression types, keyed by the corresponding
+	 *         selector name
+	 */
+	static synchronized Map<String, Integer> selectorToExpressionBridge() {
+		// Initialize selectorToExpressionBridge if needed.
+		if (null == selectorToExpressionBridge) {
+			selectorToExpressionBridge = new HashMap<>();
+
+			selectorToExpressionBridge.put(IS_EQUAL_TO, Expression.EQUAL_TO);
+			selectorToExpressionBridge.put(IS_EQUAL_TO + OBJ_C, Expression.EQUAL_TO);
+
+			selectorToExpressionBridge.put(IS_NOT_EQUAL_TO, Expression.NOT_EQUAL_TO);
+			selectorToExpressionBridge.put(IS_NOT_EQUAL_TO + OBJ_C, Expression.NOT_EQUAL_TO);
+
+			selectorToExpressionBridge.put(IS_LIKE, Expression.LIKE);
+			selectorToExpressionBridge.put(IS_LIKE + OBJ_C, Expression.LIKE);
+
+			selectorToExpressionBridge.put(CASE_INSENSITIVE_LIKE, Expression.LIKE_IGNORE_CASE);
+			selectorToExpressionBridge.put(CASE_INSENSITIVE_LIKE + OBJ_C, Expression.LIKE_IGNORE_CASE);
+
+			selectorToExpressionBridge.put(IS_LESS_THAN, Expression.LESS_THAN);
+			selectorToExpressionBridge.put(IS_LESS_THAN + OBJ_C, Expression.LESS_THAN);
+
+			selectorToExpressionBridge.put(IS_LESS_THAN_OR_EQUAL_TO, Expression.LESS_THAN_EQUAL_TO);
+			selectorToExpressionBridge.put(IS_LESS_THAN_OR_EQUAL_TO + OBJ_C, Expression.LESS_THAN_EQUAL_TO);
+
+			selectorToExpressionBridge.put(IS_GREATER_THAN, Expression.GREATER_THAN);
+			selectorToExpressionBridge.put(IS_GREATER_THAN + OBJ_C, Expression.GREATER_THAN);
+
+			selectorToExpressionBridge.put(IS_GREATER_THAN_OR_EQUAL_TO, Expression.GREATER_THAN_EQUAL_TO);
+			selectorToExpressionBridge.put(IS_GREATER_THAN_OR_EQUAL_TO + OBJ_C, Expression.GREATER_THAN_EQUAL_TO);
+		}
+
+		return selectorToExpressionBridge;
+	}
+
+	/**
+	 * isAggregate determines whether a qualifier is "aggregate" -- has
+	 * children -- or "simple".
+	 * 
+	 * @param qualifier
+	 *            - a Map containing the qualifier settings
+	 * @return boolean indicating whether the qualifier is "aggregate"
+	 *         qualifier
+	 */
+	static boolean isAggregate(Map<String, ?> qualifier) {
+		boolean result = true;
+
+		String theClass = (String) qualifier.get("class");
+		if (theClass == null) {
+			return false; // should maybe throw an exception?
+		}
+		if (theClass.equalsIgnoreCase("EOKeyValueQualifier")
+				|| theClass.equalsIgnoreCase("EOKeyComparisonQualifier")) {
+			result = false;
+		}
+
+		return result;
+	}
+
+	/**
+	 * expressionTypeForQualifier looks at a qualifier containing the
+	 * EOModeler FetchSpecification and returns the equivalent Cayenne
+	 * Expression type for its selector.
+	 * 
+	 * @param qualifierMap
+	 *            - a Map containing the qualifier settings to examine.
+	 * @return int Expression type
+	 */
+	static int expressionTypeForQualifier(Map<String, ?> qualifierMap) {
+		// get selector
+		String selector = (String) qualifierMap.get("selectorName");
+		return expressionTypeForSelector(selector);
+	}
+
+	/**
+	 * expressionTypeForSelector looks at a selector from an EOModeler
+	 * FetchSpecification and returns the equivalent Cayenne Expression
+	 * type.
+	 * 
+	 * @param selector
+	 *            - a String containing the selector name.
+	 * @return int Expression type
+	 */
+	static int expressionTypeForSelector(String selector) {
+		Integer expType = selectorToExpressionBridge().get(selector);
+		return (expType != null ? expType : -1);
+	}
+
+	/**
+	 * aggregateExpressionClassForQualifier looks at a qualifer and returns
+	 * the aggregate type: one of Expression.AND, Expression.OR, or
+	 * Expression.NOT
+	 * 
+	 * @param qualifierMap
+	 *            - containing the qualifier to examine
+	 * @return int aggregate Expression type
+	 */
+	static int aggregateExpressionClassForQualifier(Map<String, ?> qualifierMap) {
+		String qualifierClass = (String) qualifierMap.get("class");
+		if (qualifierClass != null) {
+			if (qualifierClass.equalsIgnoreCase("EOAndQualifier")) {
+				return Expression.AND;
+			} else if (qualifierClass.equalsIgnoreCase("EOOrQualifier")) {
+				return Expression.OR;
+			} else if (qualifierClass.equalsIgnoreCase("EONotQualifier")) {
+				return Expression.NOT;
+			}
+		}
+
+		return -1; // error
+	}
+
+	/**
+	 * makeQualifier recursively builds an Expression for each condition in
+	 * the qualifierMap and assembles from them the complex Expression to
+	 * represent the entire EOFetchSpecification.
+	 * 
+	 * @param qualifierMap
+	 *            - Map representation of EOFetchSpecification
+	 * @return Expression translation of the EOFetchSpecification
+	 */
+	static Expression makeQualifier(EOObjEntity entity, Map<String, ?> qualifierMap) {
+		if (isAggregate(qualifierMap)) {
+			// the fetch specification has more than one qualifier
+			int aggregateClass = aggregateExpressionClassForQualifier(qualifierMap); // AND,
+			// OR,
+			// NOT
+
+			if (aggregateClass == Expression.NOT) {
+				// NOT qualifiers only have one child, keyed with
+				// "qualifier"
+				Map<String, ?> child = (Map<String, ?>) qualifierMap.get("qualifier");
+				// build the child expression
+				Expression childExp = makeQualifier(entity, child);
+
+				return childExp.notExp(); // add the "not" clause and return
+											// the
+				// result
+			} else {
+				// AND, OR qualifiers can have multiple children, keyed with
+				// "qualifiers"
+				// get the list of children
+				List<Map<String, ?>> children = (List<Map<String, ?>>) qualifierMap.get("qualifiers");
+				if (children != null) {
+					ArrayList<Expression> childExpressions = new ArrayList<>();
+					// build an Expression for each child
+					for (Map<String, ?> child : children) {
+						Expression childExp = makeQualifier(entity, child);
+						childExpressions.add(childExp);
+					}
+					// join the child expressions and return the result
+					return ExpressionFactory.joinExp(aggregateClass, childExpressions);
+				}
+			}
+
+		} // end if isAggregate(qualifierMap)...
+
+		// the query has a single qualifier
+		// get expression selector type
+		String qualifierClass = (String) qualifierMap.get("class");
+
+		// the key or key path we're comparing
+		String key = null;
+		// the key, keyPath, value, or parameterized value against which
+		// we're
+		// comparing the key
+		Object comparisonValue = null;
+
+		if ("EOKeyComparisonQualifier".equals(qualifierClass)) {
+			// Comparing two keys or key paths
+			key = (String) qualifierMap.get("leftValue");
+			comparisonValue = qualifierMap.get("rightValue");
+			// FIXME: I think EOKeyComparisonQualifier style Expressions are not supported...
+			return null;
+		} else if ("EOKeyValueQualifier".equals(qualifierClass)) {
+			// Comparing key with a value or parameterized value
+			key = (String) qualifierMap.get("key");
+			Object value = qualifierMap.get("value");
+
+			if (value instanceof Map) {
+				Map<String, String> valueMap = (Map<String, String>) value;
+				String objClass = valueMap.get("class"); // can be a
+				// qualifier class or java type
+				if ("EOQualifierVariable".equals(objClass) && valueMap.containsKey("_key")) {
+					// make a parameterized expression
+					String paramName = valueMap.get("_key");
+					comparisonValue = new ExpressionParameter(paramName);
+				} else {
+					Object queryVal = valueMap.get("value");
+					if ("NSNumber".equals(objClass)) {
+						// comparison to NSNumber -- cast
+						comparisonValue = queryVal;
+					} else if ("EONull".equals(objClass)) {
+						// comparison to null
+						comparisonValue = null;
+					} else { // Could there be other types? boolean, date,
+								// etc.???
+								// no cast
+						comparisonValue = queryVal;
+					}
+				}
+
+			} else if (value instanceof String) {
+				// value expression
+				comparisonValue = value;
+			} // end if (value instanceof Map) else...
+		}
+
+		// check whether the key is an object path; if at least one
+		// component is not,
+		// switch to db path..
+
+		Expression keyExp = ExpressionFactory.exp(key);
+		try {
+			entity.lastPathComponent(keyExp, Collections.emptyMap());
+		} catch (ExpressionException e) {
+			try {
+				keyExp = entity.translateToDbPath(keyExp);
+			} catch (Exception dbpathEx) {
+				LOGGER.warn("Couldn't find {} in {} in EOModel", keyExp, entity.getName());
+			}
+		}
+
+		try {
+			Expression exp = ExpressionFactory.expressionOfType(expressionTypeForQualifier(qualifierMap));
+
+			exp.setOperand(0, keyExp);
+			exp.setOperand(1, comparisonValue);
+			return exp;
+		} catch (ExpressionException e) {
+			LOGGER.warn(e.getUnlabeledMessage());
+			return null;
+		}
+	}
+}
diff --git a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java
index eb1eb79e1..079ccf07b 100644
--- a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java
+++ b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java
@@ -263,7 +263,7 @@ public class EOModelProcessor {
 		// qualifiers
 		Map<String, ?> qualifierMap = (Map<String, ?>) plistMap.get("qualifier");
 		if (qualifierMap != null && !qualifierMap.isEmpty()) {
-			descriptor.setQualifier(EOQuery.EOFetchSpecificationParser.makeQualifier((EOObjEntity) root, qualifierMap));
+			descriptor.setQualifier(EOFetchSpecificationParser.makeQualifier((EOObjEntity) root, qualifierMap));
 		}
 
 		// prefetches
diff --git a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOObjEntity.java b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOObjEntity.java
index 53cba8228..515e47bdf 100644
--- a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOObjEntity.java
+++ b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOObjEntity.java
@@ -83,7 +83,7 @@ public class EOObjEntity extends ObjEntity {
      * @since 1.2
      */
     // TODO: andrus, 5/27/2006 - make public after 1.2. Also maybe move entity
-    // initialization code from EOModelProcessor to this class, kind of like EOQuery does.
+    // initialization code from EOModelProcessor to this class.
     Map getEoMap() {
         return eoMap;
     }
@@ -92,7 +92,7 @@ public class EOObjEntity extends ObjEntity {
      * @since 1.2
      */
     // TODO: andrus, 5/27/2006 - make public after 1.2. Also maybe move entity
-    // initialization code from EOModelProcessor to this class, kind of like EOQuery does.
+    // initialization code from EOModelProcessor to this class.
     void setEoMap(Map eoMap) {
         this.eoMap = eoMap;
     }
diff --git a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOQuery.java b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOQuery.java
deleted file mode 100644
index 5fecd4bc6..000000000
--- a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOQuery.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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
- *
- *    https://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 org.apache.cayenne.wocompat;
-
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionException;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.ExpressionParameter;
-import org.apache.cayenne.exp.parser.ASTObjPath;
-import org.apache.cayenne.map.Entity;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.query.ObjectSelect;
-import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.SortOrder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A descriptor of SelectQuery loaded from EOModel. It is an informal
- * "decorator" of Cayenne SelectQuery to provide access to the extra information
- * of WebObjects EOFetchSpecification.
- * 
- * @since 1.1
- * @since 5.0 this query extends {@link ObjectSelect}
- */
-@SuppressWarnings("unchecked")
-public class EOQuery<T> extends ObjectSelect<T> {
-
-	protected Map<String, ?> plistMap;
-	protected Map<String, String> bindings;
-
-	public EOQuery(ObjEntity root, Map<String, ?> plistMap) {
-		super();
-		entityName(root.getName());
-		setRoot(root);
-		this.plistMap = plistMap;
-		initFromPlist(plistMap);
-	}
-
-	protected void initFromPlist(Map<String, ?> plistMap) {
-
-		if("YES".equalsIgnoreCase((String) plistMap.get("usesDistinct"))) {
-			distinct();
-		}
-
-		Object fetchLimit = plistMap.get("fetchLimit");
-		if (fetchLimit != null) {
-			try {
-				if (fetchLimit instanceof Number) {
-					limit(((Number) fetchLimit).intValue());
-				} else {
-					limit(Integer.parseInt(fetchLimit.toString()));
-				}
-			} catch (NumberFormatException nfex) {
-				// ignoring...
-			}
-		}
-
-		// sort orderings
-		List<Map<String, String>> orderings = (List<Map<String, String>>) plistMap.get("sortOrderings");
-		if (orderings != null && !orderings.isEmpty()) {
-			for (Map<String, String> ordering : orderings) {
-				boolean asc = !"compareDescending:".equals(ordering.get("selectorName"));
-				String key = ordering.get("key");
-				if (key != null) {
-					orderBy(key, asc ? SortOrder.ASCENDING : SortOrder.DESCENDING);
-				}
-			}
-		}
-
-		// qualifiers
-		Map<String, ?> qualifierMap = (Map<String, ?>) plistMap.get("qualifier");
-		if (qualifierMap != null && !qualifierMap.isEmpty()) {
-			where(makeQualifier(qualifierMap));
-		}
-
-		// prefetches
-		List<?> prefetches = (List<?>) plistMap.get("prefetchingRelationshipKeyPaths");
-		if (prefetches != null && !prefetches.isEmpty()) {
-			for (Object prefetch : prefetches) {
-				prefetch((String) prefetch, PrefetchTreeNode.UNDEFINED_SEMANTICS);
-			}
-		}
-
-		// data rows - note that we do not support fetching individual columns
-		// in the
-		// modeler...
-		if (plistMap.containsKey("rawRowKeyPaths")) {
-			fetchDataRows();
-		}
-	}
-
-	public Collection<String> getBindingNames() {
-		if (bindings == null) {
-			initBindings();
-		}
-
-		return bindings.keySet();
-	}
-
-	public String bindingClass(String name) {
-		if (bindings == null) {
-			initBindings();
-		}
-
-		return bindings.get(name);
-	}
-
-	private synchronized void initBindings() {
-		if (bindings != null) {
-			return;
-		}
-
-		bindings = new HashMap<>();
-
-		if (!(root instanceof Entity)) {
-			return;
-		}
-
-		Map<String, ?> qualifier = (Map<String, ?>) plistMap.get("qualifier");
-		initBindings(bindings, (Entity<?,?,?>) root, qualifier);
-	}
-
-	private void initBindings(Map<String,String> bindings, Entity<?,?,?> entity, Map<String, ?> qualifier) {
-		if (qualifier == null) {
-			return;
-		}
-
-		if ("EOKeyValueQualifier".equals(qualifier.get("class"))) {
-			String key = (String) qualifier.get("key");
-			if (key == null) {
-				return;
-			}
-
-			Object value = qualifier.get("value");
-			if (!(value instanceof Map)) {
-				return;
-			}
-
-			Map<String, ?> valueMap = (Map<String, ?>) value;
-			if (!"EOQualifierVariable".equals(valueMap.get("class")) || !valueMap.containsKey("_key")) {
-				return;
-			}
-
-			String name = (String) valueMap.get("_key");
-			String className = null;
-
-			// we don't know whether its obj path or db path, so the expression
-			// can blow
-			// ... in fact we can't support DB Path as the key is different from
-			// external
-			// name,
-			// so we will use Object type for all DB path...
-			try {
-				Object lastObject = new ASTObjPath(key).evaluate(entity);
-
-				if (lastObject instanceof ObjAttribute) {
-					className = ((ObjAttribute) lastObject).getType();
-				} else if (lastObject instanceof ObjRelationship) {
-					ObjEntity target = ((ObjRelationship) lastObject).getTargetEntity();
-					if (target != null) {
-						className = target.getClassName();
-					}
-				}
-			} catch (ExpressionException ex) {
-				className = "java.lang.Object";
-			}
-
-			if (className == null) {
-				className = "java.lang.Object";
-			}
-
-			bindings.put(name, className);
-
-			return;
-		}
-
-		List<Map<String, ?>> children = (List<Map<String, ?>>) qualifier.get("qualifiers");
-		if (children != null) {
-			for (Map<String, ?> child : children) {
-				initBindings(bindings, entity, child);
-			}
-		}
-	}
-
-	/**
-	 * Creates the Expression equivalent of the EOFetchSpecification represented
-	 * by the Map.
-	 * 
-	 * @param qualifierMap
-	 *            - FetchSpecification to translate
-	 * @return Expression equivalent to FetchSpecification
-	 */
-	public synchronized Expression makeQualifier(Map<String, ?> qualifierMap) {
-		if (qualifierMap == null) {
-			return null;
-		}
-
-		return EOFetchSpecificationParser.makeQualifier((EOObjEntity) getRoot(), qualifierMap);
-	}
-
-	/**
-	 * EOFetchSpecificationParser parses EOFetchSpecifications from a
-	 * WebObjects-style EOModel. It recursively builds Cayenne Expression
-	 * objects and assembles them into the final aggregate Expression.
-	 */
-	static class EOFetchSpecificationParser {
-
-		// Xcode/EOModeler expressions have a colon at the end of the selector
-		// name
-		// (just like standard Objective-C syntax). WOLips does not. Add both
-		// sets to the hash map to handle both types of models.
-
-		// Selector strings (Java-base).
-		static final String IS_EQUAL_TO = "isEqualTo";
-		static final String IS_NOT_EQUAL_TO = "isNotEqualTo";
-		static final String IS_LIKE = "isLike";
-		static final String CASE_INSENSITIVE_LIKE = "isCaseInsensitiveLike";
-		static final String IS_LESS_THAN = "isLessThan";
-		static final String IS_LESS_THAN_OR_EQUAL_TO = "isLessThanOrEqualTo";
-		static final String IS_GREATER_THAN = "isGreaterThan";
-		static final String IS_GREATER_THAN_OR_EQUAL_TO = "isGreaterThanOrEqualTo";
-
-		private static final String OBJ_C = ":"; // Objective-C syntax addition.
-
-		private static Map<String, Integer> selectorToExpressionBridge;
-		private static final Logger LOGGER = LoggerFactory.getLogger(EOFetchSpecificationParser.class);
-
-		/**
-		 * selectorToExpressionBridge is just a mapping of EOModeler's selector
-		 * types to Cayenne Expression types.
-		 * 
-		 * @return HashMap of Expression types, keyed by the corresponding
-		 *         selector name
-		 */
-		static synchronized Map<String, Integer> selectorToExpressionBridge() {
-			// Initialize selectorToExpressionBridge if needed.
-			if (null == selectorToExpressionBridge) {
-				selectorToExpressionBridge = new HashMap<>();
-
-				selectorToExpressionBridge.put(IS_EQUAL_TO, Expression.EQUAL_TO);
-				selectorToExpressionBridge.put(IS_EQUAL_TO + OBJ_C, Expression.EQUAL_TO);
-
-				selectorToExpressionBridge.put(IS_NOT_EQUAL_TO, Expression.NOT_EQUAL_TO);
-				selectorToExpressionBridge.put(IS_NOT_EQUAL_TO + OBJ_C, Expression.NOT_EQUAL_TO);
-
-				selectorToExpressionBridge.put(IS_LIKE, Expression.LIKE);
-				selectorToExpressionBridge.put(IS_LIKE + OBJ_C, Expression.LIKE);
-
-				selectorToExpressionBridge.put(CASE_INSENSITIVE_LIKE, Expression.LIKE_IGNORE_CASE);
-				selectorToExpressionBridge.put(CASE_INSENSITIVE_LIKE + OBJ_C, Expression.LIKE_IGNORE_CASE);
-
-				selectorToExpressionBridge.put(IS_LESS_THAN, Expression.LESS_THAN);
-				selectorToExpressionBridge.put(IS_LESS_THAN + OBJ_C, Expression.LESS_THAN);
-
-				selectorToExpressionBridge.put(IS_LESS_THAN_OR_EQUAL_TO, Expression.LESS_THAN_EQUAL_TO);
-				selectorToExpressionBridge.put(IS_LESS_THAN_OR_EQUAL_TO + OBJ_C, Expression.LESS_THAN_EQUAL_TO);
-
-				selectorToExpressionBridge.put(IS_GREATER_THAN, Expression.GREATER_THAN);
-				selectorToExpressionBridge.put(IS_GREATER_THAN + OBJ_C, Expression.GREATER_THAN);
-
-				selectorToExpressionBridge.put(IS_GREATER_THAN_OR_EQUAL_TO, Expression.GREATER_THAN_EQUAL_TO);
-				selectorToExpressionBridge.put(IS_GREATER_THAN_OR_EQUAL_TO + OBJ_C, Expression.GREATER_THAN_EQUAL_TO);
-			}
-
-			return selectorToExpressionBridge;
-		}
-
-		/**
-		 * isAggregate determines whether a qualifier is "aggregate" -- has
-		 * children -- or "simple".
-		 * 
-		 * @param qualifier
-		 *            - a Map containing the qualifier settings
-		 * @return boolean indicating whether the qualifier is "aggregate"
-		 *         qualifier
-		 */
-		static boolean isAggregate(Map<String, ?> qualifier) {
-			boolean result = true;
-
-			String theClass = (String) qualifier.get("class");
-			if (theClass == null) {
-				return false; // should maybe throw an exception?
-			}
-			if (theClass.equalsIgnoreCase("EOKeyValueQualifier")
-					|| theClass.equalsIgnoreCase("EOKeyComparisonQualifier")) {
-				result = false;
-			}
-
-			return result;
-		}
-
-		/**
-		 * expressionTypeForQualifier looks at a qualifier containing the
-		 * EOModeler FetchSpecification and returns the equivalent Cayenne
-		 * Expression type for its selector.
-		 * 
-		 * @param qualifierMap
-		 *            - a Map containing the qualifier settings to examine.
-		 * @return int Expression type
-		 */
-		static int expressionTypeForQualifier(Map<String, ?> qualifierMap) {
-			// get selector
-			String selector = (String) qualifierMap.get("selectorName");
-			return expressionTypeForSelector(selector);
-		}
-
-		/**
-		 * expressionTypeForSelector looks at a selector from an EOModeler
-		 * FetchSpecification and returns the equivalent Cayenne Expression
-		 * type.
-		 * 
-		 * @param selector
-		 *            - a String containing the selector name.
-		 * @return int Expression type
-		 */
-		static int expressionTypeForSelector(String selector) {
-			Integer expType = selectorToExpressionBridge().get(selector);
-			return (expType != null ? expType : -1);
-		}
-
-		/**
-		 * aggregateExpressionClassForQualifier looks at a qualifer and returns
-		 * the aggregate type: one of Expression.AND, Expression.OR, or
-		 * Expression.NOT
-		 * 
-		 * @param qualifierMap
-		 *            - containing the qualifier to examine
-		 * @return int aggregate Expression type
-		 */
-		static int aggregateExpressionClassForQualifier(Map<String, ?> qualifierMap) {
-			String qualifierClass = (String) qualifierMap.get("class");
-			if (qualifierClass != null) {
-				if (qualifierClass.equalsIgnoreCase("EOAndQualifier")) {
-					return Expression.AND;
-				} else if (qualifierClass.equalsIgnoreCase("EOOrQualifier")) {
-					return Expression.OR;
-				} else if (qualifierClass.equalsIgnoreCase("EONotQualifier")) {
-					return Expression.NOT;
-				}
-			}
-
-			return -1; // error
-		}
-
-		/**
-		 * makeQualifier recursively builds an Expression for each condition in
-		 * the qualifierMap and assembles from them the complex Expression to
-		 * represent the entire EOFetchSpecification.
-		 * 
-		 * @param qualifierMap
-		 *            - Map representation of EOFetchSpecification
-		 * @return Expression translation of the EOFetchSpecification
-		 */
-		static Expression makeQualifier(EOObjEntity entity, Map<String, ?> qualifierMap) {
-			if (isAggregate(qualifierMap)) {
-				// the fetch specification has more than one qualifier
-				int aggregateClass = aggregateExpressionClassForQualifier(qualifierMap); // AND,
-				// OR,
-				// NOT
-
-				if (aggregateClass == Expression.NOT) {
-					// NOT qualifiers only have one child, keyed with
-					// "qualifier"
-					Map<String, ?> child = (Map<String, ?>) qualifierMap.get("qualifier");
-					// build the child expression
-					Expression childExp = makeQualifier(entity, child);
-
-					return childExp.notExp(); // add the "not" clause and return
-												// the
-					// result
-				} else {
-					// AND, OR qualifiers can have multiple children, keyed with
-					// "qualifiers"
-					// get the list of children
-					List<Map<String, ?>> children = (List<Map<String, ?>>) qualifierMap.get("qualifiers");
-					if (children != null) {
-						ArrayList<Expression> childExpressions = new ArrayList<>();
-						// build an Expression for each child
-						for (Map<String, ?> child : children) {
-							Expression childExp = makeQualifier(entity, child);
-							childExpressions.add(childExp);
-						}
-						// join the child expressions and return the result
-						return ExpressionFactory.joinExp(aggregateClass, childExpressions);
-					}
-				}
-
-			} // end if isAggregate(qualifierMap)...
-
-			// the query has a single qualifier
-			// get expression selector type
-			String qualifierClass = (String) qualifierMap.get("class");
-
-			// the key or key path we're comparing
-			String key = null;
-			// the key, keyPath, value, or parameterized value against which
-			// we're
-			// comparing the key
-			Object comparisonValue = null;
-
-			if ("EOKeyComparisonQualifier".equals(qualifierClass)) {
-				// Comparing two keys or key paths
-				key = (String) qualifierMap.get("leftValue");
-				comparisonValue = qualifierMap.get("rightValue");
-				// FIXME: I think EOKeyComparisonQualifier style Expressions are not supported...
-				return null;
-			} else if ("EOKeyValueQualifier".equals(qualifierClass)) {
-				// Comparing key with a value or parameterized value
-				key = (String) qualifierMap.get("key");
-				Object value = qualifierMap.get("value");
-
-				if (value instanceof Map) {
-					Map<String, String> valueMap = (Map<String, String>) value;
-					String objClass = valueMap.get("class"); // can be a
-					// qualifier class or java type
-					if ("EOQualifierVariable".equals(objClass) && valueMap.containsKey("_key")) {
-						// make a parameterized expression
-						String paramName = valueMap.get("_key");
-						comparisonValue = new ExpressionParameter(paramName);
-					} else {
-						Object queryVal = valueMap.get("value");
-						if ("NSNumber".equals(objClass)) {
-							// comparison to NSNumber -- cast
-							comparisonValue = queryVal;
-						} else if ("EONull".equals(objClass)) {
-							// comparison to null
-							comparisonValue = null;
-						} else { // Could there be other types? boolean, date,
-									// etc.???
-									// no cast
-							comparisonValue = queryVal;
-						}
-					}
-
-				} else if (value instanceof String) {
-					// value expression
-					comparisonValue = value;
-				} // end if (value instanceof Map) else...
-			}
-
-			// check whether the key is an object path; if at least one
-			// component is not,
-			// switch to db path..
-
-			Expression keyExp = ExpressionFactory.exp(key);
-			try {
-				entity.lastPathComponent(keyExp, Collections.emptyMap());
-			} catch (ExpressionException e) {
-				try {
-					keyExp = entity.translateToDbPath(keyExp);
-				} catch (Exception dbpathEx) {
-					LOGGER.warn("Couldn't find {} in {} in EOModel", keyExp, entity.getName());
-				}
-			}
-
-			try {
-				Expression exp = ExpressionFactory.expressionOfType(expressionTypeForQualifier(qualifierMap));
-
-				exp.setOperand(0, keyExp);
-				exp.setOperand(1, comparisonValue);
-				return exp;
-			} catch (ExpressionException e) {
-				LOGGER.warn(e.getUnlabeledMessage());
-				return null;
-			}
-		}
-	}
-}
diff --git a/cayenne-wocompat/src/test/java/org/apache/cayenne/wocompat/EOQueryTest.java b/cayenne-wocompat/src/test/java/org/apache/cayenne/wocompat/EOFetchSpecificationParserTest.java
similarity index 55%
rename from cayenne-wocompat/src/test/java/org/apache/cayenne/wocompat/EOQueryTest.java
rename to cayenne-wocompat/src/test/java/org/apache/cayenne/wocompat/EOFetchSpecificationParserTest.java
index ab423976c..8761dd4cc 100644
--- a/cayenne-wocompat/src/test/java/org/apache/cayenne/wocompat/EOQueryTest.java
+++ b/cayenne-wocompat/src/test/java/org/apache/cayenne/wocompat/EOFetchSpecificationParserTest.java
@@ -20,48 +20,55 @@
 package org.apache.cayenne.wocompat;
 
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.map.SelectQueryDescriptor;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.PrefetchTreeNode;
 import org.junit.jupiter.api.Test;
 
 import java.net.URL;
 import java.util.Collection;
-import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class EOQueryTest {
+public class EOFetchSpecificationParserTest {
 
     @Test
-    public void constructor() throws Exception {
+    public void loadedFetchSpecification() throws Exception {
 
         URL url = getClass().getClassLoader().getResource("wotests/fetchspec.eomodeld/");
         assertNotNull(url);
 
-        EOModelProcessor processor = new EOModelProcessor();
-        DataMap map = processor.loadEOModel(url);
+        DataMap map = new EOModelProcessor().loadEOModel(url);
 
-        @SuppressWarnings("unchecked")
-        Map<String, ?> fspecMap = (Map<String, ?>) PropertyListSerialization.propertyListFromStream(getClass()
-                .getClassLoader()
-                .getResourceAsStream("wotests/fetchspec.eomodeld/Entity1.fspec"));
-        assertNotNull(fspecMap);
-        assertNotNull(fspecMap.get("E1FS1"));
+        QueryDescriptor descriptor = map.getQueryDescriptor("Entity1_E1FS1");
+        assertNotNull(descriptor);
+        SelectQueryDescriptor selectDescriptor = assertInstanceOf(SelectQueryDescriptor.class, descriptor);
+        assertSame(map.getObjEntity("Entity1"), selectDescriptor.getRoot());
 
-        @SuppressWarnings("unchecked")
-        EOQuery query = new EOQuery(map.getObjEntity("Entity1"), (Map<String, ?>) fspecMap.get("E1FS1"));
-
-        assertNotNull(query.getWhere());
+        assertNotNull(selectDescriptor.getQualifier());
         assertEquals(
                 "(name = \"aa\") and (db:ID >= 7) and ((e2.name = \"bb\") or (db:e2.ID != 5))",
-                query.getWhere().toString());
+                selectDescriptor.getQualifier().toString());
+
+        ObjectSelect<?> query = selectDescriptor.buildQuery();
 
         assertNotNull(query.getPrefetches());
+        Collection<PrefetchTreeNode> prefetches = query.getPrefetches().getChildren();
+        assertEquals(1, prefetches.size());
+        assertEquals("e2", prefetches.iterator().next().getName());
 
-        Collection children = query.getPrefetches().getChildren();
-        assertEquals(1, children.size());
-        assertEquals("e2", ((PrefetchTreeNode) children.iterator().next()).getName());
+        Collection<Ordering> orderings = query.getOrderings();
+        assertEquals(1, orderings.size());
+        Ordering ordering = orderings.iterator().next();
+        assertEquals("name", ordering.getSortSpecString());
+        assertFalse(ordering.isAscending());
 
         assertTrue(query.isFetchingDataRows());
         assertEquals(500, query.getLimit());