This is an automated email from the ASF dual-hosted git repository.

jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 67f63e09fa5 Implement Extract expression in TableModel
67f63e09fa5 is described below

commit 67f63e09fa5702d9b773163b18124a4d9eeef921
Author: Weihao Li <[email protected]>
AuthorDate: Fri Jul 18 14:09:18 2025 +0800

    Implement Extract expression in TableModel
---
 .../org/apache/iotdb/db/it/utils/TestUtils.java    |  33 ++-
 .../query/recent/extract/IoTDBExtractTable2IT.java |  72 +++++
 .../query/recent/extract/IoTDBExtractTable3IT.java |  72 +++++
 .../query/recent/extract/IoTDBExtractTableIT.java  | 317 +++++++++++++++++++++
 .../fragment/FragmentInstanceContext.java          |   6 +-
 .../relational/ColumnTransformerBuilder.java       |  23 ++
 .../queryengine/plan/analyze/PredicateUtils.java   |  13 +-
 .../plan/planner/TableOperatorGenerator.java       |  24 +-
 .../plan/planner/plan/TableModelTimePredicate.java |   6 +-
 .../plan/planner/plan/TimePredicate.java           |   4 +-
 .../plan/planner/plan/TreeModelTimePredicate.java  |   4 +-
 .../relational/analyzer/AggregationAnalyzer.java   |   6 +
 .../relational/analyzer/ExpressionAnalyzer.java    |  18 ++
 .../relational/analyzer/StatementAnalyzer.java     |  17 ++
 .../predicate/ConvertPredicateToFilterVisitor.java |   7 +-
 .../ConvertPredicateToTimeFilterVisitor.java       |  80 +++++-
 .../PredicateCombineIntoTableScanChecker.java      |  23 +-
 .../planner/IrExpressionInterpreter.java           |  19 ++
 .../plan/relational/planner/IrTypeAnalyzer.java    |   7 +
 .../relational/planner/ir/ExpressionRewriter.java  |   6 +
 .../planner/ir/ExpressionTreeRewriter.java         |  20 ++
 .../ir/GlobalTimePredicateExtractVisitor.java      |  16 +-
 .../optimizations/PushPredicateIntoTableScan.java  |  13 +-
 .../plan/relational/sql/ast/AstVisitor.java        |   4 +
 .../sql/ast/DefaultTraversalVisitor.java           |   5 +
 .../plan/relational/sql/ast/Expression.java        |   3 +
 .../plan/relational/sql/ast/Extract.java           | 139 +++++++++
 .../relational/sql/ast/TableExpressionType.java    |   3 +-
 .../plan/relational/sql/parser/AstBuilder.java     |  14 +
 .../relational/sql/util/ExpressionFormatter.java   |   6 +
 .../column/unary/scalar/ExtractTransformer.java    | 106 +++++++
 .../org/apache/iotdb/db/utils/DateTimeUtils.java   |  14 +-
 .../relational/analyzer/ExtractExpressionTest.java |  66 +++++
 .../db/relational/grammar/sql/RelationalSql.g4     |   1 +
 pom.xml                                            |   2 +-
 35 files changed, 1135 insertions(+), 34 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java 
