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/03/20 10:18:28 UTC

[3/3] cayenne git commit: CAY-2269 Add support for date/time components extraction in expression functions

CAY-2269 Add support for date/time components extraction in expression functions


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

Branch: refs/heads/master
Commit: 05a77250221c23aede7267835fb55d997560c5b0
Parents: e56a93f
Author: Nikita Timofeev <st...@gmail.com>
Authored: Mon Mar 20 13:00:23 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Mon Mar 20 13:00:23 2017 +0300

----------------------------------------------------------------------
 .../translator/select/QualifierTranslator.java  |  15 +-
 .../cayenne/dba/db2/DB2QualifierTranslator.java |  20 +
 .../dba/derby/DerbyQualifierTranslator.java     |  17 +
 .../firebird/FirebirdQualifierTranslator.java   |  36 +
 .../frontbase/FrontBaseQualifierTranslator.java |  34 +
 .../dba/hsqldb/HSQLQualifierTranslator.java     |  15 +
 .../dba/ingres/IngresQualifierTranslator.java   |  15 +
 .../dba/mysql/MySQLQualifierTranslator.java     |  15 +
 .../openbase/OpenBaseQualifierTranslator.java   |  15 +
 .../dba/oracle/OracleQualifierTranslator.java   |  47 +
 .../postgres/PostgresQualifierTranslator.java   |  37 +
 .../cayenne/dba/sqlite/SQLiteAdapter.java       |   4 +-
 .../cayenne/dba/sqlite/SQLiteDateType.java      |  70 +-
 .../dba/sqlite/SQLiteQualifierTranslator.java   |  60 ++
 .../SQLServerTrimmingQualifierTranslator.java   |  35 +
 .../dba/sybase/SybaseQualifierTranslator.java   |  36 +
 .../cayenne/exp/FunctionExpressionFactory.java  | 155 +++
 .../cayenne/exp/parser/ASTCurrentDate.java      |   5 -
 .../cayenne/exp/parser/ASTCurrentTime.java      |   5 -
 .../cayenne/exp/parser/ASTCurrentTimestamp.java |   5 -
 .../apache/cayenne/exp/parser/ASTExtract.java   | 125 +++
 .../cayenne/exp/parser/ASTFunctionCall.java     |  24 +-
 .../cayenne/exp/parser/ExpressionParser.java    | 302 ++++--
 .../exp/parser/ExpressionParserConstants.java   |  64 +-
 .../parser/ExpressionParserTokenManager.java    | 979 ++++++++++---------
 .../parser/ExpressionParserTreeConstants.java   |  10 +-
 .../cayenne/exp/parser/ExpressionParser.jjt     |  49 +-
 .../cayenne/exp/parser/ASTCurrentDateTest.java  |  51 +
 .../cayenne/exp/parser/ASTCurrentTimeTest.java  |  50 +
 .../exp/parser/ASTCurrentTimestampTest.java     |  54 +
 .../apache/cayenne/exp/parser/ASTExtractIT.java | 240 +++++
 .../cayenne/exp/parser/ASTExtractTest.java      | 162 +++
 .../apache/cayenne/unit/DerbyUnitDbAdapter.java |  12 +
 .../cayenne/unit/FrontBaseUnitDbAdapter.java    |  11 +
 .../org/apache/cayenne/unit/UnitDbAdapter.java  |   5 +
 .../ServerCaseDataSourceInfoProvider.java       |   2 +-
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 37 files changed, 2161 insertions(+), 621 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index aad7eca..deb7d52 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -30,6 +30,7 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.TraversalHandler;
 import org.apache.cayenne.exp.parser.ASTDbPath;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.ASTObjPath;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
