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 2018/12/26 12:19:06 UTC

[2/6] cayenne git commit: CAY-2467 New type-aware Property API core API

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
new file mode 100644
index 0000000..8c32512
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
@@ -0,0 +1,140 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
+
+/**
+ * Property that represents date/time attribute.
+ * <pre>{@code
+ * ObjectSelect.query(Artist.class)
+ *      .where(Artist.DATE_OF_BIRTH.year().lte(1900))
+ *      .or(Artist.DATE_OF_BIRTH.month().between(6, 8))
+ * }</pre>
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class DateProperty<E> extends BaseProperty<E> implements ComparableProperty<E> {
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name       of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param type       of the property
+     */
+    protected DateProperty(String name, Expression expression, Class<E> type) {
+        super(name, expression, type);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has year component
+     *
+     * @return new property that represents year component of this date property
+     * @see FunctionExpressionFactory#yearExp(Expression)
+     */
+    public NumericProperty<Integer> year() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.yearExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has month component
+     *
+     * @return new property that represents month component of this date property
+     * @see FunctionExpressionFactory#monthExp(Expression)
+     */
+    public NumericProperty<Integer> month() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.monthExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has day component
+     *
+     * @return new property that represents day of month component of this date property
+     * @see FunctionExpressionFactory#dayOfMonthExp(Expression)
+     */
+    public NumericProperty<Integer> dayOfMonth() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.dayOfMonthExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has day component
+     *
+     * @return new property that represents day of year component of this date property
+     * @see FunctionExpressionFactory#dayOfMonthExp(Expression)
+     */
+    public NumericProperty<Integer> dayOfYear() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.dayOfYearExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has time component
+     *
+     * @return new property that represents hour component of this time property
+     * @see FunctionExpressionFactory#hourExp(Expression)
+     */
+    public NumericProperty<Integer> hour() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.hourExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has time component
+     *
+     * @return new property that represents minute component of this time property
+     * @see FunctionExpressionFactory#minuteExp(Expression)
+     */
+    public NumericProperty<Integer> minute() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.minuteExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * It is a caller responsibility to check that underlying attribute has time component
+     *
+     * @return new property that represents second component of this time property
+     * @see FunctionExpressionFactory#secondExp(Expression)
+     */
+    public NumericProperty<Integer> second() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.secondExp(getExpression()), Integer.class);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public DateProperty<E> alias(String alias) {
+        return PropertyFactory.createDate(alias, this.getExpression(), this.getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#maxExp(Expression)
+     */
+    public DateProperty<E> max() {
+        return PropertyFactory.createDate(FunctionExpressionFactory.maxExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#minExp(Expression)
+     */
+    public DateProperty<E> min() {
+        return PropertyFactory.createDate(FunctionExpressionFactory.minExp(getExpression()), getType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
new file mode 100644
index 0000000..6398a22
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * Property that represents to-one relationships.
+ * <p>
+ * Usage examples in where clause: <pre>{@code
+ * ObjectSelect.query(Paintings.class)
+ *      .where(Painting.TO_ARTIST.dot(Artist.ARTIST_NAME).eq("Pablo Picasso"));}</pre>
+ * <p>
+ * Usage examples in column select, in this case full Artist entity will be
+ * returned in the result: <pre>{@code
+ * ObjectSelect
+ *      .columnQuery(Paintings.class, Painting.PAINTING_TITLE, Painting.TO_ARTIST);}</pre>
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class EntityProperty<E extends Persistent> extends BaseProperty<E> implements RelationshipProperty<E> {
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name       of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param type       of the property
+     * @see PropertyFactory#createBase(String, Expression, Class)
+     */
+    protected EntityProperty(String name, Expression expression, Class<E> type) {
+        super(name, expression, type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public EntityProperty<E> alias(String alias) {
+        return PropertyFactory.createEntity(alias, getExpression(), getType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public EntityProperty<E> outer() {
+        return getName().endsWith("+")
+                ? this
+                : PropertyFactory.createEntity(getName() + "+", getType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
new file mode 100644
index 0000000..410d6db
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
@@ -0,0 +1,67 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import java.util.List;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * Property that represents to-many relationship mapped on {@link List}.
+ * <pre>{@code
+ * ObjectSelect.query(Artist.class)
+ *      .where(Artist.PAINTING_ARRAY.contains(painting));
+ * }</pre>
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class ListProperty<V extends Persistent> extends CollectionProperty<V, List<V>> {
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name           of the property (will be used as alias for the expression)
+     * @param expression     expression for property
+     * @param entityType     type of related entity
+     */
+    protected ListProperty(String name, Expression expression, Class<V> entityType) {
+        super(name, expression, List.class, entityType);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ListProperty<V> alias(String alias) {
+        return PropertyFactory.createList(alias, this.getExpression(), this.getEntityType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ListProperty<V> outer() {
+        return getName().endsWith("+")
+                ? this
+                : PropertyFactory.createList(getName() + "+", getEntityType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
new file mode 100644
index 0000000..0137659
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
@@ -0,0 +1,233 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+
+/**
+ * Property that represents to-many relationship mapped on {@link Map}.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class MapProperty<K, V extends Persistent> extends BaseProperty<Map<K, V>> implements RelationshipProperty<Map<K, V>> {
+
+    protected Class<K> keyType;
+
+    protected Class<V> entityType;
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name       of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param keyType    type of keys of the property
+     * @param entityType type of related entities
+     * @see PropertyFactory#createMap(String, Expression, Class, Class)
+     */
+    protected MapProperty(String name, Expression expression, Class<K> keyType, Class<V> entityType) {
+        super(name, expression, Map.class);
+        this.keyType = keyType;
+        this.entityType = entityType;
+    }
+
+    /**
+     * <p>Create new "flat" property for toMany relationship.</p>
+     * <p>
+     *     Example:
+     *     <pre>{@code
+     *     List<Object[]> result = ObjectSelect
+     *          .columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.flat(Painting.class))
+     *          .select(context);
+     *     }</pre>
+     * </p>
+     */
+    public EntityProperty<V> flat() {
+        return PropertyFactory.createEntity(ExpressionFactory.fullObjectExp(getExpression()), getEntityType());
+    }
+
+    // TODO: move all *contains* methods to RelationshipProperty once Property class is removed
+
+    /**
+     * @return An expression representing equality to a value.
+     */
+    public Expression contains(V value) {
+        return ExpressionFactory.matchExp(getExpression(), value);
+    }
+
+
+    /**
+     * @return An expression representing inequality to a value.
+     */
+    public Expression notContains(V value) {
+        return ExpressionFactory.noMatchExp(getExpression(), value);
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    @SafeVarargs
+    public final Expression contains(V firstValue, V... 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(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values in the given set.
+     */
+    public Expression contains(Collection<V> values) {
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given set.
+     */
+    public Expression notContains(Collection<V> values) {
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given set.
+     */
+    @SafeVarargs
+    public final Expression notContains(V firstValue, V... 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(getExpression(), values);
+    }
+
+    /**
+     * @param id object id
+     * @return An expression for finding object with given id.
+     */
+    public Expression containsId(Object id) {
+        return ExpressionFactory.matchExp(getExpression(), id);
+    }
+
+    /**
+     * @return An expression for finding objects with given id set
+     */
+    public Expression containsId(Object firstId, Object... moreId) {
+
+        int moreValuesLength = moreId != null ? moreId.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstId;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreId, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.inExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects with given id set.
+     */
+    public Expression containsId(Collection<Object> ids) {
+        return ExpressionFactory.inExp(getExpression(), ids);
+    }
+
+    /**
+     * @param id object id
+     * @return An expression for finding object without given id.
+     */
+    public Expression notContainsId(Object id) {
+        return ExpressionFactory.noMatchExp(getExpression(), id);
+    }
+
+    /**
+     * @return An expression for finding objects without given id set.
+     */
+    public Expression notContainsId(Object firstId, Object... moreId) {
+
+        int moreValuesLength = moreId != null ? moreId.length : 0;
+
+        Object[] values = new Object[moreValuesLength + 1];
+        values[0] = firstId;
+
+        if (moreValuesLength > 0) {
+            System.arraycopy(moreId, 0, values, 1, moreValuesLength);
+        }
+
+        return ExpressionFactory.notInExp(getExpression(), values);
+    }
+
+    /**
+     * @return An expression for finding objects without given id set.
+     */
+    public Expression notContainsId(Collection<Object> ids) {
+        return ExpressionFactory.notInExp(getExpression(), ids);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MapProperty<K, V> alias(String alias) {
+        return PropertyFactory.createMap(alias, this.getExpression(), getKeyType(), getEntityType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MapProperty<K, V> outer() {
+        return getName().endsWith("+")
+                ? this
+                : PropertyFactory.createMap(getName() + "+", getKeyType(), getEntityType());
+    }
+
+    /**
+     * @return type of keys in represented attribute
+     */
+    protected Class<K> getKeyType() {
+        return keyType;
+    }
+
+    /**
+     * @return type of object entity in represented attribute
+     */
+    protected Class<V> getEntityType() {
+        return entityType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
new file mode 100644
index 0000000..14e07d1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
@@ -0,0 +1,186 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
+import org.apache.cayenne.exp.parser.ASTAdd;
+import org.apache.cayenne.exp.parser.ASTDivide;
+import org.apache.cayenne.exp.parser.ASTMultiply;
+import org.apache.cayenne.exp.parser.ASTNegate;
+import org.apache.cayenne.exp.parser.ASTSubtract;
+
+/**
+ * Property that represents attributes mapped on numeric types
+ * <p>
+ * Numeric type is an any type inherited from {@link Number}.
+ * <p>
+ * Provides basic math functions like {@link #mod(Number)}, {@link #abs()} and {@link #sqrt()}.
+ * It is also implements {@link ComparableProperty} interface.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class NumericProperty<E extends Number> extends BaseProperty<E> implements ComparableProperty<E> {
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name       of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param type       of the property
+     * @see PropertyFactory#createNumeric(String, Expression, Class)
+     */
+    protected NumericProperty(String name, Expression expression, Class<E> type) {
+        super(name, expression, type);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#avgExp(Expression)
+     */
+    public NumericProperty<E> avg() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.avgExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#sumExp(Expression)
+     */
+    public NumericProperty<E> sum() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.sumExp(getExpression()), getType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NumericProperty<E> max() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.maxExp(getExpression()), getType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NumericProperty<E> min() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.minExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#modExp(Expression, Number)
+     */
+    public NumericProperty<E> mod(Number number) {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.modExp(getExpression(), number), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#modExp(Expression, Number)
+     */
+    public NumericProperty<E> mod(NumericProperty<?> number) {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.modExp(getExpression(), number.getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#absExp(Expression)
+     *
+     * @return new property that represents abs() function call with current property as argument
+     */
+    public NumericProperty<E> abs() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.absExp(getExpression()), getType());
+    }
+
+    /**
+     * @see FunctionExpressionFactory#sqrtExp(Expression)
+     *
+     * @return new property that represents sqrt() function call with current property as argument
+     */
+    public NumericProperty<E> sqrt() {
+        return PropertyFactory.createNumeric(FunctionExpressionFactory.sqrtExp(getExpression()), getType());
+    }
+
+    /**
+     * @return new property that represents '+' operator with current property as argument
+     */
+    public NumericProperty<E> add(E value) {
+        return PropertyFactory.createNumeric(new ASTAdd(getExpression(), value), getType());
+    }
+
+    /**
+     * @return new property that represents '+' operator with current property as argument
+     */
+    public NumericProperty<E> add(NumericProperty<?> value) {
+        return PropertyFactory.createNumeric(new ASTAdd(getExpression(), value.getExpression()), getType());
+    }
+
+    /**
+     * @return new property that represents '-' operator with current property as argument
+     */
+    public NumericProperty<E> sub(E value) {
+        return PropertyFactory.createNumeric(new ASTSubtract(getExpression(), value), getType());
+    }
+
+    /**
+     * @return new property that represents '-' operator with current property as argument
+     */
+    public NumericProperty<E> sub(NumericProperty<?> value) {
+        return PropertyFactory.createNumeric(new ASTSubtract(getExpression(), value.getExpression()), getType());
+    }
+
+    /**
+     * @return new property that represents '/' operator with current property as argument
+     */
+    public NumericProperty<E> div(E value) {
+        return PropertyFactory.createNumeric(new ASTDivide(getExpression(), value), getType());
+    }
+
+    /**
+     * @return new property that represents '/' operator with current property as argument
+     */
+    public NumericProperty<E> div(NumericProperty<?> value) {
+        return PropertyFactory.createNumeric(new ASTDivide(getExpression(), value.getExpression()), getType());
+    }
+
+    /**
+     * @return new property that represents '*' operator with current property as argument
+     */
+    public NumericProperty<E> mul(E value) {
+        return PropertyFactory.createNumeric(new ASTMultiply(getExpression(), value), getType());
+    }
+
+    /**
+     * @return new property that represents '*' operator with current property as argument
+     */
+    public NumericProperty<E> mul(NumericProperty<?> value) {
+        return PropertyFactory.createNumeric(new ASTMultiply(getExpression(), value.getExpression()), getType());
+    }
+
+    /**
+     * @return new property that represents negative value of current property
+     */
+    public NumericProperty<E> neg() {
+        return PropertyFactory.createNumeric(new ASTNegate(getExpression()), getType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public NumericProperty<E> alias(String alias) {
+        return PropertyFactory.createNumeric(alias, this.getExpression(), this.getType());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/Property.java
new file mode 100644
index 0000000..50e69fe
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/Property.java
@@ -0,0 +1,45 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * Base interface for all types of properties
+ * @since 4.2
+ */
+public interface Property<E> {
+
+    /**
+     * @return name of this property, can be null
+     */
+    String getName();
+
+    /**
+     * @return expression that defines this property, not null
+     */
+    Expression getExpression();
+
+    /**
+     * @return java type of this property, can be null
+     */
+    Class<E> getType();
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java
new file mode 100644
index 0000000..c355d73
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/PropertyFactory.java
@@ -0,0 +1,375 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import java.time.LocalDateTime;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
+
+/**
+ *
+ * Factory class that produces all property types.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class PropertyFactory {
+
+    /**
+     * Property that can be used to select {@code COUNT(*)}
+     * <p>
+     * Usage:<pre>{@code
+     * ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME, PropertyFactory.COUNT);
+     * }</pre>
+     * @see org.apache.cayenne.query.ObjectSelect#selectCount(ObjectContext)
+     */
+    public static final NumericProperty<Long> COUNT = createNumeric(FunctionExpressionFactory.countExp(), Long.class);
+
+    /**
+     * Property that corresponds to SQL function {@code NOW()}
+     * <p>
+     * Usage:<pre>{@code
+     * ObjectSelect.query(Artist.class).where(Artist.DATE_OF_BIRTH.year().lt(PropertyFactory.NOW.year().sub(100)));
+     * }</pre>
+     */
+    public static final DateProperty<LocalDateTime> NOW = createDate(FunctionExpressionFactory.currentTimestamp(), LocalDateTime.class);
+
+    // BaseProperty
+
+    /**
+     * Create base property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression
+     */
+    public static <T> BaseProperty<T> createBase(String name, Expression expression, Class<T> type) {
+        return new BaseProperty<>(name, expression, type);
+    }
+
+    /**
+     * Create base property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new path property
+     */
+    public static <T> BaseProperty<T> createBase(String name, Class<T> type) {
+        return createBase(name, null, type);
+    }
+
+    /**
+     * Create base property
+     *
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression without name
+     */
+    public static <T> BaseProperty<T> createBase(Expression expression, Class<T> type) {
+        return createBase(null, expression, type);
+    }
+
+    // StringProperty
+
+    /**
+     * Create string property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression
+     */
+    public static <T extends CharSequence> StringProperty<T> createString(String name, Expression expression, Class<T> type) {
+        return new StringProperty<>(name, expression, type);
+    }
+
+    /**
+     * Create string property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new path property
+     */
+    public static <T extends CharSequence> StringProperty<T> createString(String name, Class<T> type) {
+        return createString(name, null, type);
+    }
+
+    /**
+     * Create string property
+     *
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression without name
+     */
+    public static <T extends CharSequence> StringProperty<T> createString(Expression expression, Class<T> type) {
+        return createString(null, expression, type);
+    }
+
+    // NumericProperty
+
+    /**
+     * Create numeric property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression
+     */
+    public static <T extends Number> NumericProperty<T> createNumeric(String name, Expression expression, Class<T> type) {
+        return new NumericProperty<>(name, expression, type);
+    }
+
+    /**
+     * Create numeric property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new path property
+     */
+    public static <T extends Number> NumericProperty<T> createNumeric(String name, Class<T> type) {
+        return createNumeric(name, null, type);
+    }
+
+    /**
+     * Create numeric property
+     *
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression without name
+     */
+    public static <T extends Number> NumericProperty<T> createNumeric(Expression expression, Class<T> type) {
+        return createNumeric(null, expression, type);
+    }
+
+    // DateProperty
+
+    /**
+     * Create date property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression
+     */
+    public static <T> DateProperty<T> createDate(String name, Expression expression, Class<T> type) {
+        return new DateProperty<>(name, expression, type);
+    }
+
+    /**
+     * Create date property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new path property
+     */
+    public static <T> DateProperty<T> createDate(String name, Class<T> type) {
+        return createDate(name, null, type);
+    }
+
+    /**
+     * Create date property
+     *
+     * @param expression that property will use
+     * @param type type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression without name
+     */
+    public static <T> DateProperty<T> createDate(Expression expression, Class<T> type) {
+        return createDate(null, expression, type);
+    }
+
+    // ToOne relationship property
+
+    /**
+     * Create entity property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param entityType type of represented relationship entity
+     * @param <T> type of represented relationship entity
+     * @return new property with custom expression
+     */
+    public static <T extends Persistent> EntityProperty<T> createEntity(String name, Expression expression, Class<T> entityType) {
+        return new EntityProperty<>(name, expression, entityType);
+    }
+
+    /**
+     * Create entity property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param type type of represented relationship entity
+     * @param <T> type of represented relationship entity
+     * @return new path property
+     */
+    public static <T extends Persistent> EntityProperty<T> createEntity(String name, Class<T> type) {
+        return createEntity(name, null, type);
+    }
+
+    /**
+     * Create entity property
+     *
+     * @param expression that property will use
+     * @param type type of represented relationship entity
+     * @param <T> type of represented relationship entity
+     * @return new property with custom expression without name
+     */
+    public static <T extends Persistent> EntityProperty<T> createEntity(Expression expression, Class<T> type) {
+        return createEntity(null, expression, type);
+    }
+
+    // Self properties
+
+    /**
+     * <b>Self</b> property allows to create column queries that return
+     * full objects along with custom column set.
+     *  <p>
+     *  Usage example, query will return object with dependent objects count:<pre>{@code
+     *  List<Object[]> result = ObjectSelect.columnQuery(Artist.class,
+     *          PropertyFactory.createSelf(Artist.class),
+     *          Artist.PAINTING_ARRAY.count())
+     *      .select(context); }</pre>
+     *
+     * @param type of represented entity
+     * @param <T> type of represented entity
+     * @return new 'self' property
+     */
+    public static <T extends Persistent> EntityProperty<T> createSelf(Class<T> type) {
+        return createEntity(ExpressionFactory.fullObjectExp(), type);
+    }
+
+    /**
+     * <b>Self</b> property allows to create column queries that return
+     * full objects along with custom column set.
+     *  <p>
+     *  This method is not much useful, as to-one property can be used as is in this case,
+     *  example is purely for demonstration purpose only. See {@link EntityProperty} usage examples.
+     *  <p>
+     *  Usage example, query will return object with dependent objects count:<pre>{@code
+     *  List<Object[]> result = ObjectSelect.columnQuery(Painting.class,
+     *          Painting.PAINTING_TITLE,
+     *          PropertyFactory.createSelf(Painting.TO_ARTIST.getExpression(), Painting.class))
+     *      .select(context); }</pre>
+     *
+     * @param expression expression to be used for this property (usually it will be path from other property)
+     * @param type of represented entity
+     * @param <T> type of represented entity
+     * @return new 'self' property
+     */
+    public static <T extends Persistent> EntityProperty<T> createSelf(Expression expression, Class<T> type) {
+        return createEntity(ExpressionFactory.fullObjectExp(expression), type);
+    }
+
+    // ToMany relationship properties
+
+    /**
+     * Create to-many relationship mapped on list property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param entityType type of represented relationship entity
+     * @param <T> type of represented relationship entity
+     * @return new property with custom expression
+     */
+    public static <T extends Persistent> ListProperty<T> createList(String name, Expression expression, Class<T> entityType) {
+        return new ListProperty<>(name, expression, entityType);
+    }
+
+    /**
+     * Create to-many relationship mapped on list property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param entityType type of represented relationship entity
+     * @param <T> type of represented relationship entity
+     * @return new path property
+     */
+    public static <T extends Persistent> ListProperty<T> createList(String name, Class<T> entityType) {
+        return createList(name, null, entityType);
+    }
+
+    /**
+     * Create to-many relationship mapped on set property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param entityType type of represented attribute
+     * @param <T> type of represented attribute
+     * @return new property with custom expression
+     */
+    public static <T extends Persistent> SetProperty<T> createSet(String name, Expression expression, Class<T> entityType) {
+        return new SetProperty<>(name, expression, entityType);
+    }
+
+    /**
+     * Create to-many relationship mapped on set property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param entityType type of represented relationship entity
+     * @param <T> type of represented relationship entity
+     * @return new path property
+     */
+    public static <T extends Persistent> SetProperty<T> createSet(String name, Class<T> entityType) {
+        return createSet(name, null, entityType);
+    }
+
+    /**
+     * Create to-many relationship mapped on map property
+     *
+     * @param name of the property
+     * @param expression that property will use
+     * @param keyType type of represented relationship keys
+     * @param entityType type of represented relationship values
+     * @param <K> type of represented relationship keys
+     * @param <V> type of represented relationship values
+     * @return new property with custom expression
+     */
+    public static <K, V extends Persistent> MapProperty<K, V> createMap(String name, Expression expression, Class<K> keyType, Class<V> entityType) {
+        return new MapProperty<>(name, expression, keyType, entityType);
+    }
+
+    /**
+     * Create to-many relationship mapped on map property
+     *
+     * @param name of the property, will be used as value for path expression
+     * @param keyType type of represented relationship keys
+     * @param entityType type of represented relationship values
+     * @param <K> type of represented relationship keys
+     * @param <V> type of represented relationship values
+     * @return new path property
+     */
+    public static <K, V extends Persistent> MapProperty<K, V> createMap(String name, Class<K> keyType, Class<V> entityType) {
+        return createMap(name, null, keyType, entityType);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
new file mode 100644
index 0000000..13b9fdc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/RelationshipProperty.java
@@ -0,0 +1,158 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.query.PrefetchTreeNode;
+
+/**
+ * Interface (or "Trait") that provides basic functionality for all types of relationships.
+ * <p>
+ * Provides "dot", prefetch and "outer" functionality.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public interface RelationshipProperty<E> extends Property<E> {
+
+    /**
+     * Constructs a property path by appending the argument to the existing property separated by a dot.
+     *
+     * @return a newly created Property object.
+     */
+    default BaseProperty<Object> dot(String property) {
+        return PropertyFactory.createBase(getName() + "." + property, null);
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T> BaseProperty<T> dot(BaseProperty<T> property) {
+        return PropertyFactory.createBase(getName() + "." + property.getName(), property.getType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T extends Number> NumericProperty<T> dot(NumericProperty<T> property) {
+        return PropertyFactory.createNumeric(getName() + "." + property.getName(), property.getType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T extends CharSequence> StringProperty<T> dot(StringProperty<T> property) {
+        return PropertyFactory.createString(getName() + "." + property.getName(), property.getType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T> DateProperty<T> dot(DateProperty<T> property) {
+        return PropertyFactory.createDate(getName() + "." + property.getName(), property.getType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T extends Persistent> EntityProperty<T> dot(EntityProperty<T> property) {
+        return PropertyFactory.createEntity(getName() + "." + property.getName(), property.getType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T extends Persistent> ListProperty<T> dot(ListProperty<T> property) {
+        return PropertyFactory.createList(getName() + "." + property.getName(), property.getEntityType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <T extends Persistent> SetProperty<T> dot(SetProperty<T> property) {
+        return PropertyFactory.createSet(getName() + "." + property.getName(), property.getEntityType());
+    }
+
+    /**
+     * Constructs a new property path by appending the argument to the existing property separated by a dot.
+     *
+     * @param property to append to path
+     * @return a newly created Property object.
+     */
+    default <K, V extends Persistent> MapProperty<K, V> dot(MapProperty<K, V> property) {
+        return PropertyFactory.createMap(getName() + "." + property.getName(), property.getKeyType(), property.getEntityType());
+    }
+
+    /**
+     * 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.
+     */
+    BaseProperty<E> outer();
+
+    /**
+     * 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.
+     */
+    default PrefetchTreeNode joint() {
+        return PrefetchTreeNode.withPath(getName(), 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.
+     */
+    default PrefetchTreeNode disjoint() {
+        return PrefetchTreeNode.withPath(getName(), 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.
+     */
+    default PrefetchTreeNode disjointById() {
+        return PrefetchTreeNode.withPath(getName(), PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
new file mode 100644
index 0000000..f7ded0b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
@@ -0,0 +1,63 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import java.util.Set;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * Property that represents to-many relationship mapped on {@link Set}.
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class SetProperty<V extends Persistent> extends CollectionProperty<V, Set<V>> {
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name           of the property (will be used as alias for the expression)
+     * @param expression     expression for property
+     * @param entityType     type of related entity
+     */
+    protected SetProperty(String name, Expression expression, Class<V> entityType) {
+        super(name, expression, Set.class, entityType);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SetProperty<V> alias(String alias) {
+        return PropertyFactory.createSet(alias, this.getExpression(), this.getEntityType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SetProperty<V> outer() {
+        return getName().endsWith("+")
+                ? this
+                : PropertyFactory.createSet(getName() + "+", getEntityType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
new file mode 100644
index 0000000..87a6e0f
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
@@ -0,0 +1,304 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.property;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
+
+/**
+ * Property that represents attributes mapped on string types
+ * <p>
+ * String type is an any type inherited from {@link CharSequence}.
+ * <p>
+ * Provides basic string functions like {@link #like(String)}, {@link #concat(Object...)}, {@link #upper()}
+ * and {@link #contains(String)}}.
+ * <p>
+ * Example:<pre>{@code
+ * ObjectSelect.query(Artist.class)
+ *      .where(Artist.FIRST_NAME.trim().concat(Artist.LAST_NAME.trim()).length().gt(30))
+ * }</pre>
+ *
+ * @see org.apache.cayenne.exp.property
+ * @since 4.2
+ */
+public class StringProperty<E extends CharSequence> extends BaseProperty<E> {
+
+    /**
+     * Constructs a new property with the given name and expression
+     *
+     * @param name       of the property (will be used as alias for the expression)
+     * @param expression expression for property
+     * @param type       of the property
+     * @see PropertyFactory#createString(String, Expression, Class)
+     */
+    protected StringProperty(String name, Expression expression, Class<E> type) {
+        super(name, expression, type);
+    }
+
+    /**
+     * @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(getExpression(), pattern);
+    }
+
+    /**
+     * @param pattern a pattern matching property value. Pattern may include "_" and
+     *                "%" wildcard symbols to match any single character or a
+     *                sequence of characters.
+     * @return An expression for a Database "LIKE" query.
+     */
+    public Expression like(StringProperty<?> pattern) {
+        return ExpressionFactory.likeExp(getExpression(), pattern.getExpression());
+    }
+
+    /**
+     * @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(getExpression(), pattern, escapeChar);
+    }
+
+    /**
+     * @return An expression for a case insensitive "LIKE" query.
+     */
+    public Expression likeIgnoreCase(String pattern) {
+        return ExpressionFactory.likeIgnoreCaseExp(getExpression(), pattern);
+    }
+
+    /**
+     * @return An expression for a case insensitive "LIKE" query.
+     */
+    public Expression likeIgnoreCase(StringProperty<?> pattern) {
+        return ExpressionFactory.likeIgnoreCaseExp(getExpression(), pattern.getExpression());
+    }
+
+    /**
+     * @return An expression for a Database "NOT LIKE" query.
+     */
+    public Expression nlike(String value) {
+        return ExpressionFactory.notLikeExp(getExpression(), value);
+    }
+
+    /**
+     * @return An expression for a Database "NOT LIKE" query.
+     */
+    public Expression nlike(StringProperty<?> value) {
+        return ExpressionFactory.notLikeExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * @return An expression for a case insensitive "NOT LIKE" query.
+     */
+    public Expression nlikeIgnoreCase(String value) {
+        return ExpressionFactory.notLikeIgnoreCaseExp(getExpression(), value);
+    }
+
+    /**
+     * @return An expression for a case insensitive "NOT LIKE" query.
+     */
+    public Expression nlikeIgnoreCase(StringProperty<?> value) {
+        return ExpressionFactory.notLikeIgnoreCaseExp(getExpression(), value.getExpression());
+    }
+
+    /**
+     * 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(getExpression(), 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(getExpression(), 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(getExpression(), value);
+    }
+
+    /**
+     * Same as {@link #contains(String)}, only using case-insensitive
+     * comparison.
+     */
+    public Expression containsIgnoreCase(String value) {
+        return ExpressionFactory.containsIgnoreCaseExp(getExpression(), value);
+    }
+
+    /**
+     * Same as {@link #startsWith(String)}, only using case-insensitive
+     * comparison.
+     */
+    public Expression startsWithIgnoreCase(String value) {
+        return ExpressionFactory.startsWithIgnoreCaseExp(getExpression(), value);
+    }
+
+    /**
+     * Same as {@link #endsWith(String)}, only using case-insensitive
+     * comparison.
+     */
+    public Expression endsWithIgnoreCase(String value) {
+        return ExpressionFactory.endsWithIgnoreCaseExp(getExpression(), value);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#lengthExp(Expression)
+     */
+    public NumericProperty<Integer> length() {
+        return PropertyFactory.createNumeric(
+                FunctionExpressionFactory.lengthExp(getExpression()),
+                Integer.class
+        );
+    }
+
+    /**
+     * @see FunctionExpressionFactory#locateExp(String, Expression)
+     */
+    public NumericProperty<Integer> locate(String string) {
+        return PropertyFactory.createNumeric(
+                FunctionExpressionFactory.locateExp(ExpressionFactory.wrapScalarValue(string), getExpression()),
+                Integer.class
+        );
+    }
+
+    /**
+     * @see FunctionExpressionFactory#locateExp(Expression, Expression)
+     */
+    public NumericProperty<Integer> locate(StringProperty<? extends String> property) {
+        return PropertyFactory.createNumeric(
+                FunctionExpressionFactory.locateExp(property.getExpression(), getExpression()),
+                Integer.class
+        );
+    }
+
+    /**
+     * @see FunctionExpressionFactory#trimExp(Expression)
+     */
+    public StringProperty<String> trim() {
+        return PropertyFactory.createString(FunctionExpressionFactory.trimExp(getExpression()), String.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#upperExp(Expression)
+     */
+    public StringProperty<String> upper() {
+        return PropertyFactory.createString(FunctionExpressionFactory.upperExp(getExpression()), String.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#lowerExp(Expression)
+     */
+    public StringProperty<String> lower() {
+        return PropertyFactory.createString(FunctionExpressionFactory.lowerExp(getExpression()), String.class);
+    }
+
+    /**
+     * <p>Arguments will be converted as follows:
+     * <ul>
+     *      <li>if argument is a {@link BaseProperty} 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 StringProperty<String> concat(Object... args) {
+        Expression[] exp = new Expression[args.length + 1];
+        int i = 0;
+        exp[i++] = getExpression();
+        for(Object arg : args) {
+            if(arg instanceof BaseProperty) {
+                exp[i++] = ((BaseProperty) arg).getExpression();
+            } else if(arg instanceof Expression) {
+                exp[i++] = (Expression) arg;
+            } else if(arg != null) {
+                exp[i++] = ExpressionFactory.wrapScalarValue(arg.toString());
+            }
+        }
+        return PropertyFactory.createString(FunctionExpressionFactory.concatExp(exp), String.class);
+    }
+
+    /**
+     * @see FunctionExpressionFactory#substringExp(Expression, int, int)
+     */
+    public StringProperty<String> substring(int offset, int length) {
+        return PropertyFactory.createString(
+                FunctionExpressionFactory.substringExp(getExpression(), offset, length),
+                String.class
+        );
+    }
+
+    /**
+     * @see FunctionExpressionFactory#substringExp(Expression, Expression, Expression)
+     */
+    public StringProperty<String> substring(NumericProperty<?> offset, NumericProperty<?> length) {
+        return PropertyFactory.createString(
+                FunctionExpressionFactory.substringExp(getExpression(), offset.getExpression(), length.getExpression()),
+                String.class
+        );
+    }
+
+    /**
+     * Creates alias with different name for this property
+     */
+    @Override
+    public StringProperty<E> alias(String alias) {
+        return PropertyFactory.createString(alias, this.getExpression(), this.getType());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/exp/property/package-info.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/package-info.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/package-info.java
new file mode 100644
index 0000000..79bc254
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/package-info.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+/**
+ * <h3>Property API</h3>
+ * <p>
+ * This API allows to use type aware {@link org.apache.cayenne.exp.Expression expression} factories aka Properties.<br>
+ * These properties are normally generated as static constants in model classes, but they can also be created manually by
+ * {@link org.apache.cayenne.exp.property.PropertyFactory} if needed.
+ * <p>
+ * Typical usage in select queries:
+ * <pre>{@code
+ * Painting painting = ...
+ * Artist artist = ObjectSelect.query(Artist.class)
+ *        .where(Artist.PAINTING_ARRAY.contains(painting))
+ *        .and(Artist.DATE_OF_BIRTH.year().gt(1950))
+ *        .and(Artist.ARTIST_NAME.like("Pablo%"))
+ *        .orderBy(Artist.ARTIST_NAME.asc())
+ *        .prefetch(Artist.PAINTING_ARRAY.disjointById())
+ *        .selectOne(context);
+ * }</pre>
+ * <p>
+ * Currently supported Property types:
+ * <ul>
+ *     <li>{@link org.apache.cayenne.exp.property.NumericProperty} for all data types inherited from {@link java.lang.Number}.<br>
+ *     Supports comparision and math functions (like {@link org.apache.cayenne.exp.property.NumericProperty#sqrt() sqrt()}).
+ *     <br>
+ *     <li>{@link org.apache.cayenne.exp.property.StringProperty} for all data types inherited from {@link java.lang.CharSequence}.<br>
+ *     Supports multiple string functions ({@link org.apache.cayenne.exp.property.StringProperty#like(java.lang.String) like()},
+ *     {@link org.apache.cayenne.exp.property.StringProperty#concat(java.lang.Object...) concat()}, etc.)
+ *     <br>
+ *     <li>{@link org.apache.cayenne.exp.property.DateProperty} for {@link java.util.Date} (and {@link java.sql} variants)
+ *     and {@link java.time.LocalDate}, {@link java.time.LocalTime}, {@link java.time.LocalDateTime}.<br>
+ *     Supports date functions like {@link org.apache.cayenne.exp.property.DateProperty#year() year()}.
+ *     <br>
+ *     <li>{@link org.apache.cayenne.exp.property.EntityProperty} for to-one relationships.<br>
+ *     Supports prefetch related methods, {@link org.apache.cayenne.exp.property.RelationshipProperty#dot(org.apache.cayenne.exp.property.BaseProperty) dot()} methods, etc.
+ *     <br>
+ *     <li>{@link org.apache.cayenne.exp.property.ListProperty}, {@link org.apache.cayenne.exp.property.SetProperty}
+ *     and {@link org.apache.cayenne.exp.property.MapProperty} are for to-many relationships.<br>
+ *     In addition to to-one related methods these properties support collection comparision methods
+ *     like {@link org.apache.cayenne.exp.property.ListProperty#contains(org.apache.cayenne.Persistent) contains()}.
+ *     <br>
+ *     <li>{@link org.apache.cayenne.exp.property.BaseProperty} for all other data types, supports basic operations (equality, sorting).
+ * </ul>
+ *
+ * @since 4.2
+ */
+package org.apache.cayenne.exp.property;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
index f72fc85..35c36f2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
@@ -26,9 +26,13 @@ import java.util.Collection;
 import java.util.Collections;
 
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.exp.property.BaseProperty;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.ComparableProperty;
+import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
@@ -56,13 +60,13 @@ import org.apache.cayenne.map.ObjEntity;
  * </pre>
  * </p>
  * <p><b>Note: this class can't be instantiated directly. Use {@link ObjectSelect}.</b></p>
- * @see ObjectSelect#columnQuery(Class, Property)
+ * @see ObjectSelect#columnQuery(Class, BaseProperty)
  *
  * @since 4.0
  */
 public class ColumnSelect<T> extends FluentSelect<T> {
 
-    private Collection<Property<?>> columns;
+    private Collection<BaseProperty<?>> columns;
     private boolean havingExpressionIsActive = false;
     // package private for tests
     boolean singleColumn = true;
@@ -137,7 +141,6 @@ public class ColumnSelect<T> extends FluentSelect<T> {
         return resetEntity(null, null, dbEntityName);
     }
 
-    @SuppressWarnings("unchecked")
     private ColumnSelect<T> resetEntity(Class<?> entityType, String entityName, String dbEntityName) {
         this.entityType = entityType;
         this.entityName = entityName;
@@ -185,7 +188,6 @@ public class ColumnSelect<T> extends FluentSelect<T> {
      *
      * @return this object
      */
-    @SuppressWarnings("unchecked")
     public ColumnSelect<T> or(Expression... expressions) {
         if (expressions == null || expressions.length == 0) {
             return this;
@@ -440,11 +442,11 @@ public class ColumnSelect<T> extends FluentSelect<T> {
      *
      * @param firstProperty first property
      * @param otherProperties array of properties to select
-     * @see ColumnSelect#column(Property)
+     * @see ColumnSelect#column(BaseProperty)
      * @see ColumnSelect#columns(Collection)
      */
     @SuppressWarnings("unchecked")
-    public ColumnSelect<Object[]> columns(Property<?> firstProperty, Property<?>... otherProperties) {
+    public ColumnSelect<Object[]> columns(BaseProperty<?> firstProperty, BaseProperty<?>... otherProperties) {
         if (columns == null) {
             columns = new ArrayList<>(otherProperties.length + 1);
         }
@@ -461,10 +463,10 @@ public class ColumnSelect<T> extends FluentSelect<T> {
      * (root entity properties, function call expressions, properties of relationships, etc).</p>
      * <p>
      * @param properties collection of properties, <b>must</b> contain at least one element
-     * @see ColumnSelect#columns(Property, Property[])
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
      */
     @SuppressWarnings("unchecked")
-    public ColumnSelect<Object[]> columns(Collection<Property<?>> properties) {
+    public ColumnSelect<Object[]> columns(Collection<BaseProperty<?>> properties) {
         if (properties == null){
             throw new NullPointerException("properties is null");
         }
@@ -483,7 +485,7 @@ public class ColumnSelect<T> extends FluentSelect<T> {
     }
 
     @SuppressWarnings("unchecked")
-    protected  <E> ColumnSelect<E> column(Property<E> property) {
+    protected  <E> ColumnSelect<E> column(BaseProperty<E> property) {
         if (this.columns == null) {
             this.columns = new ArrayList<>(1);
         } else {
@@ -495,10 +497,10 @@ public class ColumnSelect<T> extends FluentSelect<T> {
     }
 
     /**
-     * <p>Shortcut for {@link #columns(Property, Property[])} columns}(Property.COUNT)</p>
+     * <p>Shortcut for {@link #columns(BaseProperty, BaseProperty[])} columns}(Property.COUNT)</p>
      */
     public ColumnSelect<Object[]> count() {
-        return columns(Property.COUNT);
+        return columns(PropertyFactory.COUNT);
     }
 
     /**
@@ -506,43 +508,63 @@ public class ColumnSelect<T> extends FluentSelect<T> {
      * <p>Can return different result than COUNT(*) as it will count only non null values</p>
      * @see ColumnSelect#count()
      */
-    public ColumnSelect<Object[]> count(Property<?> property) {
+    public ColumnSelect<Object[]> count(BaseProperty<?> property) {
         return columns(property.count());
     }
 
     /**
      * <p>Select minimum value of property</p>
-     * @see ColumnSelect#columns(Property, Property[])
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
      */
-    public ColumnSelect<Object[]> min(Property<?> property) {
+    public ColumnSelect<Object[]> min(ComparableProperty<?> property) {
         return columns(property.min());
     }
 
     /**
      * <p>Select maximum value of property</p>
-     * @see ColumnSelect#columns(Property, Property[])
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
      */
-    public ColumnSelect<Object[]> max(Property<?> property) {
+    public ColumnSelect<Object[]> max(ComparableProperty<?> property) {
         return columns(property.max());
     }
 
     /**
      * <p>Select average value of property</p>
-     * @see ColumnSelect#columns(Property, Property[])
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
+     * @deprecated since 4.2 use {@link #avg(NumericProperty)}
      */
+    @Deprecated
     public ColumnSelect<Object[]> avg(Property<?> property) {
         return columns(property.avg());
     }
 
     /**
+     * <p>Select average value of property</p>
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
+     */
+    public ColumnSelect<Object[]> avg(NumericProperty<?> property) {
+        return columns(property.avg());
+    }
+
+    /**
      * <p>Select sum of values</p>
-     * @see ColumnSelect#columns(Property, Property[])
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
+     * @deprecated since 4.2 use {@link #sum(NumericProperty)}
      */
+    @Deprecated
     public <E extends Number> ColumnSelect<Object[]> sum(Property<E> property) {
         return columns(property.sum());
     }
 
     /**
+     * <p>Select sum of values</p>
+     * @see ColumnSelect#columns(BaseProperty, BaseProperty[])
+     */
+    public <E extends Number> ColumnSelect<Object[]> sum(NumericProperty<E> property) {
+        return columns(property.sum());
+    }
+
+    /**
      * Appends a having qualifier expression of this query. An equivalent to
      * {@link #and(Expression...)} that can be used a syntactic sugar.
      *
@@ -653,7 +675,7 @@ public class ColumnSelect<T> extends FluentSelect<T> {
         }
     }
 
-    public Collection<Property<?>> getColumns() {
+    public Collection<BaseProperty<?>> getColumns() {
         return columns;
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
index f5e3c99..e1e6942 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
@@ -20,9 +20,13 @@ package org.apache.cayenne.query;
 
 import org.apache.cayenne.DataRow;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.exp.property.BaseProperty;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.ComparableProperty;
+import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
@@ -131,7 +135,7 @@ public class ObjectSelect<T> extends FluentSelect<T> {
      * @param entityType base persistent class that will be used as a root for this query
      * @param column single column to select
      */
-    public static <E> ColumnSelect<E> columnQuery(Class<?> entityType, Property<E> column) {
+    public static <E> ColumnSelect<E> columnQuery(Class<?> entityType, BaseProperty<E> column) {
         return new ColumnSelect<>().entityType(entityType).column(column);
     }
 
@@ -142,7 +146,7 @@ public class ObjectSelect<T> extends FluentSelect<T> {
      * @param firstColumn column to select
      * @param otherColumns columns to select
      */
-    public static ColumnSelect<Object[]> columnQuery(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
+    public static ColumnSelect<Object[]> columnQuery(Class<?> entityType, BaseProperty<?> firstColumn, BaseProperty<?>... otherColumns) {
         return new ColumnSelect<Object[]>().entityType(entityType).columns(firstColumn, otherColumns);
     }
 
@@ -560,9 +564,9 @@ public class ObjectSelect<T> extends FluentSelect<T> {
      * </pre>
      *
      * @param properties array of properties to select
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
      */
-    public ColumnSelect<Object[]> columns(Property<?> firstProperty, Property<?>... properties) {
+    public ColumnSelect<Object[]> columns(BaseProperty<?> firstProperty, BaseProperty<?>... properties) {
         return new ColumnSelect<>(this).columns(firstProperty, properties);
     }
 
@@ -570,7 +574,7 @@ public class ObjectSelect<T> extends FluentSelect<T> {
      * <p>Select one specific property.</p>
      * <p>Can be any property that can be resolved against root entity type
      * (root entity's property, function call expression, property of relationships, etc)</p>
-     * <p>If you need several columns use {@link ObjectSelect#columns(Property, Property[])} method.</p>
+     * <p>If you need several columns use {@link ObjectSelect#columns(BaseProperty, BaseProperty[])} method.</p>
      * <p>
      * <pre>
      * {@code
@@ -581,63 +585,99 @@ public class ObjectSelect<T> extends FluentSelect<T> {
      * </pre>
      * </p>
      * @param property single property to select
-     * @see ObjectSelect#columns(Property, Property[])
+     * @see ObjectSelect#columns(BaseProperty, BaseProperty[])
      */
-    public <E> ColumnSelect<E> column(Property<E> property) {
+    public <E> ColumnSelect<E> column(BaseProperty<E> property) {
         return new ColumnSelect<>(this).column(property);
     }
 
     /**
      * Select COUNT(*)
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
      */
     public ColumnSelect<Long> count() {
-        return column(Property.COUNT);
+        return column(PropertyFactory.COUNT);
     }
 
     /**
      * <p>Select COUNT(property)</p>
      * <p>Can return different result than COUNT(*) as it will count only non null values</p>
      * @see ObjectSelect#count()
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
      */
-    public ColumnSelect<Long> count(Property<?> property) {
+    public ColumnSelect<Long> count(BaseProperty<?> property) {
         return column(property.count());
     }
 
     /**
      * <p>Select minimum value of property</p>
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
      */
-    public <E> ColumnSelect<E> min(Property<E> property) {
+    public <E> ColumnSelect<E> min(ComparableProperty<E> property) {
         return column(property.min());
     }
 
     /**
+     * <p>Select minimum value of property</p>
+     * @see ObjectSelect#column(BaseProperty)
+     */
+    public <E extends Number> ColumnSelect<E> min(NumericProperty<E> property) {
+        return column(property.min());
+    }
+
+    /**
+     * <p>Select maximum value of property</p>
+     * @see ObjectSelect#column(BaseProperty)
+     */
+    public <E> ColumnSelect<E> max(ComparableProperty<E> property) {
+        return column(property.max());
+    }
+
+    /**
      * <p>Select maximum value of property</p>
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
      */
-    public <E> ColumnSelect<E> max(Property<E> property) {
+    public <E extends Number> ColumnSelect<E> max(NumericProperty<E> property) {
         return column(property.max());
     }
 
     /**
      * <p>Select average value of property</p>
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
+     * @deprecated since 4.2 use {@link #avg(NumericProperty)}
      */
+    @Deprecated
     public <E> ColumnSelect<E> avg(Property<E> property) {
         return column(property.avg());
     }
 
     /**
+     * <p>Select average value of property</p>
+     * @see ObjectSelect#column(BaseProperty)
+     */
+    public <E extends Number> ColumnSelect<E> avg(NumericProperty<E> property) {
+        return column(property.avg());
+    }
+
+    /**
      * <p>Select sum of values</p>
-     * @see ObjectSelect#column(Property)
+     * @see ObjectSelect#column(BaseProperty)
+     * @deprecated since 4.2 use {@link #sum(NumericProperty)}
      */
+    @Deprecated
     public <E extends Number> ColumnSelect<E> sum(Property<E> property) {
         return column(property.sum());
     }
 
     /**
+     * <p>Select sum of values</p>
+     * @see ObjectSelect#column(BaseProperty)
+     */
+    public <E extends Number> ColumnSelect<E> sum(NumericProperty<E> property) {
+        return column(property.sum());
+    }
+
+    /**
      * <p>Quick way to select count of records</p>
      * <p>Usage:
      * <pre>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2050c2e1/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
index 405ef2b..8534335 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
@@ -24,17 +24,12 @@ import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ResultBatchIterator;
 import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.ResultIteratorCallback;
-import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.exp.property.BaseProperty;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.map.QueryDescriptor;
-import org.apache.cayenne.util.XMLEncoder;
-import org.apache.cayenne.util.XMLSerializable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -63,7 +58,7 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	/**
 	 * @since 4.0
 	 */
-	protected Collection<Property<?>> columns;
+	protected Collection<BaseProperty<?>> columns;
 
 	/**
 	 * @since 4.0
@@ -760,14 +755,14 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	 * @since 4.0
 	 * @see SelectQuery#setCanReturnScalarValue(boolean)
 	 */
-	public void setColumns(Collection<Property<?>> columns) {
+	public void setColumns(Collection<BaseProperty<?>> columns) {
 		this.columns = columns;
 	}
 
 	/**
 	 * @since 4.0
 	 */
-	public void setColumns(Property<?>... columns) {
+	public void setColumns(BaseProperty<?>... columns) {
 		if(columns == null || columns.length == 0) {
 			return;
 		}
@@ -797,7 +792,7 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	/**
 	 * @since 4.0
 	 */
-	public Collection<Property<?>> getColumns() {
+	public Collection<BaseProperty<?>> getColumns() {
 		return columns;
 	}