From d935981320292ecbd44ae63fb3a9607439a223d8 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 7 Feb 2014 19:09:04 +0100 Subject: [PATCH] [#470] Add support for the Oracle TRUNC function for datetime arithmetic --- .../jooq/test/_/testcases/DataTypeTests.java | 81 +++++++- .../src/org/jooq/test/jOOQAbstractTest.java | 5 + jOOQ/src/main/java/org/jooq/impl/DSL.java | 37 ++-- .../main/java/org/jooq/impl/TruncDate.java | 192 ++++++++++++++++++ 4 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/TruncDate.java diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/DataTypeTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/DataTypeTests.java index 1cf3065453..9f590f5860 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/DataTypeTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/DataTypeTests.java @@ -1617,15 +1617,92 @@ extends BaseTest r1 = create().select( + DSL.trunc(new Timestamp(cal.getTimeInMillis()), DatePart.YEAR) .as("yy"), + DSL.trunc(new Timestamp(cal.getTimeInMillis()), DatePart.MONTH) .as("mm"), + DSL.trunc(new Timestamp(cal.getTimeInMillis()), DatePart.DAY) .as("dd"), + DSL.trunc(new Timestamp(cal.getTimeInMillis()), DatePart.HOUR) .as("hh"), + DSL.trunc(new Timestamp(cal.getTimeInMillis()), DatePart.MINUTE).as("mi"), + DSL.trunc(new Timestamp(cal.getTimeInMillis()), DatePart.SECOND).as("ss") + ).fetchOne(); + + cal = cal(-1); + cal.set(Calendar.MONTH , 0); + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.set(Calendar.HOUR_OF_DAY , 0); + cal.set(Calendar.MINUTE , 0); + cal.set(Calendar.SECOND , 0); + cal.set(Calendar.MILLISECOND , 0); + assertEquals(new Timestamp(cal.getTimeInMillis()), r1.value1()); + + cal = cal(-1); + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.set(Calendar.HOUR_OF_DAY , 0); + cal.set(Calendar.MINUTE , 0); + cal.set(Calendar.SECOND , 0); + cal.set(Calendar.MILLISECOND , 0); + assertEquals(new Timestamp(cal.getTimeInMillis()), r1.value2()); + + cal = cal(-1); + cal.set(Calendar.HOUR_OF_DAY , 0); + cal.set(Calendar.MINUTE , 0); + cal.set(Calendar.SECOND , 0); + cal.set(Calendar.MILLISECOND , 0); + assertEquals(new Timestamp(cal.getTimeInMillis()), r1.value3()); + + cal = cal(-1); + cal.set(Calendar.MINUTE , 0); + cal.set(Calendar.SECOND , 0); + cal.set(Calendar.MILLISECOND , 0); + assertEquals(new Timestamp(cal.getTimeInMillis()), r1.value4()); + + cal = cal(-1); + cal.set(Calendar.SECOND , 0); + cal.set(Calendar.MILLISECOND , 0); + assertEquals(new Timestamp(cal.getTimeInMillis()), r1.value5()); + + cal = cal(-1); + cal.set(Calendar.MILLISECOND , 0); + assertEquals(new Timestamp(cal.getTimeInMillis()), r1.value6()); + } + @Test public void testCurrentDate() throws Exception { Field ts = currentDate().cast(Timestamp.class).as("ts"); diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 57d16116cb..c4c60ac613 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -1785,6 +1785,11 @@ public abstract class jOOQAbstractTest< new DataTypeTests(this).testFunctionsOnDates_DATE_ADD(); } + @Test + public void testFunctionsOnDates_TRUNC() throws Exception { + new DataTypeTests(this).testFunctionsOnDates_TRUNC(); + } + @Test public void testCurrentDate() throws Exception { new DataTypeTests(this).testCurrentDate(); diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index 0542fcac8b..af40bf5edb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -7809,51 +7809,52 @@ public class DSL { return new TimestampDiff(nullSafe(timestamp1), nullSafe(timestamp2)); } - // ------------------------------------------------------------------------- - // [#470] TRUNC(datetime) will be implemented in a future release - // ------------------------------------------------------------------------- - /** - * This is not yet implemented. + * Truncate a date to the beginning of the day. */ - static Field trunc(Date date) { + @Support({ CUBRID, HSQLDB, POSTGRES }) + public static Field trunc(Date date) { return trunc(date, DatePart.DAY); } /** - * This is not yet implemented. + * Truncate a date to a given datepart. */ - static Field trunc(Date date, DatePart part) { + @Support({ CUBRID, HSQLDB, POSTGRES }) + public static Field trunc(Date date, DatePart part) { return trunc(Utils.field(date), part); } /** - * This is not yet implemented. + * Truncate a timestamp to the beginning of the day. */ - static Field trunc(Timestamp timestamp) { + @Support({ CUBRID, HSQLDB, POSTGRES }) + public static Field trunc(Timestamp timestamp) { return trunc(timestamp, DatePart.DAY); } /** - * This is not yet implemented. + * Truncate a timestamp to a given datepart. */ - static Field trunc(Timestamp timestamp, DatePart part) { + @Support({ CUBRID, HSQLDB, POSTGRES }) + public static Field trunc(Timestamp timestamp, DatePart part) { return trunc(Utils.field(timestamp), part); } /** - * This is not yet implemented. + * Truncate a date or a timestamp to the beginning of the day. */ - static Field trunc(Field date) { + @Support({ CUBRID, HSQLDB, POSTGRES }) + public static Field trunc(Field date) { return trunc(date, DatePart.DAY); } /** - * This is not yet implemented. + * Truncate a date or a timestamp to a given datepart. */ - @SuppressWarnings("unused") - static Field trunc(Field date, DatePart part) { - throw new UnsupportedOperationException("This is not yet implemented"); + @Support({ CUBRID, HSQLDB, POSTGRES }) + public static Field trunc(Field date, DatePart part) { + return new TruncDate(date, part); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/TruncDate.java b/jOOQ/src/main/java/org/jooq/impl/TruncDate.java new file mode 100644 index 0000000000..1ec95cc304 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/TruncDate.java @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * Licensed 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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.impl; + +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.inline; + +import java.sql.Date; + +import org.jooq.Configuration; +import org.jooq.DatePart; +import org.jooq.Field; +import org.jooq.QueryPart; + +/** + * @author Lukas Eder + */ +class TruncDate extends AbstractFunction { + + /** + * Generated UID + */ + private static final long serialVersionUID = -4617792768119885313L; + + private final Field date; + private final DatePart part; + + TruncDate(Field date, DatePart part) { + super("trunc", date.getDataType()); + + this.date = date; + this.part = part; + } + + @Override + final QueryPart getFunction0(Configuration configuration) { + String keyword = null; + + switch (configuration.dialect().family()) { + + // [http://jira.cubrid.org/browse/ENGINE-120] This currently doesn't work for all date parts in CUBRID + case CUBRID: + case HSQLDB: { + switch (part) { + case YEAR: keyword = "YY"; break; + case MONTH: keyword = "MM"; break; + case DAY: keyword = "DD"; break; + case HOUR: keyword = "HH"; break; + case MINUTE: keyword = "MI"; break; + case SECOND: keyword = "SS"; break; + default: throwUnsupported(); + } + + return field("{trunc}({0}, {1})", getDataType(), date, inline(keyword)); + } + +// These don't work yet and need better integration-testing: +// --------------------------------------------------------- +// case MARIADB: +// case MYSQL: { +// switch (part) { +// case YEAR: return field("{str_to_date}({date_format}({0}, '%Y-00-00 00:00:00.0'), '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// case MONTH: return field("{str_to_date}({date_format}({0}, '%Y-%m-00 00:00:00.0'), '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// case DAY: return field("{str_to_date}({date_format}({0}, '%Y-%m-%d 00:00:00.0'), '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// case HOUR: return field("{str_to_date}({date_format}({0}, '%Y-%m-%d %H:00:00.0'), '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// case MINUTE: return field("{str_to_date}({date_format}({0}, '%Y-%m-%d %H:%i:00.0'), '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// case SECOND: return field("{str_to_date}({date_format}({0}, '%Y-%m-%d %H:%i:%s.0'), '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// default: throwUnsupported(); +// } +// } + + case POSTGRES: { + switch (part) { + case YEAR: keyword = "year"; break; + case MONTH: keyword = "month"; break; + case DAY: keyword = "day"; break; + case HOUR: keyword = "hour"; break; + case MINUTE: keyword = "minute"; break; + case SECOND: keyword = "second"; break; + default: throwUnsupported(); + } + + return field("{date_trunc}({0}, {1})", getDataType(), inline(keyword), date); + } + +// These don't work yet and need better integration-testing: +// --------------------------------------------------------- +// case SQLITE: { +// switch (part) { +// case YEAR: return field("{strftime}({0}, '%Y-00-00 00:00:00.0')", getDataType(), date); +// case MONTH: return field("{strftime}({0}, '%Y-%m-00 00:00:00.0')", getDataType(), date); +// case DAY: return field("{strftime}({0}, '%Y-%m-%d 00:00:00.0')", getDataType(), date); +// case HOUR: return field("{strftime}({0}, '%Y-%m-%d %H:00:00.0')", getDataType(), date); +// case MINUTE: return field("{strftime}({0}, '%Y-%m-%d %H:%i:00.0')", getDataType(), date); +// case SECOND: return field("{strftime}({0}, '%Y-%m-%d %H:%i:%s.0')", getDataType(), date); +// default: throwUnsupported(); +// } +// } + + /* [pro] xx + xxxx xxxx x + xxxxxx xxxxxx x + xxxx xxxxx xxxxxxx x xxxxxxx xxxxxx + xxxx xxxxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxxxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxxxxx xxxxxxx x xxxxx xxxxxx + xxxxxxxx xxxxxxxxxxxxxxxxxxx + x + + xxxxxx xxxxxxxxxxxxxxxxxxx xxxxxx xxxxxxxxxxxxxx xxxxx xxxxxxxxxxxxxxxxx + x + + xxxx xxxxxxx x + xxxxxx xxxxxx x + xxxx xxxxx xxxxxxx x xxxxxxx xxxxxx + xxxx xxxxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxxxxx xxxxxxx x xxxxx xxxxxx + xxxx xxxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxxxx + xxxxxxxx xxxxxxxxxxxxxxxxxxx + x + + xxxxxx xxxxxxxxxxxxxxxxxxx xxxxxx xxxxxxxxxxxxxx xxxxx xxxxxxxxxxxxxxxxx + x + +xx xxxxx xxxxx xxxx xxx xxx xxxx xxxxxx xxxxxxxxxxxxxxxxxxxx +xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +xx xxxx xxxxxxxxxx +xx xxxx xxxxxxx x +xx xxxxxx xxxxxx x +xx xxxx xxxxx xxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xx xxxxx xxxx xxxxxxxxxxxxxx xxxxxx +xx xxxx xxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xx xxxxx xxxx xxxxxxxxxxxxxx xxxxxx +xx xxxx xxxx xxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xx xxxxx xxxx xxxxxxxxxxxxxx xxxxxx +xx xxxx xxxxx xxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xx xxxxx xxxx xxxxxxxxxxxxxx xxxxxx +xx xxxx xxxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xx xxxxx xxxx xxxxxxxxxxxxxx xxxxxx +xx xxxx xxxxxxx xxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xx xxxxx xxxx xxxxxxxxxxxxxx xxxxxx +xx xxxxxxxx xxxxxxxxxxxxxxxxxxx +xx x +xx x + + xx [/pro] */ + + default: + return field("{trunc}({0}, {1})", getDataType(), date, inline(part.name())); + } + } + + private final void throwUnsupported() { + throw new UnsupportedOperationException("Unknown date part : " + part); + } +}