@@ -413,7 +414,11 @@ public class QualifierTranslator extends QueryAssemblerHelper implements Travers
 		boolean parenthesisNeeded = parenthesisNeeded(node, parentNode);
 
 		if(node.getType() == Expression.FUNCTION_CALL) {
-			appendFunction((ASTFunctionCall)node);
+			if(node instanceof ASTExtract) {
+				appendExtractFunction((ASTExtract) node);
+			} else {
+				appendFunction((ASTFunctionCall) node);
+			}
 			if(parenthesisNeeded) {
 				out.append("(");
 			}
@@ -623,6 +628,14 @@ public class QualifierTranslator extends QueryAssemblerHelper implements Travers
 	}
 
 	/**
+	 * Special case for extract date/time parts functions as they have many variants
+	 * @since 4.0
+	 */
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		appendFunction(functionExpression);
+	}
+
+	/**
 	 * Append scalar argument of a function call
 	 * Used only for values stored in ASTScalar other
 	 * expressions appended in objectNode() method

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
index 09b369a..6a60233 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2QualifierTranslator.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.parser.ASTEqual;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.ASTNotEqual;
 import org.apache.cayenne.exp.parser.SimpleNode;
@@ -151,4 +152,23 @@ public class DB2QualifierTranslator extends TrimmingQualifierTranslator {
 			super.clearLastFunctionArgDivider(functionExpression);
 		}
 	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		switch (functionExpression.getPart()) {
+			case DAY_OF_MONTH:
+				out.append("DAY");
+				break;
+			case DAY_OF_WEEK:
+			case DAY_OF_YEAR:
+				// db2 variants are without '_'
+				out.append(functionExpression.getPart().name().replace("_", ""));
+				break;
+			default:
+				appendFunction(functionExpression);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
index 84c5889..2767417 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyQualifierTranslator.java
@@ -21,10 +21,12 @@ package org.apache.cayenne.dba.derby;
 import java.io.IOException;
 import java.sql.Types;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.parser.ASTEqual;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.ASTNotEqual;
 import org.apache.cayenne.exp.parser.SimpleNode;
@@ -120,4 +122,19 @@ public class DerbyQualifierTranslator extends TrimmingQualifierTranslator {
 			super.clearLastFunctionArgDivider(functionExpression);
 		}
 	}
+
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		switch (functionExpression.getPart()) {
+			case DAY_OF_MONTH:
+				out.append("DAY");
+				break;
+			case DAY_OF_WEEK:
+			case DAY_OF_YEAR:
+			case WEEK:
+				throw new CayenneRuntimeException("Function " + functionExpression.getPartCamelCaseName() + "() is unsupported in Derby.");
+			default:
+				super.appendExtractFunction(functionExpression);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
index 503358f..eae42f7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdQualifierTranslator.java
@@ -23,6 +23,7 @@ import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.dba.oracle.OracleQualifierTranslator;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 
 import java.io.IOException;
@@ -141,5 +142,40 @@ public class FirebirdQualifierTranslator extends QualifierTranslator {
 			default:
 				super.clearLastFunctionArgDivider(functionExpression);
 		}
+		if(functionExpression instanceof ASTExtract) {
+			out.append(")");
+		}
+	}
+
+	@Override
+	protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+		if (node.getType() == Expression.FUNCTION_CALL) {
+			if (node instanceof ASTExtract) {
+				return false;
+			}
+		}
+
+		return super.parenthesisNeeded(node, parentNode);
+	}
+
+
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		out.append("EXTRACT(");
+		switch (functionExpression.getPart()) {
+			case DAY_OF_MONTH:
+				out.append("DAY");
+				break;
+			case DAY_OF_WEEK:
+				out.append("WEEKDAY");
+				break;
+			case DAY_OF_YEAR:
+				out.append("YEARDAY");
+				break;
+			default:
+				out.append(functionExpression.getPartCamelCaseName());
+		}
+
+		out.append(" FROM ");
 	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
index 615bf68..8315a4c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
@@ -19,8 +19,11 @@
 
 package org.apache.cayenne.dba.frontbase;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 
 /**
@@ -96,5 +99,36 @@ public class FrontBaseQualifierTranslator extends QualifierTranslator {
             default:
                 super.clearLastFunctionArgDivider(functionExpression);
         }
+        if(functionExpression instanceof ASTExtract) {
+            out.append(")");
+        }
+    }
+
+    @Override
+    protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+        if (node.getType() == Expression.FUNCTION_CALL) {
+            if (node instanceof ASTExtract) {
+                return false;
+            }
+        }
+
+        return super.parenthesisNeeded(node, parentNode);
+    }
+
+    @Override
+    protected void appendExtractFunction(ASTExtract functionExpression) {
+        out.append("EXTRACT(");
+        switch (functionExpression.getPart()) {
+            case DAY_OF_WEEK:
+            case DAY_OF_YEAR:
+            case WEEK:
+                throw new CayenneRuntimeException("Function " + functionExpression.getPartCamelCaseName() + "() is unsupported in FrontBase.");
+            case DAY_OF_MONTH:
+                out.append("DAY");
+                break;
+            default:
+                out.append(functionExpression.getPart().name());
+        }
+        out.append(" FROM ");
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
index bc3990d..513a707 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
 
@@ -69,4 +70,18 @@ public class HSQLQualifierTranslator extends TrimmingQualifierTranslator {
             super.appendFunction(functionExpression);
         }
     }
+
+    @Override
+    protected void appendExtractFunction(ASTExtract functionExpression) {
+        switch (functionExpression.getPart()) {
+            case DAY_OF_WEEK:
+            case DAY_OF_MONTH:
+            case DAY_OF_YEAR:
+                // hsqldb variants are without '_'
+                out.append(functionExpression.getPart().name().replace("_", ""));
+                break;
+            default:
+                appendFunction(functionExpression);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
index 222fe96..8334ff4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.Node;
 
@@ -98,6 +99,20 @@ class IngresQualifierTranslator extends TrimmingQualifierTranslator {
         }
     }
 
+    @Override
+    protected void appendExtractFunction(ASTExtract functionExpression) {
+        switch (functionExpression.getPart()) {
+            case DAY_OF_WEEK:
+            case DAY_OF_MONTH:
+            case DAY_OF_YEAR:
+                // ingres variants are without '_'
+                out.append(functionExpression.getPart().name().replace("_", ""));
+                break;
+            default:
+                appendFunction(functionExpression);
+        }
+    }
+
     private void swapNodeChildren(Node node, int i, int j) {
         Node ni = node.jjtGetChild(i);
         Node nj = node.jjtGetChild(j);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
index 77f4dde..ac520e2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
@@ -24,6 +24,7 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
 
 class MySQLQualifierTranslator extends QualifierTranslator {
@@ -88,4 +89,18 @@ class MySQLQualifierTranslator extends QualifierTranslator {
             }
         }
     }
+
+    @Override
+    protected void appendExtractFunction(ASTExtract functionExpression) {
+        switch (functionExpression.getPart()) {
+            case DAY_OF_WEEK:
+            case DAY_OF_MONTH:
+            case DAY_OF_YEAR:
+                // mysql variants are without '_'
+                out.append(functionExpression.getPart().name().replace("_", ""));
+                break;
+            default:
+                appendFunction(functionExpression);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
index 42383b9..dbedc60 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
 import org.apache.cayenne.map.DbAttribute;
 
@@ -159,4 +160,18 @@ public class OpenBaseQualifierTranslator extends QualifierTranslator {
 			objectMatchTranslator.setExpression(node);
 		}
 	}
+
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		switch (functionExpression.getPart()) {
+			case DAY_OF_WEEK:
+			case DAY_OF_MONTH:
+			case DAY_OF_YEAR:
+				// openbase variants are without '_'
+				out.append(functionExpression.getPart().name().replace("_", ""));
+				break;
+			default:
+				appendFunction(functionExpression);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
index 011a33b..fadd468 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.dba.oracle;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.ASTIn;
 import org.apache.cayenne.exp.parser.ASTList;
@@ -152,6 +153,52 @@ public class OracleQualifierTranslator extends TrimmingQualifierTranslator {
 		} else {
 			super.clearLastFunctionArgDivider(functionExpression);
 		}
+
+		if(functionExpression instanceof ASTExtract) {
+			switch (((ASTExtract)functionExpression).getPart()) {
+				case DAY_OF_YEAR:
+					out.append(", 'DDD'");
+					break;
+				case DAY_OF_WEEK:
+					out.append(", 'D'");
+					break;
+				case WEEK:
+					out.append(", 'IW'");
+					break;
+			}
+			out.append(")");
+		}
+	}
+
+	@Override
+	protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+		if (node.getType() == Expression.FUNCTION_CALL) {
+			if (node instanceof ASTExtract) {
+				return false;
+			}
+		}
+
+		return super.parenthesisNeeded(node, parentNode);
+	}
+
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		switch (functionExpression.getPart()) {
+			// use TO_CHAR(date, format) function for parts that is unsupported by EXTRACT()
+			case DAY_OF_YEAR:
+			case DAY_OF_WEEK:
+			case WEEK:
+				out.append("TO_CHAR(");
+				break;
+			case DAY_OF_MONTH:
+				out.append("EXTRACT(DAY FROM ");
+				break;
+			default:
+				out.append("EXTRACT(");
+				out.append(functionExpression.getPart().name());
+				out.append(" FROM ");
+		}
+
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
index 42937bb..3fdc22c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
 
@@ -148,5 +149,41 @@ public class PostgresQualifierTranslator extends TrimmingQualifierTranslator {
 		} else {
 			super.clearLastFunctionArgDivider(functionExpression);
 		}
+
+		if(functionExpression instanceof ASTExtract) {
+			out.append(")");
+		}
+	}
+
+	@Override
+	protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+		if (node.getType() == Expression.FUNCTION_CALL) {
+			if (node instanceof ASTExtract) {
+				return false;
+			}
+		}
+
+		return super.parenthesisNeeded(node, parentNode);
+	}
+
+
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		out.append("EXTRACT(");
+		switch (functionExpression.getPart()) {
+			case DAY_OF_MONTH:
+				out.append("day");
+				break;
+			case DAY_OF_WEEK:
+				out.append("dow");
+				break;
+			case DAY_OF_YEAR:
+				out.append("doy");
+				break;
+			default:
+				out.append(functionExpression.getPartCamelCaseName());
+		}
+
+		out.append(" FROM ");
 	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
index b390ebc..1796700 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
@@ -79,8 +79,8 @@ public class SQLiteAdapter extends JdbcAdapter {
         map.registerType(new SQLiteBigDecimalType());
         map.registerType(new SQLiteFloatType());
         map.registerType(new SQLiteByteArrayType());
-        map.registerType(new SQLiteCalendarType(GregorianCalendar.class));
-        map.registerType(new SQLiteCalendarType(Calendar.class));
+        map.registerType(new SQLiteCalendarType<>(GregorianCalendar.class));
+        map.registerType(new SQLiteCalendarType<>(Calendar.class));
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteDateType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteDateType.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteDateType.java
index 0dba203..497cc81 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteDateType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteDateType.java
@@ -39,48 +39,27 @@ import org.apache.cayenne.access.types.UtilDateType;
 // handling fun.
 class SQLiteDateType extends UtilDateType {
 
-    private DateFormat timestampFormat;
-    private DateFormat dateFormat;
-    private DateFormat timeFormat;
+    private final DateFormat timestampFormat;
+    private final DateFormat dateFormat;
+    private final DateFormat timeFormat;
 
     public SQLiteDateType() {
-        timestampFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");
+        timestampFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss.SSS");
         dateFormat = new SimpleDateFormat("yyyy-MM-dd");
         timeFormat = new SimpleDateFormat("kk:mm:ss");
     }
 
     @Override
     public Date materializeObject(ResultSet rs, int index, int type) throws Exception {
-
-        String string = rs.getString(index);
-
-        if (string == null) {
-            return null;
-        }
-
-        long ts = getLongTimestamp(string);
-        if (ts >= 0) {
-            return new Date(ts);
-        }
-
-        switch (type) {
-            case Types.TIMESTAMP:
-                return getTimestamp(string);
-            case Types.DATE:
-                return getDate(string);
-            case Types.TIME:
-                return rs.getTime(index);
-            default:
-                return getTimestamp(string);
-        }
+        return parseDate(rs.getString(index), type);
     }
 
     @Override
-    public Date materializeObject(CallableStatement rs, int index, int type)
-            throws Exception {
-
-        String string = rs.getString(index);
+    public Date materializeObject(CallableStatement rs, int index, int type) throws Exception {
+        return parseDate(rs.getString(index), type);
+    }
 
+    protected Date parseDate(String string, int type) throws SQLException {
         if (string == null) {
             return null;
         }
@@ -91,12 +70,10 @@ class SQLiteDateType extends UtilDateType {
         }
 
         switch (type) {
-            case Types.TIMESTAMP:
-                return getTimestamp(string);
-            case Types.DATE:
-                return getDate(string);
             case Types.TIME:
                 return getTime(string);
+            case Types.DATE:
+            case Types.TIMESTAMP:
             default:
                 return getTimestamp(string);
         }
@@ -107,17 +84,9 @@ class SQLiteDateType extends UtilDateType {
             synchronized (timestampFormat) {
                 return timestampFormat.parse(string);
             }
-        }
-        catch (ParseException e) {
+        } catch (ParseException e) {
             // also try date format...
-            try {
-                synchronized (dateFormat) {
-                    return dateFormat.parse(string);
-                }
-            }
-            catch (ParseException e1) {
-                throw new SQLException("Unparsable timestamp string: " + string);
-            }
+            return getDate(string);
         }
     }
 
@@ -126,9 +95,8 @@ class SQLiteDateType extends UtilDateType {
             synchronized (dateFormat) {
                 return dateFormat.parse(string);
             }
-        }
-        catch (ParseException e) {
-            throw new SQLException("Unparsable date string: " + string);
+        } catch (ParseException e) {
+            throw new SQLException("Unparsable date/time string: " + string);
         }
     }
 
@@ -137,17 +105,15 @@ class SQLiteDateType extends UtilDateType {
             synchronized (timeFormat) {
                 return timeFormat.parse(string);
             }
-        }
-        catch (ParseException e) {
-            throw new SQLException("Unparsable time string: " + string);
+        } catch (ParseException e) {
+            return getTimestamp(string);
         }
     }
 
     protected long getLongTimestamp(String string) {
         try {
             return Long.parseLong(string);
-        }
-        catch (NumberFormatException e) {
+        } catch (NumberFormatException e) {
             return -1;
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
index bc2bb2e..d31019b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
@@ -22,6 +22,7 @@ package org.apache.cayenne.dba.sqlite;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.Node;
 
@@ -91,6 +92,65 @@ public class SQLiteQualifierTranslator extends QualifierTranslator {
             default:
                 super.clearLastFunctionArgDivider(functionExpression);
         }
+        if(functionExpression instanceof ASTExtract) {
+            out.append(") as integer)");
+        }
+    }
+
+    @Override
+    protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+        if (node.getType() == Expression.FUNCTION_CALL) {
+            if (node instanceof ASTExtract) {
+                return false;
+            }
+        }
+
+        return super.parenthesisNeeded(node, parentNode);
+    }
+
+
+    /**
+     * Translates to cast(strftime('format', column) as integer).
+     * Depends on connection property "date_class", can be set in connection URL (date_class=text).
+     *
+     * https://www.sqlite.org/lang_datefunc.html
+     */
+    @Override
+    protected void appendExtractFunction(ASTExtract functionExpression) {
+        out.append("cast(strftime(");
+
+        switch (functionExpression.getPart()) {
+            case YEAR:
+                out.append("'%Y'");
+                break;
+            case MONTH:
+                out.append("'%m'");
+                break;
+            case WEEK:
+                out.append("'%W'");
+                break;
+            case DAY:
+            case DAY_OF_MONTH:
+                out.append("'%d'");
+                break;
+            case DAY_OF_WEEK:
+                out.append("'%w'");
+                break;
+            case DAY_OF_YEAR:
+                out.append("'%j'");
+                break;
+            case HOUR:
+                out.append("'%H'");
+                break;
+            case MINUTE:
+                out.append("'%M'");
+                break;
+            case SECOND:
+                out.append("'%S'");
+                break;
+        }
+
+        out.append(", ");
     }
 
     private void swapNodeChildren(Node node, int i, int j) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
