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

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


The following commit(s) were added to refs/heads/master by this push:
     new 844d4ab8f9f [timeseries] Introducing M3ql JavaCC parser to replace 
custom tokenizer implementation (#17192)
844d4ab8f9f is described below

commit 844d4ab8f9fcdc8fa71ffd750ac31ee858a815d0
Author: Shaurya Chaturvedi <[email protected]>
AuthorDate: Thu Nov 13 20:26:39 2025 -0800

    [timeseries] Introducing M3ql JavaCC parser to replace custom tokenizer 
implementation (#17192)
    
    Co-authored-by: shauryachats <[email protected]>
---
 .../pinot-timeseries-m3ql/pom.xml                  |  26 ++
 .../src/main/codegen/templates/M3qlParser.jj       | 262 +++++++++++++++++++++
 .../pinot/tsdb/m3ql/M3TimeSeriesPlanner.java       |  11 +-
 .../apache/pinot/tsdb/m3ql/parser/Tokenizer.java   | 119 ----------
 .../pinot/tsdb/m3ql/parser/M3qlParserTest.java     | 167 +++++++++++++
 5 files changed, 463 insertions(+), 122 deletions(-)

diff --git a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/pom.xml 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/pom.xml
index f6f85f0a3b1..3ded4336eb0 100644
--- a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/pom.xml
+++ b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/pom.xml
@@ -55,6 +55,32 @@
     </dependency>
   </dependencies>
 
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>javacc-maven-plugin</artifactId>
+        <version>3.0.1</version>
+        <executions>
+          <execution>
+            <id>javacc-m3ql</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>javacc</goal>
+            </goals>
+            <configuration>
+              
<sourceDirectory>${project.basedir}/src/main/codegen/templates</sourceDirectory>
+              
<outputDirectory>${project.build.directory}/generated-sources/javacc</outputDirectory>
+              <includes>
+                <include>M3qlParser.jj</include>
+              </includes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
   <profiles>
     <profile>
       <id>build-shaded-jar</id>
diff --git 
a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/codegen/templates/M3qlParser.jj
 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/codegen/templates/M3qlParser.jj
new file mode 100644
index 00000000000..a01dcc1df1c
--- /dev/null
+++ 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/codegen/templates/M3qlParser.jj
@@ -0,0 +1,262 @@
+/**
+ * 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.
+ */
+
+/**
+ * M3QL Parser - JavaCC Grammar for M3 Query Language
+ *
+ * Query syntax:
+ *   fetch{table="name", ts_column="col", ts_unit="SECONDS", filter="...", 
value="..."}
+ *   | sum{col1,col2}
+ *   | keepLastValue{}
+ *   | transformNull{0.0}
+ */
+
+options {
+    STATIC = false;
+    IGNORE_CASE = false;
+    UNICODE_INPUT = true;
+    JDK_VERSION = "1.8";
+}
+
+PARSER_BEGIN(M3qlParser)
+
+package org.apache.pinot.tsdb.m3ql.parser;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * M3QL Parser - Parses M3 Query Language syntax.
+ *
+ * This parser converts M3QL queries into a structured representation
+ * that can be used to build execution plans.
+ */
+public class M3qlParser {
+
+    /**
+     * Parse an M3QL query string.
+     *
+     * @param query the M3QL query string
+     * @return List of commands, where each command is a list of strings
+     * @throws ParseException if the query is invalid
+     */
+    public static List<List<String>> parse(String query) throws ParseException 
{
+        M3qlParser parser = new M3qlParser(new StringReader(query));
+        return parser.Query();
+    }
+}
+
+PARSER_END(M3qlParser)
+
+/* SKIP WHITESPACE */
+SKIP : {
+    " "
+  | "\t"
+  | "\n"
+  | "\r"
+}
+
+/* RESERVED WORDS */
+TOKEN : {
+    < FETCH: "fetch" >
+  | < SUM: "sum" >
+  | < MIN: "min" >
+  | < MAX: "max" >
+  | < KEEP_LAST_VALUE: "keepLastValue" >
+  | < TRANSFORM_NULL: "transformNull" >
+}
+
+/* OPERATORS AND SEPARATORS */
+TOKEN : {
+    < PIPE: "|" >
+  | < LBRACE: "{" >
+  | < RBRACE: "}" >
+  | < COMMA: "," >
+  | < EQUALS: "=" >
+}
+
+/* LITERALS */
+TOKEN : {
+    < QUOTED_STRING: "\"" (~["\"","\\"] | "\\" ~[])* "\"" >
+  | < NUMBER: (["-"])? (["0"-"9"])+ ("." (["0"-"9"])+)? >
+  | < IDENTIFIER: ["a"-"z","A"-"Z","_"] (["a"-"z","A"-"Z","0"-"9","_"])* >
+}
+
+/**
+ * Main query production rule.
+ * Parses: command | command | command ...
+ */
+List<List<String>> Query() :
+{
+    List<List<String>> commands = new ArrayList<List<String>>();
+    List<String> cmd;
+}
+{
+    cmd = Command()
+    { commands.add(cmd); }
+    (
+        <PIPE>
+        cmd = Command()
+        { commands.add(cmd); }
+    )*
+    <EOF>
+    { return commands; }
+}
+
+/**
+ * Command production rule.
+ * Parses: commandName{...}
+ */
+List<String> Command() :
+{
+    List<String> result;
+}
+{
+    (
+        result = FetchCommand()
+      | result = AggregationCommand()
+      | result = KeepLastValueCommand()
+      | result = TransformNullCommand()
+    )
+    { return result; }
+}
+
+/**
+ * Fetch command production rule.
+ * Parses: fetch{table="name", ts_column="col", ts_unit="SECONDS", 
filter="...", value="..."}
+ */
+List<String> FetchCommand() :
+{
+    List<String> result = new ArrayList<String>();
+    Token key, value;
+    String valueStr;
+}
+{
+    <FETCH>
+    { result.add("fetch"); }
+    <LBRACE>
+    (
+        key = <IDENTIFIER>
+        <EQUALS>
+        value = <QUOTED_STRING>
+        {
+            result.add(key.image);
+            // Remove quotes from the string value
+            valueStr = value.image;
+            result.add(valueStr.substring(1, valueStr.length() - 1));
+        }
+        (
+            <COMMA>
+            key = <IDENTIFIER>
+            <EQUALS>
+            value = <QUOTED_STRING>
+            {
+                result.add(key.image);
+                valueStr = value.image;
+                result.add(valueStr.substring(1, valueStr.length() - 1));
+            }
+        )*
+    )?
+    <RBRACE>
+    { return result; }
+}
+
+/**
+ * Aggregation command production rule.
+ * Parses: sum{col1,col2} or min{} or max{col1}
+ */
+List<String> AggregationCommand() :
+{
+    List<String> result = new ArrayList<String>();
+    Token cmdToken, col;
+    StringBuilder groupByCols = new StringBuilder();
+    boolean first = true;
+}
+{
+    (
+        cmdToken = <SUM>
+      | cmdToken = <MIN>
+      | cmdToken = <MAX>
+    )
+    { result.add(cmdToken.image); }
+    <LBRACE>
+    (
+        col = <IDENTIFIER>
+        {
+            if (!first) {
+                groupByCols.append(",");
+            }
+            groupByCols.append(col.image);
+            first = false;
+        }
+        (
+            <COMMA>
+            col = <IDENTIFIER>
+            {
+                groupByCols.append(",");
+                groupByCols.append(col.image);
+            }
+        )*
+    )?
+    <RBRACE>
+    {
+        if (groupByCols.length() > 0) {
+            result.add(groupByCols.toString());
+        }
+        return result;
+    }
+}
+
+/**
+ * KeepLastValue command production rule.
+ * Parses: keepLastValue{}
+ */
+List<String> KeepLastValueCommand() :
+{
+    List<String> result = new ArrayList<String>();
+}
+{
+    <KEEP_LAST_VALUE>
+    { result.add("keepLastValue"); }
+    <LBRACE>
+    <RBRACE>
+    { return result; }
+}
+
+/**
+ * TransformNull command production rule.
+ * Parses: transformNull{} or transformNull{0.0}
+ */
+List<String> TransformNullCommand() :
+{
+    List<String> result = new ArrayList<String>();
+    Token value = null;
+}
+{
+    <TRANSFORM_NULL>
+    { result.add("transformNull"); }
+    <LBRACE>
+    (
+        value = <NUMBER>
+        { result.add(value.image); }
+    )?
+    <RBRACE>
+    { return result; }
+}
diff --git 
a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/M3TimeSeriesPlanner.java
 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/M3TimeSeriesPlanner.java
index 83d2d8025e2..57fc6ab8bc4 100644
--- 
a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/M3TimeSeriesPlanner.java
+++ 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/M3TimeSeriesPlanner.java
@@ -29,7 +29,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.pinot.spi.env.PinotConfiguration;
-import org.apache.pinot.tsdb.m3ql.parser.Tokenizer;
+import org.apache.pinot.tsdb.m3ql.parser.M3qlParser;
+import org.apache.pinot.tsdb.m3ql.parser.ParseException;
 import org.apache.pinot.tsdb.m3ql.plan.KeepLastValuePlanNode;
 import org.apache.pinot.tsdb.m3ql.plan.TransformNullPlanNode;
 import org.apache.pinot.tsdb.m3ql.time.TimeBucketComputer;
@@ -63,8 +64,12 @@ public class M3TimeSeriesPlanner implements 
TimeSeriesLogicalPlanner {
 
   public BaseTimeSeriesPlanNode planQuery(RangeTimeSeriesRequest request) {
     PlanIdGenerator planIdGenerator = new PlanIdGenerator();
-    Tokenizer tokenizer = new Tokenizer(request.getQuery());
-    List<List<String>> commands = tokenizer.tokenize();
+    List<List<String>> commands;
+    try {
+      commands = M3qlParser.parse(request.getQuery());
+    } catch (ParseException e) {
+      throw new IllegalArgumentException("Failed to parse M3QL query: " + 
e.getMessage(), e);
+    }
     Preconditions.checkState(commands.size() > 1,
         "At least two commands required. " + "Query should start with a fetch 
followed by an aggregation.");
     BaseTimeSeriesPlanNode lastNode = null;
diff --git 
a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/parser/Tokenizer.java
 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/parser/Tokenizer.java
deleted file mode 100644
index 75ec610a771..00000000000
--- 
a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/main/java/org/apache/pinot/tsdb/m3ql/parser/Tokenizer.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * 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.pinot.tsdb.m3ql.parser;
-
-import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
-* TODO: Dummy implementation. Will be switched out with a proper 
implementation soon.
-*/
-public class Tokenizer {
-  private final String _query;
-
-  public Tokenizer(String query) {
-    _query = query;
-  }
-
-  public List<List<String>> tokenize() {
-    String[] pipelines = _query.split("\\|");
-    List<List<String>> result = new ArrayList<>();
-    for (String pipeline : pipelines) {
-      Preconditions.checkState(isValidToken(pipeline), String.format("Invalid 
token: %s", pipeline));
-      String command = pipeline.trim().substring(0, pipeline.indexOf("{"));
-      if (command.equals("fetch")) {
-        result.add(consumeFetch(pipeline.trim()));
-      } else {
-        result.add(consumeGeneric(pipeline.trim()));
-      }
-    }
-    return result;
-  }
-
-  private List<String> consumeFetch(String pipeline) {
-    pipeline = pipeline.trim();
-    String command = pipeline.substring(0, 5);
-    Preconditions.checkState(command.equals("fetch"), "Invalid command: %s", 
command);
-    pipeline = pipeline.substring(5).trim();
-    int start = pipeline.indexOf("{");
-    int end = pipeline.indexOf("}");
-    String args = pipeline.substring(start + 1, end);
-    List<String> result = new ArrayList<>();
-    result.add("fetch");
-    int indexOfEquals = args.indexOf("=");
-    while (indexOfEquals != -1) {
-      args = args.strip();
-      int equalIndex = args.indexOf("=");
-      int indexOfQuotes = args.indexOf("\"");
-      int lastQuote = indexOfQuotes + 1 + args.substring(indexOfQuotes + 
1).indexOf("\"");
-      String key = args.substring(0, equalIndex);
-      String value = args.substring(indexOfQuotes + 1, lastQuote);
-      args = args.substring(lastQuote + 1);
-      result.add(key);
-      result.add(value);
-      if (args.strip().startsWith(",")) {
-        args = args.strip().substring(1);
-      }
-      indexOfEquals = args.indexOf("=");
-    }
-    return result;
-  }
-
-  private List<String> consumeGeneric(String pipeline) {
-    List<String> result = new ArrayList<>();
-    int indexOfOpenBracket = pipeline.indexOf("{");
-    int indexOfClosedBracket = pipeline.indexOf("}");
-    result.add(pipeline.substring(0, indexOfOpenBracket));
-    String arg = pipeline.substring(indexOfOpenBracket + 1, 
indexOfClosedBracket);
-    if (!arg.isEmpty()) {
-      result.add(arg);
-    }
-    return result;
-  }
-
-  public static boolean isValidToken(String input) {
-    if (input == null || input.length() < 2) {
-      return false;
-    }
-    int openIndex = -1;
-    int closeIndex = -1;
-    // Find first '{' from the front
-    for (int i = 0; i < input.length(); i++) {
-      if (input.charAt(i) == '{') {
-        openIndex = i;
-        break;
-      }
-    }
-    // If no '{' found
-    if (openIndex == -1) {
-      return false;
-    }
-    // Find first '}' from the back
-    for (int i = input.length() - 1; i > openIndex; i--) {
-      if (input.charAt(i) == '}') {
-        closeIndex = i;
-        break;
-      }
-    }
-    // Valid only if '}' found after '{'
-    return closeIndex != -1;
-  }
-}
diff --git 
a/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/test/java/org/apache/pinot/tsdb/m3ql/parser/M3qlParserTest.java
 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/test/java/org/apache/pinot/tsdb/m3ql/parser/M3qlParserTest.java
new file mode 100644
index 00000000000..671ca4628f5
--- /dev/null
+++ 
b/pinot-plugins/pinot-timeseries-lang/pinot-timeseries-m3ql/src/test/java/org/apache/pinot/tsdb/m3ql/parser/M3qlParserTest.java
@@ -0,0 +1,167 @@
+/**
+ * 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.pinot.tsdb.m3ql.parser;
+
+import java.util.List;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+
+public class M3qlParserTest {
+
+  @Test
+  public void testBasicFetchCommand() throws Exception {
+    String query = "fetch{table=\"metrics\", ts_column=\"timestamp\", 
ts_unit=\"SECONDS\", value=\"cpu\"}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 1);
+    assertEquals(commands.get(0).size(), 9);
+    assertEquals(commands.get(0).get(0), "fetch");
+    assertEquals(commands.get(0).get(1), "table");
+    assertEquals(commands.get(0).get(2), "metrics");
+    assertEquals(commands.get(0).get(3), "ts_column");
+    assertEquals(commands.get(0).get(4), "timestamp");
+    assertEquals(commands.get(0).get(5), "ts_unit");
+    assertEquals(commands.get(0).get(6), "SECONDS");
+    assertEquals(commands.get(0).get(7), "value");
+    assertEquals(commands.get(0).get(8), "cpu");
+  }
+
+  @Test
+  public void testFetchWithFilter() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", 
ts_unit=\"MILLISECONDS\", "
+                 + "filter=\"host='web1'\", value=\"v\"}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 1);
+    assertEquals(commands.get(0).get(0), "fetch");
+    assertTrue(commands.get(0).contains("filter"));
+    assertTrue(commands.get(0).contains("host='web1'"));
+  }
+
+  @Test
+  public void testFetchWithSumNoGroupBy() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | sum{}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(0).get(0), "fetch");
+    assertEquals(commands.get(1).get(0), "sum");
+    assertEquals(commands.get(1).size(), 1);
+  }
+
+  @Test
+  public void testFetchWithSumSingleGroupBy() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | sum{hostname}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "sum");
+    assertEquals(commands.get(1).get(1), "hostname");
+  }
+
+  @Test
+  public void testFetchWithSumMultipleGroupBy() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} "
+                 + "| sum{hostname,region,datacenter}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "sum");
+    assertEquals(commands.get(1).get(1), "hostname,region,datacenter");
+  }
+
+  @Test
+  public void testMinAggregation() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | min{region}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "min");
+    assertEquals(commands.get(1).get(1), "region");
+  }
+
+  @Test
+  public void testMaxAggregation() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | max{}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "max");
+    assertEquals(commands.get(1).size(), 1);
+  }
+
+  @Test
+  public void testKeepLastValue() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | keepLastValue{}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "keepLastValue");
+    assertEquals(commands.get(1).size(), 1);
+  }
+
+  @Test
+  public void testTransformNullWithoutValue() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | transformNull{}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "transformNull");
+    assertEquals(commands.get(1).size(), 1);
+  }
+
+  @Test
+  public void testTransformNullWithValue() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | transformNull{0.0}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 2);
+    assertEquals(commands.get(1).get(0), "transformNull");
+    assertEquals(commands.get(1).get(1), "0.0");
+  }
+
+  @Test
+  public void testFullPipeline() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} "
+                 + "| sum{host} "
+                 + "| keepLastValue{} "
+                 + "| transformNull{0.0}";
+    List<List<String>> commands = M3qlParser.parse(query);
+
+    assertEquals(commands.size(), 4);
+    assertEquals(commands.get(0).get(0), "fetch");
+    assertEquals(commands.get(1).get(0), "sum");
+    assertEquals(commands.get(2).get(0), "keepLastValue");
+    assertEquals(commands.get(3).get(0), "transformNull");
+  }
+
+  @Test(expectedExceptions = ParseException.class)
+  public void testMissingClosingBrace() throws Exception {
+    String query = "fetch{table=\"m\"";
+    M3qlParser.parse(query);
+  }
+
+  @Test(expectedExceptions = ParseException.class)
+  public void testInvalidCommand() throws Exception {
+    String query = "fetch{table=\"m\", ts_column=\"t\", ts_unit=\"SECONDS\", 
value=\"v\"} | invalid{}";
+    M3qlParser.parse(query);
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to