You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/01/26 08:15:07 UTC

cayenne git commit: CAY-2211 shortcuts for SQL functions in Property class

Repository: cayenne
Updated Branches:
  refs/heads/master 3f8a8f196 -> 44bad6412


CAY-2211 shortcuts for SQL functions in Property class


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

Branch: refs/heads/master
Commit: 44bad641295010807ecd47e6fe5cb7d904f8177a
Parents: 3f8a8f1
Author: Nikita Timofeev <st...@gmail.com>
Authored: Thu Jan 26 11:13:38 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Thu Jan 26 11:13:38 2017 +0300

----------------------------------------------------------------------
 .../apache/cayenne/exp/ExpressionFactory.java   |   9 +
 .../cayenne/exp/FunctionExpressionFactory.java  |  42 ++-
 .../java/org/apache/cayenne/exp/Property.java   | 168 ++++++++++-
 .../org/apache/cayenne/exp/PropertyTest.java    | 279 ++++++++++++++++---
 .../apache/cayenne/query/ColumnSelectIT.java    |  18 +-
 5 files changed, 450 insertions(+), 66 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/44bad641/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 8d132a9..e9fa6b0 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
@@ -52,6 +52,7 @@ import org.apache.cayenne.exp.parser.ASTNotLikeIgnoreCase;
 import org.apache.cayenne.exp.parser.ASTObjPath;
 import org.apache.cayenne.exp.parser.ASTOr;
 import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.exp.parser.ASTScalar;
 import org.apache.cayenne.exp.parser.ASTSubtract;
 import org.apache.cayenne.exp.parser.ASTTrue;
 import org.apache.cayenne.exp.parser.ExpressionParser;