index 9def833..9847742 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.dba.sqlserver;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.map.DbAttribute;
 
@@ -168,5 +169,39 @@ class SQLServerTrimmingQualifierTranslator extends TrimmingQualifierTranslator {
 				out.append(")");
 			}
 		}
+
+		if(functionExpression instanceof ASTExtract) {
+			out.append(")");
+		}
+	}
+
+	@Override
+	protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+		if (node.getType() == Expression.FUNCTION_CALL) {
+			if (node instanceof ASTExtract) {
+				return false;
+			}
+		}
+
+		return super.parenthesisNeeded(node, parentNode);
+	}
+
+	@Override
+	protected void appendExtractFunction(ASTExtract functionExpression) {
+		out.append("DATEPART(");
+		switch (functionExpression.getPart()) {
+			case DAY_OF_MONTH:
+				out.append("DAY");
+				break;
+			case DAY_OF_WEEK:
+				out.append("WEEKDAY");
+				break;
+			case DAY_OF_YEAR:
+				out.append("DAYOFYEAR");
+				break;
+			default:
+				out.append(functionExpression.getPart().name());
+		}
+		out.append(" , ");
 	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
index 1ad939e..0f6e239 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseQualifierTranslator.java
@@ -21,6 +21,8 @@ package org.apache.cayenne.dba.sybase;
 
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 
 /**
@@ -74,5 +76,39 @@ public class SybaseQualifierTranslator extends QualifierTranslator {
             default:
                 super.clearLastFunctionArgDivider(functionExpression);
         }
+
+        if(functionExpression instanceof ASTExtract) {
+            out.append(")");
+        }
+    }
+
+    @Override
+    protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+        if (node.getType() == Expression.FUNCTION_CALL) {
+            if (node instanceof ASTExtract) {
+                return false;
+            }
+        }
+
+        return super.parenthesisNeeded(node, parentNode);
+    }
+
+    @Override
+    protected void appendExtractFunction(ASTExtract functionExpression) {
+        out.append("datepart(");
+        switch (functionExpression.getPart()) {
+            case DAY_OF_MONTH:
+                out.append("day");
+                break;
+            case DAY_OF_WEEK:
+                out.append("weekday");
+                break;
+            case DAY_OF_YEAR:
+                out.append("dayofyear");
+                break;
+            default:
+                out.append(functionExpression.getPart().name().toLowerCase());
+        }
+        out.append(" , ");
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/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 e4cbf07..114fb1d 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
@@ -26,6 +26,7 @@ import org.apache.cayenne.exp.parser.ASTCount;
 import org.apache.cayenne.exp.parser.ASTCurrentDate;
 import org.apache.cayenne.exp.parser.ASTCurrentTime;
 import org.apache.cayenne.exp.parser.ASTCurrentTimestamp;
+import org.apache.cayenne.exp.parser.ASTExtract;
 import org.apache.cayenne.exp.parser.ASTLength;
 import org.apache.cayenne.exp.parser.ASTLocate;
 import org.apache.cayenne.exp.parser.ASTLower;
@@ -370,4 +371,158 @@ public class FunctionExpressionFactory {
     public static Expression currentTimestamp() {
         return new ASTCurrentTimestamp();
     }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return year(exp) function expression
+     */
+    public static Expression yearExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.YEAR);
+    }
+
+    /**
+     * @param path String path
+     * @return year(path) function expression
+     */
+    public static Expression yearExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.YEAR);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return month(exp) function expression
+     */
+    public static Expression monthExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.MONTH);
+    }
+
+    /**
+     * @param path String path
+     * @return month(path) function expression
+     */
+    public static Expression monthExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.MONTH);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return week(exp) function expression
+     */
+    public static Expression weekExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.WEEK);
+    }
+
+    /**
+     * @param path String path
+     * @return week(path) function expression
+     */
+    public static Expression weekExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.WEEK);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return dayOfYear(exp) function expression
+     */
+    public static Expression dayOfYearExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.DAY_OF_YEAR);
+    }
+
+    /**
+     * @param path String path
+     * @return dayOfYear(path) function expression
+     */
+    public static Expression dayOfYearExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.DAY_OF_YEAR);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return dayOfMonth(exp) function expression, synonym for day()
+     */
+    public static Expression dayOfMonthExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.DAY_OF_MONTH);
+    }
+
+    /**
+     * @param path String path
+     * @return dayOfMonth(path) function expression, synonym for day()
+     */
+    public static Expression dayOfMonthExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.DAY_OF_MONTH);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return dayOfWeek(exp) function expression
+     */
+    public static Expression dayOfWeekExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.DAY_OF_WEEK);
+    }
+
+    /**
+     * @param path String path
+     * @return dayOfWeek(path) function expression
+     */
+    public static Expression dayOfWeekExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.DAY_OF_WEEK);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return hour(exp) function expression
+     */
+    public static Expression hourExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.HOUR);
+    }
+
+    /**
+     * @param path String path
+     * @return hour(path) function expression
+     */
+    public static Expression hourExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.HOUR);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return minute(exp) function expression
+     */
+    public static Expression minuteExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.MINUTE);
+    }
+
+    /**
+     * @param path String path
+     * @return minute(path) function expression
+     */
+    public static Expression minuteExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.MINUTE);
+    }
+
+    /**
+     * @param exp date/timestamp expression
+     * @return second(exp) function expression
+     */
+    public static Expression secondExp(Expression exp) {
+        return extractExp(exp, ASTExtract.DateTimePart.SECOND);
+    }
+
+    /**
+     * @param path String path
+     * @return second(path) function expression
+     */
+    public static Expression secondExp(String path) {
+        return extractExp(path, ASTExtract.DateTimePart.SECOND);
+    }
+
+    static Expression extractExp(String path, ASTExtract.DateTimePart part) {
+        return extractExp(ExpressionFactory.pathExp(path), part);
+    }
+
+    static Expression extractExp(Expression exp, ASTExtract.DateTimePart part) {
+        ASTExtract extract = new ASTExtract(exp);
+        extract.setPart(part);
+        return extract;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
index e453e8f..73743df 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
@@ -53,11 +53,6 @@ public class ASTCurrentDate extends ASTFunctionCall {
     }
 
     @Override
-    protected void appendFunctionNameAsString(Appendable out) throws IOException {
-        out.append("currentDate");
-    }
-
-    @Override
     public Expression shallowCopy() {
         return new ASTCurrentDate(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
index feb9dfa..6cee32c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
@@ -53,11 +53,6 @@ public class ASTCurrentTime extends ASTFunctionCall {
     }
 
     @Override
-    protected void appendFunctionNameAsString(Appendable out) throws IOException {
-        out.append("currentTime");
-    }
-
-    @Override
     public Expression shallowCopy() {
         return new ASTCurrentTime(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
index df55492..9edf263 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
@@ -53,11 +53,6 @@ public class ASTCurrentTimestamp extends ASTFunctionCall {
     }
 
     @Override
-    protected void appendFunctionNameAsString(Appendable out) throws IOException {
-        out.append("currentTimestamp");
-    }
-
-    @Override
     public Expression shallowCopy() {
         return new ASTCurrentTimestamp(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExtract.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExtract.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExtract.java
new file mode 100644
index 0000000..184d298
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExtract.java
@@ -0,0 +1,125 @@
+/*****************************************************************
+ *   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.parser;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.exp.Expression;
+
+public class ASTExtract extends ASTFunctionCall {
+
+    /**
+     * Available components of date/time.
+     * Names must be in sync with tokens used in dateTimeExtractingFunction() rule in ExpressionParser.jjt
+     */
+    public enum DateTimePart {
+        YEAR, MONTH, WEEK,
+        // day options, day is synonym for dayOfMonth
+        DAY_OF_YEAR, DAY, DAY_OF_MONTH, DAY_OF_WEEK,
+        HOUR, MINUTE, SECOND
+    }
+
+    /**
+     * Map from camelCase name to enum elements.
+     * @see ASTFunctionCall#nameToCamelCase(String)
+     */
+    private static final Map<String, DateTimePart> NAME_TO_PART = new HashMap<>();
+    static {
+        for(DateTimePart part : DateTimePart.values()) {
+            NAME_TO_PART.put(nameToCamelCase(part.name()), part);
+        }
+    }
+
+    /**
+     * camelCase name, found in ExpressionParser.jjt tokens
+     */
+    private String partName;
+
+    private DateTimePart part;
+
+    ASTExtract(int id) {
+        super(id, "EXTRACT");
+    }
+
+    public ASTExtract(Expression expression) {
+        super(ExpressionParserTreeConstants.JJTEXTRACT, "EXTRACT", expression);
+    }
+
+    @Override
+    public String getFunctionName() {
+        return part.name();
+    }
+
+    @Override
+    protected void appendFunctionNameAsString(Appendable out) throws IOException {
+        out.append(partName);
+    }
+
+    /**
+     * This method is used by {@link ExpressionParser}
+     * @param partToken {@link Token#image} from {@link ExpressionParser}
+     */
+    void setPartToken(String partToken) {
+        part = NAME_TO_PART.get(partToken);
+        if(part == null) {
+            throw new CayenneRuntimeException("Unknown timestamp part: " + partToken);
+        }
+        this.partName = partToken;
+    }
+
+    /**
+     * This method is used by FunctionExpressionFactory
+     * @param part date/time part to extract
+     */
+    public void setPart(DateTimePart part) {
+        this.part = part;
+        this.partName = nameToCamelCase(part.name());
+    }
+
+    public DateTimePart getPart() {
+        return part;
+    }
+
+    public String getPartCamelCaseName() {
+        return partName;
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        ASTExtract copy = new ASTExtract(id);
+        copy.partName = partName;
+        copy.part = part;
+        return copy;
+    }
+
+    @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
index d31c148..3766793 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
@@ -80,7 +80,7 @@ public abstract class ASTFunctionCall extends EvaluatedNode {
     }
 
     protected void appendFunctionNameAsString(Appendable out) throws IOException {
-        out.append(getFunctionName().toLowerCase());
+        out.append(nameToCamelCase(getFunctionName()));
     }
 
     @Override
@@ -103,4 +103,26 @@ public abstract class ASTFunctionCall extends EvaluatedNode {
         super.appendChildrenAsEJBQL(parameterAccumulator, out, rootId);
         out.append(")");
     }
+
+    /**
+     *
+     * @param functionName in UPPER_UNDERSCORE convention
+     * @return functionName in camelCase convention
+     */
+    protected static String nameToCamelCase(String functionName) {
+        String[] parts = functionName.split("_");
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        for(String part : parts) {
+            if(first) {
+                sb.append(part.toLowerCase());
+                first = false;
+            } else {
+                char[] chars = part.toLowerCase().toCharArray();
+                chars[0] = Character.toTitleCase(chars[0]);
+                sb.append(chars);
+            }
+        }
+        return sb.toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParser.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParser.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParser.java
index 732110d..ff0fde8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParser.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParser.java
@@ -184,10 +184,20 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case CURRENT_DATE:
     case CURRENT_TIME:
     case CURRENT_TIMESTAMP:
-    case 54:
-    case 55:
-    case 56:
-    case 57:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+    case 64:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
     case SINGLE_QUOTED_STRING:
     case DOUBLE_QUOTED_STRING:
@@ -251,10 +261,20 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case CURRENT_DATE:
     case CURRENT_TIME:
     case CURRENT_TIMESTAMP:
-    case 54:
-    case 55:
-    case 56:
-    case 57:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+    case 64:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
     case SINGLE_QUOTED_STRING:
     case DOUBLE_QUOTED_STRING:
@@ -524,7 +544,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                  jjtree.openNodeScope(jjtn011);
           try {
             switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
-            case 54:
+            case 64:
               namedParameter();
               break;
             case 16:
@@ -683,7 +703,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                        jjtree.openNodeScope(jjtn003);
       try {
         switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
-        case 54:
+        case 64:
           namedParameter();
           break;
         case 16:
@@ -803,10 +823,20 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case ABS:
     case SQRT:
     case MOD:
-    case 54:
-    case 55:
-    case 56:
-    case 57:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+    case 64:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
     case INT_LITERAL:
     case FLOAT_LITERAL:
@@ -854,9 +884,9 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
 
   final public void stringParameter() throws ParseException {
     switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
-    case 55:
-    case 56:
-    case 57:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
       pathExpression();
       break;
@@ -947,10 +977,20 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case CURRENT_DATE:
     case CURRENT_TIME:
     case CURRENT_TIMESTAMP:
-    case 54:
-    case 55:
-    case 56:
-    case 57:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+    case 64:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
     case SINGLE_QUOTED_STRING:
     case DOUBLE_QUOTED_STRING:
@@ -1027,22 +1067,22 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                                    }
       }
       break;
-    case 54:
+    case 64:
       namedParameter();
       break;
     case INT_LITERAL:
       jj_consume_token(INT_LITERAL);
-                          ASTScalar jjtn003 = new ASTScalar(JJTSCALAR);
-                          boolean jjtc003 = true;
-                          jjtree.openNodeScope(jjtn003);
+                            ASTScalar jjtn003 = new ASTScalar(JJTSCALAR);
+                            boolean jjtc003 = true;
+                            jjtree.openNodeScope(jjtn003);
       try {
-                          jjtree.closeNodeScope(jjtn003,  0);
-                          jjtc003 = false;
-                          jjtn003.setValue(token_source.literalValue);
-      } finally {
-                          if (jjtc003) {
                             jjtree.closeNodeScope(jjtn003,  0);
-                          }
+                            jjtc003 = false;
+                            jjtn003.setValue(token_source.literalValue);
+      } finally {
+                            if (jjtc003) {
+                              jjtree.closeNodeScope(jjtn003,  0);
+                            }
       }
       break;
     case FLOAT_LITERAL:
@@ -1062,17 +1102,17 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
       break;
     case TRUE:
       jj_consume_token(TRUE);
-                   ASTScalar jjtn005 = new ASTScalar(JJTSCALAR);
-                   boolean jjtc005 = true;
-                   jjtree.openNodeScope(jjtn005);
+                    ASTScalar jjtn005 = new ASTScalar(JJTSCALAR);
+                    boolean jjtc005 = true;
+                    jjtree.openNodeScope(jjtn005);
       try {
-                   jjtree.closeNodeScope(jjtn005,  0);
-                   jjtc005 = false;
-                   jjtn005.setValue(true);
+                    jjtree.closeNodeScope(jjtn005,  0);
+                    jjtc005 = false;
+                    jjtn005.setValue(true);
       } finally {
-                   if (jjtc005) {
-                     jjtree.closeNodeScope(jjtn005,  0);
-                   }
+                    if (jjtc005) {
+                      jjtree.closeNodeScope(jjtn005,  0);
+                    }
       }
       break;
     case FALSE:
@@ -1459,10 +1499,20 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case ABS:
     case SQRT:
     case MOD:
-    case 54:
-    case 55:
-    case 56:
-    case 57:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+    case 64:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
     case INT_LITERAL:
     case FLOAT_LITERAL:
@@ -1511,10 +1561,20 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case ABS:
     case SQRT:
     case MOD:
-    case 54:
-    case 55:
-    case 56:
-    case 57:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+    case 64:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
     case INT_LITERAL:
     case FLOAT_LITERAL:
@@ -1599,7 +1659,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                             }
       }
       break;
-    case 54:
+    case 64:
       namedParameter();
       break;
     case LENGTH:
@@ -1607,11 +1667,21 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case ABS:
     case SQRT:
     case MOD:
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
       functionsReturningNumerics();
       break;
-    case 55:
-    case 56:
-    case 57:
+    case 65:
+    case 66:
+    case 67:
     case PROPERTY_PATH:
       pathExpression();
       break;
@@ -1842,6 +1912,18 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     case MOD:
       mod();
       break;
+    case YEAR:
+    case MONTH:
+    case WEEK:
+    case DAY_OF_YEAR:
+    case DAY:
+    case DAY_OF_MONTH:
+    case DAY_OF_WEEK:
+    case HOUR:
+    case MINUTE:
+    case SECOND:
+      dateTimeExtractingFunction();
+      break;
     default:
       jj_la1[35] = jj_gen;
       jj_consume_token(-1);
@@ -2067,9 +2149,9 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
       case ASTERISK:
         asterisk();
         break;
-      case 55:
-      case 56:
-      case 57:
+      case 65:
+      case 66:
+      case 67:
       case PROPERTY_PATH:
         pathExpression();
         break;
@@ -2290,9 +2372,77 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     }
   }
 
+/* Date/time parts extracting function */
+  final public void dateTimeExtractingFunction() throws ParseException {
+                                                 /*@bgen(jjtree) #Extract( 1) */
+    ASTExtract jjtn000 = new ASTExtract(JJTEXTRACT);
+    boolean jjtc000 = true;
+    jjtree.openNodeScope(jjtn000);Token t;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case YEAR:
+        t = jj_consume_token(YEAR);
+        break;
+      case MONTH:
+        t = jj_consume_token(MONTH);
+        break;
+      case WEEK:
+        t = jj_consume_token(WEEK);
+        break;
+      case DAY_OF_YEAR:
+        t = jj_consume_token(DAY_OF_YEAR);
+        break;
+      case DAY:
+        t = jj_consume_token(DAY);
+        break;
+      case DAY_OF_MONTH:
+        t = jj_consume_token(DAY_OF_MONTH);
+        break;
+      case DAY_OF_WEEK:
+        t = jj_consume_token(DAY_OF_WEEK);
+        break;
+      case HOUR:
+        t = jj_consume_token(HOUR);
+        break;
+      case MINUTE:
+        t = jj_consume_token(MINUTE);
+        break;
+      case SECOND:
+        t = jj_consume_token(SECOND);
+        break;
+      default:
+        jj_la1[40] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+        jjtn000.setPartToken(t.image);
+      jj_consume_token(16);
+      pathExpression();
+      jj_consume_token(17);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000,  1);
+      }
+    }
+  }
+
   final public void namedParameter() throws ParseException {
         Token t;
-    jj_consume_token(54);
+    jj_consume_token(64);
     t = jj_consume_token(PROPERTY_PATH);
                                   ASTNamedParameter jjtn001 = new ASTNamedParameter(JJTNAMEDPARAMETER);
                                   boolean jjtc001 = true;
@@ -2326,8 +2476,8 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                                    }
       }
       break;
-    case 55:
-      jj_consume_token(55);
+    case 65:
+      jj_consume_token(65);
       t = jj_consume_token(PROPERTY_PATH);
                                    ASTObjPath jjtn002 = new ASTObjPath(JJTOBJPATH);
                                    boolean jjtc002 = true;
@@ -2342,8 +2492,8 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                                    }
       }
       break;
