From e19c75a9c3e067ff75e6bd7444d8e18785418036 Mon Sep 17 00:00:00 2001 From: Fay Wang Date: Tue, 25 Aug 2009 23:22:59 +0000 Subject: [PATCH] OPENJPA-1266: JDBC escape syntax for date, time, timestamp git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@807851 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/openjpa/jdbc/kernel/exps/Lit.java | 10 +- .../apache/openjpa/kernel/exps/Literal.java | 3 + .../kernel/jpql/JPQLExpressionBuilder.java | 9 ++ .../org/apache/openjpa/kernel/jpql/JPQL.jjt | 43 ++++++++- .../openjpa/persistence/query/Employee.java | 36 ++++++++ .../persistence/query/TestJDBCEscapeDate.java | 91 +++++++++++++++++++ 6 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestJDBCEscapeDate.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java index 6111ac8fc..11dd5fb03 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/Lit.java @@ -44,6 +44,9 @@ public class Lit public Lit(Object val, int ptype) { _val = val; _ptype = ptype; + if (_ptype == Literal.TYPE_DATE || _ptype == Literal.TYPE_TIME || + _ptype == Literal.TYPE_TIMESTAMP) + _isRaw = true; } public Class getType() { @@ -116,7 +119,8 @@ public class Lit sql.appendValue(((Object[]) lstate.sqlValue)[index], lstate.getColumn(index)); else if (_isRaw) { - if (getParseType() == Literal.TYPE_ENUM) { + int parseType = getParseType(); + if (parseType == Literal.TYPE_ENUM) { StringBuilder value = new StringBuilder(); boolean isOrdinal = false; if (lstate.sqlValue instanceof Integer) @@ -128,6 +132,10 @@ public class Lit value.append("'"); lstate.sqlValue = new Raw(value.toString()); _rawVal = lstate.sqlValue; + } else if (parseType == Literal.TYPE_DATE || parseType == Literal.TYPE_TIME || + parseType == Literal.TYPE_TIMESTAMP) { + lstate.sqlValue = new Raw(_val.toString()); + _rawVal = lstate.sqlValue; } else { lstate.sqlValue = new Raw(_val instanceof String ? "'"+_val+"'" : _val.toString()); _rawVal = lstate.sqlValue; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java index afe629caa..e4b5c719d 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Literal.java @@ -35,6 +35,9 @@ public interface Literal public static final int TYPE_CLASS = 5; public static final int TYPE_ENUM = 6; public static final int TYPE_COLLECTION = 7; + public static final int TYPE_DATE = 8; + public static final int TYPE_TIME = 9; + public static final int TYPE_TIMESTAMP = 10; /** * The value of this literal. diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java index eaa068ead..80617590c 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java @@ -1375,6 +1375,15 @@ public class JPQLExpressionBuilder assertQueryExtensions("ORDER BY"); return eval(onlyChild(node)); + case JJTDATELITERAL: + return factory.newLiteral(node.text, Literal.TYPE_DATE); + + case JJTTIMELITERAL: + return factory.newLiteral(node.text, Literal.TYPE_TIME); + + case JJTTIMESTAMPLITERAL: + return factory.newLiteral(node.text, Literal.TYPE_TIMESTAMP); + default: throw parseException(EX_FATAL, "bad-tree", new Object[]{ node }, null); diff --git a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt index 0efff7235..b5463f0e1 100644 --- a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt +++ b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/JPQL.jjt @@ -293,6 +293,24 @@ TOKEN : /* literals */ ) "'" > + | < DATE_LITERAL: "{d '" (["0"-"9"])(["0"-"9"])(["0"-"9"])(["0"-"9"]) ("-") + (["0"-"1"])(["0"-"9"]) ("-") + (["0"-"3"])(["0"-"9"]) "'}" + > + | < TIME_LITERAL: "{t '" (["0"-"2"])(["0"-"9"]) (":") + (["0"-"6"])(["0"-"9"]) (":") + (["0"-"6"])(["0"-"9"]) "'}" + > + | < TIMESTAMP_LITERAL: "{ts '" (["0"-"9"])(["0"-"9"])(["0"-"9"])(["0"-"9"]) ("-") + (["0"-"1"])(["0"-"9"]) ("-") + (["0"-"3"])(["0"-"9"]) (" ") + (["0"-"2"])(["0"-"9"]) (":") + (["0"-"6"])(["0"-"9"]) (":") + (["0"-"6"])(["0"-"9"]) + ((".") (["0"-"9"]) (["0"-"9"])? (["0"-"9"])? (["0"-"9"])? + (["0"-"9"])? (["0"-"9"])? )? + "'}" + > } TOKEN [ IGNORE_CASE ]: /* boolean literals can be case-insensitive */ @@ -1105,6 +1123,7 @@ void datetime_expression() : { } void datetime_primary() : { } { + date_literal() | time_literal() | timestamp_literal() | LOOKAHEAD(path()) path() | functions_returning_datetime() | input_parameter() | aggregate_select_expression() | LOOKAHEAD(qualified_path()) qualified_path() | LOOKAHEAD(general_identification_variable()) general_identification_variable() @@ -1408,7 +1427,8 @@ void path_component() #IDENTIFICATIONVARIABLE : void literal() : { } { - numeric_literal() | boolean_literal() | string_literal() | enum_literal() + numeric_literal() | boolean_literal() | string_literal() | enum_literal() | date_literal() | + time_literal() | timestamp_literal() } @@ -1446,6 +1466,27 @@ void string_literal() #STRINGLITERAL : } +void date_literal() #DATELITERAL : +{ Token t; } +{ + t = { jjtThis.setToken (t); } +} + + +void time_literal() #TIMELITERAL : +{ Token t; } +{ + t = { jjtThis.setToken (t); } +} + + +void timestamp_literal() #TIMESTAMPLITERAL : +{ Token t; } +{ + t = { jjtThis.setToken (t); } +} + + void input_parameter() : { } { named_input_parameter() | positional_input_parameter() diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/Employee.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/Employee.java index f15fd6e67..6f81264fa 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/Employee.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/Employee.java @@ -18,11 +18,15 @@ */ package org.apache.openjpa.persistence.query; +import java.util.Date; + import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; @NamedQueries( { @NamedQuery(name = "Employee.findByName", @@ -41,7 +45,16 @@ public class Employee { private long someLong; private int statusId; + + @Temporal(TemporalType.DATE) + private Date hireDate; + + @Temporal(TemporalType.TIME) + private Date hireTime; + @Temporal(TemporalType.TIMESTAMP) + private Date hireTimestamp; + public int getStatusId() { return statusId; } @@ -74,5 +87,28 @@ public class Employee { this.someLong = someLong; } + public Date getHireDate() { + return hireDate; + } + + public void setHireDate(Date hireDate) { + this.hireDate = hireDate; + } + + public Date getHireTime() { + return hireTime; + } + + public void setHireTime(Date hireTime) { + this.hireTime = hireTime; + } + + public Date getHireTimestamp() { + return hireTimestamp; + } + + public void setHireTimestamp(Date hireTimestamp) { + this.hireTimestamp = hireTimestamp; + } } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestJDBCEscapeDate.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestJDBCEscapeDate.java new file mode 100644 index 000000000..3d107e0e1 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/query/TestJDBCEscapeDate.java @@ -0,0 +1,91 @@ +/* + * 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.openjpa.persistence.query; + +import java.util.Date; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.Query; + +import junit.framework.Assert; + +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * Test JDBC escape syntax for date, time, and timestamp literals + */ +public class TestJDBCEscapeDate extends SingleEMFTestCase { + + public void setUp() { + setUp(Employee.class, DROP_TABLES); + } + + public void testJDBCEscape() { + EntityManager em = emf.createEntityManager(); + EntityTransaction tran = em.getTransaction(); + Employee e = new Employee(); + e.setEmpId(1); + e.setName("name1"); + e.setHireDate(new Date()); + e.setHireTime(new Date()); + e.setHireTimestamp(new Date()); + em.persist(e); + tran.begin(); + em.flush(); + tran.commit(); + em.clear(); + + String[] jpql = { + "select a from Employee a where a.hireDate >= {d '2009-08-25'}", + "select a from Employee a where a.hireTime >= {t '00:00:00'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00.1'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00.11'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00.111'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00.1111'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00.11111'}", + "select a from Employee a where a.hireTimestamp >= {ts '2009-08-25 00:00:00.111111'}", + "select {t '00:00:00'}, a.empId from Employee a", + }; + + for (int i = 0; i < jpql.length; i++) { + Query q = em.createQuery(jpql[i]); + List results = q.getResultList(); + Assert.assertEquals(1, results.size()); + } + + String wrongTs = "select a from Employee a where a.hireTimestamp > {ts '2009-08-25 00:00:00.1111111'}"; + try { + Query q = em.createQuery(wrongTs); + List results = q.getResultList(); + Assert.fail(); + } catch (Exception ex) { + } + + em.getTransaction().begin(); + String update = "update Employee a set a.hireTimestamp = {ts '2009-08-25 00:00:00.111111'} where a.empId = 1"; + Query q = em.createQuery(update); + int updateCnt = q.executeUpdate(); + em.getTransaction().commit(); + Assert.assertEquals(1, updateCnt); + em.close(); + } +}