Make slimit and soffset available in align by device with template

diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceWithTemplateIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceWithTemplateIT.java
index 78cda6b..9d64fa8 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceWithTemplateIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/alignbydevice/IoTDBAlignByDeviceWithTemplateIT.java
@@ -578,6 +578,88 @@
   }
 
   @Test
+  public void sLimitOffsetTest() {
+    // 1. original
+    String[] expectedHeader = new String[] {"Time,Device,s1"};
+    String[] retArray =
+        new String[] {
+          "1,root.sg1.d1,1.1,",
+          "2,root.sg1.d1,2.2,",
+          "1,root.sg1.d2,11.1,",
+          "2,root.sg1.d2,22.2,",
+          "1,root.sg1.d3,111.1,",
+          "4,root.sg1.d3,444.4,",
+          "1,root.sg1.d4,1111.1,",
+          "5,root.sg1.d4,5555.5,",
+        };
+    resultSetEqualTest(
+        "SELECT * FROM root.sg1.** slimit 1 soffset 1 ALIGN BY DEVICE;", expectedHeader, retArray);
+    retArray =
+        new String[] {
+          "1,root.sg2.d1,1.1,",
+          "2,root.sg2.d1,2.2,",
+          "1,root.sg2.d2,11.1,",
+          "2,root.sg2.d2,22.2,",
+          "1,root.sg2.d3,111.1,",
+          "4,root.sg2.d3,444.4,",
+          "1,root.sg2.d4,1111.1,",
+          "5,root.sg2.d4,5555.5,",
+        };
+    resultSetEqualTest(
+        "SELECT * FROM root.sg2.** slimit 1 soffset 1 ALIGN BY DEVICE;", expectedHeader, retArray);
+
+    expectedHeader = new String[] {"Time,Device,s1,s2"};
+    retArray =
+        new String[] {
+          "1,root.sg1.d1,1.1,false,",
+          "2,root.sg1.d1,2.2,false,",
+          "1,root.sg1.d2,11.1,false,",
+          "2,root.sg1.d2,22.2,false,",
+          "1,root.sg1.d3,111.1,true,",
+          "4,root.sg1.d3,444.4,true,",
+          "1,root.sg1.d4,1111.1,true,",
+          "5,root.sg1.d4,5555.5,false,",
+        };
+    resultSetEqualTest(
+        "SELECT *, s1 FROM root.sg1.** slimit 2 soffset 1 ALIGN BY DEVICE;",
+        expectedHeader,
+        retArray);
+    retArray =
+        new String[] {
+          "1,root.sg2.d1,1.1,false,",
+          "2,root.sg2.d1,2.2,false,",
+          "1,root.sg2.d2,11.1,false,",
+          "2,root.sg2.d2,22.2,false,",
+          "1,root.sg2.d3,111.1,true,",
+          "4,root.sg2.d3,444.4,true,",
+          "1,root.sg2.d4,1111.1,true,",
+          "5,root.sg2.d4,5555.5,false,",
+        };
+    resultSetEqualTest(
+        "SELECT *, s1 FROM root.sg2.** slimit 2 soffset 1 ALIGN BY DEVICE;",
+        expectedHeader,
+        retArray);
+
+    expectedHeader = new String[] {"Time,Device,s1"};
+    retArray =
+        new String[] {
+          "1,root.sg1.d1,1.1,", "2,root.sg1.d1,2.2,", "1,root.sg1.d2,11.1,", "2,root.sg1.d2,22.2,",
+        };
+    resultSetEqualTest(
+        "SELECT * FROM root.sg1.d1,root.sg1.d2,root.sg1.d6 soffset 1 slimit 1 ALIGN BY DEVICE;",
+        expectedHeader,
+        retArray);
+    retArray =
+        new String[] {
+          "1,root.sg2.d1,1.1,", "2,root.sg2.d1,2.2,", "1,root.sg2.d2,11.1,", "2,root.sg2.d2,22.2,",
+        };
+    resultSetEqualTest(
+        "SELECT * FROM root.sg2.d1,root.sg2.d2,root.sg2.d6 soffset 1 slimit 1 ALIGN BY DEVICE;",
+        expectedHeader,
+        retArray);
+  }
+
+  @Test
   public void emptyResultTest() {
     String[] expectedHeader = new String[] {"Time,Device,s3,s1,s2"};
     String[] retArray = new String[] {};
diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TemplatedAnalyze.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TemplatedAnalyze.java
index 5ec7997..a98503d 100644
--- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TemplatedAnalyze.java
+++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/TemplatedAnalyze.java
@@ -113,29 +113,47 @@
     Template template = templates.get(0);
 
     List<Pair<Expression, String>> outputExpressions = new ArrayList<>();
+    ColumnPaginationController paginationController =
+        new ColumnPaginationController(
+            queryStatement.getSeriesLimit(), queryStatement.getSeriesOffset(), false);
     if (template != null) {
       for (ResultColumn resultColumn : queryStatement.getSelectComponent().getResultColumns()) {
         Expression expression = resultColumn.getExpression();
         if ("*".equals(expression.getOutputSymbol())) {
           for (Map.Entry<String, IMeasurementSchema> entry : template.getSchemaMap().entrySet()) {
-            String measurementName = entry.getKey();
-            IMeasurementSchema measurementSchema = entry.getValue();
-            TimeSeriesOperand measurementPath =
-                new TimeSeriesOperand(
-                    new MeasurementPath(new String[] {measurementName}, measurementSchema));
-            outputExpressions.add(new Pair<>(measurementPath, null));
+            if (paginationController.hasCurOffset()) {
+              paginationController.consumeOffset();
+            } else if (paginationController.hasCurLimit()) {
+              String measurementName = entry.getKey();
+              IMeasurementSchema measurementSchema = entry.getValue();
+              TimeSeriesOperand measurementPath =
+                  new TimeSeriesOperand(
+                      new MeasurementPath(new String[] {measurementName}, measurementSchema));
+              outputExpressions.add(new Pair<>(measurementPath, null));
+              paginationController.consumeLimit();
+            } else {
+              break;
+            }
           }
-          if (queryStatement.getSelectComponent().getResultColumns().size() == 1) {
+          if (queryStatement.getSelectComponent().getResultColumns().size() == 1
+              && !paginationController.hasCurOffset()
+              && !paginationController.hasCurLimit()) {
             analysis.setTemplateWildCardQuery();
           }
         } else if (expression instanceof TimeSeriesOperand) {
           String measurementName = ((TimeSeriesOperand) expression).getPath().getMeasurement();
           if (template.getSchemaMap().containsKey(measurementName)) {
-            IMeasurementSchema measurementSchema = template.getSchemaMap().get(measurementName);
-            TimeSeriesOperand measurementPath =
-                new TimeSeriesOperand(
-                    new MeasurementPath(new String[] {measurementName}, measurementSchema));
-            outputExpressions.add(new Pair<>(measurementPath, resultColumn.getAlias()));
+            if (paginationController.hasCurOffset()) {
+              paginationController.consumeOffset();
+            } else if (paginationController.hasCurLimit()) {
+              IMeasurementSchema measurementSchema = template.getSchemaMap().get(measurementName);
+              TimeSeriesOperand measurementPath =
+                  new TimeSeriesOperand(
+                      new MeasurementPath(new String[] {measurementName}, measurementSchema));
+              outputExpressions.add(new Pair<>(measurementPath, resultColumn.getAlias()));
+            } else {
+              break;
+            }
           }
         } else {
           return false;