-    case 56:
-      jj_consume_token(56);
+    case 66:
+      jj_consume_token(66);
       t = jj_consume_token(PROPERTY_PATH);
                                    ASTDbPath jjtn003 = new ASTDbPath(JJTDBPATH);
                                    boolean jjtc003 = true;
@@ -2358,8 +2508,8 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
                                    }
       }
       break;
-    case 57:
-      jj_consume_token(57);
+    case 67:
+      jj_consume_token(67);
       t = jj_consume_token(PROPERTY_PATH);
                                    ASTScalar jjtn004 = new ASTScalar(JJTSCALAR);
                                    boolean jjtc004 = true;
@@ -2375,7 +2525,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
       }
       break;
     default:
-      jj_la1[40] = jj_gen;
+      jj_la1[41] = jj_gen;
       jj_consume_token(-1);
       throw new ParseException();
     }
@@ -2390,7 +2540,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
   public Token jj_nt;
   private int jj_ntk;
   private int jj_gen;
-  final private int[] jj_la1 = new int[41];
+  final private int[] jj_la1 = new int[42];
   static private int[] jj_la1_0;
   static private int[] jj_la1_1;
   static private int[] jj_la1_2;
@@ -2400,13 +2550,13 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
       jj_la1_init_2();
    }
    private static void jj_la1_init_0() {
-      jj_la1_0 = new int[] {0x2,0x4,0x18,0x16010018,0x60,0x180,0x10000,0x4fff8,0x4fff8,0x16010000,0x18,0x10000,0x4e000,0x80000,0x16010000,0x0,0x0,0x16010000,0x0,0x100000,0x200000,0x400000,0x1800000,0x1800000,0x6000000,0x6000000,0x8000000,0x8000000,0x16010000,0x2000000,0x6010000,0x10000,0x0,0x80000,0x80000,0x0,0x80000,0x0,0x0,0x0,0x0,};
+      jj_la1_0 = new int[] {0x2,0x4,0x18,0x16010018,0x60,0x180,0x10000,0x4fff8,0x4fff8,0x16010000,0x18,0x10000,0x4e000,0x80000,0x16010000,0x0,0x0,0x16010000,0x0,0x100000,0x200000,0x400000,0x1800000,0x1800000,0x6000000,0x6000000,0x8000000,0x8000000,0x16010000,0x2000000,0x6010000,0x10000,0x0,0x80000,0x80000,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,};
    }
    private static void jj_la1_init_1() {
-      jj_la1_1 = new int[] {0x0,0x0,0x0,0xbfffffe,0x0,0x0,0x400000,0x0,0x0,0xbfffffe,0x0,0x400000,0x0,0x0,0xbfffff2,0xb803e00,0x3e00,0xbfffffe,0x40000c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000000,0x4000000,0xbc7c000,0x0,0xbc7c000,0xbc7c000,0x3e00,0x0,0x0,0x7c000,0x0,0x1f0,0xf800000,0x380000,0xb800000,};
+      jj_la1_1 = new int[] {0x0,0x0,0x0,0xfffffffe,0x0,0x0,0x0,0x0,0x0,0xfffffffe,0x0,0x0,0x0,0x0,0xfffffff2,0x3e00,0x3e00,0xfffffffe,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc7c000,0x0,0xffc7c000,0xffc7c000,0x3e00,0x0,0x0,0xffc7c000,0x0,0x1f0,0x0,0x380000,0xffc00000,0x0,};
    }
    private static void jj_la1_init_2() {
-      jj_la1_2 = new int[] {0x0,0x0,0x0,0x1c8,0x0,0x0,0x0,0x0,0x0,0x1c8,0x0,0x0,0x0,0x0,0x1c8,0x48,0x48,0x1c8,0x1c8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180,0x0,0x180,0x180,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
+      jj_la1_2 = new int[] {0x0,0x0,0x0,0x7202f,0x0,0x0,0x1,0x0,0x0,0x7202f,0x0,0x1,0x0,0x0,0x7202f,0x1202e,0x12000,0x7202f,0x72001,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x10,0x6002f,0x0,0x6002f,0x6002f,0x0,0x0,0x0,0x0,0x0,0x0,0x3e,0x0,0x0,0x2e,};
    }
 
   /** Constructor with InputStream. */
@@ -2420,7 +2570,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     token = new Token();
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 41; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 42; i++) jj_la1[i] = -1;
   }
 
   /** Reinitialise. */
