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 2016/12/11 08:18:48 UTC

[1/4] cayenne git commit: updating expressions docs for 4.0

Repository: cayenne
Updated Branches:
  refs/heads/master 6caca2f61 -> 835711858


updating expressions docs for 4.0


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/242cf253
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/242cf253
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/242cf253

Branch: refs/heads/master
Commit: 242cf25342a9d23dd91c7b945d5a340e554cde8f
Parents: 6caca2f
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 10:11:27 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 10:11:27 2016 +0300

----------------------------------------------------------------------
 .../cayenne-guide/src/docbkx/expressions.xml    | 94 ++++++++++----------
 1 file changed, 49 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/242cf253/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
----------------------------------------------------------------------
diff --git a/docs/docbook/cayenne-guide/src/docbkx/expressions.xml b/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
index 01deb9c..6cba888 100644
--- a/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
+++ b/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
@@ -106,26 +106,27 @@
         <title>Creating Expressions from Strings </title>
         <para>While in most cases users are likely to rely on API from the following section for
             expression creation, we&apos;ll start by showing String expressions, as this will help
-            understanding the semantics. A Cayenne expression can be represented as a String, which
-            can be later converted to an expression object using <code>Expression.fromString</code>
-            static method. Here is an
+            to understand the semantics. A Cayenne expression can be represented as a String, which
+            can be converted to an expression object using <code>ExpressionFactory.exp</code> static
+            method. Here is an
             example:<programlisting language="java">String expString = &quot;name like &apos;A%&apos; and price &lt; 1000&quot;;
-Expression exp = Expression.fromString(expString);</programlisting>This
-            particular expression may be used to match Paintings with names that start with &quot;A&quot; and
-            a price less than $1000. While this example is pretty self-explanatory, there are a few
-            points worth mentioning. &quot;name&quot; and &quot;price&quot; here are object paths discussed earlier. As
-            always, paths themselves are not attached to a specific root entity and can be applied
-            to any entity that has similarly named attributes or relationships. So when we are
-            saying that this expression &quot;may be used to match Paintings&quot;, we are implying that there
-            may be other entities, for which this expression is valid. Now the expression details... </para>
+Expression exp = ExpressionFactory.exp(expString);</programlisting>This
+            particular expression may be used to match Paintings whose names that start with
+            &quot;A&quot; and whose price is less than $1000. While this example is pretty
+            self-explanatory, there are a few points worth mentioning. &quot;name&quot; and
+            &quot;price&quot; here are object paths discussed earlier. As always, paths themselves
+            are not attached to a specific root entity and can be applied to any entity that has
+            similarly named attributes or relationships. So when we are saying that this expression
+            &quot;may be used to match Paintings&quot;, we are implying that there may be other
+            entities, for which this expression is valid. Now the expression details... </para>
         <para><emphasis role="italic">Character constants</emphasis> that are not paths or numeric values
             should be enclosed in single or double quotes. Two of the expressions below are
             equivalent:<programlisting language="SQL">name = &apos;ABC&apos;
 
 // double quotes are escaped inside Java Strings of course
 name = \&quot;ABC\&quot;</programlisting></para>
-        <para><emphasis role="italic">Case sensitivity.</emphasis> Expression operators are all case
-            sensitive and are usually lowercase. Complex words follow the java camel-case
+        <para><emphasis role="italic">Case sensitivity.</emphasis> Expression operators are case
+            sensitive and are usually lowercase. Complex words follow the Java camel-case
             style:<programlisting language="SQL">// valid
 name likeIgnoreCase &apos;A%&apos;
 
@@ -155,49 +156,52 @@ name = enum:org.foo.EnumClass.VALUE1</programlisting></para>
         <para>
             <emphasis role="italic">Binary conditions</emphasis> are expressions that contain a path
             on the left, a value on the right, and some operation between them, such as equals,
-            like, etc. They can be used as qualifiers in
-                SelectQueries:<programlisting language="SQL">name like &apos;A%&apos;</programlisting>
-            <emphasis role="italic">Named parameters.</emphasis> Expressions can have named parameters (names that
-            start with &quot;$&quot;). Parameterized expressions allow to create reusable expression
-            templates. Also if an Expression contains a complex object that doesn&apos;t have a simple
-            String representation (e.g. a Date, a DataObject, an ObjectId), parameterizing such
-            expression is the only way to represent it as String. Here are some
-            examples:<programlisting language="java">Expression template = Expression.fromString(&quot;name = $name&quot;);
+            like, etc. They can be used as qualifiers in SelectQueries:<programlisting language="SQL">name like &apos;A%&apos;</programlisting>
+            <emphasis role="italic">Parameters.</emphasis> Expressions can contain named parameters
+            (names that start with &quot;$&quot;) that can be substituted with values either by name
+            or by position. Parameterized expressions allow to create reusable expression templates.
+            Also if an Expression contains a complex object that doesn&apos;t have a simple String
+            representation (e.g. a Date, a DataObject, an ObjectId), parameterizing such expression
+            is the only way to represent it as String. Here are the examples of both positional and
+            named parameter
+            bindings:<programlisting language="java">Expression template = ExpressionFactory.exp(&quot;name = $name&quot;);
 ...
+// name binding
 Map p1 = Collections.singletonMap(&quot;name&quot;, &quot;Salvador Dali&quot;);
-Expression qualifier1 = template.expWithParameters(p1);
+Expression qualifier1 = template.params(p1);
 ...
-Map p2 = Collections.singletonMap(&quot;name&quot;, &quot;Monet&quot;);
-Expression qualifier2 = template.expWithParameters(p2);</programlisting>To
-            create a named parameterized expression with a LIKE clause, SQL wildcards must be part
-            of the values in the Map and not the expression string
-            itself:<programlisting language="java">Expression template = Expression.fromString(&quot;name like $name&quot;);
-...
-Map p1 = Collections.singletonMap(&quot;name&quot;, &quot;Salvador%&quot;);
-Expression qualifier1 = template.expWithParameters(p1);</programlisting>When
+// positional binding
+Expression qualifier2 = template.paramsArray(&quot;Monet&quot;);</programlisting></para>
+        <para>Positional binding is usually shorter. You can pass positional bindings to the
+                <code>"exp(..)"</code> factory method (its second argument is a params
+            vararg):<programlisting>Expression qualifier = ExpressionFactory.exp(&quot;name = $name&quot;, &quot;Monet&quot;);</programlisting></para>
+        <para>To create a named parameterized expression with a LIKE clause, SQL wildcards must be
+            part of the values in the Map and not the expression string
+            itself:<programlisting language="java">Expression qualifier = ExpressionFactory.exp(&quot;name like $name&quot;, &quot;Salvador%&quot;);</programlisting>When
             matching on a relationship, parameters can be Persistent objects or
-            ObjectIds:<programlisting language="java">Expression template = Expression.fromString(&quot;artist = $artist&quot;);
-...
-Artist dali = // asume we fetched this one already
-Map p1 = Collections.singletonMap(&quot;artist&quot;, dali);
-Expression qualifier1 = template.expWithParameters(p1);</programlisting>Uninitialized
-            parameters will be automatically pruned from expressions, so a user can omit some
-            parameters when creating an expression from a parameterized
-            template:<programlisting language="java">Expression template = Expression.fromString(&quot;name like $name and dateOfBirth &gt; $date&quot;);
+            ObjectIds:<programlisting language="java">Artist dali = ... // asume we fetched this one already
+Expression qualifier1 = ExpressionFactory.exp(&quot;artist = $artist&quot;, dali);</programlisting>When
+            using positional binding, Cayenne would expect values for <emphasis role="bold"
+                >all</emphasis> parameters to be present. Binding by name offers extra flexibility:
+            subexpressions with uninitialized parameters are automatically pruned from the
+            expression. So e.g. if certain parts of the expression criteria are not provided to the
+            application, you can still build a valid
+            expression:<programlisting language="java">Expression template = ExpressionFactory.exp(&quot;name like $name and dateOfBirth &gt; $date&quot;);
 ...
 Map p1 = Collections.singletonMap(&quot;name&quot;, &quot;Salvador%&quot;);
-Expression qualifier1 = template.expWithParameters(p1);
+Expression qualifier1 = template.params(p1);
 
-// qualifier1 is now equals to &quot;name like &apos;Salvador%&apos;&quot;, the &apos;dateOfBirth&apos; condition was
-// pruned, as no value was specified for the $date parameter</programlisting></para>
+// "qualifier1" is now &quot;name like &apos;Salvador%&apos;&quot;.
+// &apos;dateOfBirth &gt; $date&apos; condition was pruned, as no value was specified for 
+// the $date parameter</programlisting></para>
         <para><emphasis role="italic">Null handling.</emphasis> Handling of Java nulls as operands
             is no different from normal values. Instead of using special conditional operators, like
-            SQL does (IS NULL, IS NOT NULL), &quot;=&quot; and &quot;!=&quot; expressions can be used directly with null
-            values. It is up to Cayenne to translate expressions with nulls to the valid SQL.</para>
+            SQL does (IS NULL, IS NOT NULL), &quot;=&quot; and &quot;!=&quot; expressions are used
+            directly with null values. It is up to Cayenne to translate expressions with nulls to
+            the valid SQL.</para>
         <para>
             <note>
-                <para>A formal definition of all possible valid expressions in a form of JavaCC
-                    grammar is provided in Appendix C</para>
+                <para>A formal definition of the expression grammar is provided in Appendix C</para>
             </note>
         </para>
     </section>


[4/4] cayenne git commit: CAY-2163 Property.path() , ExpressionFactory.pathExp()

Posted by aa...@apache.org.
CAY-2163 Property.path() , ExpressionFactory.pathExp()

(also switching some docs to Property API)


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/83571185
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/83571185
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/83571185

Branch: refs/heads/master
Commit: 83571185859a9588c40917f98a54ee8b67402454
Parents: 94d4d83
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 10:55:41 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 11:18:15 2016 +0300

