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]