From 79aa991535c036418fbf7f77b951390d4356f8f7 Mon Sep 17 00:00:00 2001 From: linghengqian Date: Wed, 6 Dec 2023 20:13:10 +0800 Subject: [PATCH] Add Interval and IntervalJapaneseDate implementation of InlineExpressionParser SPI --- .../content/dev-manual/sharding.cn.md | 12 +- .../content/dev-manual/sharding.en.md | 12 +- .../builtin-algorithm/expr.cn.md | 48 +++- .../builtin-algorithm/expr.en.md | 52 ++++- infra/expr/type/interval/pom.xml | 43 ++++ .../IntervalInlineExpressionParser.java | 215 ++++++++++++++++++ ...valJapaneseDateInlineExpressionParser.java | 183 +++++++++++++++ ...here.infra.expr.spi.InlineExpressionParser | 19 ++ .../IntervalInlineExpressionParserTest.java | 87 +++++++ ...apaneseDateInlineExpressionParserTest.java | 73 ++++++ infra/expr/type/pom.xml | 1 + 11 files changed, 733 insertions(+), 12 deletions(-) create mode 100644 infra/expr/type/interval/pom.xml create mode 100644 infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParser.java create mode 100644 infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParser.java create mode 100644 infra/expr/type/interval/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser create mode 100644 infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParserTest.java create mode 100644 infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParserTest.java diff --git a/docs/document/content/dev-manual/sharding.cn.md b/docs/document/content/dev-manual/sharding.cn.md index 4c6e9f61997bda..8f300d2b42f04e 100644 --- a/docs/document/content/dev-manual/sharding.cn.md +++ b/docs/document/content/dev-manual/sharding.cn.md @@ -92,8 +92,10 @@ chapter = true ### 已知实现 -| *配置标识* | *详细说明* | *全限定类名* | -|----------|----------------------------------------------------|--------------------------------------------------------------------------------| -| GROOVY | 使用 Groovy 语法的行表达式 | `org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser` | -| LITERAL | 使用标准列表的行表达式 | `org.apache.shardingsphere.infra.expr.literal.LiteralInlineExpressionParser` | -| ESPRESSO | 基于 GraalVM Truffle 的 Espresso 实现的使用 Groovy 语法的行表达式 | `org.apache.shardingsphere.infra.expr.espresso.EspressoInlineExpressionParser` | +| *配置标识* | *详细说明* | *全限定类名* | +|------------------------|----------------------------------------------------|--------------------------------------------------------------------------------------------| +| GROOVY | 使用 Groovy 语法的行表达式 | `org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser` | +| LITERAL | 使用标准列表的行表达式 | `org.apache.shardingsphere.infra.expr.literal.LiteralInlineExpressionParser` | +| INTERVAL | 基于固定时间范围的 Key-Value 语法的行表达式 | `org.apache.shardingsphere.infra.expr.interval.IntervalInlineExpressionParser` | +| INTERVAL.JAPANESE.DATE | 基于固定时间范围和 JapaneseDate 格式的 Key-Value 语法的行表达式 | `org.apache.shardingsphere.infra.expr.interval.IntervalJapaneseDateInlineExpressionParser` | +| ESPRESSO | 基于 GraalVM Truffle 的 Espresso 实现的使用 Groovy 语法的行表达式 | `org.apache.shardingsphere.infra.expr.espresso.EspressoInlineExpressionParser` | diff --git a/docs/document/content/dev-manual/sharding.en.md b/docs/document/content/dev-manual/sharding.en.md index 9452548ecba362..4d26ba4139e2bc 100644 --- a/docs/document/content/dev-manual/sharding.en.md +++ b/docs/document/content/dev-manual/sharding.en.md @@ -92,8 +92,10 @@ Row Value Expressions definition ### Implementation classes -| *Configuration Type* | *Description* | *Fully-qualified class name* | -|----------------------|------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------| -| GROOVY | Row Value Expressions that uses the Groovy syntax | `org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser` | -| LITERAL | Row Value Expressions that uses a standard list | `org.apache.shardingsphere.infra.expr.literal.LiteralInlineExpressionParser` | -| ESPRESSO | Row Value Expressions that uses the Groovy syntax based on GraalVM Truffle's Espresso implementation | `org.apache.shardingsphere.infra.expr.espresso.EspressoInlineExpressionParser` | +| *Configuration Type* | *Description* | *Fully-qualified class name* | +|------------------------|------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| GROOVY | Row Value Expressions that uses the Groovy syntax | `org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser` | +| LITERAL | Row Value Expressions that uses a standard list | `org.apache.shardingsphere.infra.expr.literal.LiteralInlineExpressionParser` | +| INTERVAL | Row Value Expressions based on fixed interval that uses the Key-Value syntax | `org.apache.shardingsphere.infra.expr.interval.IntervalInlineExpressionParser` | | +| INTERVAL.JAPANESE.DATE | Row Value Expressions based on fixed interval that uses the Key-Value syntax and JapaneseDate format | `org.apache.shardingsphere.infra.expr.interval.IntervalJapaneseDateInlineExpressionParser` | | +| ESPRESSO | Row Value Expressions that uses the Groovy syntax based on GraalVM Truffle's Espresso implementation | `org.apache.shardingsphere.infra.expr.espresso.EspressoInlineExpressionParser` | diff --git a/docs/document/content/user-manual/common-config/builtin-algorithm/expr.cn.md b/docs/document/content/user-manual/common-config/builtin-algorithm/expr.cn.md index c2264fdf86077a..acfbe458890a17 100644 --- a/docs/document/content/user-manual/common-config/builtin-algorithm/expr.cn.md +++ b/docs/document/content/user-manual/common-config/builtin-algorithm/expr.cn.md @@ -27,9 +27,55 @@ weight = 10 - `t_order_1, t_order_2, t_order_3` 将被转化为 `t_order_1, t_order_2, t_order_3` - `t_order_${1..3}` 将被转化为 `t_order_${1..3}` +## 基于固定时间范围的 Key-Value 语法的行表达式 + +`INTERVAL` 实现引入了 Key-Value 风格的属性语法,来通过单行字符串定义一组时间范围内的字符串。这通常用于简化对 `数据分片` 功能的 `actualDataNodes` 的定义。 + +`INTERVAL` 实现定义多个属性的方式为 `Key1=Value1;Key2=Value2`,通过 `;` 号分割键值对,通过 `=` 号分割 `Key` 值和 `Value` 值。 + +此实现主动忽视了 `SP` 的时区信息,这意味着当 `DL` 和 `DU` 含有时区信息时,不会因为时区不一致而发生时区转换。 + +此实现键值对的顺序不敏感, 行表达式尾部不携带 `;` 号。 + +`INTERVAL` 实现引入了如下 Key 的键值: + +1. `P` 代表 prefix 的缩写,意为结果列表单元的前缀, 通常代表真实表的前缀格式。 +2. `SP` 代表 suffix pattern 的缩写,意为结果列表单元的后缀的时间戳格式, 通常代表真实表的后缀格式,必须遵循 Java DateTimeFormatter 的格式。 +例如:yyyyMMdd,yyyyMM 或 yyyy 等。但不支持与 `java.time.chrono.JapaneseDate` 相关的 `GGGGyyyy_MM_dd` 等。 +3. `DIA` 代表 datetime interval amount 的缩写,意为结果列表单元的时间间隔。 +4. `DIU` 代表 datetime interval unit 的缩写,意为分片键时间间隔单位,必须遵循 Java ChronoUnit 的枚举值。例如:`Months`。 +5. `DL` 代表 datetime lower 的缩写,意为时间上界值,格式与 `SP` 定义的时间戳格式一致。 +6. `DU` 代表 datetime upper 的缩写,意为时间上界值,格式与 `SP` 定义的时间戳格式一致。 + +类型:INTERVAL + +用例: + +- `P=t_order_;SP=yyyy_MMdd;DIA=1;DIU=Days;DL=2023_1202;DU=2023_1204` 将被转化为 `t_order_2023_1202, t_order_2023_1203, t_order_2023_1204` +- `P=t_order_;SP=yyyy_MM;DIA=1;DIU=Months;DL=2023_10;DU=2023_12` 将被转化为 `t_order_2023_10, t_order_2023_11, t_order_2023_12` +- `P=t_order_;SP=yyyy;DIA=1;DIU=Years;DL=2021;DU=2023` 将被转化为 `t_order_2021, t_order_2022, t_order_2023` +- `P=t_order_;SP=HH_mm_ss_SSS;DIA=1;DIU=Millis;DL=22_48_52_131;DU=22_48_52_133` 将被转化为 `t_order_22_48_52_131, t_order_22_48_52_132, t_order_22_48_52_133` +- `P=t_order_;SP=yyyy_MM_dd_HH_mm_ss_SSS;DIA=1;DIU=Days;DL=2023_12_04_22_48_52_131;DU=2023_12_06_22_48_52_131` 将被转化为 `t_order_2023_12_04_22_48_52_131, t_order_2023_12_05_22_48_52_131, t_order_2023_12_06_22_48_52_131` +- `P=t_order_;SP=MM;DIA=1;DIU=Months;DL=10;DU=12` 将被转化为 `t_order_10, t_order_11, t_order_12` + +## 基于固定时间范围和 JapaneseDate 格式的 Key-Value 语法的行表达式 + +`INTERVAL.JAPANESE.DATE` 仅支持 `java.time.chrono.JapaneseDate` 相关的 `GGGGyyyy_MM_dd` 等时间戳后缀。 +`INTERVAL.JAPANESE.DATE` 是对 `INTERVAL` 不支持特性的补充。 + +受 https://bugs.openjdk.org/browse/JDK-8068571 影响,此实现仅在 JDK 11+ 上可用。 + +类型:INTERVAL.JAPANESE.DATE + +用例: + +- `P=t_order_;SP=GGGGyyyy_MM_dd;DIA=1;DIU=Days;DL=平成0001_12_05;DU=平成0001_12_06` 将被转化为 `t_order_平成0001_12_05, t_order_平成0001_12_06` +- `P=t_order_;SP=GGGGyyy_MM_dd;DIA=1;DIU=Days;DL=平成001_12_05;DU=平成001_12_06` 将被转化为 `t_order_平成001_12_05, t_order_平成001_12_06` +- `P=t_order_;SP=GGGGy_MM_dd;DIA=1;DIU=Days;DL=平成1_12_05;DU=平成1_12_06` 将被转化为 `t_order_平成1_12_05, t_order_平成1_12_06` + ## 基于 GraalVM Truffle 的 Espresso 实现的使用 Groovy 语法的行表达式 -此为可选实现,你需要在自有项目的 `pom.xml` 主动声明如下依赖。并且请确保自有项目通过 GraalVM CE 23.0.1 For JDK17 编译。 +此为可选实现,你需要在自有项目的 `pom.xml` 主动声明如下依赖。并且请确保自有项目通过 GraalVM CE 23.0.1 For JDK 17.0.9 编译。 ```xml diff --git a/docs/document/content/user-manual/common-config/builtin-algorithm/expr.en.md b/docs/document/content/user-manual/common-config/builtin-algorithm/expr.en.md index f77b8e9de2f7c5..a7d2234f9c7c3a 100644 --- a/docs/document/content/user-manual/common-config/builtin-algorithm/expr.en.md +++ b/docs/document/content/user-manual/common-config/builtin-algorithm/expr.en.md @@ -31,10 +31,60 @@ Example: - `t_order_1, t_order_2, t_order_3` will be converted to `t_order_1, t_order_2, t_order_3` - `t_order_${1..3}` will be converted to `t_order_${1..3}` +## Row Value Expressions based on fixed interval that uses the Key-Value syntax + +The `INTERVAL` implementation introduces a Key-Value style property syntax to define a set of time ranges of strings via a single line string. +This is often used to simplify the definition of `actualDataNodes` for `Sharding` feature. + +`INTERVAL` implements the method of defining multiple attributes as `Key1=Value1;Key2=Value2`, using `;` to separate key-value pairs, and `=` to separate `Key` values and `Value` values. + +This implementation actively ignores the time zone information of `SP`, which means that when `DL` and `DU` contain time zone information, +no time zone conversion will occur due to inconsistent time zones. + +This implementation is not sensitive to the order of key-value pairs, and the line expression does not carry the `;` sign at the end. + +The `INTERVAL` implementation introduces the following Key values: + +1. `P` stands for the abbreviation of prefix, which means the prefix of the result list unit, usually representing the prefix format of the real table. +2. `SP` stands for the abbreviation of suffix pattern, which means the timestamp format of the suffix of the result list unit. +It usually represents the suffix format of the real table and must follow the format of Java DateTimeFormatter. +For example: yyyyMMdd, yyyyMM or yyyy etc. But `GGGGyyyy_MM_dd` etc. related to `java.time.chrono.JapaneseDate` are not supported. +3. `DIA` stands for the abbreviation of datetime interval amount, which means the time interval of the result list unit. +4. `DIU` stands for the abbreviation of datetime interval unit, which means the shard key time interval unit. +It must follow the enumeration value of Java ChronoUnit. For example: `Months`. +5. `DL` stands for the abbreviation of datetime lower, which means the upper bound of time. The format is consistent with the timestamp format defined by `SP`. +6. `DU` stands for the abbreviation of datetime upper, which means the upper bound value of time. The format is consistent with the timestamp format defined by `SP`. + +Type: INTERVAL + +Example: + +- `P=t_order_;SP=yyyy_MMdd;DIA=1;DIU=Days;DL=2023_1202;DU=2023_1204` will be converted to `t_order_2023_1202, t_order_2023_1203, t_order_2023_1204` +- `P=t_order_;SP=yyyy_MM;DIA=1;DIU=Months;DL=2023_10;DU=2023_12` will be converted to `t_order_2023_10, t_order_2023_11, t_order_2023_12` +- `P=t_order_;SP=yyyy;DIA=1;DIU=Years;DL=2021;DU=2023` will be converted to `t_order_2021, t_order_2022, t_order_2023` +- `P=t_order_;SP=HH_mm_ss_SSS;DIA=1;DIU=Millis;DL=22_48_52_131;DU=22_48_52_133` will be converted to `t_order_22_48_52_131, t_order_22_48_52_132, t_order_22_48_52_133` +- `P=t_order_;SP=yyyy_MM_dd_HH_mm_ss_SSS;DIA=1;DIU=Days;DL=2023_12_04_22_48_52_131;DU=2023_12_06_22_48_52_131` will be converted to `t_order_2023_12_04_22_48_52_131, t_order_2023_12_05_22_48_52_131, t_order_2023_12_06_22_48_52_131` +- `P=t_order_;SP=MM;DIA=1;DIU=Months;DL=10;DU=12` will be converted to `t_order_10, t_order_11, t_order_12` + +## Row Value Expressions based on fixed interval that uses the Key-Value syntax and JapaneseDate format + +`INTERVAL.JAPANESE.DATE` only supports timestamp suffixes such as `GGGGyyyy_MM_dd` related to `java.time.chrono.JapaneseDate`. +`INTERVAL.JAPANESE.DATE` is in addition to features not supported by `INTERVAL`. + +Affected by https://bugs.openjdk.org/browse/JDK-8068571, this implementation is only available on JDK 11+. + +Type: INTERVAL.JAPANESE.DATE + +Example: + +- `P=t_order_;SP=GGGGyyyy_MM_dd;DIA=1;DIU=Days;DL=平成0001_12_05;DU=平成0001_12_06` will be converted to `t_order_平成0001_12_05, t_order_平成0001_12_06` +- `P=t_order_;SP=GGGGyyy_MM_dd;DIA=1;DIU=Days;DL=平成001_12_05;DU=平成001_12_06` will be converted to `t_order_平成001_12_05, t_order_平成001_12_06` +- `P=t_order_;SP=GGGGy_MM_dd;DIA=1;DIU=Days;DL=平成1_12_05;DU=平成1_12_06` will be converted to `t_order_平成1_12_05, t_order_平成1_12_06` + ## Row Value Expressions that uses the Groovy syntax based on GraalVM Truffle's Espresso implementation This is an optional implementation, and you need to actively declare the following dependencies in the `pom.xml` of your own project. -And make sure your own project is compiled with GraalVM CE 23.0.1 For JDK17. +And make sure your own project is compiled with GraalVM CE 23.0.1 For JDK 17.0.9. ```xml diff --git a/infra/expr/type/interval/pom.xml b/infra/expr/type/interval/pom.xml new file mode 100644 index 00000000000000..8c7f5837bfa07d --- /dev/null +++ b/infra/expr/type/interval/pom.xml @@ -0,0 +1,43 @@ + + + + + 4.0.0 + + org.apache.shardingsphere + shardingsphere-infra-expr-type + 5.4.2-SNAPSHOT + + shardingsphere-infra-expr-interval + ${project.artifactId} + + + + org.apache.shardingsphere + shardingsphere-infra-expr-spi + ${project.version} + + + + org.apache.shardingsphere + shardingsphere-test-util + ${project.version} + test + + + diff --git a/infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParser.java b/infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParser.java new file mode 100644 index 00000000000000..83b46b68fbed8d --- /dev/null +++ b/infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParser.java @@ -0,0 +1,215 @@ +/* + * 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.shardingsphere.infra.expr.interval; + +import com.google.common.base.Strings; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.Year; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +/** + * Interval inline expression parser. + */ +public class IntervalInlineExpressionParser implements InlineExpressionParser { + + /** + * Abbreviation for prefix. + */ + private static final String PREFIX_KEY = "P"; + + /** + * Abbreviation for suffix pattern. + */ + private static final String SUFFIX_PATTERN_KEY = "SP"; + + /** + * Abbreviation for datetime interval amount. + */ + private static final String INTERVAL_AMOUNT_KEY = "DIA"; + + /** + * Abbreviation for datetime interval unit. + */ + private static final String INTERVAL_UNIT_KEY = "DIU"; + + /** + * Abbreviation for datetime lower. + */ + private static final String DATE_TIME_LOWER_KEY = "DL"; + + /** + * Abbreviation for datetime upper. + */ + private static final String DATE_TIME_UPPER_KEY = "DU"; + + private TemporalAccessor startTime; + + private TemporalAccessor endTime; + + private String prefix; + + private DateTimeFormatter dateTimeFormatterForSuffixPattern; + + private int stepAmount; + + private ChronoUnit stepUnit; + + private String inlineExpression; + + @Override + public void init(final Properties props) { + inlineExpression = props.getProperty(INLINE_EXPRESSION_KEY); + Map propsMap = Arrays.stream(inlineExpression.split(";")) + .collect(Collectors.toMap(string -> string.split("=")[0], string -> string.split("=")[1])); + prefix = getPrefix(propsMap); + dateTimeFormatterForSuffixPattern = getSuffixPattern(propsMap); + startTime = getDateTimeLower(propsMap); + endTime = getDateTimeUpper(propsMap); + stepAmount = getStepAmount(propsMap); + stepUnit = getStepUnit(propsMap); + } + + @Override + public String handlePlaceHolder() { + return inlineExpression; + } + + @Override + public List splitAndEvaluate() { + return Strings.isNullOrEmpty(inlineExpression) ? Collections.emptyList() : split(); + } + + private String getPrefix(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(PREFIX_KEY), + () -> new RuntimeException(String.format("%s can not be null.", PREFIX_KEY))); + return props.get(PREFIX_KEY); + } + + private TemporalAccessor getDateTimeLower(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(DATE_TIME_LOWER_KEY), + () -> new RuntimeException(String.format("%s can not be null.", DATE_TIME_LOWER_KEY))); + return getDateTime(props.get(DATE_TIME_LOWER_KEY)); + } + + private TemporalAccessor getDateTimeUpper(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(DATE_TIME_UPPER_KEY), + () -> new RuntimeException(String.format("%s can not be null.", DATE_TIME_UPPER_KEY))); + return getDateTime(props.get(DATE_TIME_UPPER_KEY)); + } + + private TemporalAccessor getDateTime(final String dateTimeValue) { + try { + return dateTimeFormatterForSuffixPattern.parse(dateTimeValue); + } catch (final DateTimeParseException dateTimeParseException) { + throw new RuntimeException(dateTimeParseException); + } + } + + private DateTimeFormatter getSuffixPattern(final Map props) { + String suffix = props.get(SUFFIX_PATTERN_KEY); + ShardingSpherePreconditions.checkState(!Strings.isNullOrEmpty(suffix), + () -> new RuntimeException(String.format("%s can not be null or empty.", SUFFIX_PATTERN_KEY))); + return DateTimeFormatter.ofPattern(suffix); + } + + private int getStepAmount(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(INTERVAL_AMOUNT_KEY), + () -> new RuntimeException(String.format("%s can not be null.", INTERVAL_AMOUNT_KEY))); + return Integer.parseInt(props.get(INTERVAL_AMOUNT_KEY)); + } + + private ChronoUnit getStepUnit(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(INTERVAL_UNIT_KEY), + () -> new RuntimeException(String.format("%s can not be null.", INTERVAL_UNIT_KEY))); + String stepUnit = props.get(INTERVAL_UNIT_KEY); + return Arrays.stream(ChronoUnit.values()) + .filter(chronoUnit -> chronoUnit.toString().equals(stepUnit)) + .findFirst() + .orElseThrow(() -> new RuntimeException(String.format("Cannot find step unit for specified %s property: `%s`", INTERVAL_UNIT_KEY, stepUnit))); + } + + private List split() { + TemporalAccessor calculateTime = startTime; + if (!calculateTime.isSupported(ChronoField.NANO_OF_DAY)) { + if (calculateTime.isSupported(ChronoField.EPOCH_DAY)) { + return convertStringFromTemporal(startTime.query(LocalDate::from), endTime.query(LocalDate::from)); + } + if (calculateTime.isSupported(ChronoField.YEAR) && calculateTime.isSupported(ChronoField.MONTH_OF_YEAR)) { + return convertStringFromTemporal(startTime.query(YearMonth::from), endTime.query(YearMonth::from)); + } + if (calculateTime.isSupported(ChronoField.YEAR)) { + return convertStringFromTemporal(startTime.query(Year::from), endTime.query(Year::from)); + } + if (calculateTime.isSupported(ChronoField.MONTH_OF_YEAR)) { + return convertStringFromMonth(); + } + } + if (!calculateTime.isSupported(ChronoField.EPOCH_DAY)) { + return convertStringFromTemporal(startTime.query(LocalTime::from), endTime.query(LocalTime::from)); + } + return convertStringFromTemporal(startTime.query(LocalDateTime::from), endTime.query(LocalDateTime::from)); + } + + private List convertStringFromMonth() { + Month startTimeAsMonth = startTime.query(Month::from); + Month endTimeAsMonth = endTime.query(Month::from); + return LongStream.iterate(0, x -> x + stepAmount) + .limit((endTimeAsMonth.getValue() - startTimeAsMonth.getValue()) / stepAmount + 1) + .parallel() + .boxed() + .map(startTimeAsMonth::plus) + .map(dateTimeFormatterForSuffixPattern::format) + .map(suffix -> prefix + suffix) + .collect(Collectors.toList()); + } + + private List convertStringFromTemporal(final Temporal startTimeAsTemporal, final Temporal endTimeAsTemporal) { + return LongStream.iterate(0, x -> x + stepAmount) + .limit(stepUnit.between(startTimeAsTemporal, endTimeAsTemporal) / stepAmount + 1) + .parallel() + .boxed() + .map(arithmeticSequence -> startTimeAsTemporal.plus(arithmeticSequence, stepUnit)) + .map(dateTimeFormatterForSuffixPattern::format) + .map(suffix -> prefix + suffix) + .collect(Collectors.toList()); + } + + @Override + public String getType() { + return "INTERVAL"; + } +} diff --git a/infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParser.java b/infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParser.java new file mode 100644 index 00000000000000..447daa324bfabc --- /dev/null +++ b/infra/expr/type/interval/src/main/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParser.java @@ -0,0 +1,183 @@ +/* + * 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.shardingsphere.infra.expr.interval; + +import com.google.common.base.Strings; +import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions; +import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser; + +import java.time.chrono.JapaneseChronology; +import java.time.chrono.JapaneseDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +/** + * Interval JapaneseDate inline expression parser. + * + * @see java.time.chrono.JapaneseDate + */ +public class IntervalJapaneseDateInlineExpressionParser implements InlineExpressionParser { + + /** + * Abbreviation for prefix. + */ + private static final String PREFIX_KEY = "P"; + + /** + * Abbreviation for suffix pattern. + */ + private static final String SUFFIX_PATTERN_KEY = "SP"; + + /** + * Abbreviation for datetime interval amount. + */ + private static final String INTERVAL_AMOUNT_KEY = "DIA"; + + /** + * Abbreviation for datetime interval unit. + */ + private static final String INTERVAL_UNIT_KEY = "DIU"; + + /** + * Abbreviation for datetime lower. + */ + private static final String DATE_TIME_LOWER_KEY = "DL"; + + /** + * Abbreviation for datetime upper. + */ + private static final String DATE_TIME_UPPER_KEY = "DU"; + + private TemporalAccessor startTime; + + private TemporalAccessor endTime; + + private String prefix; + + private DateTimeFormatter dateTimeFormatterForSuffixPattern; + + private int stepAmount; + + private ChronoUnit stepUnit; + + private String inlineExpression; + + @Override + public void init(final Properties props) { + inlineExpression = props.getProperty(INLINE_EXPRESSION_KEY); + Map propsMap = Arrays.stream(inlineExpression.split(";")) + .collect(Collectors.toMap(string -> string.split("=")[0], string -> string.split("=")[1])); + prefix = getPrefix(propsMap); + dateTimeFormatterForSuffixPattern = getSuffixPattern(propsMap); + startTime = getDateTimeLower(propsMap); + endTime = getDateTimeUpper(propsMap); + stepAmount = getStepAmount(propsMap); + stepUnit = getStepUnit(propsMap); + } + + @Override + public String handlePlaceHolder() { + return inlineExpression; + } + + @Override + public List splitAndEvaluate() { + return Strings.isNullOrEmpty(inlineExpression) ? Collections.emptyList() : convertStringFromJapaneseDate(); + } + + private String getPrefix(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(PREFIX_KEY), + () -> new RuntimeException(String.format("%s can not be null.", PREFIX_KEY))); + return props.get(PREFIX_KEY); + } + + private TemporalAccessor getDateTimeLower(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(DATE_TIME_LOWER_KEY), + () -> new RuntimeException(String.format("%s can not be null.", DATE_TIME_LOWER_KEY))); + return getDateTime(props.get(DATE_TIME_LOWER_KEY)); + } + + private TemporalAccessor getDateTimeUpper(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(DATE_TIME_UPPER_KEY), + () -> new RuntimeException(String.format("%s can not be null.", DATE_TIME_UPPER_KEY))); + return getDateTime(props.get(DATE_TIME_UPPER_KEY)); + } + + private TemporalAccessor getDateTime(final String dateTimeValue) { + try { + return dateTimeFormatterForSuffixPattern.withChronology(JapaneseChronology.INSTANCE).parse(dateTimeValue); + } catch (final DateTimeParseException ignore) { + try { + return dateTimeFormatterForSuffixPattern.withChronology(JapaneseChronology.INSTANCE).parse(dateTimeValue); + } catch (final DateTimeParseException dateTimeParseException) { + throw new RuntimeException(dateTimeParseException); + } + } + } + + private DateTimeFormatter getSuffixPattern(final Map props) { + String suffix = props.get(SUFFIX_PATTERN_KEY); + ShardingSpherePreconditions.checkState(!Strings.isNullOrEmpty(suffix), + () -> new RuntimeException(String.format("%s can not be null or empty.", SUFFIX_PATTERN_KEY))); + return DateTimeFormatter.ofPattern(suffix); + } + + private ChronoUnit getStepUnit(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(INTERVAL_UNIT_KEY), + () -> new RuntimeException(String.format("%s can not be null.", INTERVAL_UNIT_KEY))); + String stepUnit = props.get(INTERVAL_UNIT_KEY); + return Arrays.stream(ChronoUnit.values()) + .filter(chronoUnit -> chronoUnit.toString().equals(stepUnit)) + .findFirst() + .orElseThrow(() -> new RuntimeException(String.format("Cannot find step unit for specified %s property: `%s`", INTERVAL_UNIT_KEY, stepUnit))); + } + + private int getStepAmount(final Map props) { + ShardingSpherePreconditions.checkState(props.containsKey(INTERVAL_AMOUNT_KEY), + () -> new RuntimeException(String.format("%s can not be null.", INTERVAL_AMOUNT_KEY))); + return Integer.parseInt(props.get(INTERVAL_AMOUNT_KEY)); + } + + private List convertStringFromJapaneseDate() { + Temporal startTimeAsJapaneseDate = startTime.query(JapaneseDate::from); + Temporal endTimeAsJapaneseDate = endTime.query(JapaneseDate::from); + return LongStream.iterate(0, x -> x + stepAmount) + .limit(stepUnit.between(startTimeAsJapaneseDate, endTimeAsJapaneseDate) / stepAmount + 1) + .parallel() + .boxed() + .map(arithmeticSequence -> startTimeAsJapaneseDate.plus(arithmeticSequence, stepUnit)) + .map(temporal -> dateTimeFormatterForSuffixPattern.withChronology(JapaneseChronology.INSTANCE).format(temporal)) + .map(suffix -> prefix + suffix) + .collect(Collectors.toList()); + } + + @Override + public String getType() { + return "INTERVAL.JAPANESE.DATE"; + } +} diff --git a/infra/expr/type/interval/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser b/infra/expr/type/interval/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser new file mode 100644 index 00000000000000..b43a3b65a3a936 --- /dev/null +++ b/infra/expr/type/interval/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser @@ -0,0 +1,19 @@ +# +# 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. +# + +org.apache.shardingsphere.infra.expr.interval.IntervalInlineExpressionParser +org.apache.shardingsphere.infra.expr.interval.IntervalJapaneseDateInlineExpressionParser diff --git a/infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParserTest.java b/infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParserTest.java new file mode 100644 index 00000000000000..f4e87968d9e50b --- /dev/null +++ b/infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalInlineExpressionParserTest.java @@ -0,0 +1,87 @@ +/* + * 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.shardingsphere.infra.expr.interval; + +import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.test.util.PropertiesBuilder; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class IntervalInlineExpressionParserTest { + + @Test + void assertEvaluateForSimple() { + List expected = getResultList("P=ds-0.t_order;SP=yyyyMMdd;DIA=1;DIU=Days;DL=20231202;DU=20231202"); + assertThat(expected.size(), is(1)); + assertThat(expected, hasItems("ds-0.t_order20231202")); + } + + @Test + void assertEvaluateForLocalDate() { + List expected = getResultList("P=ds-0.t_order_;SP=yyyy_MMdd;DIA=1;DIU=Days;DL=2023_1202;DU=2023_1204"); + assertThat(expected.size(), is(3)); + assertThat(expected, hasItems("ds-0.t_order_2023_1202", "ds-0.t_order_2023_1203", "ds-0.t_order_2023_1204")); + } + + @Test + void assertEvaluateForYearMonth() { + List expected = getResultList("P=ds-0.t_order_;SP=yyyy_MM;DIA=1;DIU=Months;DL=2023_10;DU=2023_12"); + assertThat(expected.size(), is(3)); + assertThat(expected, hasItems("ds-0.t_order_2023_10", "ds-0.t_order_2023_11", "ds-0.t_order_2023_12")); + } + + @Test + void assertEvaluateForYear() { + List expected = getResultList("P=ds-0.t_order_;SP=yyyy;DIA=1;DIU=Years;DL=2021;DU=2023"); + assertThat(expected.size(), is(3)); + assertThat(expected, hasItems("ds-0.t_order_2021", "ds-0.t_order_2022", "ds-0.t_order_2023")); + } + + @Test + void assertEvaluateForLocalTime() { + List expected = getResultList("P=ds-0.t_order_;SP=HH_mm_ss_SSS;DIA=1;DIU=Millis;DL=22_48_52_131;DU=22_48_52_133"); + assertThat(expected.size(), is(3)); + assertThat(expected, hasItems("ds-0.t_order_22_48_52_131", "ds-0.t_order_22_48_52_132", "ds-0.t_order_22_48_52_133")); + } + + @Test + void assertEvaluateForLocalDateTime() { + List expected = getResultList("P=ds-0.t_order_;SP=yyyy_MM_dd_HH_mm_ss_SSS;DIA=1;DIU=Days;DL=2023_12_04_22_48_52_131;DU=2023_12_06_22_48_52_131"); + assertThat(expected.size(), is(3)); + assertThat(expected, hasItems("ds-0.t_order_2023_12_04_22_48_52_131", "ds-0.t_order_2023_12_05_22_48_52_131", "ds-0.t_order_2023_12_06_22_48_52_131")); + } + + @Test + void assertEvaluateForMonth() { + List expected = getResultList("P=ds-0.t_order_;SP=MM;DIA=1;DIU=Months;DL=10;DU=12"); + assertThat(expected.size(), is(3)); + assertThat(expected, hasItems("ds-0.t_order_10", "ds-0.t_order_11", "ds-0.t_order_12")); + } + + private List getResultList(final String inlineExpression) { + return TypedSPILoader.getService(InlineExpressionParser.class, "INTERVAL", PropertiesBuilder.build(new PropertiesBuilder.Property( + InlineExpressionParser.INLINE_EXPRESSION_KEY, inlineExpression))) + .splitAndEvaluate(); + } +} diff --git a/infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParserTest.java b/infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParserTest.java new file mode 100644 index 00000000000000..08038b9c11695f --- /dev/null +++ b/infra/expr/type/interval/src/test/java/org/apache/shardingsphere/infra/expr/interval/IntervalJapaneseDateInlineExpressionParserTest.java @@ -0,0 +1,73 @@ +/* + * 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.shardingsphere.infra.expr.interval; + +import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.test.util.PropertiesBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Background reference JDK-8068571. + */ +@EnabledForJreRange(min = JRE.JAVA_11, max = JRE.OTHER) +public class IntervalJapaneseDateInlineExpressionParserTest { + + @Test + void assertEvaluateForSimple() { + List expected = getResultList("P=ds-0.t_order_;SP=GGGGyyyy_MM_dd;DIA=1;DIU=Days;DL=平成0001_12_05;DU=平成0001_12_05"); + assertThat(expected.size(), is(1)); + assertThat(expected, hasItems("ds-0.t_order_平成0001_12_05")); + } + + @Test + void assertEvaluateForJapaneseDate() { + List expectedBYGGGGyyyy = getResultList("P=ds-0.t_order_;SP=GGGGyyyy_MM_dd;DIA=1;DIU=Days;DL=平成0001_12_05;DU=平成0001_12_06"); + assertThat(expectedBYGGGGyyyy.size(), is(2)); + assertThat(expectedBYGGGGyyyy, hasItems("ds-0.t_order_平成0001_12_05", "ds-0.t_order_平成0001_12_06")); + List expectedByGGGGyyy = getResultList("P=ds-0.t_order_;SP=GGGGyyy_MM_dd;DIA=1;DIU=Days;DL=平成001_12_05;DU=平成001_12_06"); + assertThat(expectedByGGGGyyy.size(), is(2)); + assertThat(expectedByGGGGyyy, hasItems("ds-0.t_order_平成001_12_05", "ds-0.t_order_平成001_12_06")); + List expectedByGGGGy = getResultList("P=ds-0.t_order_;SP=GGGGy_MM_dd;DIA=1;DIU=Days;DL=平成1_12_05;DU=平成1_12_06"); + assertThat(expectedByGGGGy.size(), is(2)); + assertThat(expectedByGGGGy, hasItems("ds-0.t_order_平成1_12_05", "ds-0.t_order_平成1_12_06")); + } + + /** + * Unable to parse `GGGGyy_MM_dd` due to OpenJDK 21.0.1 limitation. + */ + @Test + void assertEvaluateForJapaneseDateByGGGGyy() { + assertThrows(RuntimeException.class, () -> getResultList("P=ds-0.t_order_;SP=GGGGyy_MM_dd;DIA=1;DIU=Days;DL=平成01_12_05;DU=平成01_12_06")); + } + + private List getResultList(final String inlineExpression) { + return TypedSPILoader.getService(InlineExpressionParser.class, "INTERVAL.JAPANESE.DATE", PropertiesBuilder.build(new PropertiesBuilder.Property( + InlineExpressionParser.INLINE_EXPRESSION_KEY, inlineExpression))) + .splitAndEvaluate(); + } +} diff --git a/infra/expr/type/pom.xml b/infra/expr/type/pom.xml index a8725c59aeddc8..0b3aec2b2b5e76 100644 --- a/infra/expr/type/pom.xml +++ b/infra/expr/type/pom.xml @@ -31,5 +31,6 @@ groovy literal espresso + interval