b/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java
index 3bd2a17e3f8..7e4d69f9b1b 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/utils/TestUtils.java
@@ -229,7 +229,24 @@ public class TestUtils {
         expectedRetArray,
         SessionConfig.DEFAULT_USER,
         SessionConfig.DEFAULT_PASSWORD,
-        database);
+        database,
+        "+00:00");
+  }
+
+  public static void tableResultSetEqualTest(
+      String sql,
+      String timeZone,
+      String[] expectedHeader,
+      String[] expectedRetArray,
+      String database) {
+    tableResultSetEqualTest(
+        sql,
+        expectedHeader,
+        expectedRetArray,
+        SessionConfig.DEFAULT_USER,
+        SessionConfig.DEFAULT_PASSWORD,
+        database,
+        timeZone);
   }
 
   public static void tableResultSetEqualTest(
@@ -239,9 +256,21 @@ public class TestUtils {
       String userName,
       String password,
       String database) {
+    tableResultSetEqualTest(
+        sql, expectedHeader, expectedRetArray, userName, password, database, 
"+00:00");
+  }
+
+  public static void tableResultSetEqualTest(
+      String sql,
+      String[] expectedHeader,
+      String[] expectedRetArray,
+      String userName,
+      String password,
+      String database,
+      String timeZone) {
     try (Connection connection =
         EnvFactory.getEnv().getConnection(userName, password, 
BaseEnv.TABLE_SQL_DIALECT)) {
-      connection.setClientInfo("time_zone", "+00:00");
+      connection.setClientInfo("time_zone", timeZone);
       try (Statement statement = connection.createStatement()) {
         statement.execute("use " + database);
         try (ResultSet resultSet = statement.executeQuery(sql)) {
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTable2IT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTable2IT.java
new file mode 100644
index 00000000000..7986961db36
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTable2IT.java
@@ -0,0 +1,72 @@
+/*
+ * 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.iotdb.relational.it.query.recent.extract;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
+
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBExtractTable2IT extends IoTDBExtractTableIT {
+  public IoTDBExtractTable2IT() {
+    this.decimal = "123456";
+  }
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    
EnvFactory.getEnv().getConfig().getCommonConfig().setTimestampPrecision("us");
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareTableData(createSqls);
+  }
+
+  @Test
+  public void extractUsNsTest() {
+    String[] expectedHeader = new String[] {"time", "_col1", "_col2"};
+    String[] retArray =
+        new String[] {
+          getTimeStrUTC("2025-07-08T01:18:51") + ",456,0,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, extract(us from time),extract(ns from time)"
+            + " FROM table1 order by time limit 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    retArray =
+        new String[] {
+          getTimeStrUTC8("2025-07-08T09:18:51") + ",456,0,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, extract(us from time),extract(ns from time)"
+            + " FROM table1 order by time limit 1",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+}
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTable3IT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTable3IT.java
new file mode 100644
index 00000000000..0d362dc0820
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTable3IT.java
@@ -0,0 +1,72 @@
+/*
+ * 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.iotdb.relational.it.query.recent.extract;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
+
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBExtractTable3IT extends IoTDBExtractTableIT {
+  public IoTDBExtractTable3IT() {
+    this.decimal = "123456789";
+  }
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    
EnvFactory.getEnv().getConfig().getCommonConfig().setTimestampPrecision("ns");
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareTableData(createSqls);
+  }
+
+  @Test
+  public void extractUsNsTest() {
+    String[] expectedHeader = new String[] {"time", "_col1", "_col2"};
+    String[] retArray =
+        new String[] {
+          getTimeStrUTC("2025-07-08T01:18:51") + ",456,789,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, extract(us from time),extract(ns from time)"
+            + " FROM table1 order by time limit 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    retArray =
+        new String[] {
+          getTimeStrUTC8("2025-07-08T09:18:51") + ",456,789,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, extract(us from time),extract(ns from time)"
+            + " FROM table1 order by time limit 1",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+}
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTableIT.java
new file mode 100644
index 00000000000..8b065b1571f
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/extract/IoTDBExtractTableIT.java
@@ -0,0 +1,317 @@
+/*
+ * 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.iotdb.relational.it.query.recent.extract;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBExtractTableIT {
+  protected static final String DATABASE_NAME = "test";
+  protected static final String[] createSqls =
+      new String[] {
+        "CREATE DATABASE " + DATABASE_NAME,
+        "USE " + DATABASE_NAME,
+        "CREATE TABLE table1(device_id STRING TAG, ts TIMESTAMP FIELD, s1 
INT32 FIELD)",
+        "INSERT INTO table1(time,device_id,ts,s1) values(2025/07/08 
01:18:51.123456789,'d1',2025/07/08 01:18:51.123456789,1)",
+        "INSERT INTO table1(time,device_id,ts,s1) values(2025/07/08 
02:18:51.123456789,'d1',2025/07/08 02:18:51.123456789,2)",
+        "INSERT INTO table1(time,device_id,ts,s1) values(2025/07/09 
00:17:50.123456789,'d1',2025/07/09 00:17:50.123456789,3)",
+        "FLUSH"
+      };
+  protected String decimal = "123";
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareTableData(createSqls);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void extractTest() {
+
+    String[] expectedHeader =
+        new String[] {
+          "time", "_col1", "_col2", "_col3", "_col4", "_col5", "_col6", 
"_col7", "_col8", "_col9",
+          "_col10", "_col11"
+        };
+    String[] retArray =
+        new String[] {getTimeStrUTC("2025-07-08T01:18:51") + 
",2025,3,7,27,8,2,189,1,18,51,123,"};
+    tableResultSetEqualTest(
+        "SELECT time, extract(year from time),extract(quarter from 
time),extract(month from time),extract(week from time),extract(day from time),"
+            + "extract(dow from time),extract(doy from time),extract(hour from 
time),extract(minute from time),extract(second from time),extract(ms from time)"
+            + " FROM table1 order by time limit 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    retArray =
+        new String[] {getTimeStrUTC8("2025-07-08T09:18:51") + 
",2025,3,7,27,8,2,189,9,18,51,123,"};
+    tableResultSetEqualTest(
+        "SELECT time,extract(year from time),extract(quarter from 
time),extract(month from time),extract(week from time),extract(day from time),"
+            + "extract(dow from time),extract(doy from time),extract(hour from 
time),extract(minute from time),extract(second from time),extract(ms from time)"
+            + " FROM table1 order by time limit 1",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void extractUsNsTest() {
+    String[] expectedHeader = new String[] {"time", "_col1", "_col2"};
+    String[] retArray =
+        new String[] {
+          getTimeStrUTC("2025-07-08T01:18:51") + ",0,0,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, extract(us from time),extract(ns from time)"
+            + " FROM table1 order by time limit 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    retArray =
+        new String[] {
+          getTimeStrUTC8("2025-07-08T09:18:51") + ",0,0,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, extract(us from time),extract(ns from time)"
+            + " FROM table1 order by time limit 1",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void extractTimeFilterPushDownTest() {
+    String[] expectedHeader = new String[] {"time", "s1"};
+    String[] retArray =
+        new String[] {
+          getTimeStrUTC("2025-07-08T02:18:51") + ",2,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) > 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    // verify the pushdown result is same with non-pushdown
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) > 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) >= 2",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    // verify the pushdown result is same with non-pushdown
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) >= 2",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    retArray =
+        new String[] {
+          getTimeStrUTC8("2025-07-08T10:18:51") + ",2,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) > 9",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) > 9",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) >= 10",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) >= 10",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    expectedHeader = new String[] {"time", "s1"};
+    retArray =
+        new String[] {
+          getTimeStrUTC("2025-07-09T00:17:50") + ",3,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) < 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) < 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) <= 0",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) <= 0",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) = 0",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) = 0",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    retArray =
+        new String[] {
+          getTimeStrUTC8("2025-07-09T08:17:50") + ",3,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) < 9",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) < 9",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) <= 8",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) <= 8",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) = 8",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) = 8",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    retArray =
+        new String[] {
+          getTimeStrUTC("2025-07-08T01:18:51") + ",1,",
+          getTimeStrUTC("2025-07-08T02:18:51") + ",2,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) != 0",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) != 0",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    retArray =
+        new String[] {
+          getTimeStrUTC8("2025-07-08T09:18:51") + ",1,",
+          getTimeStrUTC8("2025-07-08T10:18:51") + ",2,",
+        };
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from time) != 8",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "SELECT time, s1 FROM table1 where extract(hour from ts) != 8",
+        "+08:00",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void testExtractFromComplexExpression() {
+    String[] expectedHeader = new String[] {"_col0"};
+    String[] retArray = new String[] {"0,"};
+    tableResultSetEqualTest(
+        "SELECT extract(hour from cast(s1 AS TIMESTAMP))" + " FROM table1 
order by time limit 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void testExtractFromConstant() {
+    String[] expectedHeader = new String[] {"_col0"};
+    String[] retArray = new String[] {"1,"};
+    tableResultSetEqualTest(
+        "SELECT extract(hour from 2025/07/08 01:18:51)" + " FROM table1 order 
by time limit 1",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  protected String getTimeStrUTC(String time) {
+    return time + "." + decimal + 'Z';
+  }
+
+  protected String getTimeStrUTC8(String time) {
+    return time + "." + decimal + "+08:00";
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
index 8b9a2a274a1..8fc462bbd42 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
@@ -45,6 +45,7 @@ import 
org.apache.iotdb.db.storageengine.dataregion.read.QueryDataSourceForRegio
 import org.apache.iotdb.db.storageengine.dataregion.read.QueryDataSourceType;
 import 
org.apache.iotdb.db.storageengine.dataregion.read.control.FileReaderManager;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
+import org.apache.iotdb.db.utils.TimestampPrecisionUtils;
 import org.apache.iotdb.db.utils.datastructure.TVList;
 import org.apache.iotdb.mpp.rpc.thrift.TFetchFragmentInstanceStatisticsResp;
 
@@ -235,7 +236,10 @@ public class FragmentInstanceContext extends QueryContext {
     this.sessionInfo = sessionInfo;
     this.dataRegion = dataRegion;
     this.globalTimeFilter =
-        globalTimePredicate == null ? null : 
globalTimePredicate.convertPredicateToTimeFilter();
+        globalTimePredicate == null
+            ? null
+            : globalTimePredicate.convertPredicateToTimeFilter(
+                sessionInfo.getZoneId(), 
TimestampPrecisionUtils.currPrecision);
     this.dataNodeQueryContextMap = dataNodeQueryContextMap;
     this.dataNodeQueryContext = dataNodeQueryContextMap.get(id.getQueryId());
     this.memoryReservationManager =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java
index d0b7e35e94c..0e915ab258c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/relational/ColumnTransformerBuilder.java
@@ -48,6 +48,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentUser;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DecimalLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DoubleLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IfExpression;
@@ -122,6 +123,7 @@ import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.Di
 import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.EndsWith2ColumnTransformer;
 import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.EndsWithColumnTransformer;
 import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.ExpColumnTransformer;
+import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.ExtractTransformer;
 import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.FloorColumnTransformer;
 import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.FormatColumnTransformer;
 import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.LTrim2ColumnTransformer;
@@ -343,6 +345,27 @@ public class ColumnTransformerBuilder
     return getColumnTransformerFromCacheAndAddReferenceCount(node, context);
   }
 
+  @Override
+  protected ColumnTransformer visitExtract(Extract node, Context context) {
+    if (!context.cache.containsKey(node)) {
+      if (context.hasSeen.containsKey(node)) {
+        ColumnTransformer columnTransformer = context.hasSeen.get(node);
+        appendIdentityColumnTransformer(
+            node,
+            columnTransformer.getType(),
+            getTSDataType(columnTransformer.getType()),
+            context,
+            columnTransformer);
+      } else {
+        ColumnTransformer child = this.process(node.getExpression(), context);
+        context.cache.put(
+            node,
+            new ExtractTransformer(INT64, child, node.getField(), 
context.sessionInfo.getZoneId()));
+      }
+    }
+    return getColumnTransformerFromCacheAndAddReferenceCount(node, context);
+  }
+
   @Override
   protected ColumnTransformer visitBooleanLiteral(BooleanLiteral node, Context 
context) {
     ColumnTransformer res =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/PredicateUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/PredicateUtils.java
index 9fb41fc152d..cf4b3a8294c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/PredicateUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/PredicateUtils.java
@@ -54,6 +54,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 public class PredicateUtils {
@@ -282,13 +283,15 @@ public class PredicateUtils {
   }
 
   public static Filter convertPredicateToTimeFilter(
-      org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression 
predicate) {
+      org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression 
predicate,
+      ZoneId zoneId,
+      TimeUnit currPrecision) {
     if (predicate == null) {
       return null;
     }
     return predicate.accept(
         new org.apache.iotdb.db.queryengine.plan.relational.analyzer.predicate
-            .ConvertPredicateToTimeFilterVisitor(),
+            .ConvertPredicateToTimeFilterVisitor(zoneId, currPrecision),
         null);
   }
 
@@ -311,13 +314,15 @@ public class PredicateUtils {
       org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression 
predicate,
       Map<String, Integer> measurementColumnsIndexMap,
       Map<Symbol, ColumnSchema> schemaMap,
-      String timeColumnName) {
+      String timeColumnName,
+      ZoneId zoneId,
+      TimeUnit currPrecision) {
     if (predicate == null) {
       return null;
     }
     return predicate.accept(
         new org.apache.iotdb.db.queryengine.plan.relational.analyzer.predicate
-            .ConvertPredicateToFilterVisitor(timeColumnName),
+            .ConvertPredicateToFilterVisitor(timeColumnName, zoneId, 
currPrecision),
         new org.apache.iotdb.db.queryengine.plan.relational.analyzer.predicate
             
.ConvertPredicateToFilterVisitor.Context(measurementColumnsIndexMap, 
schemaMap));
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
index 75e2275275b..a4fd11867dc 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/TableOperatorGenerator.java
@@ -232,6 +232,7 @@ import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.Da
 import 
org.apache.iotdb.db.schemaengine.schemaregion.read.resp.info.IDeviceSchemaInfo;
 import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
 import org.apache.iotdb.db.schemaengine.table.DataNodeTreeViewSchemaUtils;
+import org.apache.iotdb.db.utils.TimestampPrecisionUtils;
 import org.apache.iotdb.db.utils.datastructure.SortKey;
 import org.apache.iotdb.udf.api.relational.TableFunction;
 import 
org.apache.iotdb.udf.api.relational.table.TableFunctionProcessorProvider;
@@ -614,7 +615,11 @@ public class TableOperatorGenerator extends 
PlanVisitor<Operator, LocalExecution
             Filter timeFilter = null;
             if (node.getTimePredicate().isPresent()) {
               Expression timePredicate = node.getTimePredicate().get();
-              timeFilter = timePredicate.accept(new 
ConvertPredicateToTimeFilterVisitor(), null);
+              timeFilter =
+                  timePredicate.accept(
+                      new ConvertPredicateToTimeFilterVisitor(
+                          context.getZoneId(), 
TimestampPrecisionUtils.currPrecision),
+                      null);
               context
                   .getDriverContext()
                   .getFragmentInstanceContext()
@@ -654,7 +659,9 @@ public class TableOperatorGenerator extends 
PlanVisitor<Operator, LocalExecution
                         pushDownPredicateForCurrentMeasurement,
                         Collections.singletonMap(symbol.getName(), 0),
                         commonParameter.columnSchemaMap,
-                        commonParameter.timeColumnName));
+                        commonParameter.timeColumnName,
+                        context.getZoneId(),
+                        TimestampPrecisionUtils.currPrecision));
               }
               if (isSingleColumn
                   || (pushDownOffsetAndLimitToLeftChildSeriesScanOperator
@@ -1235,7 +1242,11 @@ public class TableOperatorGenerator extends 
PlanVisitor<Operator, LocalExecution
       LocalExecutionPlanContext context, @NotNull Expression timePredicate) {
     SeriesScanOptions.Builder scanOptionsBuilder = new 
SeriesScanOptions.Builder();
 
-    Filter timeFilter = timePredicate.accept(new 
ConvertPredicateToTimeFilterVisitor(), null);
+    Filter timeFilter =
+        timePredicate.accept(
+            new ConvertPredicateToTimeFilterVisitor(
+                context.getZoneId(), TimestampPrecisionUtils.currPrecision),
+            null);
     
context.getDriverContext().getFragmentInstanceContext().setTimeFilterForTableModel(timeFilter);
     // time filter may be stateful, so we need to copy it
     scanOptionsBuilder.withGlobalTimeFilter(timeFilter.copy());
@@ -3113,7 +3124,12 @@ public class TableOperatorGenerator extends 
PlanVisitor<Operator, LocalExecution
     if (pushDownPredicate != null) {
       scanOptionsBuilder.withPushDownFilter(
           convertPredicateToFilter(
-              pushDownPredicate, measurementColumnsIndexMap, columnSchemaMap, 
timeColumnName));
+              pushDownPredicate,
+              measurementColumnsIndexMap,
+              columnSchemaMap,
+              timeColumnName,
+              context.getZoneId(),
+              TimestampPrecisionUtils.currPrecision));
     }
     return scanOptionsBuilder.build();
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TableModelTimePredicate.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TableModelTimePredicate.java
index b299c04e6d7..dcdb79bf79e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TableModelTimePredicate.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TableModelTimePredicate.java
@@ -27,7 +27,9 @@ import org.apache.tsfile.utils.ReadWriteIOUtils;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.time.ZoneId;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 public class TableModelTimePredicate implements TimePredicate {
 
@@ -44,8 +46,8 @@ public class TableModelTimePredicate implements TimePredicate 
{
   }
 
   @Override
-  public Filter convertPredicateToTimeFilter() {
-    return PredicateUtils.convertPredicateToTimeFilter(timePredicate);
+  public Filter convertPredicateToTimeFilter(ZoneId zoneId, TimeUnit 
currPrecision) {
+    return PredicateUtils.convertPredicateToTimeFilter(timePredicate, zoneId, 
currPrecision);
   }
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TimePredicate.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TimePredicate.java
index 440c34a65aa..d424aa82d09 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TimePredicate.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TimePredicate.java
@@ -27,12 +27,14 @@ import org.apache.tsfile.utils.ReadWriteIOUtils;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.time.ZoneId;
+import java.util.concurrent.TimeUnit;
 
 public interface TimePredicate {
 
   void serialize(DataOutputStream stream) throws IOException;
 
-  Filter convertPredicateToTimeFilter();
+  Filter convertPredicateToTimeFilter(ZoneId zoneId, TimeUnit currPrecision);
 
   static TimePredicate deserialize(ByteBuffer byteBuffer) {
     // 0 for tree model, 1 for table model
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TreeModelTimePredicate.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TreeModelTimePredicate.java
index 5cc00125aa7..132077d801b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TreeModelTimePredicate.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/TreeModelTimePredicate.java
@@ -27,7 +27,9 @@ import org.apache.tsfile.utils.ReadWriteIOUtils;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.time.ZoneId;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 public class TreeModelTimePredicate implements TimePredicate {
 
@@ -44,7 +46,7 @@ public class TreeModelTimePredicate implements TimePredicate {
   }
 
   @Override
-  public Filter convertPredicateToTimeFilter() {
+  public Filter convertPredicateToTimeFilter(ZoneId zoneId, TimeUnit 
currPrecision) {
     return PredicateUtils.convertPredicateToTimeFilter(timePredicate);
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
index 4e3e12f723a..93f7847c5fa 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
@@ -32,6 +32,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentTime;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DereferenceExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FieldReference;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Identifier;
@@ -212,6 +213,11 @@ class AggregationAnalyzer {
       return process(node.getFirst(), context) && process(node.getSecond(), 
context);
     }
 
+    @Override
+    protected Boolean visitExtract(Extract node, Void context) {
+      return process(node.getExpression(), context);
+    }
+
     @Override
     protected Boolean visitBetweenPredicate(BetweenPredicate node, Void 
context) {
       return process(node.getMin(), context)
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
index 13923444e0c..6157612b423 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExpressionAnalyzer.java
@@ -61,6 +61,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DereferenceExpres
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DoubleLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FieldReference;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FrameBound;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
@@ -106,6 +107,7 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Multimap;
 import org.apache.tsfile.read.common.type.RowType;
+import org.apache.tsfile.read.common.type.TimestampType;
 import org.apache.tsfile.read.common.type.Type;
 
 import javax.annotation.Nullable;
@@ -1555,6 +1557,22 @@ public class ExpressionAnalyzer {
       return setExpressionType(node, resultType);
     }
 
+    @Override
+    protected Type visitExtract(Extract node, 
StackableAstVisitorContext<Context> context) {
+      if (node.getExpression() instanceof LongLiteral) {
+        // Don't visit child here to avoid setting its Type to INT32
+        setExpressionType(node.getExpression(), INT64);
+      } else {
+        Type type = process(node.getExpression(), context);
+
+        if (!(type instanceof TimestampType)) {
+          throw new SemanticException(String.format("Cannot extract from %s", 
type));
+        }
+      }
+
+      return setExpressionType(node, INT64);
+    }
+
     @Override
     protected Type visitBetweenPredicate(
         BetweenPredicate node, StackableAstVisitorContext<Context> context) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index a0afd0453af..2ec3676acae 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -95,6 +95,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Explain;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExplainAnalyze;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FetchDevice;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FieldReference;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Fill;
@@ -1996,6 +1997,22 @@ public class StatementAnalyzer {
             String.format("%s are not supported now", 
node.getClass().getSimpleName()));
       }
 
+      @Override
+      protected List<Expression> visitExtract(Extract node, Scope context) {
+        List<Expression> childResult = process(node.getExpression(), context);
+        if (expandedExpressions == null) {
+          // no Columns need to be expanded
+          return Collections.singletonList(node);
+        }
+
+        ImmutableList.Builder<Expression> resultBuilder = new 
ImmutableList.Builder<>();
+        for (Expression expression : childResult) {
+          resultBuilder.add(new Extract(expression, node.getField()));
+        }
+
+        return resultBuilder.build();
+      }
+
       @Override
       protected List<Expression> visitSearchedCaseExpression(
           SearchedCaseExpression node, Scope context) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToFilterVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToFilterVisitor.java
index 1704ccab7e0..0c9f42b3e63 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToFilterVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToFilterVisitor.java
@@ -58,11 +58,13 @@ import org.apache.tsfile.utils.Binary;
 
 import javax.annotation.Nullable;
 
+import java.time.ZoneId;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -78,9 +80,10 @@ public class ConvertPredicateToFilterVisitor
   @Nullable private final String timeColumnName;
   private final ConvertPredicateToTimeFilterVisitor timeFilterVisitor;
 
-  public ConvertPredicateToFilterVisitor(@Nullable String timeColumnName) {
+  public ConvertPredicateToFilterVisitor(
+      @Nullable String timeColumnName, ZoneId zoneId, TimeUnit currPrecision) {
     this.timeColumnName = timeColumnName;
-    this.timeFilterVisitor = new ConvertPredicateToTimeFilterVisitor();
+    this.timeFilterVisitor = new ConvertPredicateToTimeFilterVisitor(zoneId, 
currPrecision);
   }
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java
index b9ab2109a15..7368c5d28c2 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/ConvertPredicateToTimeFilterVisitor.java
@@ -22,6 +22,7 @@ package 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.predicate;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BetweenPredicate;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IfExpression;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InListExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InPredicate;
@@ -36,20 +37,31 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SearchedCaseExpre
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SimpleCaseExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SymbolReference;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.tsfile.read.filter.basic.Filter;
 import org.apache.tsfile.read.filter.factory.FilterFactory;
 import org.apache.tsfile.read.filter.factory.TimeFilterApi;
+import org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators;
 
+import java.time.ZoneId;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
 /** The caller must make sure that the Expression only contains valid time 
predicate */
 public class ConvertPredicateToTimeFilterVisitor extends 
PredicateVisitor<Filter, Void> {
+  private final ZoneId zoneId;
+  private final TimeUnit currPrecision;
+
+  public ConvertPredicateToTimeFilterVisitor(ZoneId zoneId, TimeUnit 
currPrecision) {
+    this.zoneId = zoneId;
+    this.currPrecision = currPrecision;
+  }
 
   @Override
   protected Filter visitInPredicate(InPredicate node, Void context) {
@@ -109,7 +121,7 @@ public class ConvertPredicateToTimeFilterVisitor extends 
PredicateVisitor<Filter
   @Override
   protected Filter visitComparisonExpression(ComparisonExpression node, Void 
context) {
     long value;
-    if (node.getLeft() instanceof LongLiteral) {
+    if (node.getRight() instanceof SymbolReference) {
       value = getLongValue(node.getLeft());
       switch (node.getOperator()) {
         case EQUAL:
@@ -127,7 +139,7 @@ public class ConvertPredicateToTimeFilterVisitor extends 
PredicateVisitor<Filter
         default:
           throw new IllegalArgumentException("Unsupported operator: " + 
node.getOperator());
       }
-    } else if (node.getRight() instanceof LongLiteral) {
+    } else if (node.getLeft() instanceof SymbolReference) {
       value = getLongValue(node.getRight());
       switch (node.getOperator()) {
         case EQUAL:
@@ -145,9 +157,51 @@ public class ConvertPredicateToTimeFilterVisitor extends 
PredicateVisitor<Filter
         default:
           throw new IllegalArgumentException("Unsupported operator: " + 
node.getOperator());
       }
+    } else if (node.getRight() instanceof Extract) {
+      Extract extract = (Extract) node.getRight();
+      value = getLongValue(node.getLeft());
+      ExtractTimeFilterOperators.Field field =
+          
ExtractTimeFilterOperators.Field.values()[extract.getField().ordinal()];
+      switch (node.getOperator()) {
+        case EQUAL:
+          return TimeFilterApi.extractTimeEq(value, field, zoneId, 
currPrecision);
+        case NOT_EQUAL:
+          return TimeFilterApi.extractTimeNotEq(value, field, zoneId, 
currPrecision);
+        case GREATER_THAN:
+          return TimeFilterApi.extractTimeLt(value, field, zoneId, 
currPrecision);
+        case GREATER_THAN_OR_EQUAL:
+          return TimeFilterApi.extractTimeLtEq(value, field, zoneId, 
currPrecision);
+        case LESS_THAN:
+          return TimeFilterApi.extractTimeGt(value, field, zoneId, 
currPrecision);
+        case LESS_THAN_OR_EQUAL:
+          return TimeFilterApi.extractTimeGtEq(value, field, zoneId, 
currPrecision);
+        default:
+          throw new IllegalArgumentException("Unsupported operator: " + 
node.getOperator());
+      }
+    } else if (node.getLeft() instanceof Extract) {
+      Extract extract = (Extract) node.getLeft();
+      value = getLongValue(node.getRight());
+      ExtractTimeFilterOperators.Field field =
+          
ExtractTimeFilterOperators.Field.values()[extract.getField().ordinal()];
+      switch (node.getOperator()) {
+        case EQUAL:
+          return TimeFilterApi.extractTimeEq(value, field, zoneId, 
currPrecision);
+        case NOT_EQUAL:
+          return TimeFilterApi.extractTimeNotEq(value, field, zoneId, 
currPrecision);
+        case GREATER_THAN:
+          return TimeFilterApi.extractTimeGt(value, field, zoneId, 
currPrecision);
+        case GREATER_THAN_OR_EQUAL:
+          return TimeFilterApi.extractTimeGtEq(value, field, zoneId, 
currPrecision);
+        case LESS_THAN:
+          return TimeFilterApi.extractTimeLt(value, field, zoneId, 
currPrecision);
+        case LESS_THAN_OR_EQUAL:
+          return TimeFilterApi.extractTimeLtEq(value, field, zoneId, 
currPrecision);
+        default:
+          throw new IllegalArgumentException("Unsupported operator: " + 
node.getOperator());
+      }
     } else {
       throw new IllegalStateException(
-          "Either left or right operand of Time ComparisonExpression should be 
LongLiteral");
+          "Either left or right operand of ComparisonExpression should have 
time column.");
     }
   }
 
@@ -218,6 +272,26 @@ public class ConvertPredicateToTimeFilterVisitor extends 
PredicateVisitor<Filter
           value >= minValue,
           String.format("Predicate [%s] should be simplified in previous 
step", node));
       return TimeFilterApi.gtEq(value);
+    } else if (firstExpression instanceof Extract) {
+      long minValue = getLongValue(secondExpression);
+      long maxValue = getLongValue(thirdExpression);
+      Extract extract = (Extract) firstExpression;
+      ExtractTimeFilterOperators.Field field =
+          
ExtractTimeFilterOperators.Field.values()[extract.getField().ordinal()];
+
+      if (minValue == maxValue) {
+        return TimeFilterApi.extractTimeEq(minValue, field, zoneId, 
currPrecision);
+      }
+      return FilterFactory.and(
+          ImmutableList.of(
+              TimeFilterApi.extractTimeGtEq(minValue, field, zoneId, 
currPrecision),
+              TimeFilterApi.extractTimeLtEq(maxValue, field, zoneId, 
currPrecision)));
+    } else if (secondExpression instanceof Extract) {
+      throw new IllegalStateException(
+          "Should not reach here before GlobalTimePredicateExtractVisitor 
support Extract push-down in second child");
+    } else if (thirdExpression instanceof Extract) {
+      throw new IllegalStateException(
+          "Should not reach here before GlobalTimePredicateExtractVisitor 
support Extract push-down in third child");
     } else {
       throw new IllegalStateException(
           "Three operand of between expression should have time column.");
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/PredicateCombineIntoTableScanChecker.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/PredicateCombineIntoTableScanChecker.java
index 579f1562876..b0764213dfe 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/PredicateCombineIntoTableScanChecker.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/predicate/PredicateCombineIntoTableScanChecker.java
@@ -41,17 +41,23 @@ import java.util.Set;
 
 import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.predicate.PredicatePushIntoScanChecker.isLiteral;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.predicate.PredicatePushIntoScanChecker.isSymbolReference;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.GlobalTimePredicateExtractVisitor.isExtractTimeColumn;
 
 public class PredicateCombineIntoTableScanChecker extends 
PredicateVisitor<Boolean, Void> {
 
   private final Set<String> measurementColumns;
+  private final String timeColumnName;
 
-  public static boolean check(Set<String> measurementColumns, Expression 
expression) {
-    return new 
PredicateCombineIntoTableScanChecker(measurementColumns).process(expression);
+  public static boolean check(
+      Set<String> measurementColumns, String timeColumnName, Expression 
expression) {
+    return new PredicateCombineIntoTableScanChecker(measurementColumns, 
timeColumnName)
+        .process(expression);
   }
 
-  public PredicateCombineIntoTableScanChecker(Set<String> measurementColumns) {
+  public PredicateCombineIntoTableScanChecker(
+      Set<String> measurementColumns, String timeColumnName) {
     this.measurementColumns = measurementColumns;
+    this.timeColumnName = timeColumnName;
   }
 
   @Override
@@ -115,14 +121,19 @@ public class PredicateCombineIntoTableScanChecker extends 
PredicateVisitor<Boole
   @Override
   protected Boolean visitComparisonExpression(ComparisonExpression node, Void 
context) {
     return (isMeasurementColumn(node.getLeft()) && isLiteral(node.getRight()))
-        || (isMeasurementColumn(node.getRight()) && isLiteral(node.getLeft()));
+        || (isMeasurementColumn(node.getRight()) && isLiteral(node.getLeft()))
+        || (isExtractTimeColumn(node.getLeft(), timeColumnName) && 
isLiteral(node.getRight()))
+        || (isExtractTimeColumn(node.getRight(), timeColumnName) && 
isLiteral(node.getLeft()));
   }
 
   @Override
   protected Boolean visitBetweenPredicate(BetweenPredicate node, Void context) 
{
     return (isMeasurementColumn(node.getValue())
-        && isLiteral(node.getMin())
-        && isLiteral(node.getMax()));
+            && isLiteral(node.getMin())
+            && isLiteral(node.getMax()))
+        || (isExtractTimeColumn(node.getValue(), timeColumnName)
+            && isLiteral(node.getMin())
+            && isLiteral(node.getMax()));
     // TODO After Constant-Folding introduced
     /*|| (isLiteral(node.getValue())
         && isMeasurementColumn(node.getMin())
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java
index 208da696a0c..ae17e5eab7b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrExpressionInterpreter.java
@@ -37,6 +37,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Cast;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CoalesceExpression;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IfExpression;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InListExpression;
@@ -80,6 +81,7 @@ import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils
 import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticUnaryExpression.Sign.MINUS;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticUnaryExpression.Sign.PLUS;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.type.TypeSignatureTranslator.toTypeSignature;
+import static 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.scalar.ExtractTransformer.constructEvaluateFunction;
 
 public class IrExpressionInterpreter {
 
@@ -916,6 +918,23 @@ public class IrExpressionInterpreter {
       }
     }
 
+    @Override
+    protected Object visitExtract(Extract node, Object context) {
+      Object value = processWithExceptionHandling(node.getExpression(), 
context);
+      if (value == null) {
+        return null;
+      }
+
+      // if is Extract from constant, the constant must be INT64 type, so it 
will be Long after the
+      // process
+      if (value instanceof Long) {
+        return constructEvaluateFunction(node.getField(), 
session.getZoneId()).apply((Long) value);
+      }
+
+      checkState(value instanceof Expression, "Value reach here must be 
Expression");
+      return new Extract((Expression) value, node.getField());
+    }
+
     @Override
     protected Object visitExpression(Expression node, Object context) {
       throw new SemanticException("not yet implemented: " + 
node.getClass().getName());
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
index 1ecf2dad77d..b92e50041e3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/IrTypeAnalyzer.java
@@ -40,6 +40,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentDatabase;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentUser;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DoubleLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IfExpression;
@@ -306,6 +307,12 @@ public class IrTypeAnalyzer {
       return setExpressionType(node, process(node.getValue(), context));
     }
 
+    @Override
+    protected Type visitExtract(Extract node, Context context) {
+      process(node.getExpression(), context);
+      return setExpressionType(node, INT64);
+    }
+
     @Override
     protected Type visitArithmeticBinary(ArithmeticBinaryExpression node, 
Context context) {
       ImmutableList.Builder<Type> argumentTypes = ImmutableList.builder();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java
index d232101d603..159655d4255 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionRewriter.java
@@ -30,6 +30,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.CurrentUser;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DereferenceExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FieldReference;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericDataType;
@@ -174,6 +175,11 @@ public class ExpressionRewriter<C> {
     return rewriteExpression(node, context, treeRewriter);
   }
 
+  public Expression rewriteExtract(
+      Extract node, C context, ExpressionTreeRewriter<C> treeRewriter) {
+    return rewriteExpression(node, context, treeRewriter);
+  }
+
   //    public Expression rewriteBindExpression(BindExpression node, C context,
   // ExpressionTreeRewriter<C> treeRewriter) {
   //        return rewriteExpression(node, context, treeRewriter);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java
index 585829e2540..d84ac593306 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/ExpressionTreeRewriter.java
@@ -33,6 +33,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DataTypeParameter
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DereferenceExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FieldReference;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericDataType;
@@ -625,6 +626,25 @@ public final class ExpressionTreeRewriter<C> {
       return node;
     }
 
+    @Override
+    protected Expression visitExtract(Extract node, Context<C> context) {
+      if (!context.isDefaultRewrite()) {
+        Expression result =
+            rewriter.rewriteExtract(node, context.get(), 
ExpressionTreeRewriter.this);
+        if (result != null) {
+          return result;
+        }
+      }
+
+      Expression expression = rewrite(node.getExpression(), context.get());
+
+      if (node.getExpression() != expression) {
+        return new Extract(expression, node.getField());
+      }
+
+      return node;
+    }
+
     @Override
     public Expression visitCast(Cast node, Context<C> context) {
       if (!context.isDefaultRewrite()) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GlobalTimePredicateExtractVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GlobalTimePredicateExtractVisitor.java
index f168eb50d83..09d040b12f3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GlobalTimePredicateExtractVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GlobalTimePredicateExtractVisitor.java
@@ -25,6 +25,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AstVisitor;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.BetweenPredicate;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.IfExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.InPredicate;
@@ -197,7 +198,8 @@ public class GlobalTimePredicateExtractVisitor
     Expression thirdExpression = node.getMax();
 
     boolean isTimeFilter = false;
-    if (isTimeColumn(firstExpression, context.timeColumnName)) {
+    if (isTimeColumn(firstExpression, context.timeColumnName)
+        || isExtractTimeColumn(firstExpression, context.timeColumnName)) {
       isTimeFilter = checkBetweenConstantSatisfy(secondExpression, 
thirdExpression);
     }
     // TODO After Constant-Folding introduced
@@ -269,9 +271,19 @@ public class GlobalTimePredicateExtractVisitor
         && ((SymbolReference) e).getName().equalsIgnoreCase(timeColumnName);
   }
 
+  public static boolean isExtractTimeColumn(Expression e, String 
timeColumnName) {
+    return e instanceof Extract
+        && ((Extract) e).getExpression() instanceof SymbolReference
+        && ((SymbolReference) ((Extract) e).getExpression())
+            .getName()
+            .equalsIgnoreCase(timeColumnName);
+  }
+
   private static boolean checkIsTimeFilter(
       Expression timeExpression, String timeColumnName, Expression 
valueExpression) {
-    return isTimeColumn(timeExpression, timeColumnName) && valueExpression 
instanceof LongLiteral;
+    return (isTimeColumn(timeExpression, timeColumnName)
+            || isExtractTimeColumn(timeExpression, timeColumnName))
+        && valueExpression instanceof LongLiteral;
   }
 
   private static boolean checkBetweenConstantSatisfy(Expression e1, Expression 
e2) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
index f6be45cd2ac..4284c37a03a 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/PushPredicateIntoTableScan.java
@@ -68,6 +68,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LogicalExpression
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Node;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.NullLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SymbolReference;
+import org.apache.iotdb.db.utils.TimestampPrecisionUtils;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -530,7 +531,7 @@ public class PushPredicateIntoTableScan implements 
PlanOptimizer {
           if (PredicatePushIntoMetadataChecker.check(idOrAttributeColumnNames, 
expression)) {
             metadataExpressions.add(expression);
           } else if (PredicateCombineIntoTableScanChecker.check(
-              measurementColumnNames, expression)) {
+              measurementColumnNames, timeColumnName, expression)) {
             expressionsCanPushDown.add(expression);
           } else {
             expressionsCannotPushDown.add(expression);
@@ -543,7 +544,8 @@ public class PushPredicateIntoTableScan implements 
PlanOptimizer {
 
       if (PredicatePushIntoMetadataChecker.check(idOrAttributeColumnNames, 
predicate)) {
         metadataExpressions.add(predicate);
-      } else if 
(PredicateCombineIntoTableScanChecker.check(measurementColumnNames, predicate)) 
{
+      } else if (PredicateCombineIntoTableScanChecker.check(
+          measurementColumnNames, timeColumnName, predicate)) {
         expressionsCanPushDown.add(predicate);
       } else {
         expressionsCannotPushDown.add(predicate);
@@ -616,7 +618,12 @@ public class PushPredicateIntoTableScan implements 
PlanOptimizer {
         final Filter timeFilter =
             tableScanNode
                 .getTimePredicate()
-                .map(value -> value.accept(new 
ConvertPredicateToTimeFilterVisitor(), null))
+                .map(
+                    value ->
+                        value.accept(
+                            new ConvertPredicateToTimeFilterVisitor(
+                                queryContext.getZoneId(), 
TimestampPrecisionUtils.currPrecision),
+                            null))
                 .orElse(null);
 
         tableScanNode.setTimeFilter(timeFilter);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
index 3259e6fbc02..80b5642fd6e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java
@@ -229,6 +229,10 @@ public abstract class AstVisitor<R, C> {
     return visitExpression(node, context);
   }
 
+  protected R visitExtract(Extract node, C context) {
+    return visitExpression(node, context);
+  }
+
   protected R visitWindowDefinition(WindowDefinition node, C context) {
     return visitNode(node, context);
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java
index 164c86d425c..f22af4b020e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java
@@ -20,6 +20,11 @@
 package org.apache.iotdb.db.queryengine.plan.relational.sql.ast;
 
 public abstract class DefaultTraversalVisitor<C> extends AstVisitor<Void, C> {
+  @Override
+  protected Void visitExtract(Extract node, C context) {
+    process(node.getExpression(), context);
+    return null;
+  }
 
   @Override
   protected Void visitArithmeticBinary(ArithmeticBinaryExpression node, C 
context) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Expression.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Expression.java
index 53eadfb149c..f4bc64e2552 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Expression.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Expression.java
@@ -161,6 +161,9 @@ public abstract class Expression extends Node {
       case 31:
         expression = new Row(byteBuffer);
         break;
+      case 32:
+        expression = new Extract(byteBuffer);
+        break;
       default:
         throw new IllegalArgumentException("Invalid expression type: " + type);
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Extract.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Extract.java
new file mode 100644
index 00000000000..e9e04073ad2
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/Extract.java
@@ -0,0 +1,139 @@
+/*
+ * 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.iotdb.db.queryengine.plan.relational.sql.ast;
+
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class Extract extends Expression {
+  private final Expression expression;
+  private final Field field;
+
+  public enum Field {
+    YEAR,
+    QUARTER,
+    MONTH,
+    WEEK,
+    DAY,
+    DAY_OF_MONTH,
+    DAY_OF_WEEK,
+    DOW,
+    DAY_OF_YEAR,
+    DOY,
+    HOUR,
+    MINUTE,
+    SECOND,
+    MS,
+    US,
+    NS
+  }
+
+  public Extract(Expression expression, Field field) {
+    this(null, expression, field);
+  }
+
+  public Extract(NodeLocation location, Expression expression, Field field) {
+    super(location);
+    requireNonNull(expression, "expression is null");
+    requireNonNull(field, "field is null");
+
+    this.expression = expression;
+    this.field = field;
+  }
+
+  public Extract(ByteBuffer byteBuffer) {
+    super(null);
+    expression = deserialize(byteBuffer);
+    field = Field.values()[ReadWriteIOUtils.readInt(byteBuffer)];
+  }
+
+  @Override
+  protected void serialize(ByteBuffer byteBuffer) {
+    serialize(expression, byteBuffer);
+    ReadWriteIOUtils.write(field.ordinal(), byteBuffer);
+  }
+
+  @Override
+  protected void serialize(DataOutputStream stream) throws IOException {
+    serialize(expression, stream);
+    ReadWriteIOUtils.write(field.ordinal(), stream);
+  }
+
+  @Override
+  public TableExpressionType getExpressionType() {
+    return TableExpressionType.EXTRACT;
+  }
+
+  public Expression getExpression() {
+    return expression;
+  }
+
+  public Field getField() {
+    return field;
+  }
+
+  @Override
+  public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
+    return visitor.visitExtract(this, context);
+  }
+
+  @Override
+  public List<Node> getChildren() {
+    return ImmutableList.of(expression);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    Extract that = (Extract) o;
+    return Objects.equals(expression, that.expression) && (field == 
that.field);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(expression, field);
+  }
+
+  @Override
+  public boolean shallowEquals(Node other) {
+    if (!sameClass(this, other)) {
+      return false;
+    }
+
+    Extract otherExtract = (Extract) other;
+    return field.equals(otherExtract.field);
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TableExpressionType.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TableExpressionType.java
index 78ef4feb09e..9d0b7ae0b4a 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TableExpressionType.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/TableExpressionType.java
@@ -50,7 +50,8 @@ public enum TableExpressionType {
   WHEN_CLAUSE((short) 28),
   CURRENT_DATABASE((short) 29),
   CURRENT_USER((short) 30),
-  ROW((short) 31);
+  ROW((short) 31),
+  EXTRACT((short) 32);
 
   TableExpressionType(short type) {
     this.type = type;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
index df0d86bf69c..a00ad658c76 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
@@ -93,6 +93,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Explain;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExplainAnalyze;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExtendRegion;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Fill;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Flush;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FrameBound;
@@ -285,6 +286,7 @@ import java.util.stream.Collectors;
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static java.lang.Long.parseLong;
+import static java.util.Locale.ENGLISH;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
 import static org.apache.iotdb.commons.schema.table.TsTable.TIME_COLUMN_NAME;
@@ -3098,6 +3100,18 @@ public class AstBuilder extends 
RelationalSqlBaseVisitor<Node> {
     return new CurrentUser(getLocation(ctx));
   }
 
+  @Override
+  public Node visitExtract(RelationalSqlParser.ExtractContext context) {
+    String fieldString = context.identifier().getText();
+    Extract.Field field;
+    try {
+      field = Extract.Field.valueOf(fieldString.toUpperCase(ENGLISH));
+    } catch (IllegalArgumentException e) {
+      throw parseError("Invalid EXTRACT field: " + fieldString, context);
+    }
+    return new Extract(getLocation(context), (Expression) 
visit(context.valueExpression()), field);
+  }
+
   @Override
   public Node 
visitSubqueryExpression(RelationalSqlParser.SubqueryExpressionContext ctx) {
     return new SubqueryExpression(getLocation(ctx), (Query) 
visit(ctx.query()));
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
index 47e00fb0fb6..9f1afa6a5f3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/ExpressionFormatter.java
@@ -39,6 +39,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DereferenceExpres
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DoubleLiteral;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ExistsPredicate;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FieldReference;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FrameBound;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
@@ -184,6 +185,11 @@ public final class ExpressionFormatter {
       return builder.toString();
     }
 
+    @Override
+    protected String visitExtract(Extract node, Void context) {
+      return "EXTRACT(" + node.getField() + " FROM " + 
process(node.getExpression(), context) + ")";
+    }
+
     @Override
     protected String visitBooleanLiteral(BooleanLiteral node, Void context) {
       return literalFormatter
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ExtractTransformer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ExtractTransformer.java
new file mode 100644
index 00000000000..76d4c1c7e10
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/transformation/dag/column/unary/scalar/ExtractTransformer.java
@@ -0,0 +1,106 @@
+/*
+ * 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.iotdb.db.queryengine.transformation.dag.column.unary.scalar;
+
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Extract;
+import 
org.apache.iotdb.db.queryengine.transformation.dag.column.ColumnTransformer;
+import 
org.apache.iotdb.db.queryengine.transformation.dag.column.unary.UnaryColumnTransformer;
+
+import org.apache.tsfile.block.column.Column;
+import org.apache.tsfile.block.column.ColumnBuilder;
+import org.apache.tsfile.read.common.type.Type;
+
+import java.time.ZoneId;
+import java.util.function.Function;
+
+import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
+import static 
org.apache.iotdb.db.utils.DateTimeUtils.EXTRACT_TIMESTAMP_MS_PART;
+import static 
org.apache.iotdb.db.utils.DateTimeUtils.EXTRACT_TIMESTAMP_NS_PART;
+import static 
org.apache.iotdb.db.utils.DateTimeUtils.EXTRACT_TIMESTAMP_US_PART;
+import static org.apache.iotdb.db.utils.DateTimeUtils.convertToZonedDateTime;
+
+public class ExtractTransformer extends UnaryColumnTransformer {
+  private final Function<Long, Long> evaluateFunction;
+
+  public ExtractTransformer(
+      Type type, ColumnTransformer child, Extract.Field field, ZoneId zoneId) {
+    super(type, child);
+    this.evaluateFunction = constructEvaluateFunction(field, zoneId);
+  }
+
+  public static Function<Long, Long> constructEvaluateFunction(Extract.Field 
field, ZoneId zoneId) {
+    switch (field) {
+      case YEAR:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getYear();
+      case QUARTER:
+        return timestamp -> (convertToZonedDateTime(timestamp, 
zoneId).getMonthValue() + 2L) / 3L;
+      case MONTH:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getMonthValue();
+      case WEEK:
+        return timestamp -> convertToZonedDateTime(timestamp, 
zoneId).getLong(ALIGNED_WEEK_OF_YEAR);
+      case DAY:
+      case DAY_OF_MONTH:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getDayOfMonth();
+      case DAY_OF_WEEK:
+      case DOW:
+        return timestamp ->
+            (long) convertToZonedDateTime(timestamp, 
zoneId).getDayOfWeek().getValue();
+      case DAY_OF_YEAR:
+      case DOY:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getDayOfYear();
+      case HOUR:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getHour();
+      case MINUTE:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getMinute();
+      case SECOND:
+        return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getSecond();
+      case MS:
+        return EXTRACT_TIMESTAMP_MS_PART;
+      case US:
+        return EXTRACT_TIMESTAMP_US_PART;
+      case NS:
+        return EXTRACT_TIMESTAMP_NS_PART;
+      default:
+        throw new UnsupportedOperationException("Unexpected extract field: " + 
field);
+    }
+  }
+
+  @Override
+  protected void doTransform(Column column, ColumnBuilder columnBuilder) {
+    for (int i = 0, n = column.getPositionCount(); i < n; i++) {
+      if (!column.isNull(i)) {
+        columnBuilder.writeLong(evaluateFunction.apply(column.getLong(i)));
+      } else {
+        columnBuilder.appendNull();
+      }
+    }
+  }
+
+  @Override
+  protected void doTransform(Column column, ColumnBuilder columnBuilder, 
boolean[] selection) {
+    for (int i = 0, n = column.getPositionCount(); i < n; i++) {
+      if (selection[i] && !column.isNull(i)) {
+        columnBuilder.writeLong(evaluateFunction.apply(column.getLong(i)));
+      } else {
+        columnBuilder.appendNull();
+      }
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
index a94bda73033..b413ae5ff37 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DateTimeUtils.java
@@ -86,22 +86,34 @@ public class DateTimeUtils {
     }
   }
 
-  private static Function<Long, Long> CAST_TIMESTAMP_TO_MS;
+  public static final Function<Long, Long> CAST_TIMESTAMP_TO_MS;
+  public static final Function<Long, Long> EXTRACT_TIMESTAMP_MS_PART;
+  public static final Function<Long, Long> EXTRACT_TIMESTAMP_US_PART;
+  public static final Function<Long, Long> EXTRACT_TIMESTAMP_NS_PART;
 
   static {
     switch 
(CommonDescriptor.getInstance().getConfig().getTimestampPrecision()) {
       case "us":
       case "microsecond":
         CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000;
+        EXTRACT_TIMESTAMP_MS_PART = timestamp -> Math.floorMod(timestamp, 
1000_000L) / 1000;
+        EXTRACT_TIMESTAMP_US_PART = timestamp -> Math.floorMod(timestamp, 
1000L);
+        EXTRACT_TIMESTAMP_NS_PART = timestamp -> 0L;
         break;
       case "ns":
       case "nanosecond":
         CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000000;
+        EXTRACT_TIMESTAMP_MS_PART = timestamp -> Math.floorMod(timestamp, 
1000_000_000L) / 1000_000;
+        EXTRACT_TIMESTAMP_US_PART = timestamp -> Math.floorMod(timestamp, 
1000_000L) / 1000;
+        EXTRACT_TIMESTAMP_NS_PART = timestamp -> Math.floorMod(timestamp, 
1000L);
         break;
       case "ms":
       case "millisecond":
       default:
         CAST_TIMESTAMP_TO_MS = timestamp -> timestamp;
+        EXTRACT_TIMESTAMP_MS_PART = timestamp -> Math.floorMod(timestamp, 
1000L);
+        EXTRACT_TIMESTAMP_US_PART = timestamp -> 0L;
+        EXTRACT_TIMESTAMP_NS_PART = timestamp -> 0L;
         break;
     }
   }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExtractExpressionTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExtractExpressionTest.java
new file mode 100644
index 00000000000..ce432bf42c4
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExtractExpressionTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.iotdb.db.queryengine.plan.relational.analyzer;
+
+import org.apache.iotdb.db.queryengine.plan.relational.planner.PlanTester;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.LongLiteral;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
+import static 
org.apache.iotdb.db.queryengine.plan.relational.analyzer.TestUtils.assertAnalyzeSemanticException;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanAssert.assertPlan;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.expression;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.output;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.project;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.tableScan;
+
+public class ExtractExpressionTest {
+  @Test
+  public void pushDownTest() {
+    PlanTester planTester = new PlanTester();
+
+    assertPlan(
+        planTester.createPlan("select * from table1 where extract(ms from 
time) > 5"),
+        output(tableScan("testdb.table1")));
+  }
+
+  @Test
+  public void constantFoldTest() {
+    PlanTester planTester = new PlanTester();
+    assertPlan(
+        planTester.createPlan("select extract(hour from 2025/07/08 01:18:51) 
from table1"),
+        output(
+            project(
+                ImmutableMap.of("expr", expression(new LongLiteral("1"))),
+                tableScan("testdb.table1"))));
+  }
+
+  @Test
+  public void exceptionTest() {
+    String errMsg = "Invalid EXTRACT field: s";
+    // Wrong extract field
+    assertAnalyzeSemanticException("select * from table1 where extract(s from 
time) > 5", errMsg);
+
+    errMsg = "Cannot extract from INT64";
+    // Wrong extract input type
+    assertAnalyzeSemanticException("select * from table1 where extract(ms from 
s1) > 5", errMsg);
+  }
+}
diff --git 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
index 1cb017af4ac..8aa8ac380d2 100644
--- 
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
+++ 
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
@@ -1126,6 +1126,7 @@ primaryExpression
         trimSource=valueExpression ')'                                         
           #trim
     | TRIM '(' trimSource=valueExpression ',' trimChar=valueExpression ')'     
           #trim
     | SUBSTRING '(' valueExpression FROM valueExpression (FOR 
valueExpression)? ')'       #substring
+    | EXTRACT '(' identifier FROM valueExpression ')'                          
           #extract
     | DATE_BIN '(' timeDuration ',' valueExpression (',' timeValue)? ')'       
           #dateBin
     | DATE_BIN_GAPFILL '(' timeDuration ',' valueExpression (',' timeValue)? 
')'          #dateBinGapFill
     | '(' expression ')'                                                       
           #parenthesizedExpression
diff --git a/pom.xml b/pom.xml
index 51320ee0158..21a7316c1ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -175,7 +175,7 @@
         <thrift.version>0.14.1</thrift.version>
         <xz.version>1.9</xz.version>
         <zstd-jni.version>1.5.6-3</zstd-jni.version>
-        <tsfile.version>2.1.0-250709-SNAPSHOT</tsfile.version>
+        <tsfile.version>2.2.0-250717-SNAPSHOT</tsfile.version>
     </properties>
     <!--
     if we claim dependencies in dependencyManagement, then we do not claim


Reply via email to