@@ -2435,7 +2585,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     jj_ntk = -1;
     jjtree.reset();
     jj_gen = 0;
-    for (int i = 0; i < 41; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 42; i++) jj_la1[i] = -1;
   }
 
   /** Constructor. */
@@ -2445,7 +2595,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     token = new Token();
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 41; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 42; i++) jj_la1[i] = -1;
   }
 
   /** Reinitialise. */
@@ -2456,7 +2606,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     jj_ntk = -1;
     jjtree.reset();
     jj_gen = 0;
-    for (int i = 0; i < 41; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 42; i++) jj_la1[i] = -1;
   }
 
   /** Constructor with generated Token Manager. */
@@ -2465,7 +2615,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     token = new Token();
     jj_ntk = -1;
     jj_gen = 0;
-    for (int i = 0; i < 41; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 42; i++) jj_la1[i] = -1;
   }
 
   /** Reinitialise. */
@@ -2475,7 +2625,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
     jj_ntk = -1;
     jjtree.reset();
     jj_gen = 0;
-    for (int i = 0; i < 41; i++) jj_la1[i] = -1;
+    for (int i = 0; i < 42; i++) jj_la1[i] = -1;
   }
 
   private Token jj_consume_token(int kind) throws ParseException {
@@ -2526,12 +2676,12 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
   /** Generate ParseException. */
   public ParseException generateParseException() {
     jj_expentries.clear();
-    boolean[] la1tokens = new boolean[77];
+    boolean[] la1tokens = new boolean[87];
     if (jj_kind >= 0) {
       la1tokens[jj_kind] = true;
       jj_kind = -1;
     }
-    for (int i = 0; i < 41; i++) {
+    for (int i = 0; i < 42; i++) {
       if (jj_la1[i] == jj_gen) {
         for (int j = 0; j < 32; j++) {
           if ((jj_la1_0[i] & (1<<j)) != 0) {
@@ -2546,7 +2696,7 @@ public class ExpressionParser/*@bgen(jjtree)*/implements ExpressionParserTreeCon
         }
       }
     }
-    for (int i = 0; i < 77; i++) {
+    for (int i = 0; i < 87; i++) {
       if (la1tokens[i]) {
         jj_expentry = new int[1];
         jj_expentry[0] = i;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/05a77250/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParserConstants.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParserConstants.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParserConstants.java
index a4c1a77..18c00e2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParserConstants.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ExpressionParserConstants.java
@@ -73,35 +73,55 @@ public interface ExpressionParserConstants {
   /** RegularExpression Id. */
   int CURRENT_TIMESTAMP = 53;
   /** RegularExpression Id. */
-  int ASTERISK = 58;
+  int YEAR = 54;
   /** RegularExpression Id. */
-  int PROPERTY_PATH = 59;
+  int MONTH = 55;
   /** RegularExpression Id. */
-  int IDENTIFIER = 60;
+  int WEEK = 56;
   /** RegularExpression Id. */
-  int LETTER = 61;
+  int DAY_OF_YEAR = 57;
   /** RegularExpression Id. */
-  int DIGIT = 62;
+  int DAY = 58;
   /** RegularExpression Id. */
-  int ESC = 65;
+  int DAY_OF_MONTH = 59;
   /** RegularExpression Id. */
-  int SINGLE_QUOTED_STRING = 67;
+  int DAY_OF_WEEK = 60;
   /** RegularExpression Id. */
-  int STRING_ESC = 68;
+  int HOUR = 61;
   /** RegularExpression Id. */
-  int DOUBLE_QUOTED_STRING = 70;
+  int MINUTE = 62;
   /** RegularExpression Id. */
-  int INT_LITERAL = 71;
+  int SECOND = 63;
   /** RegularExpression Id. */
-  int FLOAT_LITERAL = 72;
+  int ASTERISK = 68;
   /** RegularExpression Id. */
-  int DEC_FLT = 73;
+  int PROPERTY_PATH = 69;
   /** RegularExpression Id. */
-  int DEC_DIGITS = 74;
+  int IDENTIFIER = 70;
   /** RegularExpression Id. */
-  int EXPONENT = 75;
+  int LETTER = 71;
   /** RegularExpression Id. */
-  int FLT_SUFF = 76;
+  int DIGIT = 72;
+  /** RegularExpression Id. */
+  int ESC = 75;
+  /** RegularExpression Id. */
+  int SINGLE_QUOTED_STRING = 77;
+  /** RegularExpression Id. */
+  int STRING_ESC = 78;
+  /** RegularExpression Id. */
+  int DOUBLE_QUOTED_STRING = 80;
+  /** RegularExpression Id. */
+  int INT_LITERAL = 81;
+  /** RegularExpression Id. */
+  int FLOAT_LITERAL = 82;
+  /** RegularExpression Id. */
+  int DEC_FLT = 83;
+  /** RegularExpression Id. */
+  int DEC_DIGITS = 84;
+  /** RegularExpression Id. */
+  int EXPONENT = 85;
+  /** RegularExpression Id. */
+  int FLT_SUFF = 86;
 
   /** Lexical state. */
   int DEFAULT = 0;
@@ -166,6 +186,16 @@ public interface ExpressionParserConstants {
     "\"currentDate\"",
     "\"currentTime\"",
     "<CURRENT_TIMESTAMP>",
+    "\"year\"",
+    "\"month\"",
+    "\"week\"",
+    "\"dayOfYear\"",
+    "\"day\"",
+    "\"dayOfMonth\"",
+    "\"dayOfWeek\"",
+    "\"hour\"",
+    "\"minute\"",
+    "\"second\"",
     "\"$\"",
     "\"obj:\"",
     "\"db:\"",
@@ -178,10 +208,10 @@ public interface ExpressionParserConstants {
     "\"\\\'\"",
     "\"\\\"\"",
     "<ESC>",
-    "<token of kind 66>",
+    "<token of kind 76>",
     "\"\\\'\"",
     "<STRING_ESC>",
-    "<token of kind 69>",
+    "<token of kind 79>",
     "\"\\\"\"",
     "<INT_LITERAL>",
     "<FLOAT_LITERAL>",