@@ -1295,6 +1296,14 @@ public class ExpressionFactory {
 	}
 
 	/**
+	 * Wrap value into ASTScalar
+	 * @since 4.0
+	 */
+	static Expression wrapScalarValue(Object value) {
+		return new ASTScalar(value);
+	}
+
+	/**
 	 * Parses string, converting it to Expression. If string does not represent
 	 * a semantically correct expression, an ExpressionException is thrown.
 	 * 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/44bad641/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
index 89a8893..e4cbf07 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
@@ -243,17 +243,24 @@ public class FunctionExpressionFactory {
     }
 
     /**
+     * <p>
      * Factory method for expression to call CONCAT(string1, string2, ...) function
-     * Can be used like:
+     * </p>
+     * <p>
+     * Can be used like: <pre>
      *  Expression concat = concatExp(SomeClass.POPERTY_1.getPath(), SomeClass.PROPERTY_2.getPath());
-     *
+     * </pre>
+     * </p>
+     * <p>
      * SQL generation note:
-     * - if DB supports CONCAT function with vararg then it will be used
-     * - if DB supports CONCAT function with two args but also supports concat operator, then operator (eg ||) will be used
-     * - if DB supports only CONCAT function with two args then it will be used what can lead to SQL exception if
+     * <ul>
+     *      <li> if DB supports CONCAT function with vararg then it will be used
+     *      <li> if DB supports CONCAT function with two args but also supports concat operator, then operator (eg ||) will be used
+     *      <li> if DB supports only CONCAT function with two args then it will be used what can lead to SQL exception if
      * used with more than two arguments
-     *
-     * Currently only known DB with limited concatenation functionality is Openbase.
+     * </ul>
+     * </p>
+     * <p>Currently only known DB with limited concatenation functionality is Openbase.</p>
      *
      * @param expressions array of expressions
      * @return CONCAT() call expression
@@ -267,17 +274,24 @@ public class FunctionExpressionFactory {
     }
 
     /**
+     * <p>
      * Factory method for expression to call CONCAT(string1, string2, ...) function
-     * Can be used like:
+     * </p>
+     * <p>
+     * Can be used like:<pre>
      *  Expression concat = concatExp("property1", "property2");
-     *
+     * </pre>
+     * </p>
+     * <p>
      * SQL generation note:
-     * - if DB supports CONCAT function with vararg then it will be used
-     * - if DB supports CONCAT function with two args but also supports concat operator, then operator (eg ||) will be used
-     * - if DB supports only CONCAT function with two args then it will be used what can lead to SQL exception if
+     * <ul>
+     *      <li> if DB supports CONCAT function with vararg then it will be used
+     *      <li> if DB supports CONCAT function with two args but also supports concat operator, then operator (eg ||) will be used
+     *      <li> if DB supports only CONCAT function with two args then it will be used what can lead to SQL exception if
      * used with more than two arguments
-     *
-     * Currently only known DB with limited concatenation functionality is Openbase.
+     * </ul>
+     * </p>
+     * <p>Currently only Openbase DB has limited concatenation functionality.</p>
      *
      * @param paths array of paths
      * @return CONCAT() call expression

http://git-wip-us.apache.org/repos/asf/cayenne/blob/44bad641/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 f1bb1ce..59eb17d 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
@@ -30,14 +30,16 @@ import java.util.List;
 
 /**
  * <p>
- * A property in a DataObject.
+ * A property in a {@link org.apache.cayenne.DataObject}.
  * </p>
  * <p>
- * Used to construct Expressions quickly and with type-safety, and to construct Orderings
+ * Used to construct Expressions quickly and with type-safety, and to construct Orderings.
  * </p>
  * <p>
- * Instances of this class are immutable
- * Construct via factory methods Property.create(..)
+ * Instances of this class are immutable.
+ * </p>
+ * <p>
+ * Must be created via factory methods {@link Property#create(String, Class) Property.create(..)}
  * </p>
  *
  * @param <E> The type this property returns.
@@ -50,6 +52,17 @@ import java.util.List;
  */
 public class Property<E> {
 
+    /**
+     * <p>Property that can be used in COUNT(*) queries</p>
+     * <p>
+     * <pre>{@code
+     * List<Object[]> result = ObjectSelect
+     *         .columnQuery(Artist.class, Property.COUNT, Artist.ARTIST_NAME)
+     *         .having(Property.COUNT.gt(1L))
+     *         .select(context);
+     * }</pre>
+     * </p>
+     */
     public static final Property<Long> COUNT = Property.create(FunctionExpressionFactory.countExp(), Long.class);
 
     /**
@@ -59,13 +72,11 @@ public class Property<E> {
 
     /**
      * Expression provider for the property
-     * @since 4.0
      */
     private final ExpressionProvider expressionProvider;
 
     /**
      * Explicit type of the property
-     * @since 4.0
      */
     private final Class<? super E> type;
 
@@ -106,7 +117,6 @@ public class Property<E> {
      * @param name of the property (will be used as alias for the expression)
      * @param expression expression for property
      * @param type of the property
-     * @since 4.0
      *
      * @see Property#create(String, Expression, Class)
      */
@@ -129,7 +139,6 @@ public class Property<E> {
     }
 
     /**
-     * @since 4.0
      * @return alias for this property
      */
     public String getAlias() {
@@ -149,7 +158,7 @@ public class Property<E> {
     }
 
     /**
-     * @since 4.0
+     * @return expression that represents this Property
      */
     public Expression getExpression() {
         return expressionProvider.get();
@@ -635,26 +644,146 @@ public class Property<E> {
         }
     }
 
+    /**
+     * @see FunctionExpressionFactory#countExp(Expression)
+     */
     public Property<Long> count() {
         return create(FunctionExpressionFactory.countExp(getExpression()), Long.class);
     }
 
+    /**
+     * @see FunctionExpressionFactory#maxExp(Expression)
+     */
     public Property<E> max() {
         return create(FunctionExpressionFactory.maxExp(getExpression()), getType());
     }
 
+    /**
+     * @see FunctionExpressionFactory#minExp(Expression)
+     */
     public Property<E> min() {
         return create(FunctionExpressionFactory.minExp(getExpression()), getType());
     }
 
+    /**
+     * @see FunctionExpressionFactory#avgExp(Expression)
+     */
     public Property<E> avg() {
         return create(FunctionExpressionFactory.avgExp(getExpression()), getType());
     }
 
+    /**
+     * @see FunctionExpressionFactory#sumExp(Expression)
+     */
     public Property<E> sum() {
         return create(FunctionExpressionFactory.sumExp(getExpression()), getType());
     }
 
+    /**
+     * @see FunctionExpressionFactory#modExp(Expression, Number)
+     */
+    public Property<E> mod(Number number) {
+        return create(FunctionExpressionFactory.modExp(getExpression(), number), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#absExp(Expression)
+     */
+    public Property<E> abs() {
+        return create(FunctionExpressionFactory.absExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#sqrtExp(Expression)
+     */
+    public Property<E> sqrt() {
+        return create(FunctionExpressionFactory.sqrtExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#lengthExp(Expression)
+     */
+    public Property<Integer> length() {
+        return create(FunctionExpressionFactory.lengthExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#locateExp(String, Expression)
+     */
+    public Property<Integer> locate(String string) {
+        return create(FunctionExpressionFactory.locateExp(ExpressionFactory.wrapScalarValue(string), getExpression()), Integer.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#locateExp(Expression, Expression)
+     */
+    public Property<Integer> locate(Property<? extends String> property) {
+        return create(FunctionExpressionFactory.locateExp(property.getExpression(), getExpression()), Integer.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#trimExp(Expression)
+     */
+    public Property<String> trim() {
+        return create(FunctionExpressionFactory.trimExp(getExpression()), String.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#upperExp(Expression)
+     */
+    public Property<String> upper() {
+        return create(FunctionExpressionFactory.upperExp(getExpression()), String.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#lowerExp(Expression)
+     */
+    public Property<String> lower() {
+        return create(FunctionExpressionFactory.lowerExp(getExpression()), String.class);
+    }
+
+    /**
+     * <p>Arguments will be converted as follows:
+     * <ul>
+     *      <li>if argument is a {@link Property} than its expression will be used</li>
+     *      <li>if argument is a {@link Expression} than it will be used as is </li>
+     *      <li>all other values will be converted to String</li>
+     * </ul>
+     * </p>
+     * <p>
+     *     Usage:
+     *     <pre>{@code
+     *     Property<String> fullName = Artist.FIRST_NAME.concat(" ", Artist.SECOND_NAME);
+     *     }</pre>
+     * </p>
+     * @see FunctionExpressionFactory#concatExp(Expression...)
+     */
+    public Property<String> concat(Object... args) {
+        Expression[] exp = new Expression[args.length + 1];
+        int i = 0;
+        exp[i++] = getExpression();
+        for(Object arg : args) {
+            if(arg instanceof Property) {
+                exp[i++] = ((Property) arg).getExpression();
+            } else if(arg instanceof Expression) {
+                exp[i++] = (Expression) arg;
+            } else if(arg != null) {
+                exp[i++] = ExpressionFactory.wrapScalarValue(arg.toString());
+            }
+        }
+        return create(FunctionExpressionFactory.concatExp(exp), String.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#substringExp(Expression, int, int)
+     */
+    public Property<String> substring(int offset, int length) {
+        return create(FunctionExpressionFactory.substringExp(getExpression(), offset, length), String.class);
+    }
+
+    /**
+     * Creates alias with different name for this property
+     */
     public Property<E> alias(String alias) {
         return new Property<>(alias, this.getExpression(), this.getType());
     }
@@ -663,18 +792,39 @@ public class Property<E> {
         return type;
     }
 
+    /**
+     * Creates property with name and type
+     * @see Property#create(Expression, Class)
+     * @see Property#create(String, Expression, Class)
+     */
     public static <T> Property<T> create(String name, Class<? super T> type) {
         return new Property<>(name, type);
     }
 
+    /**
+     * Creates property with expression and type
+     * @see Property#create(String, Class)
+     * @see Property#create(String, Expression, Class)
+     */
     public static <T> Property<T> create(Expression expression, Class<? super T> type) {
         return new Property<>(null, expression, type);
     }
 
+    /**
+     * Creates property with name, expression and type
+     * @see Property#create(String, Class)
+     * @see Property#create(Expression, Class)
+     */
     public static <T> Property<T> create(String name, Expression expression, Class<? super T> type) {
         return new Property<>(name, expression, type);
     }
 
+    /**
+     * Since Expression is mutable we need to provide clean Expression for every getter call.
+     * So to keep Property itself immutable we use ExpressionProvider.
+     * @see Property#Property(String, Class)
+     * @see Property#Property(String, Expression, Class)
+     */
     private interface ExpressionProvider {
         Expression get();
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/44bad641/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 a489d37..83b5868 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
@@ -26,7 +26,23 @@ import static org.junit.Assert.assertTrue;
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.cayenne.exp.parser.ASTAbs;
+import org.apache.cayenne.exp.parser.ASTAvg;
+import org.apache.cayenne.exp.parser.ASTConcat;
+import org.apache.cayenne.exp.parser.ASTCount;
+import org.apache.cayenne.exp.parser.ASTLength;
+import org.apache.cayenne.exp.parser.ASTLocate;
+import org.apache.cayenne.exp.parser.ASTLower;
+import org.apache.cayenne.exp.parser.ASTMax;
+import org.apache.cayenne.exp.parser.ASTMin;
+import org.apache.cayenne.exp.parser.ASTMod;
 import org.apache.cayenne.exp.parser.ASTObjPath;
+import org.apache.cayenne.exp.parser.ASTScalar;
+import org.apache.cayenne.exp.parser.ASTSqrt;
+import org.apache.cayenne.exp.parser.ASTSubstring;
+import org.apache.cayenne.exp.parser.ASTSum;
+import org.apache.cayenne.exp.parser.ASTTrim;
+import org.apache.cayenne.exp.parser.ASTUpper;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
 import org.apache.cayenne.reflect.TstJavaBean;
 import org.junit.Test;
@@ -35,14 +51,14 @@ public class PropertyTest {
 
     @Test
     public void testPath() {
-        Property<String> p = new Property<>("x.y");
+        Property<String> p = Property.create("x.y", String.class);
         Expression pp = p.path();
         assertEquals(ExpressionFactory.exp("x.y"), pp);
     }
 
     @Test
     public void testIn() {
-        Property<String> p = new Property<>("x.y");
+        Property<String> p = Property.create("x.y", String.class);
 
         Expression e1 = p.in("a");
         assertEquals("x.y in (\"a\")", e1.toString());
@@ -58,7 +74,7 @@ public class PropertyTest {
     public void testGetFrom() {
         TstJavaBean bean = new TstJavaBean();
         bean.setIntField(7);
-        Property<Integer> INT_FIELD = new Property<>("intField");
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
         assertEquals(Integer.valueOf(7), INT_FIELD.getFrom(bean));
     }
 
@@ -68,7 +84,7 @@ public class PropertyTest {
         TstJavaBean nestedBean = new TstJavaBean();
         nestedBean.setIntField(7);
         bean.setObjectField(nestedBean);
-        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        Property<Integer> OBJECT_FIELD_INT_FIELD = Property.create("objectField.intField", Integer.class);
         assertEquals(Integer.valueOf(7), OBJECT_FIELD_INT_FIELD.getFrom(bean));
     }
 
@@ -76,7 +92,7 @@ public class PropertyTest {
     public void testGetFromNestedNull() {
         TstJavaBean bean = new TstJavaBean();
         bean.setObjectField(null);
-        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        Property<Integer> OBJECT_FIELD_INT_FIELD = Property.create("objectField.intField", Integer.class);
         assertNull(OBJECT_FIELD_INT_FIELD.getFrom(bean));
     }
 
@@ -90,14 +106,14 @@ public class PropertyTest {
 
         List<TstJavaBean> beans = Arrays.asList(bean, bean2);
 
-        Property<Integer> INT_FIELD = new Property<>("intField");
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
         assertEquals(Arrays.asList(7, 8), INT_FIELD.getFromAll(beans));
     }
 
     @Test
     public void testSetIn() {
         TstJavaBean bean = new TstJavaBean();
-        Property<Integer> INT_FIELD = new Property<>("intField");
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
         INT_FIELD.setIn(bean, 7);
         assertEquals(7, bean.getIntField());
     }
@@ -107,7 +123,7 @@ public class PropertyTest {
         TstJavaBean bean = new TstJavaBean();
         bean.setObjectField(new TstJavaBean());
 
-        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        Property<Integer> OBJECT_FIELD_INT_FIELD = Property.create("objectField.intField", Integer.class);
 
         OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
         assertEquals(7, ((TstJavaBean) bean.getObjectField()).getIntField());
@@ -117,7 +133,7 @@ public class PropertyTest {
     public void testSetInNestedNull() {
         TstJavaBean bean = new TstJavaBean();
         bean.setObjectField(null);
-        Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<>("objectField.intField");
+        Property<Integer> OBJECT_FIELD_INT_FIELD = Property.create("objectField.intField", Integer.class);
         OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
     }
 
@@ -127,7 +143,7 @@ public class PropertyTest {
         TstJavaBean bean2 = new TstJavaBean();
         List<TstJavaBean> beans = Arrays.asList(bean, bean2);
 
-        Property<Integer> INT_FIELD = new Property<>("intField");
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
         INT_FIELD.setInAll(beans, 7);
         assertEquals(7, bean.getIntField());
         assertEquals(7, bean2.getIntField());
@@ -135,8 +151,8 @@ public class PropertyTest {
 
     @Test
     public void testEqualsWithName() {
-        Property<Integer> INT_FIELD = new Property<>("intField");
-        Property<Integer> INT_FIELD2 = new Property<>("intField");
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
+        Property<Integer> INT_FIELD2 = Property.create("intField", Integer.class);
 
         assertTrue(INT_FIELD != INT_FIELD2);
         assertTrue(INT_FIELD.equals(INT_FIELD2));
@@ -144,9 +160,9 @@ public class PropertyTest {
 
     @Test
     public void testHashCodeWithName() {
-        Property<Integer> INT_FIELD = new Property<>("intField");
-        Property<Integer> INT_FIELD2 = new Property<>("intField");
-        Property<Long> LONG_FIELD = new Property<>("longField");
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
+        Property<Integer> INT_FIELD2 = Property.create("intField", Integer.class);
+        Property<Long> LONG_FIELD = Property.create("longField", Long.class);
 
         assertTrue(INT_FIELD.hashCode() == INT_FIELD2.hashCode());
         assertTrue(INT_FIELD.hashCode() != LONG_FIELD.hashCode());
@@ -154,8 +170,8 @@ public class PropertyTest {
 
     @Test
     public void testEqualsWithNameAndType() {
-        Property<Integer> INT_FIELD = new Property<>("intField", Integer.class);
-        Property<Integer> INT_FIELD2 = new Property<>("intField", Integer.class);
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
+        Property<Integer> INT_FIELD2 = Property.create("intField", Integer.class);
 
         assertTrue(INT_FIELD != INT_FIELD2);
         assertTrue(INT_FIELD.equals(INT_FIELD2));
@@ -163,9 +179,9 @@ public class PropertyTest {
 
     @Test
     public void testHashCodeWithNameAndType() {
-        Property<Integer> INT_FIELD = new Property<>("intField", Integer.class);
-        Property<Integer> INT_FIELD2 = new Property<>("intField", Integer.class);
-        Property<Long> LONG_FIELD = new Property<>("longField", Long.class);
+        Property<Integer> INT_FIELD = Property.create("intField", Integer.class);
+        Property<Integer> INT_FIELD2 = Property.create("intField", Integer.class);
+        Property<Long> LONG_FIELD = Property.create("longField", Long.class);
 
         assertTrue(INT_FIELD.hashCode() == INT_FIELD2.hashCode());
         assertTrue(INT_FIELD.hashCode() != LONG_FIELD.hashCode());
@@ -192,33 +208,33 @@ public class PropertyTest {
 
     @Test
     public void testOuter() {
-        Property<String> inner = new Property<>("xyz");
+        Property<String> inner = Property.create("xyz", String.class);
         assertEquals("xyz+", inner.outer().getName());
 
-        Property<String> inner1 = new Property<>("xyz.xxx");
+        Property<String> inner1 = Property.create("xyz.xxx", String.class);
         assertEquals("xyz.xxx+", inner1.outer().getName());
 
-        Property<String> outer = new Property<>("xyz+");
+        Property<String> outer = Property.create("xyz+", String.class);
         assertEquals("xyz+", outer.outer().getName());
     }
 
     @Test
     public void testLike() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.like("abc");
         assertEquals("prop like \"abc\"", e.toString());
     }
 
     @Test
     public void testLikeIgnoreCase() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.likeIgnoreCase("abc");
         assertEquals("prop likeIgnoreCase \"abc\"", e.toString());
     }
 
     @Test
     public void testLike_NoEscape() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.like("ab%c");
         assertEquals("prop like \"ab%c\"", e.toString());
         assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
@@ -226,7 +242,7 @@ public class PropertyTest {
 
     @Test
     public void testContains() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.contains("abc");
         assertEquals("prop like \"%abc%\"", e.toString());
         assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
@@ -234,7 +250,7 @@ public class PropertyTest {
 
     @Test
     public void testStartsWith() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.startsWith("abc");
         assertEquals("prop like \"abc%\"", e.toString());
         assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
@@ -242,7 +258,7 @@ public class PropertyTest {
 
     @Test
     public void testEndsWith() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.endsWith("abc");
         assertEquals("prop like \"%abc\"", e.toString());
         assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
@@ -250,7 +266,7 @@ public class PropertyTest {
 
     @Test
     public void testContains_Escape1() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.contains("a%bc");
         assertEquals("prop like \"%a!%bc%\"", e.toString());
         assertEquals('!', ((PatternMatchNode) e).getEscapeChar());
@@ -258,7 +274,7 @@ public class PropertyTest {
 
     @Test
     public void testContains_Escape2() {
-        Property<String> p = new Property<>("prop");
+        Property<String> p = Property.create("prop", String.class);
         Expression e = p.contains("a_!bc");
         assertEquals("prop like \"%a#_!bc%\"", e.toString());
         assertEquals('#', ((PatternMatchNode) e).getEscapeChar());
@@ -271,4 +287,205 @@ public class PropertyTest {
         Expression ex = p.getExpression();
         assertEquals("test.path", ex.toString());
     }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testDeprecatedConstruct() {
+        Property<String> p = new Property<>("p");
+        assertNull(p.getType());
+        assertEquals("p", p.getName());
+        assertEquals(new ASTObjPath("p"), p.getExpression());
+    }
+
+    @Test
+    public void testCreationWithName() {
+        Property<String> p1 = new Property<>("p1", String.class);
+        assertEquals(String.class, p1.getType());
+        assertEquals("p1", p1.getName());
+        assertEquals(new ASTObjPath("p1"), p1.getExpression());
+
+        Property<String> p2 = Property.create("p1", String.class);
+        assertEquals(p1, p2);
+    }
+
+    @Test
+    public void testCreationWithExp() {
+        Expression exp = FunctionExpressionFactory.currentTime();
+
+        Property<String> p1 = new Property<>(null, exp, String.class);
+        assertEquals(String.class, p1.getType());
+        assertEquals(null, p1.getName());
+        assertEquals(exp, p1.getExpression());
+
+        Property<String> p2 = Property.create(exp, String.class);
+        assertEquals(p1, p2);
+    }
+
+    @Test
+    public void testCreationWithNameAndExp() {
+        Expression exp = FunctionExpressionFactory.currentTime();
+
+        Property<String> p1 = new Property<>("p1", exp, String.class);
+        assertEquals(String.class, p1.getType());
+        assertEquals("p1", p1.getName());
+        assertEquals(exp, p1.getExpression());
+
+        Property<String> p2 = Property.create("p1", exp, String.class);
+        assertEquals(p1, p2);
+    }
+
+    @Test
+    public void testAlias() {
+        Expression exp = FunctionExpressionFactory.currentTime();
+
+        Property<String> p1 = new Property<>("p1", exp, String.class);
+        assertEquals(String.class, p1.getType());
+        assertEquals("p1", p1.getName());
+        assertEquals(exp, p1.getExpression());
+
+        Property<String> p2 = p1.alias("p2");
+        assertEquals(String.class, p2.getType());
+        assertEquals("p2", p2.getName());
+        assertEquals(exp, p2.getExpression());
+    }
+
+    @Test
+    public void testCount() {
+        Property<String> p = Property.create("test", String.class);
+        Property<Long> newProp = p.count();
+        assertTrue(newProp.getExpression() instanceof ASTCount);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testMin() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.min();
+        assertTrue(newProp.getExpression() instanceof ASTMin);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testMax() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.max();
+        assertTrue(newProp.getExpression() instanceof ASTMax);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testSum() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.sum();
+        assertTrue(newProp.getExpression() instanceof ASTSum);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testAvg() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.avg();
+        assertTrue(newProp.getExpression() instanceof ASTAvg);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testAbs() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.abs();
+        assertTrue(newProp.getExpression() instanceof ASTAbs);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testMod() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.mod(3.0);
+        assertTrue(newProp.getExpression() instanceof ASTMod);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+        assertEquals(3.0, newProp.getExpression().getOperand(1));
+    }
+
+    @Test
+    public void testSqrt() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.sqrt();
+        assertTrue(newProp.getExpression() instanceof ASTSqrt);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testLength() {
+        Property<String> p = Property.create("test", String.class);
+        Property<Integer> newProp = p.length();
+        assertTrue(newProp.getExpression() instanceof ASTLength);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testLocateString() {
+        Property<String> p = Property.create("test", String.class);
+        Property<Integer> newProp = p.locate("test");
+        assertTrue(newProp.getExpression() instanceof ASTLocate);
+        assertEquals("test", newProp.getExpression().getOperand(0));
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(1));
+    }
+
+    @Test
+    public void testLocateProperty() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> p2 = Property.create("test2", String.class);
+        Property<Integer> newProp = p.locate(p2);
+        assertTrue(newProp.getExpression() instanceof ASTLocate);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(1));
+        assertEquals(p2.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testSustring() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.substring(1, 2);
+        assertTrue(newProp.getExpression() instanceof ASTSubstring);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+        assertEquals(1, newProp.getExpression().getOperand(1));
+        assertEquals(2, newProp.getExpression().getOperand(2));
+    }
+
+    @Test
+    public void testTrim() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.trim();
+        assertTrue(newProp.getExpression() instanceof ASTTrim);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testLower() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.lower();
+        assertTrue(newProp.getExpression() instanceof ASTLower);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testUpper() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> newProp = p.upper();
+        assertTrue(newProp.getExpression() instanceof ASTUpper);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+    }
+
+    @Test
+    public void testConcat() {
+        Property<String> p = Property.create("test", String.class);
+        Property<String> p2 = Property.create("concat", String.class);
+        Expression exp = new ASTScalar(3);
+
+        Property<String> newProp = p.concat("string", exp, p2);
+        assertTrue(newProp.getExpression() instanceof ASTConcat);
+        assertEquals(p.getExpression(), newProp.getExpression().getOperand(0));
+        assertEquals("string", newProp.getExpression().getOperand(1));
+        assertEquals(3, newProp.getExpression().getOperand(2)); // getOperand unwrapping ASTScalar
+        assertEquals(p2.getExpression(), newProp.getExpression().getOperand(3));
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/44bad641/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
index 613b659..4319398 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
@@ -117,7 +117,7 @@ public class ColumnSelectIT extends ServerCase {
 
     @Test(expected = Exception.class)
     public void testHavingOnNonGroupByColumn() throws Exception {
-        Property<String> nameSubstr = Property.create(substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
+        Property<String> nameSubstr = Artist.ARTIST_NAME.substring(1, 6);
 
         Object[] q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
                 .having(Artist.ARTIST_NAME.like("artist%"))
@@ -139,10 +139,10 @@ public class ColumnSelectIT extends ServerCase {
     @Test
     public void testSelectHavingWithExpressionAlias() throws Exception {
 
-        Property<String> nameSubstr = Property.create("name_substr", substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
         Object[] q = null;
         try {
-            q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
+            q = ObjectSelect
+                    .columnQuery(Artist.class, Artist.ARTIST_NAME.substring(1, 6).alias("name_substr"), Property.COUNT)
                     .having(Property.COUNT.gt(10L))
                     .selectOne(context);
         } catch (CayenneRuntimeException ex) {
@@ -160,10 +160,9 @@ public class ColumnSelectIT extends ServerCase {
     @Test
     public void testSelectHavingWithExpressionNoAlias() throws Exception {
 
-        Property<String> nameSubstr = Property.create(substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
         Object[] q = null;
         try {
-            q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
+            q = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME.substring(1, 6), Property.COUNT)
                     .having(Property.COUNT.gt(10L))
                     .selectOne(context);
         } catch (CayenneRuntimeException ex) {
@@ -179,13 +178,10 @@ public class ColumnSelectIT extends ServerCase {
 
     @Test
     public void testSelectWhereAndHaving() throws Exception {
-        Property<String> nameFirstLetter = Property.create(substringExp(Artist.ARTIST_NAME.path(), 1, 1), String.class);
-        Property<String> nameSubstr = Property.create("name_substr", substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
-
         Object[] q = null;
         try {
-            q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
-                    .where(nameFirstLetter.eq("a"))
+            q = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME.substring(1, 6).alias("name_substr"), Property.COUNT)
+                    .where(Artist.ARTIST_NAME.substring(1, 1).eq("a"))
                     .having(Property.COUNT.gt(10L))
                     .selectOne(context);
         } catch (CayenneRuntimeException ex) {
@@ -336,7 +332,5 @@ public class ColumnSelectIT extends ServerCase {
                 .count(Artist.DATE_OF_BIRTH)
                 .selectOne(context);
         assertEquals(count2, count3);
-
-
     }
 }