----------------------------------------------------------------------
 .../apache/cayenne/exp/ExpressionFactory.java   |  18 +
 .../java/org/apache/cayenne/exp/Property.java   |  19 +-
 .../cayenne/exp/ExpressionFactoryTest.java      |  20 +-
 .../org/apache/cayenne/exp/PropertyTest.java    | 377 ++++++++++---------
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |  10 +
 .../cayenne-guide/src/docbkx/expressions.xml    |  89 +++--
 6 files changed, 299 insertions(+), 234 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/83571185/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index cbef42d..b440f8a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -860,6 +860,24 @@ public class ExpressionFactory {
 	}
 
 	/**
+	 * @param pathSpec a String "obj:" path.
+	 * @since 4.0
+	 * @return a new "obj:" path expression for the specified String path.
+	 */
+	public static Expression pathExp(String pathSpec) {
+		return new ASTObjPath(pathSpec);
+	}
+
+	/**
+	 * @param pathSpec a String db: path.
+	 * @since 4.0
+	 * @return a new "db:" path expression for the specified String path.
+	 */
+	public static Expression dbPathExp(String pathSpec) {
+		return new ASTDbPath(pathSpec);
+	}
+
+	/**
 	 * A convenience shortcut for boolean true expression.
 	 * 
 	 * @since 3.0

http://git-wip-us.apache.org/repos/asf/cayenne/blob/83571185/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index 5560014..b9ae1c6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -77,16 +77,18 @@ public class Property<E> {
     }
 
     /**
-     * @return Constructs a property path by appending the argument to the
-     * existing property separated by a dot
+     * Constructs a property path by appending the argument to the existing property separated by a dot.
+     *
+     * @return a newly created Property object.
      */
     public Property<Object> dot(String property) {
         return new Property<Object>(getName() + "." + property);
     }
 
     /**
-     * @return Constructs a property path by appending the argument to the
-     * existing property separated by a dot
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @return a newly created Property object.
      */
     public <T> Property<T> dot(Property<T> property) {
         return new Property<T>(getName() + "." + property.getName());
@@ -106,6 +108,15 @@ public class Property<E> {
     }
 
     /**
+     * Converts this property to a path expression.
+     *
+     * @return a newly created expression.
+     */
+    public Expression path() {
+        return ExpressionFactory.pathExp(getName());
+    }
+
+    /**
      * @return An expression representing null.
      */
     public Expression isNull() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/83571185/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
index 57ff918..3cf4a46 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
@@ -175,7 +175,7 @@ public class ExpressionFactoryTest {
 
 	@Test
 	public void testInExp2() throws Exception {
-		List<Object> v = new ArrayList<Object>();
+		List<Object> v = new ArrayList<>();
 		v.add("a");
 		v.add("b");
 		Expression exp = ExpressionFactory.inExp("abc", v);
@@ -184,7 +184,7 @@ public class ExpressionFactoryTest {
 
 	@Test
 	public void testInExp3() throws Exception {
-		List<Object> v = new ArrayList<Object>();
+		List<Object> v = new ArrayList<>();
 		Expression exp = ExpressionFactory.inExp("abc", v);
 		assertEquals(Expression.FALSE, exp.getType());
 	}
@@ -308,7 +308,7 @@ public class ExpressionFactoryTest {
 
 		// check for N in (1..5)
 		for (int n = 1; n <= 5; n++) {
-			Collection<Expression> list = new ArrayList<Expression>();
+			Collection<Expression> list = new ArrayList<>();
 
 			// populate map
 			for (int i = 1; i <= n; i++) {
@@ -342,7 +342,7 @@ public class ExpressionFactoryTest {
 	public void testAnd_Collection_OneElement() {
 		Expression e1 = ExpressionFactory.matchExp("a", 1);
 
-		Collection<Expression> c = Arrays.asList(e1);
+		Collection<Expression> c = Collections.singletonList(e1);
 		Expression e = ExpressionFactory.and(c);
 
 		assertEquals("a = 1", e.toString());
@@ -457,8 +457,18 @@ public class ExpressionFactoryTest {
 	}
 
 	@Test
-	public void testExp_Vararg_InAsValues() throws Exception {
+	public void testExp_Vararg_InAsValues() {
 		Expression e = ExpressionFactory.exp("k1 in ($ap, $bp)", "a", "b");
 		assertEquals("k1 in (\"a\", \"b\")", e.toString());
 	}
+
+	@Test
+	public void testPathExp() {
+		assertEquals("abc.xyz", ExpressionFactory.pathExp("abc.xyz").toString());
+	}
+
+	@Test
+	public void testDbPathExp() {
+		assertEquals("db:abc.xyz", ExpressionFactory.dbPathExp("abc.xyz").toString());
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/83571185/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
index 0026f66..5984a94 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
@@ -31,189 +31,196 @@ import org.junit.Test;
 
 public class PropertyTest {
 
-	@Test
-	public void testIn() {
-		Property<String> p = new Property<String>("x.y");
-
-		Expression e1 = p.in("a");
-		assertEquals("x.y in (\"a\")", e1.toString());
-
-		Expression e2 = p.in("a", "b");
-		assertEquals("x.y in (\"a\", \"b\")", e2.toString());
-
-		Expression e3 = p.in(Arrays.asList("a", "b"));
-		assertEquals("x.y in (\"a\", \"b\")", e3.toString());
-	}
-
-	@Test
-	public void testGetFrom() {
-		TstJavaBean bean = new TstJavaBean();
-		bean.setIntField(7);
-		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-		assertEquals(Integer.valueOf(7), INT_FIELD.getFrom(bean));
-	}
-
-	@Test
-	public void testGetFromNestedProperty() {
-		TstJavaBean bean = new TstJavaBean();
-		TstJavaBean nestedBean = new TstJavaBean();
-		nestedBean.setIntField(7);
-		bean.setObjectField(nestedBean);
-		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-		assertEquals(Integer.valueOf(7), OBJECT_FIELD_INT_FIELD.getFrom(bean));
-	}
-
-	@Test
-	public void testGetFromNestedNull() {
-		TstJavaBean bean = new TstJavaBean();
-		bean.setObjectField(null);
-		Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-		assertNull(OBJECT_FIELD_INT_FIELD.getFrom(bean));
-	}
-
-	@Test
-	public void testGetFromAll() {
-		TstJavaBean bean = new TstJavaBean();
-		bean.setIntField(7);
-
-		TstJavaBean bean2 = new TstJavaBean();
-		bean2.setIntField(8);
-
-		List<TstJavaBean> beans = Arrays.asList(bean, bean2);
-
-		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-		assertEquals(Arrays.asList(7, 8), INT_FIELD.getFromAll(beans));
-	}
-
-	@Test
-	public void testSetIn() {
-		TstJavaBean bean = new TstJavaBean();
-		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-		INT_FIELD.setIn(bean, 7);
-		assertEquals(7, bean.getIntField());
-	}
-
-	@Test
-	public void testSetInNestedProperty() {
-		TstJavaBean bean = new TstJavaBean();
-		bean.setObjectField(new TstJavaBean());
-
-		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-
-		OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
-		assertEquals(7, ((TstJavaBean) bean.getObjectField()).getIntField());
-	}
-
-	@Test
-	public void testSetInNestedNull() {
-		TstJavaBean bean = new TstJavaBean();
-		bean.setObjectField(null);
-		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-		OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
-	}
-
-	@Test
-	public void testSetInAll() {
-		TstJavaBean bean = new TstJavaBean();
-		TstJavaBean bean2 = new TstJavaBean();
-		List<TstJavaBean> beans = Arrays.asList(bean, bean2);
-
-		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-		INT_FIELD.setInAll(beans, 7);
-		assertEquals(7, bean.getIntField());
-		assertEquals(7, bean2.getIntField());
-	}
-
-	@Test
-	public void testEquals() {
-		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-		final Property<Integer> INT_FIELD2 = new Property<Integer>("intField");
-
-		assertTrue(INT_FIELD != INT_FIELD2);
-		assertTrue(INT_FIELD.equals(INT_FIELD2));
-	}
-
-	@Test
-	public void testHashCode() {
-		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-		final Property<Integer> INT_FIELD2 = new Property<Integer>("intField");
-		final Property<Long> LONG_FIELD = new Property<Long>("longField");
-
-		assertTrue(INT_FIELD.hashCode() == INT_FIELD2.hashCode());
-		assertTrue(INT_FIELD.hashCode() != LONG_FIELD.hashCode());
-	}
-
-	@Test
-	public void testOuter() {
-		Property<String> inner = new Property<String>("xyz");
-		assertEquals("xyz+", inner.outer().getName());
-
-		Property<String> inner1 = new Property<String>("xyz.xxx");
-		assertEquals("xyz.xxx+", inner1.outer().getName());
-
-		Property<String> outer = new Property<String>("xyz+");
-		assertEquals("xyz+", outer.outer().getName());
-	}
-
-	@Test
-	public void testLike() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.like("abc");
-		assertEquals("prop like \"abc\"", e.toString());
-	}
-
-	@Test
-	public void testLikeIgnoreCase() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.likeIgnoreCase("abc");
-		assertEquals("prop likeIgnoreCase \"abc\"", e.toString());
-	}
-
-	@Test
-	public void testLike_NoEscape() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.like("ab%c");
-		assertEquals("prop like \"ab%c\"", e.toString());
-		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
-	}
-
-	@Test
-	public void testContains() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.contains("abc");
-		assertEquals("prop like \"%abc%\"", e.toString());
-		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
-	}
-
-	@Test
-	public void testStartsWith() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.startsWith("abc");
-		assertEquals("prop like \"abc%\"", e.toString());
-		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
-	}
-
-	@Test
-	public void testEndsWith() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.endsWith("abc");
-		assertEquals("prop like \"%abc\"", e.toString());
-		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
-	}
-	
-	@Test
-	public void testContains_Escape1() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.contains("a%bc");
-		assertEquals("prop like \"%a!%bc%\"", e.toString());
-		assertEquals('!', ((PatternMatchNode) e).getEscapeChar());
-	}
-	
-	@Test
-	public void testContains_Escape2() {
-		Property<String> p = new Property<String>("prop");
-		Expression e = p.contains("a_!bc");
-		assertEquals("prop like \"%a#_!bc%\"", e.toString());
-		assertEquals('#', ((PatternMatchNode) e).getEscapeChar());
-	}
+    @Test
+    public void testPath() {
+        Property<String> p = new Property<>("x.y");
+        Expression pp = p.path();
+        assertEquals(ExpressionFactory.exp("x.y"), pp);
+    }
+
+    @Test
+    public void testIn() {
+        Property<String> p = new Property<>("x.y");
+
+        Expression e1 = p.in("a");
+        assertEquals("x.y in (\"a\")", e1.toString());
+
+        Expression e2 = p.in("a", "b");
+        assertEquals("x.y in (\"a\", \"b\")", e2.toString());
+
+        Expression e3 = p.in(Arrays.asList("a", "b"));
+        assertEquals("x.y in (\"a\", \"b\")", e3.toString());
+    }
+
+    @Test
+    public void testGetFrom() {
+        TstJavaBean bean = new TstJavaBean();
+        bean.setIntField(7);
+        Property<Integer> INT_FIELD = new Property<>("intField");
+        assertEquals(Integer.valueOf(7), INT_FIELD.getFrom(bean));
+    }
+
+    @Test
+    public void testGetFromNestedProperty() {
+        TstJavaBean bean = new TstJavaBean();
+        TstJavaBean nestedBean = new TstJavaBean();
+        nestedBean.setIntField(7);
+        bean.setObjectField(nestedBean);
+        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        assertEquals(Integer.valueOf(7), OBJECT_FIELD_INT_FIELD.getFrom(bean));
+    }
+
+    @Test
+    public void testGetFromNestedNull() {
+        TstJavaBean bean = new TstJavaBean();
+        bean.setObjectField(null);
+        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        assertNull(OBJECT_FIELD_INT_FIELD.getFrom(bean));
+    }
+
+    @Test
+    public void testGetFromAll() {
+        TstJavaBean bean = new TstJavaBean();
+        bean.setIntField(7);
+
+        TstJavaBean bean2 = new TstJavaBean();
+        bean2.setIntField(8);
+
+        List<TstJavaBean> beans = Arrays.asList(bean, bean2);
+
+        Property<Integer> INT_FIELD = new Property<>("intField");
+        assertEquals(Arrays.asList(7, 8), INT_FIELD.getFromAll(beans));
+    }
+
+    @Test
+    public void testSetIn() {
+        TstJavaBean bean = new TstJavaBean();
+        Property<Integer> INT_FIELD = new Property<>("intField");
+        INT_FIELD.setIn(bean, 7);
+        assertEquals(7, bean.getIntField());
+    }
+
+    @Test
+    public void testSetInNestedProperty() {
+        TstJavaBean bean = new TstJavaBean();
+        bean.setObjectField(new TstJavaBean());
+
+        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+
+        OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
+        assertEquals(7, ((TstJavaBean) bean.getObjectField()).getIntField());
+    }
+
+    @Test
+    public void testSetInNestedNull() {
+        TstJavaBean bean = new TstJavaBean();
+        bean.setObjectField(null);
+        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
+    }
+
+    @Test
+    public void testSetInAll() {
+        TstJavaBean bean = new TstJavaBean();
+        TstJavaBean bean2 = new TstJavaBean();
+        List<TstJavaBean> beans = Arrays.asList(bean, bean2);
+
+        Property<Integer> INT_FIELD = new Property<>("intField");
+        INT_FIELD.setInAll(beans, 7);
+        assertEquals(7, bean.getIntField());
+        assertEquals(7, bean2.getIntField());
+    }
+
+    @Test
+    public void testEquals() {
+        Property<Integer> INT_FIELD = new Property<>("intField");
+        Property<Integer> INT_FIELD2 = new Property<>("intField");
+
+        assertTrue(INT_FIELD != INT_FIELD2);
+        assertTrue(INT_FIELD.equals(INT_FIELD2));
+    }
+
+    @Test
+    public void testHashCode() {
+        Property<Integer> INT_FIELD = new Property<>("intField");
+        Property<Integer> INT_FIELD2 = new Property<>("intField");
+        Property<Long> LONG_FIELD = new Property<>("longField");
+
+        assertTrue(INT_FIELD.hashCode() == INT_FIELD2.hashCode());
+        assertTrue(INT_FIELD.hashCode() != LONG_FIELD.hashCode());
+    }
+
+    @Test
+    public void testOuter() {
+        Property<String> inner = new Property<>("xyz");
+        assertEquals("xyz+", inner.outer().getName());
+
+        Property<String> inner1 = new Property<>("xyz.xxx");
+        assertEquals("xyz.xxx+", inner1.outer().getName());
+
+        Property<String> outer = new Property<>("xyz+");
+        assertEquals("xyz+", outer.outer().getName());
+    }
+
+    @Test
+    public void testLike() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.like("abc");
+        assertEquals("prop like \"abc\"", e.toString());
+    }
+
+    @Test
+    public void testLikeIgnoreCase() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.likeIgnoreCase("abc");
+        assertEquals("prop likeIgnoreCase \"abc\"", e.toString());
+    }
+
+    @Test
+    public void testLike_NoEscape() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.like("ab%c");
+        assertEquals("prop like \"ab%c\"", e.toString());
+        assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+    }
+
+    @Test
+    public void testContains() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.contains("abc");
+        assertEquals("prop like \"%abc%\"", e.toString());
+        assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+    }
+
+    @Test
+    public void testStartsWith() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.startsWith("abc");
+        assertEquals("prop like \"abc%\"", e.toString());
+        assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+    }
+
+    @Test
+    public void testEndsWith() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.endsWith("abc");
+        assertEquals("prop like \"%abc\"", e.toString());
+        assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+    }
+
+    @Test
+    public void testContains_Escape1() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.contains("a%bc");
+        assertEquals("prop like \"%a!%bc%\"", e.toString());
+        assertEquals('!', ((PatternMatchNode) e).getEscapeChar());
+    }
+
+    @Test
+    public void testContains_Escape2() {
+        Property<String> p = new Property<>("prop");
+        Expression e = p.contains("a_!bc");
+        assertEquals("prop like \"%a#_!bc%\"", e.toString());
+        assertEquals('#', ((PatternMatchNode) e).getEscapeChar());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/83571185/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 626e549..3cccbd6 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -8,6 +8,16 @@ To browse individual bug reports check out project issue tracker:
 https://issues.apache.org/jira/browse/CAY
 
 ----------------------------------
+Release: 4.0.M5
+Date:
+----------------------------------
+Changes/New Features:
+
+CAY-2153 Property.path() , ExpressionFactory.pathExp()
+
+Bug Fixes:
+
+----------------------------------
 Release: 4.0.M4
 Date:
 ----------------------------------

http://git-wip-us.apache.org/repos/asf/cayenne/blob/83571185/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
----------------------------------------------------------------------
diff --git a/docs/docbook/cayenne-guide/src/docbkx/expressions.xml b/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
index dd87c1d..48e7462 100644
--- a/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
+++ b/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
@@ -208,39 +208,52 @@ Expression qualifier1 = template.params(p1);
         </para>
     </section>
     <section xml:id="expressions-with-expressionfactory">
-        <title>Creating Expressions with API</title>
+        <title>Creating Expressions via API</title>
         <para>Creating expressions from Strings is a powerful and dynamic approach, however a safer
-            alternative is to use Java API. It provides some degree of compile-time checking of
-            expressions validity. The API is cenetred around ExpressionFactory class, and the
-            Expression class. ExpressionFactory contains a number of rather self-explanatory factory
-            methods. We won&apos;t be going over all of them in detail, but will rather show a few
-            general examples and some gotchas. </para>
-        <para>The following code recreates the expression from the previous chapter, but now using
-            expression
-            API:<programlisting language="java">// String expression: name like &apos;A%&apos; and price &lt; 1000
-Expression e1 = ExpressionFactory.likeExp(Painting.NAME_PROPERTY, &quot;A%&quot;);
-Expression e2 = ExpressionFactory.lessExp(Painting.PRICE_PROPERTY, 1000);
-Expression finalExp = e1.andExp(e2); </programlisting>This
-            is more verbose than creating it from String, but it is also more resilient to the
-            entity properties renaming and precludes semantic errors in the expression String.<note>
+            alternative is to use Java API. It provides compile-time checking of expressions
+            validity. The API in question is provided by <code>ExpressionFactory</code> class (that
+            we've seen already), <code>Property</code> class and <code>Expression</code> class
+            itself. <code>ExpressionFactory</code> contains a number of self-explanatory static
+            methods that can be used to build expressions. E.g.:</para>
+        <para>
+            <programlisting language="java">// String expression: name like &apos;A%&apos; and price &lt; 1000
+Expression e1 = ExpressionFactory.likeExp("name", &quot;A%&quot;);
+Expression e2 = ExpressionFactory.lessExp("price, 1000);
+Expression finalExp = e1.andExp(e2); </programlisting>
+            <note>
                 <para>The last line in the example above shows how to create a new expression by
-                    &quot;chaining&quot; 2 other epxressions. A common error when chaining expressions is to
-                    assume that &quot;andExp&quot; and &quot;orExp&quot; append another expression to the current
-                    expression. In fact a new expression is created. I.e. Expression API treats
-                    existing expressions as immutable.</para>
-            </note></para>
+                    &quot;chaining&quot; two other epxressions. A common error when chaining
+                    expressions is to assume that &quot;andExp&quot; and &quot;orExp&quot; append
+                    another expression to the current expression. In fact a new expression is
+                    created. I.e. Expression API treats existing expressions as immutable.</para>
+            </note>
+        </para>
         <para>As discussed earlier, Cayenne supports aliases in path Expressions, allowing to
             control how SQL joins are generated if the same path is encountered more than once in
             the same Expression. Two ExpressionFactory methods allow to implicitly generate aliases
             to &quot;split&quot; match paths into individual joins if
             needed:<programlisting language="java">Expression matchAllExp(String path, Collection values)
 Expression matchAllExp(String path, Object... values)</programlisting></para>
-        <para>&quot;Path&quot; argument to both of these methods can use a split character (a pipe symbol &apos;|&apos;)
-            instead of dot to indicate that relationship following a path should be split into a
-            separate set of joins, one per collection value. There can only be one split at most in
-            any given path. Split must always precede a relationship. E.g. &quot;|exhibits.paintings&quot;,
-            &quot;exhibits|paintings&quot;, etc. Internally Cayenne would generate distinct aliases for each
-            of the split expressions, forcing separate joins.</para>
+        <para>&quot;Path&quot; argument to both of these methods can use a split character (a pipe
+            symbol &apos;|&apos;) instead of dot to indicate that relationship following a path
+            should be split into a separate set of joins, one per collection value. There can only
+            be one split at most in any given path. Split must always precede a relationship. E.g.
+                <code>"|exhibits.paintings"</code>, <code>"exhibits|paintings"</code>, etc.
+            Internally Cayenne would generate distinct aliases for each of the split expressions,
+            forcing separate joins.</para>
+        <para>While ExpressionFactory is pretty powerful, there's an even easier way to create
+            expression using static Property objects generated by Cayenne for each persistent class.
+            Some
+            examples:<programlisting>// Artist.NAME is generated by Cayenne and has a type of Property&lt;String>
+Expression e1 = Artist.NAME.eq("Pablo");
+
+// Chaining multiple properties into a path..
+// Painting.ARTIST is generated by Cayenne and has a type of Property&lt;Artist>
+Expression e2 = Painting.ARTIST.dot(Artist.NAME).eq("Pablo");</programlisting></para>
+        <para>Property objects provide the API mostly analogius to ExpressionFactory, though it is
+            significantly shorter and is aware of the value types. It provides compile-time checks
+            of both property names and types of arguments in conditions. We will use Property-based
+            API in further examples.</para>
     </section>
     <section xml:id="expressions-in-memory">
         <title>Evaluating Expressions in Memory</title>
@@ -249,18 +262,17 @@ Expression matchAllExp(String path, Object... values)</programlisting></para>
             is done by the database engine. However the same expressions can also be used for
             accessing object properties, calculating values, in-memory filtering. </para>
         <para>Checking whether an object satisfies an
-            expression:<programlisting language="java">Expression e = ExpressionFactory.inExp(User.NAME_PROPERTY, &quot;John&quot;, &quot;Bob&quot;);
-User user = ...
-if(e.match(user)) {
+            expression:<programlisting language="java">Expression e = Artist.NAME.in(&quot;John&quot;, &quot;Bob&quot;);
+Artist artist = ...
+if(e.match(artist)) {
    ...
 }</programlisting>Reading
             property
-            value:<programlisting language="java">Expression e = Expression.fromString(User.NAME_PROPERTY);
-String name = e.evaluate(user);</programlisting></para>
+            value:<programlisting language="java">String name = Artist.NAME.path().evaluate(artist);</programlisting></para>
         <para>Filtering a list of
-            objects:<programlisting language="java">Expression e = ExpressionFactory.inExp(User.NAME_PROPERTY, &quot;John&quot;, &quot;Bob&quot;);
-List&lt;User&gt; unfiltered = ...
-List&lt;User&gt; filtered = e.filterObjects(unfiltered);</programlisting></para>
+            objects:<programlisting language="java">Expression e = Artist.NAME.in(&quot;John&quot;, &quot;Bob&quot;);
+List&lt;Artist&gt; unfiltered = ...
+List&lt;Artist&gt; filtered = e.filterObjects(unfiltered);</programlisting></para>
         <para>
             <note>
                 <para>Current limitation of in-memory expressions is that no collections are
@@ -272,21 +284,18 @@ List&lt;User&gt; filtered = e.filterObjects(unfiltered);</programlisting></para>
     <section xml:id="expressions-to-ejbql">
         <title>Translating Expressions to EJBQL</title>
         <para>
-            <link linkend="ejbqlquery">EJBQL</link> is a textual query language that can be used with Cayenne.
-            In some situations, it is convenient to be able to convert Expression instances into EJBQL.
-            Expressions support this conversion.  An example is shown below.
-
+            <link linkend="ejbqlquery">EJBQL</link> is a textual query language that can be used
+            with Cayenne. In some situations, it is convenient to be able to convert Expression
+            instances into EJBQL. Expressions support this conversion. An example is shown below.
             <programlisting language="java">String serial = ...
-Expression e = ExpressionFactory.matchExp(Pkg.SERIAL_PROPERTY, serial);
+Expression e = Pkg.SERIAL.eq(serial);
 List&lt;Object&gt; params = new ArrayList&lt;Object&gt;();
 EJBQLQuery query = new EJBQLQuery("SELECT p FROM Pkg p WHERE " + e.toEJBQL(params,&quot;p&quot;);
 
 for(int i=0;i&lt;params.size();i++) {
   query.setParameter(i+1, params.get(i));
 }</programlisting>
-
             This would be equivalent to the following purely EJBQL querying logic;
-
             <programlisting language="java">EJBQLQuery query = new EJBQLQuery("SELECT p FROM Pkg p WHERE p.serial = ?1");
 query.setParameter(1,serial);</programlisting>
         </para>


[3/4] cayenne git commit: javadocs

Posted by aa...@apache.org.
javadocs


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/94d4d839
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/94d4d839
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/94d4d839

Branch: refs/heads/master
Commit: 94d4d839ce8b3193970c4f4abd008ceca4040439
Parents: bff0e00
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 10:41:49 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 10:41:49 2016 +0300

----------------------------------------------------------------------
 .../java/org/apache/cayenne/exp/Property.java   | 966 +++++++++----------
 1 file changed, 479 insertions(+), 487 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/94d4d839/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index 2de19ef..5560014 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -32,503 +32,495 @@ import org.apache.cayenne.reflect.PropertyUtils;
  * <p>
  * A property in a DataObject.
  * </p>
- * 
+ * <p>
  * <p>
  * Used to construct Expressions quickly and with type-safety, and to construct
  * Orderings
  * </p>
- * 
+ * <p>
  * <p>
  * Instances of this class are immutable
  * </p>
- * 
- * @param <E>
- *            The type this property returns.
+ *
+ * @param <E> The type this property returns.
  * @since 4.0
  */
 public class Property<E> {
 
-	/**
-	 * Name of the property in the object
-	 */
-	private final String name;
-
-	/**
-	 * Constructs a new property with the given name.
-	 */
-	public Property(String name) {
-		this.name = name;
-	}
-
-	/**
-	 * @return Name of the property in the object.
-	 */
-	public String getName() {
-		return name;
-	}
-
-	@Override
-	public int hashCode() {
-		return getName().hashCode();
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		return obj instanceof Property && ((Property<?>) obj).getName().equals(getName());
-	}
-
-	/**
-	 * @return Constructs a property path by appending the argument to the
-	 *         existing property separated by a dot
-	 */
-	public Property<Object> dot(String property) {
-		return new Property<Object>(getName() + "." + property);
-	}
-
-	/**
-	 * @return Constructs a property path by appending the argument to the
-	 *         existing property separated by a dot
-	 */
-	public <T> Property<T> dot(Property<T> property) {
-		return new Property<T>(getName() + "." + property.getName());
-	}
-
-	/**
-	 * Returns a version of this property that represents an OUTER join. It is
-	 * up to caller to ensure that the property corresponds to a relationship,
-	 * as "outer" attributes make no sense.
-	 */
-	public Property<E> outer() {
-		return isOuter() ? this : new Property<E>(name + "+");
-	}
-
-	private boolean isOuter() {
-		return name.endsWith("+");
-	}
-
-	/**
-	 * @return An expression representing null.
-	 */
-	public Expression isNull() {
-		return ExpressionFactory.matchExp(getName(), null);
-	}
-
-	/**
-	 * @return An expression representing a non-null value.
-	 */
-	public Expression isNotNull() {
-		return ExpressionFactory.matchExp(getName(), null).notExp();
-	}
-
-	/**
-	 * @return An expression representing equality to TRUE.
-	 */
-	public Expression isTrue() {
-		return ExpressionFactory.matchExp(getName(), Boolean.TRUE);
-	}
-
-	/**
-	 * @return An expression representing equality to FALSE.
-	 */
-	public Expression isFalse() {
-		return ExpressionFactory.matchExp(getName(), Boolean.FALSE);
-	}
-
-	/**
-	 * @return An expression representing equality to a value.
-	 */
-	public Expression eq(E value) {
-		return ExpressionFactory.matchExp(getName(), value);
-	}
-
-	/**
-	 * @return An expression representing equality between two attributes
-	 *         (columns).
-	 */
-	public Expression eq(Property<?> value) {
-		return ExpressionFactory.matchExp(getName(), new ASTObjPath(value.getName()));
-	}
-
-	/**
-	 * @return An expression representing inequality to a value.
-	 */
-	public Expression ne(E value) {
-		return ExpressionFactory.noMatchExp(getName(), value);
-	}
-
-	/**
-	 * @return An expression representing inequality between two attributes
-	 *         (columns).
-	 */
-	public Expression ne(Property<?> value) {
-		return ExpressionFactory.noMatchExp(getName(), new ASTObjPath(value.getName()));
-	}
-
-	/**
-	 * @param pattern
-	 *            a pattern matching property value. Pattern may include "_" and
-	 *            "%" wildcard symbols to match any single character or a
-	 *            sequence of characters. To prevent "_" and "%" from being
-	 *            treated as wildcards, they need to be escaped and escape char
-	 *            passed with {@link #like(String, char)} method.
-	 * @return An expression for a Database "LIKE" query.
-	 */
-	public Expression like(String pattern) {
-		return ExpressionFactory.likeExp(getName(), pattern);
-	}
-
-	/**
-	 * @param pattern
-	 *            a properly escaped pattern matching property value. Pattern
-	 *            may include "_" and "%" wildcard symbols to match any single
-	 *            character or a sequence of characters.
-	 * @param escapeChar
-	 *            an escape character used in the pattern to escape "%" and "_".
-	 * 
-	 * @return An expression for a Database "LIKE" query.
-	 */
-	public Expression like(String pattern, char escapeChar) {
-		return ExpressionFactory.likeExp(getName(), pattern, escapeChar);
-	}
-
-	/**
-	 * @return An expression for a case insensitive "LIKE" query.
-	 */
-	public Expression likeIgnoreCase(String pattern) {
-		return ExpressionFactory.likeIgnoreCaseExp(getName(), pattern);
-	}
-
-	/**
-	 * @return An expression for a Database "NOT LIKE" query.
-	 */
-	public Expression nlike(String value) {
-		return ExpressionFactory.notLikeExp(getName(), value);
-	}
-
-	/**
-	 * @return An expression for a case insensitive "NOT LIKE" query.
-	 */
-	public Expression nlikeIgnoreCase(String value) {
-		return ExpressionFactory.notLikeIgnoreCaseExp(getName(), value);
-	}
-
-	/**
-	 * Creates an expression for a database "LIKE" query with the value
-	 * converted to a pattern matching anywhere in the String.
-	 * 
-	 * @param substring
-	 *            a String to match against property value. "_" and "%" symbols
-	 *            are NOT treated as wildcards and are escaped when converted to
-	 *            a LIKE expression.
-	 */
-	public Expression contains(String substring) {
-		return ExpressionFactory.containsExp(getName(), substring);
-	}
-
-	/**
-	 * Creates an expression for a database "LIKE" query with the value
-	 * converted to a pattern matching the beginning of a String.
-	 * 
-	 * @param substring
-	 *            a String to match against property value. "_" and "%" symbols
-	 *            are NOT treated as wildcards and are escaped when converted to
-	 *            a LIKE expression.
-	 */
-	public Expression startsWith(String value) {
-		return ExpressionFactory.startsWithExp(getName(), value);
-	}
-
-	/**
-	 * Creates an expression for a database "LIKE" query with the value
-	 * converted to a pattern matching the tail of a String.
-	 * 
-	 * @param substring
-	 *            a String to match against property value. "_" and "%" symbols
-	 *            are NOT treated as wildcards and are escaped when converted to
-	 *            a LIKE expression.
-	 */
-	public Expression endsWith(String value) {
-		return ExpressionFactory.endsWithExp(getName(), value);
-	}
-
-	/**
-	 * Same as {@link #contains(String)}, only using case-insensitive
-	 * comparison.
-	 */
-	public Expression containsIgnoreCase(String value) {
-		return ExpressionFactory.containsIgnoreCaseExp(getName(), value);
-	}
-
-	/**
-	 * Same as {@link #startsWith(String)}, only using case-insensitive
-	 * comparison.
-	 */
-	public Expression startsWithIgnoreCase(String value) {
-		return ExpressionFactory.startsWithIgnoreCaseExp(getName(), value);
-	}
-
-	/**
-	 * Same as {@link #endsWith(String)}, only using case-insensitive
-	 * comparison.
-	 */
-	public Expression endsWithIgnoreCase(String value) {
-		return ExpressionFactory.endsWithIgnoreCaseExp(getName(), value);
-	}
-
-	/**
-	 * @return An expression checking for objects between a lower and upper
-	 *         bound inclusive
-	 * 
-	 * @param lower
-	 *            The lower bound.
-	 * @param upper
-	 *            The upper bound.
-	 */
-	public Expression between(E lower, E upper) {
-		return ExpressionFactory.betweenExp(getName(), lower, upper);
-	}
-
-	/**
-	 * @return An expression for finding objects with values in the given set.
-	 */
-	public Expression in(E firstValue, E... moreValues) {
-
-		int moreValuesLength = moreValues != null ? moreValues.length : 0;
-
-		Object[] values = new Object[moreValuesLength + 1];
-		values[0] = firstValue;
-
-		if (moreValuesLength > 0) {
-			System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
-		}
-
-		return ExpressionFactory.inExp(getName(), values);
-	}
-
-	/**
-	 * @return An expression for finding objects with values not in the given
-	 *         set.
-	 */
-	public Expression nin(E firstValue, E... moreValues) {
-
-		int moreValuesLength = moreValues != null ? moreValues.length : 0;
-
-		Object[] values = new Object[moreValuesLength + 1];
-		values[0] = firstValue;
-
-		if (moreValuesLength > 0) {
-			System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
-		}
-
-		return ExpressionFactory.notInExp(getName(), values);
-	}
-
-	/**
-	 * @return An expression for finding objects with values in the given set.
-	 */
-	public Expression in(Collection<E> values) {
-		return ExpressionFactory.inExp(getName(), values);
-	}
-
-	/**
-	 * @return An expression for finding objects with values not in the given
-	 *         set.
-	 */
-	public Expression nin(Collection<E> values) {
-		return ExpressionFactory.notInExp(getName(), values);
-	}
-
-	/**
-	 * @return A greater than Expression.
-	 */
-	public Expression gt(E value) {
-		return ExpressionFactory.greaterExp(getName(), value);
-	}
-
-	/**
-	 * @return Represents a greater than relationship between two attributes
-	 *         (columns).
-	 */
-	public Expression gt(Property<?> value) {
-		return ExpressionFactory.greaterExp(getName(), new ASTObjPath(value.getName()));
-	}
-
-	/**
-	 * @return A greater than or equal to Expression.
-	 */
-	public Expression gte(E value) {
-		return ExpressionFactory.greaterOrEqualExp(getName(), value);
-	}
-
-	/**
-	 * @return Represents a greater than or equal relationship between two
-	 *         attributes (columns).
-	 */
-	public Expression gte(Property<?> value) {
-		return ExpressionFactory.greaterOrEqualExp(getName(), new ASTObjPath(value.getName()));
-	}
-
-	/**
-	 * @return A less than Expression.
-	 */
-	public Expression lt(E value) {
-		return ExpressionFactory.lessExp(getName(), value);
-	}
-
-	/**
-	 * @return Represents a less than relationship between two attributes
-	 *         (columns).
-	 */
-	public Expression lt(Property<?> value) {
-		return ExpressionFactory.lessExp(getName(), new ASTObjPath(value.getName()));
-	}
-
-	/**
-	 * @return A less than or equal to Expression.
-	 */
-	public Expression lte(E value) {
-		return ExpressionFactory.lessOrEqualExp(getName(), value);
-	}
-
-	/**
-	 * @return Represents a less than or equal relationship between two
-	 *         attributes (columns).
-	 */
-	public Expression lte(Property<?> value) {
-		return ExpressionFactory.lessOrEqualExp(getName(), new ASTObjPath(value.getName()));
-	}
-
-	/**
-	 * @return Ascending sort orderings on this property.
-	 */
-	public Ordering asc() {
-		return new Ordering(getName(), SortOrder.ASCENDING);
-	}
-
-	/**
-	 * @return Ascending sort orderings on this property.
-	 */
-	public List<Ordering> ascs() {
-		List<Ordering> result = new ArrayList<Ordering>(1);
-		result.add(asc());
-		return result;
-	}
-
-	/**
-	 * @return Ascending case insensitive sort orderings on this property.
-	 */
-	public Ordering ascInsensitive() {
-		return new Ordering(getName(), SortOrder.ASCENDING_INSENSITIVE);
-	}
-
-	/**
-	 * @return Ascending case insensitive sort orderings on this property.
-	 */
-	public List<Ordering> ascInsensitives() {
-		List<Ordering> result = new ArrayList<Ordering>(1);
-		result.add(ascInsensitive());
-		return result;
-	}
-
-	/**
-	 * @return Descending sort orderings on this property.
-	 */
-	public Ordering desc() {
-		return new Ordering(getName(), SortOrder.DESCENDING);
-	}
-
-	/**
-	 * @return Descending sort orderings on this property.
-	 */
-	public List<Ordering> descs() {
-		List<Ordering> result = new ArrayList<Ordering>(1);
-		result.add(desc());
-		return result;
-	}
-
-	/**
-	 * @return Descending case insensitive sort orderings on this property.
-	 */
-	public Ordering descInsensitive() {
-		return new Ordering(getName(), SortOrder.DESCENDING_INSENSITIVE);
-	}
-
-	/**
-	 * @return Descending case insensitive sort orderings on this property.
-	 */
-	public List<Ordering> descInsensitives() {
-		List<Ordering> result = new ArrayList<Ordering>(1);
-		result.add(descInsensitive());
-		return result;
-	}
-
-	/**
-	 * Returns a prefetch tree that follows this property path, potentially
-	 * spanning a number of phantom nodes, and having a single leaf with "joint"
-	 * prefetch semantics.
-	 */
-	public PrefetchTreeNode joint() {
-		return PrefetchTreeNode.withPath(name, PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
-	}
-
-	/**
-	 * Returns a prefetch tree that follows this property path, potentially
-	 * spanning a number of phantom nodes, and having a single leaf with
-	 * "disjoint" prefetch semantics.
-	 */
-	public PrefetchTreeNode disjoint() {
-		return PrefetchTreeNode.withPath(name, PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
-	}
-
-	/**
-	 * Returns a prefetch tree that follows this property path, potentially
-	 * spanning a number of phantom nodes, and having a single leaf with
-	 * "disjoint by id" prefetch semantics.
-	 */
-	public PrefetchTreeNode disjointById() {
-		return PrefetchTreeNode.withPath(name, PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
-	}
-
-	/**
-	 * Extracts property value from an object using JavaBean-compatible
-	 * introspection with one addition - a property can be a dot-separated
-	 * property name path.
-	 */
-	@SuppressWarnings("unchecked")
-	public E getFrom(Object bean) {
-		return (E) PropertyUtils.getProperty(bean, getName());
-	}
-
-	/**
-	 * Extracts property value from a collection of objects using
-	 * JavaBean-compatible introspection with one addition - a property can be a
-	 * dot-separated property name path.
-	 */
-	public List<E> getFromAll(Collection<?> beans) {
-		List<E> result = new ArrayList<E>(beans.size());
-		for (Object bean : beans) {
-			result.add(getFrom(bean));
-		}
-		return result;
-	}
-
-	/**
-	 * Sets a property value in 'obj' using JavaBean-compatible introspection
-	 * with one addition - a property can be a dot-separated property name path.
-	 */
-	public void setIn(Object bean, E value) {
-		PropertyUtils.setProperty(bean, getName(), value);
-	}
-
-	/**
-	 * Sets a property value in a collection of objects using
-	 * JavaBean-compatible introspection with one addition - a property can be a
-	 * dot-separated property name path.
-	 */
-	public void setInAll(Collection<?> beans, E value) {
-		for (Object bean : beans) {
-			setIn(bean, value);
-		}
-	}
+    /**
+     * Name of the property in the object
+     */
+    private final String name;
+
+    /**
+     * Constructs a new property with the given name.
+     */
+    public Property(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return Name of the property in the object.
+     */
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public int hashCode() {
+        return getName().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj instanceof Property && ((Property<?>) obj).getName().equals(getName());
+    }
+
+    /**
+     * @return Constructs a property path by appending the argument to the
+     * existing property separated by a dot
+     */
+    public Property<Object> dot(String property) {
+        return new Property<Object>(getName() + "." + property);
+    }
+
+    /**
+     * @return Constructs a property path by appending the argument to the
+     * existing property separated by a dot
+     */
+    public <T> Property<T> dot(Property<T> property) {
+        return new Property<T>(getName() + "." + property.getName());
+    }
+
+    /**
+     * Returns a version of this property that represents an OUTER join. It is
+     * up to caller to ensure that the property corresponds to a relationship,
+     * as "outer" attributes make no sense.
+     */
+    public Property<E> outer() {
+        return isOuter() ? this : new Property<E>(name + "+");
+    }
+
+    private boolean isOuter() {
+        return name.endsWith("+");
+    }
+
+    /**
+     * @return An expression representing null.
+     */
+    public Expression isNull() {
+        return ExpressionFactory.matchExp(getName(), null);
+    }
+
+    /**
+     * @return An expression representing a non-null value.
+     */
+    public Expression isNotNull() {
+        return ExpressionFactory.matchExp(getName(), null).notExp();
+    }
+
+    /**
+     * @return An expression representing equality to TRUE.
+     */
+    public Expression isTrue() {
+        return ExpressionFactory.matchExp(getName(), Boolean.TRUE);
+    }
+
+    /**
+     * @return An expression representing equality to FALSE.
+     */
+    public Expression isFalse() {
+        return ExpressionFactory.matchExp(getName(), Boolean.FALSE);
+    }
+
+    /**
+     * @return An expression representing equality to a value.
+     */
+    public Expression eq(E value) {
+        return ExpressionFactory.matchExp(getName(), value);
+    }
+
+    /**
+     * @return An expression representing equality between two attributes
+     * (columns).
+     */
+    public Expression eq(Property<?> value) {
+        return ExpressionFactory.matchExp(getName(), new ASTObjPath(value.getName()));
+    }
+
+    /**
+     * @return An expression representing inequality to a value.
+     */
+    public Expression ne(E value) {
+        return ExpressionFactory.noMatchExp(getName(), value);
+    }
+
+    /**
+     * @return An expression representing inequality between two attributes
+     * (columns).
+     */
+    public Expression ne(Property<?> value) {
+        return ExpressionFactory.noMatchExp(getName(), new ASTObjPath(value.getName()));
+    }
+
+    /**
+     * @param pattern a pattern matching property value. Pattern may include "_" and
+     *                "%" wildcard symbols to match any single character or a
+     *                sequence of characters. To prevent "_" and "%" from being
+     *                treated as wildcards, they need to be escaped and escape char
+     *                passed with {@link #like(String, char)} method.
+     * @return An expression for a Database "LIKE" query.
+     */
+    public Expression like(String pattern) {
+        return ExpressionFactory.likeExp(getName(), pattern);
+    }
+
+    /**
+     * @param pattern    a properly escaped pattern matching property value. Pattern
+     *                   may include "_" and "%" wildcard symbols to match any single
+     *                   character or a sequence of characters.
+     * @param escapeChar an escape character used in the pattern to escape "%" and "_".
+     * @return An expression for a Database "LIKE" query.
+     */
+    public Expression like(String pattern, char escapeChar) {
+        return ExpressionFactory.likeExp(getName(), pattern, escapeChar);
+    }
+
+    /**
+     * @return An expression for a case insensitive "LIKE" query.
+     */
+    public Expression likeIgnoreCase(String pattern) {
+        return ExpressionFactory.likeIgnoreCaseExp(getName(), pattern);
+    }
+
+    /**
+     * @return An expression for a Database "NOT LIKE" query.
+     */
+    public Expression nlike(String value) {
+        return ExpressionFactory.notLikeExp(getName(), value);
+    }
+
+    /**
+     * @return An expression for a case insensitive "NOT LIKE" query.
+     */
+    public Expression nlikeIgnoreCase(String value) {
+        return ExpressionFactory.notLikeIgnoreCaseExp(getName(), value);
+    }
+
+    /**
+     * Creates an expression for a database "LIKE" query with the value converted to a pattern matching anywhere in the
+     * String.
+     *
+     * @param substring a String to match against property value. "_" and "%" symbols
+     *                  are NOT treated as wildcards and are escaped when converted to
+     *                  a LIKE expression.
+     * @return a newly created expression.
+     */
+    public Expression contains(String substring) {
+        return ExpressionFactory.containsExp(getName(), substring);
+    }
+
+    /**
+     * Creates an expression for a database "LIKE" query with the value converted to a pattern matching the beginning of
+     * a String.
+     *
+     * @param value a String to match against property value. "_" and "%" symbols
+     *              are NOT treated as wildcards and are escaped when converted to
+     *              a LIKE expression.
+     * @return a newly created expression.
+     */
+    public Expression startsWith(String value) {
+        return ExpressionFactory.startsWithExp(getName(), value);
+    }
+
+    /**
+     * Creates an expression for a database "LIKE" query with the value
+     * converted to a pattern matching the tail of a String.
+     *
+     * @param value a String to match against property value. "_" and "%" symbols
+     *              are NOT treated as wildcards and are escaped when converted to
+     *              a LIKE expression.
+     * @return a newly created expression.
+     */
+    public Expression endsWith(String value) {
+        return ExpressionFactory.endsWithExp(getName(), value);
+    }
+
+    /**
+     * Same as {@link #contains(String)}, only using case-insensitive
+     * comparison.
+     */
+    public Expression containsIgnoreCase(String value) {
+        return ExpressionFactory.containsIgnoreCaseExp(getName(), value);
+    }
+
+    /**
+     * Same as {@link #startsWith(String)}, only using case-insensitive
+     * comparison.
+     */
+    public Expression startsWithIgnoreCase(String value) {
+        return ExpressionFactory.startsWithIgnoreCaseExp(getName(), value);
+    }
+
+    /**
+     * Same as {@link #endsWith(String)}, only using case-insensitive
+     * comparison.
+     */
+    public Expression endsWithIgnoreCase(String value) {
+        return ExpressionFactory.endsWithIgnoreCaseExp(getName(), value);
+    }
+
+    /**
+     * @param lower The lower bound.
+     * @param upper The upper bound.
+     * @return An expression checking for objects between a lower and upper
+     * bound inclusive
+     */
+    public Expression between(E lower, E upper) {
+        return ExpressionFactory.betweenExp(getName(), lower, upper);
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    public Expression in(E firstValue, E... moreValues) {
+
+        int moreValuesLength = moreValues != null ? moreValues.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstValue;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.inExp(getName(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given
+     * set.
+     */
+    public Expression nin(E firstValue, E... moreValues) {
+
+        int moreValuesLength = moreValues != null ? moreValues.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstValue;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.notInExp(getName(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    public Expression in(Collection<E> values) {
+        return ExpressionFactory.inExp(getName(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given
+     * set.
+     */
+    public Expression nin(Collection<E> values) {
+        return ExpressionFactory.notInExp(getName(), values);
+    }
+
+    /**
+     * @return A greater than Expression.
+     */
+    public Expression gt(E value) {
+        return ExpressionFactory.greaterExp(getName(), value);
+    }
+
+    /**
+     * @return Represents a greater than relationship between two attributes
+     * (columns).
+     */
+    public Expression gt(Property<?> value) {
+        return ExpressionFactory.greaterExp(getName(), new ASTObjPath(value.getName()));
+    }
+
+    /**
+     * @return A greater than or equal to Expression.
+     */
+    public Expression gte(E value) {
+        return ExpressionFactory.greaterOrEqualExp(getName(), value);
+    }
+
+    /**
+     * @return Represents a greater than or equal relationship between two
+     * attributes (columns).
+     */
+    public Expression gte(Property<?> value) {
+        return ExpressionFactory.greaterOrEqualExp(getName(), new ASTObjPath(value.getName()));
+    }
+
+    /**
+     * @return A less than Expression.
+     */
+    public Expression lt(E value) {
+        return ExpressionFactory.lessExp(getName(), value);
+    }
+
+    /**
+     * @return Represents a less than relationship between two attributes
+     * (columns).
+     */
+    public Expression lt(Property<?> value) {
+        return ExpressionFactory.lessExp(getName(), new ASTObjPath(value.getName()));
+    }
+
+    /**
+     * @return A less than or equal to Expression.
+     */
+    public Expression lte(E value) {
+        return ExpressionFactory.lessOrEqualExp(getName(), value);
+    }
+
+    /**
+     * @return Represents a less than or equal relationship between two
+     * attributes (columns).
+     */
+    public Expression lte(Property<?> value) {
+        return ExpressionFactory.lessOrEqualExp(getName(), new ASTObjPath(value.getName()));
+    }
+
+    /**
+     * @return Ascending sort orderings on this property.
+     */
+    public Ordering asc() {
+        return new Ordering(getName(), SortOrder.ASCENDING);
+    }
+
+    /**
+     * @return Ascending sort orderings on this property.
+     */
+    public List<Ordering> ascs() {
+        List<Ordering> result = new ArrayList<Ordering>(1);
+        result.add(asc());
+        return result;
+    }
+
+    /**
+     * @return Ascending case insensitive sort orderings on this property.
+     */
+    public Ordering ascInsensitive() {
+        return new Ordering(getName(), SortOrder.ASCENDING_INSENSITIVE);
+    }
+
+    /**
+     * @return Ascending case insensitive sort orderings on this property.
+     */
+    public List<Ordering> ascInsensitives() {
+        List<Ordering> result = new ArrayList<Ordering>(1);
+        result.add(ascInsensitive());
+        return result;
+    }
+
+    /**
+     * @return Descending sort orderings on this property.
+     */
+    public Ordering desc() {
+        return new Ordering(getName(), SortOrder.DESCENDING);
+    }
+
+    /**
+     * @return Descending sort orderings on this property.
+     */
+    public List<Ordering> descs() {
+        List<Ordering> result = new ArrayList<Ordering>(1);
+        result.add(desc());
+        return result;
+    }
+
+    /**
+     * @return Descending case insensitive sort orderings on this property.
+     */
+    public Ordering descInsensitive() {
+        return new Ordering(getName(), SortOrder.DESCENDING_INSENSITIVE);
+    }
+
+    /**
+     * @return Descending case insensitive sort orderings on this property.
+     */
+    public List<Ordering> descInsensitives() {
+        List<Ordering> result = new ArrayList<Ordering>(1);
+        result.add(descInsensitive());
+        return result;
+    }
+
+    /**
+     * Returns a prefetch tree that follows this property path, potentially
+     * spanning a number of phantom nodes, and having a single leaf with "joint"
+     * prefetch semantics.
+     */
+    public PrefetchTreeNode joint() {
+        return PrefetchTreeNode.withPath(name, PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+    }
+
+    /**
+     * Returns a prefetch tree that follows this property path, potentially
+     * spanning a number of phantom nodes, and having a single leaf with
+     * "disjoint" prefetch semantics.
+     */
+    public PrefetchTreeNode disjoint() {
+        return PrefetchTreeNode.withPath(name, PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+    }
+
+    /**
+     * Returns a prefetch tree that follows this property path, potentially
+     * spanning a number of phantom nodes, and having a single leaf with
+     * "disjoint by id" prefetch semantics.
+     */
+    public PrefetchTreeNode disjointById() {
+        return PrefetchTreeNode.withPath(name, PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
+    }
+
+    /**
+     * Extracts property value from an object using JavaBean-compatible
+     * introspection with one addition - a property can be a dot-separated
+     * property name path.
+     */
+    @SuppressWarnings("unchecked")
+    public E getFrom(Object bean) {
+        return (E) PropertyUtils.getProperty(bean, getName());
+    }
+
+    /**
+     * Extracts property value from a collection of objects using
+     * JavaBean-compatible introspection with one addition - a property can be a
+     * dot-separated property name path.
+     */
+    public List<E> getFromAll(Collection<?> beans) {
+        List<E> result = new ArrayList<E>(beans.size());
+        for (Object bean : beans) {
+            result.add(getFrom(bean));
+        }
+        return result;
+    }
+
+    /**
+     * Sets a property value in 'obj' using JavaBean-compatible introspection
+     * with one addition - a property can be a dot-separated property name path.
+     */
+    public void setIn(Object bean, E value) {
+        PropertyUtils.setProperty(bean, getName(), value);
+    }
+
+    /**
+     * Sets a property value in a collection of objects using
+     * JavaBean-compatible introspection with one addition - a property can be a
+     * dot-separated property name path.
+     */
+    public void setInAll(Collection<?> beans, E value) {
+        for (Object bean : beans) {
+            setIn(bean, value);
+        }
+    }
 
 }
\ No newline at end of file


[2/4] cayenne git commit: clarifying exp docs; cleaning up exp unit tests

Posted by aa...@apache.org.
clarifying exp docs; cleaning up exp unit tests


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/bff0e002
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/bff0e002
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/bff0e002

Branch: refs/heads/master
Commit: bff0e0026706ba319a51b7b3b3685d25d3b94ae9
Parents: 242cf25
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Sun Dec 11 10:24:48 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Sun Dec 11 10:37:57 2016 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/query/SelectQueryIT.java | 212 +++++++++++++------
 .../cayenne-guide/src/docbkx/expressions.xml    |  12 +-
 2 files changed, 149 insertions(+), 75 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/bff0e002/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryIT.java
index b526b3b..30c9f41 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryIT.java
@@ -114,7 +114,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testFetchLimit() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.setFetchLimit(7);
 
 		List<?> objects = context.performQuery(query);
@@ -127,9 +127,9 @@ public class SelectQueryIT extends ServerCase {
 
 		createArtistsDataSet();
 
-		int totalRows = context.select(new SelectQuery<Artist>(Artist.class)).size();
+		int totalRows = new SelectQuery<>(Artist.class).select(context).size();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.addOrdering("db:" + Artist.ARTIST_ID_PK_COLUMN, SortOrder.ASCENDING);
 		query.setFetchOffset(5);
 		List<Artist> results = context.select(query);
@@ -144,7 +144,7 @@ public class SelectQueryIT extends ServerCase {
 		createArtistsDataSet();
 		DbEntity artistDbEntity = context.getEntityResolver().getDbEntity("ARTIST");
 
-		SelectQuery<DataRow> query = new SelectQuery<DataRow>(artistDbEntity);
+		SelectQuery<DataRow> query = new SelectQuery<>(artistDbEntity);
 		List<DataRow> results = context.select(query);
 
 		assertEquals(20, results.size());
@@ -154,7 +154,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testFetchLimitWithOffset() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.addOrdering("db:" + Artist.ARTIST_ID_PK_COLUMN, SortOrder.ASCENDING);
 		query.setFetchOffset(15);
 		query.setFetchLimit(4);
@@ -167,16 +167,16 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testFetchOffsetWithQualifier() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.setQualifier(ExpressionFactory.exp("db:ARTIST_ID > 3"));
 		query.setFetchOffset(5);
 
-		List<?> objects = context.performQuery(query);
+		List<Artist> objects = query.select(context);
 		int size = objects.size();
 
-		SelectQuery<Artist> sizeQ = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> sizeQ = new SelectQuery<>(Artist.class);
 		sizeQ.setQualifier(ExpressionFactory.exp("db:ARTIST_ID > 3"));
-		List<?> objects1 = context.performQuery(sizeQ);
+		List<Artist> objects1 = sizeQ.select(context);
 		int sizeAll = objects1.size();
 		assertEquals(size, sizeAll - 5);
 	}
@@ -184,17 +184,17 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testFetchLimitWithQualifier() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.setQualifier(ExpressionFactory.exp("db:ARTIST_ID > 3"));
 		query.setFetchLimit(7);
-		List<?> objects = context.performQuery(query);
+		List<Artist> objects = query.select(context);
 		assertEquals(7, objects.size());
 	}
 
 	@Test
 	public void testSelectAllObjectsRootEntityName() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>("Artist");
+		SelectQuery<Artist> query = new SelectQuery<>("Artist");
 		List<?> objects = context.performQuery(query);
 		assertEquals(20, objects.size());
 	}
@@ -202,7 +202,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectAllObjectsRootClass() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		List<?> objects = context.performQuery(query);
 		assertEquals(20, objects.size());
 	}
@@ -211,7 +211,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testSelectAllObjectsRootObjEntity() throws Exception {
 		createArtistsDataSet();
 		ObjEntity artistEntity = context.getEntityResolver().getObjEntity(Artist.class);
-		SelectQuery<Artist> query = new SelectQuery<Artist>(artistEntity);
+		SelectQuery<Artist> query = new SelectQuery<>(artistEntity);
 
 		List<?> objects = context.performQuery(query);
 		assertEquals(20, objects.size());
@@ -220,7 +220,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectLikeExactMatch() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.likeExp("artistName", "artist1");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -230,7 +230,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectNotLikeSingleWildcardMatch() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.notLikeExp("artistName", "artist11%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -240,7 +240,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectNotLikeIgnoreCaseSingleWildcardMatch() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.notLikeIgnoreCaseExp("artistName", "aRtIsT11%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -254,7 +254,7 @@ public class SelectQueryIT extends ServerCase {
 		}
 
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.likeExp("artistName", "aRtIsT%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -264,7 +264,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectLikeSingle_WildcardMatch() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.likeExp("artistName", "artist11%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -276,7 +276,7 @@ public class SelectQueryIT extends ServerCase {
 
 		createArtistsWildcardDataSet();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.andQualifier(ExpressionFactory.likeExp("artistName", "=_%", '='));
 
 		List<?> objects = context.performQuery(query);
@@ -290,7 +290,7 @@ public class SelectQueryIT extends ServerCase {
 
 		// CAY-1978 - combining LIKE..ESCAPE with another clause generated bad
 		// syntax
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.andQualifier(ExpressionFactory.likeExp("artistName", "=_%", '='));
 		query.andQualifier(Artist.ARTIST_NAME.eq("_X"));
 
@@ -305,7 +305,7 @@ public class SelectQueryIT extends ServerCase {
 
 		// CAY-1978 - combining LIKE..ESCAPE with another clause generated bad
 		// SQL
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.andQualifier(ExpressionFactory.likeIgnoreCaseExp("artistName", "=_%", '='));
 		query.andQualifier(Artist.ARTIST_NAME.eq("_X"));
 
@@ -319,7 +319,7 @@ public class SelectQueryIT extends ServerCase {
 		tArtist.insert(1, "_X_", null);
 		tArtist.insert(2, "_X", null);
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.andQualifier(ExpressionFactory.likeExp("artistName", "#_%#_", '#'));
 		query.andQualifier(Artist.ARTIST_NAME.eq("_X_"));
 
@@ -330,7 +330,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectLikeMultiple_WildcardMatch() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.likeExp("artistName", "artist1%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -343,7 +343,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectLikeIgnoreCaseObjects1() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.likeIgnoreCaseExp("artistName", "ARTIST%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -354,7 +354,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectLikeIgnoreCaseObjects2() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.likeIgnoreCaseExp("artistName", "artist%");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -364,7 +364,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectIn() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.exp("artistName in ('artist1', 'artist2')");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -374,7 +374,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectParameterizedIn() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.exp("artistName in $list");
 		query.setQualifier(qual);
 		query = query.queryWithParameters(Collections.singletonMap("list", new Object[] { "artist1", "artist2" }));
@@ -385,7 +385,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectParameterizedEmptyIn() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.exp("artistName in $list");
 		query.setQualifier(qual);
 		query = query.queryWithParameters(Collections.singletonMap("list", new Object[] {}));
@@ -396,7 +396,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectParameterizedEmptyNotIn() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.exp("artistName not in $list");
 		query.setQualifier(qual);
 		query = query.queryWithParameters(Collections.singletonMap("list", new Object[] {}));
@@ -407,7 +407,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectEmptyIn() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.inExp("artistName");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -417,7 +417,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectEmptyNotIn() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.notInExp("artistName");
 		query.setQualifier(qual);
 		List<?> objects = context.performQuery(query);
@@ -427,7 +427,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectBooleanTrue() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.expTrue();
 		qual = qual.andExp(ExpressionFactory.matchExp("artistName", "artist1"));
 		query.setQualifier(qual);
@@ -438,7 +438,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectBooleanNotTrueOr() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.expTrue();
 		qual = qual.notExp();
 		qual = qual.orExp(ExpressionFactory.matchExp("artistName", "artist1"));
@@ -450,7 +450,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectBooleanFalse() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.expFalse();
 		qual = qual.andExp(ExpressionFactory.matchExp("artistName", "artist1"));
 		query.setQualifier(qual);
@@ -461,7 +461,7 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testSelectBooleanFalseOr() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.expFalse();
 		qual = qual.orExp(ExpressionFactory.matchExp("artistName", "artist1"));
 		query.setQualifier(qual);
@@ -473,7 +473,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testSelect() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		List<?> objects = query.select(context);
 		assertEquals(20, objects.size());
 	}
@@ -482,7 +482,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testSelectOne() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		Expression qual = ExpressionFactory.matchExp("artistName", "artist1");
 		query.setQualifier(qual);
 
@@ -494,9 +494,9 @@ public class SelectQueryIT extends ServerCase {
 	public void testSelectFirst() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.addOrdering(new Ordering(Artist.ARTIST_NAME.getName()));
-		Artist artist = (Artist) query.selectFirst(context);
+		Artist artist = query.selectFirst(context);
 
 		assertNotNull(artist);
 		assertEquals("artist1", artist.getArtistName());
@@ -506,9 +506,9 @@ public class SelectQueryIT extends ServerCase {
 	public void testSelectFirstByContext() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.addOrdering(new Ordering(Artist.ARTIST_NAME.getName()));
-		Artist artist = (Artist) context.selectFirst(query);
+		Artist artist = context.selectFirst(query);
 
 		assertNotNull(artist);
 		assertEquals("artist1", artist.getArtistName());
@@ -518,7 +518,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testIterate() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> q1 = new SelectQuery<>(Artist.class);
 		final int[] count = new int[1];
 		q1.iterate(context, new ResultIteratorCallback<Artist>() {
 
@@ -536,7 +536,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testIterator() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> q1 = new SelectQuery<>(Artist.class);
 
 		try (ResultIterator<Artist> it = q1.iterator(context);) {
 			int count = 0;
@@ -554,7 +554,7 @@ public class SelectQueryIT extends ServerCase {
 	public void testBatchIterator() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> q1 = new SelectQuery<>(Artist.class);
 
 		try (ResultBatchIterator<Artist> it = q1.batchIterator(context, 5);) {
 			int count = 0;
@@ -577,7 +577,7 @@ public class SelectQueryIT extends ServerCase {
 		EntityResolver resolver = context.getEntityResolver();
 		MockQueryRouter router = new MockQueryRouter();
 
-		SelectQuery<Artist> q = new SelectQuery<Artist>(Artist.class, ExpressionFactory.matchExp("artistName", "a"));
+		SelectQuery<Artist> q = new SelectQuery<>(Artist.class, ExpressionFactory.matchExp("artistName", "a"));
 
 		q.route(router, resolver, null);
 		assertEquals(1, router.getQueryCount());
@@ -628,7 +628,7 @@ public class SelectQueryIT extends ServerCase {
 		exhibitEntity.removeRelationship("artistExhibitArray");
 
 		Expression e = ExpressionFactory.matchExp("artistName", "artist1");
-		SelectQuery<Artist> q = new SelectQuery<Artist>(Artist.class, e);
+		SelectQuery<Artist> q = new SelectQuery<>(Artist.class, e);
 		q.addPrefetch("paintingArray");
 		q.addPrefetch("paintingArray.toGallery");
 		q.addPrefetch("artistExhibitArray.toExhibit");
@@ -657,7 +657,7 @@ public class SelectQueryIT extends ServerCase {
 		// at least makes sense)
 		Expression exp = ExpressionFactory.noMatchExp("toArtist", new Object());
 
-		SelectQuery<Painting> q = new SelectQuery<Painting>(Painting.class, exp);
+		SelectQuery<Painting> q = new SelectQuery<>(Painting.class, exp);
 		q.addPrefetch("toArtist");
 
 		// test how prefetches are resolved in this case - this was a stumbling
@@ -672,23 +672,27 @@ public class SelectQueryIT extends ServerCase {
 	@Test
 	public void testLeftJoinAndPrefetchToMany() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class, ExpressionFactory.matchExp(
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class, ExpressionFactory.matchExp(
 				"paintingArray+.toGallery", null));
 		query.addPrefetch("artistExhibitArray");
-		context.performQuery(query);
+		context.select(query);
+
+		// TODO: assertions?
 	}
 
 	@Test
 	public void testLeftJoinAndPrefetchToOne() throws Exception {
 		createArtistsDataSet();
-		SelectQuery<Painting> query = new SelectQuery<Painting>(Painting.class, ExpressionFactory.matchExp(
+		SelectQuery<Painting> query = new SelectQuery<>(Painting.class, ExpressionFactory.matchExp(
 				"toArtist+.artistName", null));
 		query.addPrefetch("toGallery");
 		context.select(query);
+
+		// TODO: assertions?
 	}
 
 	@Test
-	public void testSelect_MatchObject() {
+	public void testMatchObject() {
 
 		Artist a1 = context.newObject(Artist.class);
 		a1.setArtistName("a1");
@@ -698,24 +702,92 @@ public class SelectQueryIT extends ServerCase {
 		a3.setArtistName("a3");
 		context.commitChanges();
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class);
-
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.setQualifier(ExpressionFactory.matchExp(a2));
-		Object res = Cayenne.objectForQuery(context, query);// exception if >1
-															// result
-		assertSame(res, a2);
-		assertTrue(query.getQualifier().match(res));
+		Artist result = query.selectOne(context);
+
+		assertSame(a2, result);
+	}
+
+	@Test
+	public void testMatchObjects() {
+
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("a1");
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("a2");
+		Artist a3 = context.newObject(Artist.class);
+		a3.setArtistName("a3");
+		context.commitChanges();
 
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class);
 		query.setQualifier(ExpressionFactory.matchAnyExp(a1, a3));
-		query.addOrdering("artistName", SortOrder.ASCENDING);
-		List<Artist> list = context.select(query);
+		query.addOrdering(Artist.ARTIST_NAME.asc());
+		List<Artist> list = query.select(context);
+
 		assertEquals(list.size(), 2);
-		assertSame(list.get(0), a1);
-		assertSame(list.get(1), a3);
-		assertTrue(query.getQualifier().match(a1));
-		assertTrue(query.getQualifier().match(a3));
+		assertSame(a1, list.get(0));
+		assertSame(a3, list.get(1));
+	}
+
+	@Test
+	public void testMatchByRelatedObject() {
+
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("a1");
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("a2");
+		Painting p1 = context.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		p1.setToArtist(a1);
+		Painting p2 = context.newObject(Painting.class);
+		p2.setPaintingTitle("p2");
+		p2.setToArtist(a2);
+		context.commitChanges();
+
+		SelectQuery<Painting> query = new SelectQuery<>(Painting.class);
+		query.setQualifier(ExpressionFactory.matchExp("toArtist", a1));
+		assertSame(p1, query.selectOne(context));
+	}
+
+	@Test
+	public void testMatchByRelatedObjectId() {
+
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("a1");
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("a2");
+		Painting p1 = context.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		p1.setToArtist(a1);
+		Painting p2 = context.newObject(Painting.class);
+		p2.setPaintingTitle("p2");
+		p2.setToArtist(a2);
+		context.commitChanges();
+
+		SelectQuery<Painting> query = new SelectQuery<>(Painting.class);
+		query.setQualifier(ExpressionFactory.matchExp("toArtist", a1.getObjectId()));
+		assertSame(p1, query.selectOne(context));
+	}
+
+	@Test
+	public void testMatchByRelatedObjectIdValue() {
+
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("a1");
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("a2");
+		Painting p1 = context.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		p1.setToArtist(a1);
+		Painting p2 = context.newObject(Painting.class);
+		p2.setPaintingTitle("p2");
+		p2.setToArtist(a2);
+		context.commitChanges();
 
-		assertEquals(query.getQualifier(), ExpressionFactory.matchAnyExp(Arrays.asList(a1, a3)));
+		SelectQuery<Painting> query = new SelectQuery<>(Painting.class);
+		query.setQualifier(ExpressionFactory.matchExp("toArtist", Cayenne.longPKForObject(a1)));
+		assertSame(p1, query.selectOne(context));
 	}
 
 	@Test
@@ -730,7 +802,7 @@ public class SelectQueryIT extends ServerCase {
 		context.commitChanges();
 
 		List<Ordering> orderings = Arrays.asList(new Ordering("artistName", SortOrder.ASCENDING));
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class, null, orderings);
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class, null, orderings);
 
 		List<Artist> list = context.select(query);
 		assertEquals(list.size(), 3);
@@ -752,7 +824,7 @@ public class SelectQueryIT extends ServerCase {
 			numbers[i] = "" + i;
 		}
 
-		SelectQuery<Artist> query = new SelectQuery<Artist>(Artist.class,
+		SelectQuery<Artist> query = new SelectQuery<>(Artist.class,
 				ExpressionFactory.inExp("artistName", numbers));
 		context.performQuery(query);
 	}
@@ -761,19 +833,19 @@ public class SelectQueryIT extends ServerCase {
 	public void testCacheOffsetAndLimit() throws Exception {
 		createArtistsDataSet();
 
-		SelectQuery<Artist> query1 = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query1 = new SelectQuery<>(Artist.class);
 		query1.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
 		query1.setFetchOffset(0);
 		query1.setFetchLimit(10);
 		context.performQuery(query1);
 
-		SelectQuery<Artist> query2 = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query2 = new SelectQuery<>(Artist.class);
 		query2.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
 		query2.setFetchOffset(10);
 		query2.setFetchLimit(10);
 		context.performQuery(query2);
 
-		SelectQuery<Artist> query3 = new SelectQuery<Artist>(Artist.class);
+		SelectQuery<Artist> query3 = new SelectQuery<>(Artist.class);
 		query3.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
 		query3.setFetchOffset(10);
 		query3.setFetchLimit(10);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/bff0e002/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
----------------------------------------------------------------------
diff --git a/docs/docbook/cayenne-guide/src/docbkx/expressions.xml b/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
index 6cba888..dd87c1d 100644
--- a/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
+++ b/docs/docbook/cayenne-guide/src/docbkx/expressions.xml
@@ -175,12 +175,14 @@ Expression qualifier2 = template.paramsArray(&quot;Monet&quot;);</programlisting
         <para>Positional binding is usually shorter. You can pass positional bindings to the
                 <code>"exp(..)"</code> factory method (its second argument is a params
             vararg):<programlisting>Expression qualifier = ExpressionFactory.exp(&quot;name = $name&quot;, &quot;Monet&quot;);</programlisting></para>
-        <para>To create a named parameterized expression with a LIKE clause, SQL wildcards must be
-            part of the values in the Map and not the expression string
+        <para>In parameterized expressions with LIKE clause, SQL wildcards must be part of the
+            values in the Map and not the expression string
             itself:<programlisting language="java">Expression qualifier = ExpressionFactory.exp(&quot;name like $name&quot;, &quot;Salvador%&quot;);</programlisting>When
-            matching on a relationship, parameters can be Persistent objects or
-            ObjectIds:<programlisting language="java">Artist dali = ... // asume we fetched this one already
-Expression qualifier1 = ExpressionFactory.exp(&quot;artist = $artist&quot;, dali);</programlisting>When
+            matching on a relationship, the value parameter must be either a Persistent object, an
+                <code>org.apache.cayenne.ObjectId</code>, or a numeric ID value (for single column
+            IDs).
+            E.g.:<programlisting language="java">Artist dali = ... // asume we fetched this one already
+Expression qualifier = ExpressionFactory.exp(&quot;artist = $artist&quot;, dali);</programlisting>When
             using positional binding, Cayenne would expect values for <emphasis role="bold"
                 >all</emphasis> parameters to be present. Binding by name offers extra flexibility:
             subexpressions with uninitialized parameters are automatically pruned from the