diff --git a/fe/src/main/java/org/apache/doris/common/ErrorCode.java b/fe/src/main/java/org/apache/doris/common/ErrorCode.java index 88311987b6e39f..67cf454b2a5201 100644 --- a/fe/src/main/java/org/apache/doris/common/ErrorCode.java +++ b/fe/src/main/java/org/apache/doris/common/ErrorCode.java @@ -81,6 +81,7 @@ public enum ErrorCode { ERR_NOT_SUPPORTED_AUTH_MODE(1251, new byte[] {'0', '8', '0', '0', '4'}, "Client does not support authentication protocol requested by server; consider upgrading MySQL client"), ERR_UNKNOWN_STORAGE_ENGINE(1286, new byte[] {'4', '2', '0', '0', '0'}, "Unknown storage engine '%s'"), + ERR_UNKNOWN_TIME_ZONE(1298, new byte[] {'H', 'Y', '0', '0', '0'}, "Unknown or incorrect time zone: '%s'"), ERR_WRONG_OBJECT(1347, new byte[] {'H', 'Y', '0', '0', '0'}, "'%s'.'%s' is not '%s'"), ERR_VIEW_WRONG_LIST(1353, new byte[] {'H', 'Y', '0', '0', '0'}, "View's SELECT and view's field list have different column counts"), diff --git a/fe/src/main/java/org/apache/doris/common/util/TimeUtils.java b/fe/src/main/java/org/apache/doris/common/util/TimeUtils.java index 7adae22a674572..fa15d0a1e40ec3 100644 --- a/fe/src/main/java/org/apache/doris/common/util/TimeUtils.java +++ b/fe/src/main/java/org/apache/doris/common/util/TimeUtils.java @@ -22,13 +22,19 @@ import org.apache.doris.common.AnalysisException; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import org.apache.doris.common.DdlException; +import org.apache.doris.common.ErrorCode; +import org.apache.doris.common.ErrorReport; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; +import java.time.DateTimeException; +import java.time.ZoneId; import java.util.Calendar; import java.util.Date; import java.util.SimpleTimeZone; @@ -42,6 +48,9 @@ public class TimeUtils { private static final TimeZone TIME_ZONE; + // set CST to +08:00 instead of America/Chicago + public static final ImmutableMap timeZoneAliasMap = ImmutableMap.of("CST", "Asia/Shanghai"); + // NOTICE: Date formats are not synchronized. // it must be used as synchronized externally. private static final SimpleDateFormat DATE_FORMAT; @@ -56,6 +65,8 @@ public class TimeUtils { + "[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?" + "((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))" + "(\\s(((0?[0-9])|([1][0-9])|([2][0-3]))\\:([0-5]?[0-9])((\\s)|(\\:([0-5]?[0-9])))))?$"); + + private static final Pattern TIMEZONE_OFFSET_FORMAT_REG = Pattern.compile("^[+-]{1}\\d{2}\\:\\d{2}$"); public static Date MIN_DATE = null; public static Date MAX_DATE = null; @@ -205,4 +216,29 @@ public static long timeStringToLong(String timeStr) { } return d.getTime(); } + + // Check if the time zone_value is valid + public static void checkTimeZoneValid(String value) throws DdlException { + try { + // match offset type, such as +08:00, -07:00 + Matcher matcher = TIMEZONE_OFFSET_FORMAT_REG.matcher(value); + // it supports offset and region timezone type, "CST" use here is compatibility purposes. + boolean match = matcher.matches(); + if (!value.contains("/") && !value.equals("CST") && !match) { + ErrorReport.reportDdlException(ErrorCode.ERR_UNKNOWN_TIME_ZONE, value); + } + if (match) { + // timezone offsets around the world extended from -12:00 to +14:00 + int tz = Integer.parseInt(value.substring(1, 3)) * 100 + Integer.parseInt(value.substring(4, 6)); + if (value.charAt(0) == '-' && tz > 1200) { + ErrorReport.reportDdlException(ErrorCode.ERR_UNKNOWN_TIME_ZONE, value); + } else if (value.charAt(0) == '+' && tz > 1400) { + ErrorReport.reportDdlException(ErrorCode.ERR_UNKNOWN_TIME_ZONE, value); + } + } + ZoneId.of(value, timeZoneAliasMap); + } catch (DateTimeException ex) { + ErrorReport.reportDdlException(ErrorCode.ERR_UNKNOWN_TIME_ZONE, value); + } + } } diff --git a/fe/src/main/java/org/apache/doris/qe/GlobalVariable.java b/fe/src/main/java/org/apache/doris/qe/GlobalVariable.java index 6b8bd8fcc20941..70105aed32fd55 100644 --- a/fe/src/main/java/org/apache/doris/qe/GlobalVariable.java +++ b/fe/src/main/java/org/apache/doris/qe/GlobalVariable.java @@ -19,6 +19,8 @@ import org.apache.doris.common.Version; +import java.time.ZoneId; + // You can place your global variable in this class with public and VariableMgr.VarAttr annotation. // You can get this variable from MySQL client with statement `SELECT @@variable_name`, // and change its value through `SET variable_name = xxx` @@ -48,7 +50,7 @@ public final class GlobalVariable { // A string to be executed by the server for each client that connects @VariableMgr.VarAttr(name = "system_time_zone", flag = VariableMgr.READ_ONLY) - private static String systemTimeZone = "CST"; + public static String systemTimeZone = ZoneId.systemDefault().normalized().toString(); // The amount of memory allocated for caching query results @VariableMgr.VarAttr(name = "query_cache_size") diff --git a/fe/src/main/java/org/apache/doris/qe/VariableMgr.java b/fe/src/main/java/org/apache/doris/qe/VariableMgr.java index 31579feb2f5f43..8754103cac9143 100644 --- a/fe/src/main/java/org/apache/doris/qe/VariableMgr.java +++ b/fe/src/main/java/org/apache/doris/qe/VariableMgr.java @@ -27,6 +27,7 @@ import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; import org.apache.doris.common.PatternMatcher; +import org.apache.doris.common.util.TimeUtils; import org.apache.doris.persist.EditLog; import com.google.common.collect.ImmutableMap; @@ -203,6 +204,7 @@ private static void checkUpdate(SetVar setVar, int flag) throws DdlException { } } + // Get from show name to field public static void setVar(SessionVariable sessionVariable, SetVar setVar) throws DdlException { VarContext ctx = ctxByVarName.get(setVar.getVariable()); @@ -211,6 +213,10 @@ public static void setVar(SessionVariable sessionVariable, SetVar setVar) throws } // Check variable attribute and setVar checkUpdate(setVar, ctx.getFlag()); + // Check variable time_zone value is valid + if (setVar.getVariable().toLowerCase().equals("time_zone")) { + TimeUtils.checkTimeZoneValid(setVar.getValue().getStringValue()); + } // To modify to default value. VarAttr attr = ctx.getField().getAnnotation(VarAttr.class); diff --git a/fe/src/test/java/org/apache/doris/qe/VariableMgrTest.java b/fe/src/test/java/org/apache/doris/qe/VariableMgrTest.java index 5053e7d9f08a31..355440cf94f74d 100644 --- a/fe/src/test/java/org/apache/doris/qe/VariableMgrTest.java +++ b/fe/src/test/java/org/apache/doris/qe/VariableMgrTest.java @@ -99,11 +99,21 @@ public void testNormal() throws IllegalAccessException, DdlException, NoSuchFiel var = VariableMgr.newSessionVariable(); Assert.assertEquals(5L, var.getParallelExecInstanceNum()); + SetVar setVar3 = new SetVar(SetType.GLOBAL, "time_zone", new StringLiteral("Asia/Shanghai")); + VariableMgr.setVar(var, setVar3); + Assert.assertEquals("CST", var.getTimeZone()); + var = VariableMgr.newSessionVariable(); + Assert.assertEquals("Asia/Shanghai", var.getTimeZone()); + // Set session variable setVar = new SetVar(SetType.GLOBAL, "exec_mem_limit", new IntLiteral(1234L)); VariableMgr.setVar(var, setVar); Assert.assertEquals(1234L, var.getMaxExecMemByte()); + setVar3 = new SetVar(SetType.SESSION, "time_zone", new StringLiteral("Asia/Jakarta")); + VariableMgr.setVar(var, setVar3); + Assert.assertEquals("Asia/Jakarta", var.getTimeZone()); + // Get from name SysVariableDesc desc = new SysVariableDesc("exec_mem_limit"); Assert.assertEquals(var.getMaxExecMemByte() + "", VariableMgr.getValue(var, desc)); @@ -123,6 +133,35 @@ public void testInvalidType() throws DdlException { Assert.fail("No exception throws."); } + @Test(expected = DdlException.class) + public void testInvalidTimeZoneRegion() throws DdlException { + // Set global variable + SetVar setVar = new SetVar(SetType.GLOBAL, "time_zone", new StringLiteral("Hongkong")); + SessionVariable var = VariableMgr.newSessionVariable(); + try { + VariableMgr.setVar(var, setVar); + } catch (DdlException e) { + LOG.warn("VariableMgr throws", e); + throw e; + } + Assert.fail("No exception throws."); + } + + @Test(expected = DdlException.class) + public void testInvalidTimeZoneOffset() throws DdlException { + // Set global variable + SetVar setVar = new SetVar(SetType.GLOBAL, "time_zone", new StringLiteral("+15:00")); + SessionVariable var = VariableMgr.newSessionVariable(); + try { + VariableMgr.setVar(var, setVar); + } catch (DdlException e) { + LOG.warn("VariableMgr throws", e); + throw e; + } + Assert.fail("No exception throws."); + } + + @Test(expected = DdlException.class) public void testReadOnly() throws AnalysisException, DdlException { SysVariableDesc desc = new SysVariableDesc("version_comment");