From d26dceaa25076af6cb997cb93b22a58f7af3457e Mon Sep 17 00:00:00 2001 From: lukaseder Date: Sun, 28 Jan 2018 21:48:25 +0100 Subject: [PATCH] [#6485] Support parsing TRUNC() for date / timestamp --- .../resources/org/jooq/web/grammar-3.11.txt | 4 +- .../main/java/org/jooq/impl/ParserImpl.java | 140 +++++++++++++++--- 2 files changed, 125 insertions(+), 19 deletions(-) diff --git a/jOOQ-manual/src/main/resources/org/jooq/web/grammar-3.11.txt b/jOOQ-manual/src/main/resources/org/jooq/web/grammar-3.11.txt index 5582481987..97f50bc866 100644 --- a/jOOQ-manual/src/main/resources/org/jooq/web/grammar-3.11.txt +++ b/jOOQ-manual/src/main/resources/org/jooq/web/grammar-3.11.txt @@ -547,8 +547,9 @@ term = | 'COALESCE' '(' fields ')' | 'CUME_DIST' ( '(' ')' over | '(' fields ')' withinGroup ) | dateLiteral -| 'DENSE_RANK' ( '(' ')' over | '(' fields ')' withinGroup ) +| 'DATE_TRUNC' '(' stringLiteral ',' field ')' | 'DAY' '(' field ')' +| 'DENSE_RANK' ( '(' ')' over | '(' fields ')' withinGroup ) | ( 'DEG' | 'DEGREE' ) '(' sum ')' | 'EXTRACT' '(' datePart 'FROM' field ')' | 'EXP' '(' sum ')' @@ -640,6 +641,7 @@ term = | timestampLiteral | 'TRANSLATE' '(' field ',' field ',' field ')' | 'TRIM' '(' field ')' +| 'TRUNC' '(' field ',' stringLiteral ')' | 'TRUNC' '(' sum ',' sum ')' | truthValue | ( 'UPPER' | 'UCASE' ) '(' field ')' diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 87fe817101..d7143d3083 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -201,6 +201,7 @@ import static org.jooq.impl.DSL.time; import static org.jooq.impl.DSL.timestamp; import static org.jooq.impl.DSL.translate; import static org.jooq.impl.DSL.trim; +import static org.jooq.impl.DSL.trunc; import static org.jooq.impl.DSL.unique; import static org.jooq.impl.DSL.user; import static org.jooq.impl.DSL.values; @@ -3621,6 +3622,8 @@ final class ParserImpl implements Parser { if (D.is(type)) if ((field = parseFieldDateLiteralIf(ctx)) != null) return field; + else if ((field = parseFieldDateTruncIf(ctx)) != null) + return field; if (N.is(type)) if ((field = parseFieldDenseRankIf(ctx)) != null) @@ -3859,9 +3862,7 @@ final class ParserImpl implements Parser { return field; if (N.is(type)) - if ((field = parseFieldTruncIf(ctx)) != null) - return field; - else if (parseFunctionNameIf(ctx, "TANH")) + if (parseFunctionNameIf(ctx, "TANH")) return tanh((Field) parseFieldSumParenthesised(ctx)); else if (parseFunctionNameIf(ctx, "TAN")) return tan((Field) parseFieldSumParenthesised(ctx)); @@ -3872,6 +3873,10 @@ final class ParserImpl implements Parser { else if ((field = parseFieldTimeLiteralIf(ctx)) != null) return field; + if (N.is(type) || D.is(type)) + if ((field = parseFieldTruncIf(ctx)) != null) + return field; + break; case 'u': @@ -4087,11 +4092,37 @@ final class ParserImpl implements Parser { private static final Field parseFieldTruncIf(ParserContext ctx) { if (parseFunctionNameIf(ctx, "TRUNC")) { parse(ctx, '('); - Field arg1 = toField(ctx, parseSum(ctx, N)); + Field arg1 = parseField(ctx); parse(ctx, ','); - Field arg2 = toField(ctx, parseSum(ctx, N)); - parse(ctx, ')'); - return DSL.trunc((Field) arg1, (Field) arg2); + + String part; + if ((part = parseStringLiteralIf(ctx)) != null) { + part = part.toUpperCase(); + + DatePart p; + if ("YY".equals(part) || "YYYY".equals(part) || "YEAR".equals(part)) + p = DatePart.YEAR; + else if ("MM".equals(part) || "MONTH".equals(part)) + p = DatePart.MONTH; + else if ("DD".equals(part)) + p = DatePart.DAY; + else if ("HH".equals(part)) + p = DatePart.HOUR; + else if ("MI".equals(part)) + p = DatePart.MINUTE; + else if ("SS".equals(part)) + p = DatePart.SECOND; + else + throw ctx.unexpectedToken(); + + parse(ctx, ')'); + return DSL.trunc((Field) arg1, p); + } + else { + Field arg2 = toField(ctx, parseSum(ctx, N)); + parse(ctx, ')'); + return DSL.trunc((Field) arg1, (Field) arg2); + } } return null; @@ -4287,6 +4318,21 @@ final class ParserImpl implements Parser { return null; } + private static final Field parseFieldDateTruncIf(ParserContext ctx) { + if (parseFunctionNameIf(ctx, "DATE_TRUNC")) { + parse(ctx, '('); + DatePart part = DatePart.valueOf(parseStringLiteral(ctx).toUpperCase()); + parse(ctx, ','); + Field field = parseField(ctx, D); + parse(ctx, ')'); + + return trunc(field, part); + } + + return null; + } + + private static final Date parseDateLiteral(ParserContext ctx) { try { return Date.valueOf(parseStringLiteral(ctx)); @@ -6095,14 +6141,25 @@ final class ParserImpl implements Parser { } private static final String parseStringLiteral(ParserContext ctx) { + String result = parseStringLiteralIf(ctx); + + if (result == null) + throw ctx.unexpectedToken(); + + return result; + } + + private static final String parseStringLiteralIf(ParserContext ctx) { parseWhitespaceIf(ctx); - if (parseIf(ctx, 'q') || parseIf(ctx, 'Q')) + if (parseIf(ctx, 'q', '\'') || parseIf(ctx, 'Q', '\'')) return parseOracleQuotedStringLiteral(ctx); - else if (parseIf(ctx, 'e') || parseIf(ctx, 'E')) + else if (parseIf(ctx, 'e', '\'') || parseIf(ctx, 'E', '\'')) return parseUnquotedStringLiteral(ctx, true); - else + else if (peek(ctx, '\'')) return parseUnquotedStringLiteral(ctx, false); + else + return null; } private static final byte[] parseBinaryLiteralIf(ParserContext ctx) { @@ -6661,19 +6718,31 @@ final class ParserImpl implements Parser { } private static final boolean parseIf(ParserContext ctx, String string) { - parseWhitespaceIf(ctx); - int length = string.length(); + boolean result = peek(ctx, string); - if (ctx.sql.length < ctx.position + length) + if (result) + ctx.position = ctx.position + string.length(); + + return result; + } + + private static final boolean parseIf(ParserContext ctx, String string, String peek) { + parseWhitespaceIf(ctx); + int l1 = string.length(); + int l2 = peek.length(); + + if (ctx.sql.length < ctx.position + l1 + l2) return false; - for (int i = 0; i < length; i++) { - char c = string.charAt(i); - if (ctx.sql[ctx.position + i] != c) + for (int i = 0; i < l1; i++) + if (ctx.sql[ctx.position + i] != string.charAt(i)) return false; - } - ctx.position = ctx.position + length; + for (int i = l1; i < l2; i++) + if (ctx.sql[ctx.position + i] != peek.charAt(i)) + return false; + + ctx.position = ctx.position + l1; return true; } @@ -6683,11 +6752,23 @@ final class ParserImpl implements Parser { } private static final boolean parseIf(ParserContext ctx, char c) { + boolean result = peek(ctx, c); + + if (result) + ctx.position = ctx.position + 1; + + return result; + } + + private static final boolean parseIf(ParserContext ctx, char c, char peek) { parseWhitespaceIf(ctx); if (ctx.character() != c) return false; + if (ctx.character(ctx.position + 1) != peek) + return false; + ctx.position = ctx.position + 1; return true; } @@ -6738,6 +6819,29 @@ final class ParserImpl implements Parser { return null; } + private static final boolean peek(ParserContext ctx, char c) { + parseWhitespaceIf(ctx); + + if (ctx.character() != c) + return false; + + return true; + } + + private static final boolean peek(ParserContext ctx, String string) { + parseWhitespaceIf(ctx); + int length = string.length(); + + if (ctx.sql.length < ctx.position + length) + return false; + + for (int i = 0; i < length; i++) + if (ctx.sql[ctx.position + i] != string.charAt(i)) + return false; + + return true; + } + private static final boolean peekKeyword(ParserContext ctx, String... keywords) { for (String keyword : keywords) if (peekKeyword(ctx, keyword))