From 4102b581f04c8f11f3cc1bfa058674dd0f526e87 Mon Sep 17 00:00:00 2001 From: Vamsi Manohar Date: Thu, 20 Jul 2023 08:48:27 -0700 Subject: [PATCH 1/5] Change query range response structure (#1867) Signed-off-by: Vamsi Manohar --- .../ppl/PrometheusDataSourceCommandsIT.java | 37 ++++++++- .../ppl/explain_query_range.json | 9 +++ ...faultQueryRangeFunctionResponseHandle.java | 75 +++++++++---------- .../response/PrometheusResponse.java | 25 +------ .../storage/PrometheusMetricScan.java | 7 +- .../storage/PrometheusMetricTable.java | 4 - .../PrometheusDefaultImplementor.java | 14 ++-- ...eryRangeFunctionTableScanOperatorTest.java | 36 ++++++--- .../storage/PrometheusMetricScanTest.java | 33 -------- .../storage/PrometheusMetricTableTest.java | 26 ------- 10 files changed, 115 insertions(+), 151 deletions(-) create mode 100644 integ-test/src/test/resources/expectedOutput/ppl/explain_query_range.json diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/PrometheusDataSourceCommandsIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/PrometheusDataSourceCommandsIT.java index 9006e00ff8..d0b682594b 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/PrometheusDataSourceCommandsIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/PrometheusDataSourceCommandsIT.java @@ -7,22 +7,28 @@ package org.opensearch.sql.ppl; +import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.LABELS; import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.TIMESTAMP; import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.VALUE; +import static org.opensearch.sql.util.MatcherUtils.assertJsonEquals; import static org.opensearch.sql.util.MatcherUtils.schema; import static org.opensearch.sql.util.MatcherUtils.verifySchema; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; +import lombok.Data; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.jupiter.api.Assertions; @@ -218,4 +224,33 @@ public void testMetricSumAggregationCommand() { } } + + @Test + @SneakyThrows + public void testQueryRange() { + long currentTimestamp = new Date().getTime(); + JSONObject response = + executeQuery("source=my_prometheus.query_range('prometheus_http_requests_total'," + + ((currentTimestamp/1000)-3600) + "," + currentTimestamp/1000 + ", " + 14 + ")" ); + verifySchema(response, + schema(VALUE, "array"), + schema(TIMESTAMP, "array"), + schema(LABELS, "struct")); + Assertions.assertTrue(response.getInt("size") > 0); + } + + @Test + public void explainQueryRange() throws Exception { + String expected = loadFromFile("expectedOutput/ppl/explain_query_range.json"); + assertJsonEquals( + expected, + explainQueryToString("source = my_prometheus" + + ".query_range('prometheus_http_requests_total',1689281439,1689291439,14)") + ); + } + + String loadFromFile(String filename) throws Exception { + URI uri = Resources.getResource(filename).toURI(); + return new String(Files.readAllBytes(Paths.get(uri))); + } } diff --git a/integ-test/src/test/resources/expectedOutput/ppl/explain_query_range.json b/integ-test/src/test/resources/expectedOutput/ppl/explain_query_range.json new file mode 100644 index 0000000000..bbc00e0c43 --- /dev/null +++ b/integ-test/src/test/resources/expectedOutput/ppl/explain_query_range.json @@ -0,0 +1,9 @@ +{ + "root": { + "name": "QueryRangeFunctionTableScanOperator", + "description": { + "request": "query_range(prometheus_http_requests_total, 1689281439, 1689291439, 14)" + }, + "children": [] + } +} \ No newline at end of file diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/functions/response/DefaultQueryRangeFunctionResponseHandle.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/functions/response/DefaultQueryRangeFunctionResponseHandle.java index 7f261360f7..d4353d2f99 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/functions/response/DefaultQueryRangeFunctionResponseHandle.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/functions/response/DefaultQueryRangeFunctionResponseHandle.java @@ -5,6 +5,8 @@ package org.opensearch.sql.prometheus.functions.response; +import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.LABELS; +import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.TIMESTAMP; import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.VALUE; import java.time.Instant; @@ -12,9 +14,9 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; -import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONObject; +import org.opensearch.sql.data.model.ExprCollectionValue; import org.opensearch.sql.data.model.ExprDoubleValue; import org.opensearch.sql.data.model.ExprStringValue; import org.opensearch.sql.data.model.ExprTimestampValue; @@ -22,7 +24,6 @@ import org.opensearch.sql.data.model.ExprValue; import org.opensearch.sql.data.type.ExprCoreType; import org.opensearch.sql.executor.ExecutionEngine; -import org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants; /** * Default implementation of QueryRangeFunctionResponseHandle. @@ -40,63 +41,61 @@ public class DefaultQueryRangeFunctionResponseHandle implements QueryRangeFuncti */ public DefaultQueryRangeFunctionResponseHandle(JSONObject responseObject) { this.responseObject = responseObject; - constructIteratorAndSchema(); + constructSchema(); + constructIterator(); } - private void constructIteratorAndSchema() { + private void constructIterator() { List result = new ArrayList<>(); - List columnList = new ArrayList<>(); if ("matrix".equals(responseObject.getString("resultType"))) { JSONArray itemArray = responseObject.getJSONArray("result"); for (int i = 0; i < itemArray.length(); i++) { + LinkedHashMap linkedHashMap = new LinkedHashMap<>(); JSONObject item = itemArray.getJSONObject(i); - JSONObject metric = item.getJSONObject("metric"); - JSONArray values = item.getJSONArray("values"); - if (i == 0) { - columnList = getColumnList(metric); - } - for (int j = 0; j < values.length(); j++) { - LinkedHashMap linkedHashMap = - extractRow(metric, values.getJSONArray(j), columnList); - result.add(new ExprTupleValue(linkedHashMap)); - } + linkedHashMap.put(LABELS, extractLabels(item.getJSONObject("metric"))); + extractTimestampAndValues(item.getJSONArray("values"), linkedHashMap); + result.add(new ExprTupleValue(linkedHashMap)); } } else { throw new RuntimeException(String.format("Unexpected Result Type: %s during Prometheus " + "Response Parsing. 'matrix' resultType is expected", responseObject.getString("resultType"))); } - this.schema = new ExecutionEngine.Schema(columnList); this.responseIterator = result.iterator(); } - @NotNull - private static LinkedHashMap extractRow(JSONObject metric, - JSONArray values, List columnList) { - LinkedHashMap linkedHashMap = new LinkedHashMap<>(); - for (ExecutionEngine.Schema.Column column : columnList) { - if (PrometheusFieldConstants.TIMESTAMP.equals(column.getName())) { - linkedHashMap.put(PrometheusFieldConstants.TIMESTAMP, - new ExprTimestampValue(Instant.ofEpochMilli((long) (values.getDouble(0) * 1000)))); - } else if (column.getName().equals(VALUE)) { - linkedHashMap.put(VALUE, new ExprDoubleValue(values.getDouble(1))); - } else { - linkedHashMap.put(column.getName(), - new ExprStringValue(metric.getString(column.getName()))); - } + private static void extractTimestampAndValues(JSONArray values, + LinkedHashMap linkedHashMap) { + List timestampList = new ArrayList<>(); + List valueList = new ArrayList<>(); + for (int j = 0; j < values.length(); j++) { + JSONArray value = values.getJSONArray(j); + timestampList.add(new ExprTimestampValue( + Instant.ofEpochMilli((long) (value.getDouble(0) * 1000)))); + valueList.add(new ExprDoubleValue(value.getDouble(1))); } - return linkedHashMap; + linkedHashMap.put(TIMESTAMP, + new ExprCollectionValue(timestampList)); + linkedHashMap.put(VALUE, new ExprCollectionValue(valueList)); } + private void constructSchema() { + this.schema = new ExecutionEngine.Schema(getColumnList()); + } - private List getColumnList(JSONObject metric) { + private ExprValue extractLabels(JSONObject metric) { + LinkedHashMap labelsMap = new LinkedHashMap<>(); + metric.keySet().forEach(key + -> labelsMap.put(key, new ExprStringValue(metric.getString(key)))); + return new ExprTupleValue(labelsMap); + } + + + private List getColumnList() { List columnList = new ArrayList<>(); - columnList.add(new ExecutionEngine.Schema.Column(PrometheusFieldConstants.TIMESTAMP, - PrometheusFieldConstants.TIMESTAMP, ExprCoreType.TIMESTAMP)); - columnList.add(new ExecutionEngine.Schema.Column(VALUE, VALUE, ExprCoreType.DOUBLE)); - for (String key : metric.keySet()) { - columnList.add(new ExecutionEngine.Schema.Column(key, key, ExprCoreType.STRING)); - } + columnList.add(new ExecutionEngine.Schema.Column(TIMESTAMP, TIMESTAMP, ExprCoreType.ARRAY)); + columnList.add(new ExecutionEngine.Schema.Column(VALUE, VALUE, ExprCoreType.ARRAY)); + columnList.add(new ExecutionEngine.Schema.Column(LABELS, LABELS, ExprCoreType.STRUCT)); return columnList; } diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/response/PrometheusResponse.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/response/PrometheusResponse.java index 331605b1d5..bd9e36ccdc 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/response/PrometheusResponse.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/response/PrometheusResponse.java @@ -35,8 +35,6 @@ public class PrometheusResponse implements Iterable { private final PrometheusResponseFieldNames prometheusResponseFieldNames; - private final Boolean isQueryRangeFunctionScan; - /** * Constructor. * @@ -46,11 +44,9 @@ public class PrometheusResponse implements Iterable { * and timestamp fieldName. */ public PrometheusResponse(JSONObject responseObject, - PrometheusResponseFieldNames prometheusResponseFieldNames, - Boolean isQueryRangeFunctionScan) { + PrometheusResponseFieldNames prometheusResponseFieldNames) { this.responseObject = responseObject; this.prometheusResponseFieldNames = prometheusResponseFieldNames; - this.isQueryRangeFunctionScan = isQueryRangeFunctionScan; } @NonNull @@ -70,24 +66,7 @@ public Iterator iterator() { new ExprTimestampValue(Instant.ofEpochMilli((long) (val.getDouble(0) * 1000)))); linkedHashMap.put(prometheusResponseFieldNames.getValueFieldName(), getValue(val, 1, prometheusResponseFieldNames.getValueType())); - // Concept: - // {\"instance\":\"localhost:9090\",\"__name__\":\"up\",\"job\":\"prometheus\"}" - // This is the label string in the prometheus response. - // Q: how do we map this to columns in a table. - // For queries like source = prometheus.metric_name | .... - // we can get the labels list in prior as we know which metric we are working on. - // In case of commands like source = prometheus.query_range('promQL'); - // Any arbitrary command can be written and we don't know the labels - // in the prometheus response in prior. - // So for PPL like commands...output structure is @value, @timestamp - // and each label is treated as a separate column where as in case of query_range - // function irrespective of promQL, the output structure is - // @value, @timestamp, @labels [jsonfied string of all the labels for a data point] - if (isQueryRangeFunctionScan) { - linkedHashMap.put(LABELS, new ExprStringValue(metric.toString())); - } else { - insertLabels(linkedHashMap, metric); - } + insertLabels(linkedHashMap, metric); result.add(new ExprTupleValue(linkedHashMap)); } } diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScan.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScan.java index 8611ae04f1..7f75cb3c07 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScan.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScan.java @@ -40,10 +40,6 @@ public class PrometheusMetricScan extends TableScanOperator { private Iterator iterator; - @Setter - @Getter - private Boolean isQueryRangeFunctionScan = Boolean.FALSE; - @Setter private PrometheusResponseFieldNames prometheusResponseFieldNames; @@ -69,8 +65,7 @@ public void open() { JSONObject responseObject = prometheusClient.queryRange( request.getPromQl(), request.getStartTime(), request.getEndTime(), request.getStep()); - return new PrometheusResponse(responseObject, prometheusResponseFieldNames, - isQueryRangeFunctionScan).iterator(); + return new PrometheusResponse(responseObject, prometheusResponseFieldNames).iterator(); } catch (IOException e) { LOG.error(e.getMessage()); throw new RuntimeException("Error fetching data from prometheus server. " + e.getMessage()); diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTable.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTable.java index a03d69bc41..b3b63327d0 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTable.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTable.java @@ -97,10 +97,6 @@ public Map getFieldTypes() { public PhysicalPlan implement(LogicalPlan plan) { PrometheusMetricScan metricScan = new PrometheusMetricScan(prometheusClient); - if (prometheusQueryRequest != null) { - metricScan.setRequest(prometheusQueryRequest); - metricScan.setIsQueryRangeFunctionScan(Boolean.TRUE); - } return plan.accept(new PrometheusDefaultImplementor(), metricScan); } diff --git a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/implementor/PrometheusDefaultImplementor.java b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/implementor/PrometheusDefaultImplementor.java index 8cae250e5e..221a70e9d6 100644 --- a/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/implementor/PrometheusDefaultImplementor.java +++ b/prometheus/src/main/java/org/opensearch/sql/prometheus/storage/implementor/PrometheusDefaultImplementor.java @@ -94,14 +94,12 @@ public PhysicalPlan visitIndexAggregation(PrometheusLogicalMetricAgg node, public PhysicalPlan visitRelation(LogicalRelation node, PrometheusMetricScan context) { PrometheusMetricTable prometheusMetricTable = (PrometheusMetricTable) node.getTable(); - if (prometheusMetricTable.getMetricName() != null) { - String query = SeriesSelectionQueryBuilder.build(node.getRelationName(), null); - context.getRequest().setPromQl(query); - setTimeRangeParameters(null, context); - context.getRequest() - .setStep(StepParameterResolver.resolve(context.getRequest().getStartTime(), - context.getRequest().getEndTime(), null)); - } + String query = SeriesSelectionQueryBuilder.build(node.getRelationName(), null); + context.getRequest().setPromQl(query); + setTimeRangeParameters(null, context); + context.getRequest() + .setStep(StepParameterResolver.resolve(context.getRequest().getStartTime(), + context.getRequest().getEndTime(), null)); return context; } diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/functions/scan/QueryRangeFunctionTableScanOperatorTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/functions/scan/QueryRangeFunctionTableScanOperatorTest.java index 3aa992fc65..79da8b466c 100644 --- a/prometheus/src/test/java/org/opensearch/sql/prometheus/functions/scan/QueryRangeFunctionTableScanOperatorTest.java +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/functions/scan/QueryRangeFunctionTableScanOperatorTest.java @@ -15,6 +15,7 @@ import static org.opensearch.sql.prometheus.constants.TestConstants.QUERY; import static org.opensearch.sql.prometheus.constants.TestConstants.STARTTIME; import static org.opensearch.sql.prometheus.constants.TestConstants.STEP; +import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.LABELS; import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.TIMESTAMP; import static org.opensearch.sql.prometheus.data.constants.PrometheusFieldConstants.VALUE; import static org.opensearch.sql.prometheus.utils.TestUtils.getJson; @@ -22,6 +23,7 @@ import java.io.IOException; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import lombok.SneakyThrows; import org.json.JSONObject; @@ -30,17 +32,19 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.opensearch.sql.data.model.ExprCollectionValue; import org.opensearch.sql.data.model.ExprDoubleValue; import org.opensearch.sql.data.model.ExprStringValue; import org.opensearch.sql.data.model.ExprTimestampValue; import org.opensearch.sql.data.model.ExprTupleValue; +import org.opensearch.sql.data.model.ExprValue; import org.opensearch.sql.data.type.ExprCoreType; import org.opensearch.sql.executor.ExecutionEngine; import org.opensearch.sql.prometheus.client.PrometheusClient; import org.opensearch.sql.prometheus.request.PrometheusQueryRequest; @ExtendWith(MockitoExtension.class) -public class QueryRangeFunctionTableScanOperatorTest { +class QueryRangeFunctionTableScanOperatorTest { @Mock private PrometheusClient prometheusClient; @@ -61,22 +65,32 @@ void testQueryResponseIterator() { .thenReturn(new JSONObject(getJson("query_range_result.json"))); queryRangeFunctionTableScanOperator.open(); Assertions.assertTrue(queryRangeFunctionTableScanOperator.hasNext()); - ExprTupleValue firstRow = new ExprTupleValue(new LinkedHashMap<>() {{ - put(TIMESTAMP, new ExprTimestampValue(Instant.ofEpochMilli(1435781430781L))); - put(VALUE, new ExprDoubleValue(1)); + LinkedHashMap labelsMap = new LinkedHashMap<>() {{ put("instance", new ExprStringValue("localhost:9090")); put("__name__", new ExprStringValue("up")); put("job", new ExprStringValue("prometheus")); + }}; + ExprTupleValue firstRow = new ExprTupleValue(new LinkedHashMap<>() {{ + put(LABELS, new ExprTupleValue(labelsMap)); + put(TIMESTAMP, new ExprCollectionValue(Collections + .singletonList(new ExprTimestampValue(Instant.ofEpochMilli(1435781430781L))))); + put(VALUE, new ExprCollectionValue(Collections.singletonList(new ExprDoubleValue(1)))); } }); + assertEquals(firstRow, queryRangeFunctionTableScanOperator.next()); Assertions.assertTrue(queryRangeFunctionTableScanOperator.hasNext()); - ExprTupleValue secondRow = new ExprTupleValue(new LinkedHashMap<>() {{ - put("@timestamp", new ExprTimestampValue(Instant.ofEpochMilli(1435781430781L))); - put("@value", new ExprDoubleValue(0)); + + LinkedHashMap labelsMap2 = new LinkedHashMap<>() {{ put("instance", new ExprStringValue("localhost:9091")); put("__name__", new ExprStringValue("up")); put("job", new ExprStringValue("node")); + }}; + ExprTupleValue secondRow = new ExprTupleValue(new LinkedHashMap<>() {{ + put(LABELS, new ExprTupleValue(labelsMap2)); + put(TIMESTAMP, new ExprCollectionValue(Collections + .singletonList(new ExprTimestampValue(Instant.ofEpochMilli(1435781430781L))))); + put(VALUE, new ExprCollectionValue(Collections.singletonList(new ExprDoubleValue(0)))); } }); assertEquals(secondRow, queryRangeFunctionTableScanOperator.next()); @@ -120,11 +134,9 @@ void testQuerySchema() { .thenReturn(new JSONObject(getJson("query_range_result.json"))); queryRangeFunctionTableScanOperator.open(); ArrayList columns = new ArrayList<>(); - columns.add(new ExecutionEngine.Schema.Column(TIMESTAMP, TIMESTAMP, ExprCoreType.TIMESTAMP)); - columns.add(new ExecutionEngine.Schema.Column(VALUE, VALUE, ExprCoreType.DOUBLE)); - columns.add(new ExecutionEngine.Schema.Column("instance", "instance", ExprCoreType.STRING)); - columns.add(new ExecutionEngine.Schema.Column("__name__", "__name__", ExprCoreType.STRING)); - columns.add(new ExecutionEngine.Schema.Column("job", "job", ExprCoreType.STRING)); + columns.add(new ExecutionEngine.Schema.Column(TIMESTAMP, TIMESTAMP, ExprCoreType.ARRAY)); + columns.add(new ExecutionEngine.Schema.Column(VALUE, VALUE, ExprCoreType.ARRAY)); + columns.add(new ExecutionEngine.Schema.Column(LABELS, LABELS, ExprCoreType.STRUCT)); ExecutionEngine.Schema expectedSchema = new ExecutionEngine.Schema(columns); assertEquals(expectedSchema, queryRangeFunctionTableScanOperator.schema()); } diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScanTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScanTest.java index cb70e9e064..68e03c758c 100644 --- a/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScanTest.java +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricScanTest.java @@ -209,39 +209,6 @@ void testQueryResponseIteratorWithGivenPrometheusResponseWithBackQuotedFieldName Assertions.assertFalse(prometheusMetricScan.hasNext()); } - @Test - @SneakyThrows - void testQueryResponseIteratorForQueryRangeFunction() { - PrometheusMetricScan prometheusMetricScan = new PrometheusMetricScan(prometheusClient); - prometheusMetricScan.setIsQueryRangeFunctionScan(Boolean.TRUE); - prometheusMetricScan.getRequest().setPromQl(QUERY); - prometheusMetricScan.getRequest().setStartTime(STARTTIME); - prometheusMetricScan.getRequest().setEndTime(ENDTIME); - prometheusMetricScan.getRequest().setStep(STEP); - - when(prometheusClient.queryRange(any(), any(), any(), any())) - .thenReturn(new JSONObject(getJson("query_range_result.json"))); - prometheusMetricScan.open(); - Assertions.assertTrue(prometheusMetricScan.hasNext()); - ExprTupleValue firstRow = new ExprTupleValue(new LinkedHashMap<>() {{ - put(TIMESTAMP, new ExprTimestampValue(Instant.ofEpochMilli(1435781430781L))); - put(VALUE, new ExprLongValue(1)); - put(LABELS, new ExprStringValue( - "{\"instance\":\"localhost:9090\",\"__name__\":\"up\",\"job\":\"prometheus\"}")); - } - }); - assertEquals(firstRow, prometheusMetricScan.next()); - Assertions.assertTrue(prometheusMetricScan.hasNext()); - ExprTupleValue secondRow = new ExprTupleValue(new LinkedHashMap<>() {{ - put(TIMESTAMP, new ExprTimestampValue(Instant.ofEpochMilli(1435781430781L))); - put(VALUE, new ExprLongValue(0)); - put(LABELS, new ExprStringValue( - "{\"instance\":\"localhost:9091\",\"__name__\":\"up\",\"job\":\"node\"}")); - } - }); - assertEquals(secondRow, prometheusMetricScan.next()); - Assertions.assertFalse(prometheusMetricScan.hasNext()); - } @Test @SneakyThrows diff --git a/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTableTest.java b/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTableTest.java index de95b2bd64..d43c38fc68 100644 --- a/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTableTest.java +++ b/prometheus/src/test/java/org/opensearch/sql/prometheus/storage/PrometheusMetricTableTest.java @@ -111,32 +111,6 @@ void testGetFieldTypesFromPrometheusQueryRequest() { assertNull(prometheusMetricTable.getMetricName()); } - @Test - void testImplementWithQueryRangeFunction() { - PrometheusQueryRequest prometheusQueryRequest = new PrometheusQueryRequest(); - prometheusQueryRequest.setPromQl("test"); - prometheusQueryRequest.setStep("15m"); - PrometheusMetricTable prometheusMetricTable = - new PrometheusMetricTable(client, prometheusQueryRequest); - List finalProjectList = new ArrayList<>(); - finalProjectList.add(DSL.named(VALUE, DSL.ref(VALUE, STRING))); - finalProjectList.add(DSL.named(TIMESTAMP, DSL.ref(TIMESTAMP, ExprCoreType.TIMESTAMP))); - PhysicalPlan plan = prometheusMetricTable.implement( - project(relation("query_range", prometheusMetricTable), - finalProjectList, null)); - - - assertTrue(plan instanceof ProjectOperator); - List projectList = ((ProjectOperator) plan).getProjectList(); - List outputFields - = projectList.stream().map(NamedExpression::getName).collect(Collectors.toList()); - assertEquals(List.of(VALUE, TIMESTAMP), outputFields); - assertTrue(((ProjectOperator) plan).getInput() instanceof PrometheusMetricScan); - PrometheusMetricScan prometheusMetricScan = - (PrometheusMetricScan) ((ProjectOperator) plan).getInput(); - assertEquals(prometheusQueryRequest, prometheusMetricScan.getRequest()); - } - @Test void testImplementWithBasicMetricQuery() { PrometheusMetricTable prometheusMetricTable = From af12c028270a927474774808a1710a82c3b5c0a7 Mon Sep 17 00:00:00 2001 From: Mitchell Gale Date: Mon, 24 Jul 2023 11:54:12 -0700 Subject: [PATCH 2/5] Adding Spotless support framework (#1888) * Adding spotless plugin to project build.gradle. Signed-off-by: Mitchell Gale Remove commented out spotless changes. Signed-off-by: Mitchell Gale Adding Spotless to DEVELOPER_GUIDE.rst Signed-off-by: Mitchell Gale Added Google Java format to spotless. Signed-off-by: Mitchell Gale Update DEVELOPER_GUIDE.rst Co-authored-by: Yury-Fridlyand * Added apply false for spotless Signed-off-by: Mitchell Gale * Adding ratchetFrom to build.gradle Signed-off-by: Mitchell Gale * Adding license header to build.gradle for spotless. Signed-off-by: Mitchell Gale * Uncommenting all changes to build.gradle for spotless. Signed-off-by: Mitchell Gale * Commented out spotless checks. Signed-off-by: Mitchell Gale * Add specific version for java format spotless (1.17.0) Signed-off-by: Mitchell Gale --------- Signed-off-by: Mitchell Gale Co-authored-by: Yury-Fridlyand --- DEVELOPER_GUIDE.rst | 8 ++++++-- build.gradle | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/DEVELOPER_GUIDE.rst b/DEVELOPER_GUIDE.rst index 923cb459f9..257e3bb8f9 100644 --- a/DEVELOPER_GUIDE.rst +++ b/DEVELOPER_GUIDE.rst @@ -113,8 +113,8 @@ Note that missing license header will be detected by Gradle license plugin and f Making Code Changes =================== -Project Strucure ----------------- +Project Structure +----------------- The plugin codebase is in standard layout of Gradle project:: @@ -226,6 +226,10 @@ Most of the time you just need to run ./gradlew build which will make sure you p - Build plugin by run all tasks above (this takes time). * - ./gradlew pitest - Run PiTest mutation testing (see more info in `#1204 `_) + * - ./gradlew spotlessCheck + - Runs Spotless to check for code style. + * - ./gradlew spotlessApply + - Automatically apply spotless code style changes. For integration test, you can use ``-Dtests.class`` “UT full path” to run a task individually. For example ``./gradlew :integ-test:integTest -Dtests.class="*QueryIT"``. diff --git a/build.gradle b/build.gradle index d7458f722f..ff29eb7687 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,7 @@ plugins { id 'checkstyle' id "io.freefair.lombok" version "6.4.0" id 'jacoco' + id 'com.diffplug.spotless' version '6.19.0' } // import versions defined in https://github.com/opensearch-project/OpenSearch/blob/main/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchJavaPlugin.java#L94 @@ -79,6 +80,25 @@ repositories { maven { url 'https://jitpack.io' } } +// Spotless checks will be added as PRs are applied to resolve each style issue is approved. +spotless { + java { +// target fileTree('.') { +// include '**/*.java', 'src/*/java/**/*.java' +// exclude '**/build/**', '**/build-*/**' +// } +// importOrder() +// licenseHeader("/*\n" + +// " * Copyright OpenSearch Contributors\n" + +// " * SPDX-License-Identifier: Apache-2.0\n" + +// " */\n\n\n") +// removeUnusedImports() +// trimTrailingWhitespace() +// endWithNewline() +// googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + allprojects { version = opensearch_version.tokenize('-')[0] + '.0' if (buildVersionQualifier) { From 7b932a7c882cbabbc9fcafcd864a39bb161bd122 Mon Sep 17 00:00:00 2001 From: Mitchell Gale Date: Tue, 25 Jul 2023 17:15:01 -0700 Subject: [PATCH 3/5] Fix create_index/create_index_with_IOException issue caused by OpenSearch PR change (#1899) * Added setDefaultMediaType for create_index and create_index_with_IOException Signed-off-by: Mitchell Gale --- .../sql/opensearch/client/OpenSearchRestClientTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/opensearch/src/test/java/org/opensearch/sql/opensearch/client/OpenSearchRestClientTest.java b/opensearch/src/test/java/org/opensearch/sql/opensearch/client/OpenSearchRestClientTest.java index b521c6605c..cceb6de995 100644 --- a/opensearch/src/test/java/org/opensearch/sql/opensearch/client/OpenSearchRestClientTest.java +++ b/opensearch/src/test/java/org/opensearch/sql/opensearch/client/OpenSearchRestClientTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.opensearch.core.xcontent.MediaTypeParserRegistry.setDefaultMediaType; import static org.opensearch.sql.opensearch.client.OpenSearchClient.META_CLUSTER_NAME; import static org.opensearch.sql.opensearch.data.type.OpenSearchDataType.MappingType; @@ -34,6 +35,7 @@ import lombok.SneakyThrows; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.lucene.search.TotalHits; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -101,6 +103,11 @@ void setUp() { client = new OpenSearchRestClient(restClient); } + @BeforeAll + static void setUpJSON() { + setDefaultMediaType(XContentType.JSON); + } + @Test void is_index_exist() throws IOException { when(restClient.indices() @@ -142,7 +149,6 @@ void create_index() throws IOException { @Test void create_index_with_IOException() throws IOException { when(restClient.indices().create(any(), any())).thenThrow(IOException.class); - assertThrows(IllegalStateException.class, () -> client.createIndex("test", ImmutableMap.of())); } From c8d42a739557a4fe712a231a2d719ae387171d1b Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Tue, 25 Jul 2023 17:25:42 -0700 Subject: [PATCH 4/5] Statically init `typeActionMap` in `OpenSearchExprValueFactory`. (#310) (#1897) Signed-off-by: Yury-Fridlyand --- .../data/value/OpenSearchExprValueFactory.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java index 22a43d3444..95815d5c38 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java @@ -105,7 +105,7 @@ public void extendTypeMapping(Map typeMapping) { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final Map> typeActionMap = + private static final Map> typeActionMap = new ImmutableMap.Builder>() .put(OpenSearchDataType.of(OpenSearchDataType.MappingType.Integer), (c, dt) -> new ExprIntegerValue(c.intValue())) @@ -126,10 +126,12 @@ public void extendTypeMapping(Map typeMapping) { .put(OpenSearchDataType.of(OpenSearchDataType.MappingType.Boolean), (c, dt) -> ExprBooleanValue.of(c.booleanValue())) //Handles the creation of DATE, TIME & DATETIME - .put(OpenSearchDateType.of(TIME), this::createOpenSearchDateType) - .put(OpenSearchDateType.of(DATE), this::createOpenSearchDateType) - .put(OpenSearchDateType.of(TIMESTAMP), this::createOpenSearchDateType) - .put(OpenSearchDateType.of(DATETIME), this::createOpenSearchDateType) + .put(OpenSearchDateType.of(TIME), OpenSearchExprValueFactory::createOpenSearchDateType) + .put(OpenSearchDateType.of(DATE), OpenSearchExprValueFactory::createOpenSearchDateType) + .put(OpenSearchDateType.of(TIMESTAMP), + OpenSearchExprValueFactory::createOpenSearchDateType) + .put(OpenSearchDateType.of(DATETIME), + OpenSearchExprValueFactory::createOpenSearchDateType) .put(OpenSearchDataType.of(OpenSearchDataType.MappingType.Ip), (c, dt) -> new OpenSearchExprIpValue(c.stringValue())) .put(OpenSearchDataType.of(OpenSearchDataType.MappingType.GeoPoint), @@ -222,7 +224,7 @@ private Optional type(String field) { * @param dataType - field data type * @return Parsed value */ - private ExprValue parseDateTimeString(String value, OpenSearchDateType dataType) { + private static ExprValue parseDateTimeString(String value, OpenSearchDateType dataType) { List formatters = dataType.getAllNamedFormatters(); formatters.addAll(dataType.getAllCustomFormatters()); ExprCoreType returnFormat = (ExprCoreType) dataType.getExprType(); @@ -262,7 +264,7 @@ private ExprValue parseDateTimeString(String value, OpenSearchDateType dataType) "Construct %s from \"%s\" failed, unsupported format.", returnFormat, value)); } - private ExprValue createOpenSearchDateType(Content value, ExprType type) { + private static ExprValue createOpenSearchDateType(Content value, ExprType type) { OpenSearchDateType dt = (OpenSearchDateType) type; ExprType returnFormat = dt.getExprType(); From 1a7134b79044de655445f2c1e24ec56467544f50 Mon Sep 17 00:00:00 2001 From: Matthew Wells Date: Wed, 26 Jul 2023 09:28:38 -0700 Subject: [PATCH 5/5] Added support of timestamp/date/time using curly brackets (#1894) * Added support of timestamp/date/time using curly brackets (#297) * added bracketed time/date/timestamp input, tests, and documentation Signed-off-by: Matthew Wells * improved failing tests Signed-off-by: Matthew Wells * simplified tests for checking for failure Signed-off-by: Matthew Wells * fixed redundant tests and improved tests that should fail Signed-off-by: Matthew Wells --------- Signed-off-by: Matthew Wells --- docs/user/dql/expressions.rst | 11 ++- .../sql/sql/DateTimeFunctionIT.java | 82 +++++++++++++++++++ sql/src/main/antlr/OpenSearchSQLParser.g4 | 4 +- .../common/antlr/SyntaxParserTestBase.java | 2 - .../sql/sql/antlr/BracketedTimestampTest.java | 41 ++++++++++ .../sql/sql/antlr/SQLParserTest.java | 6 ++ 6 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 sql/src/test/java/org/opensearch/sql/sql/antlr/BracketedTimestampTest.java diff --git a/docs/user/dql/expressions.rst b/docs/user/dql/expressions.rst index 275795707c..af264b2f16 100644 --- a/docs/user/dql/expressions.rst +++ b/docs/user/dql/expressions.rst @@ -25,7 +25,7 @@ A literal is a symbol that represents a value. The most common literal values in 1. Numeric literals: specify numeric values such as integer and floating-point numbers. 2. String literals: specify a string enclosed by single or double quotes. 3. Boolean literals: ``true`` or ``false``. -4. Date and Time literals: DATE 'YYYY-MM-DD' represent the date, TIME 'hh:mm:ss' represent the time, TIMESTAMP 'YYYY-MM-DD hh:mm:ss' represent the timestamp. +4. Date and Time literals: DATE 'YYYY-MM-DD' represent the date, TIME 'hh:mm:ss' represent the time, TIMESTAMP 'YYYY-MM-DD hh:mm:ss' represent the timestamp. You can also surround the literals with curly brackets, if you do, you can replace date with d, time with t, and timestamp with ts Examples -------- @@ -49,6 +49,15 @@ Here is an example for different type of literals:: | Hello | Hello | It"s | It's | It's | "Its" | It's | It\'s | \I\t\s | +-----------+-----------+-----------+-----------+----------+-----------+-----------+-------------+------------+ + + os> SELECT {DATE '2020-07-07'}, {D '2020-07-07'}, {TIME '01:01:01'}, {T '01:01:01'}, {TIMESTAMP '2020-07-07 01:01:01'}, {TS '2020-07-07 01:01:01'} + fetched rows / total rows = 1/1 + +-----------------------+--------------------+---------------------+------------------+-------------------------------------+------------------------------+ + | {DATE '2020-07-07'} | {D '2020-07-07'} | {TIME '01:01:01'} | {T '01:01:01'} | {TIMESTAMP '2020-07-07 01:01:01'} | {TS '2020-07-07 01:01:01'} | + |-----------------------+--------------------+---------------------+------------------+-------------------------------------+------------------------------| + | 2020-07-07 | 2020-07-07 | 01:01:01 | 01:01:01 | 2020-07-07 01:01:01 | 2020-07-07 01:01:01 | + +-----------------------+--------------------+---------------------+------------------+-------------------------------------+------------------------------+ + Limitations ----------- diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java index 91457296d6..2696a9a0d6 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java @@ -29,6 +29,7 @@ import org.opensearch.client.Request; import org.opensearch.client.RequestOptions; import org.opensearch.client.Response; +import org.opensearch.client.ResponseException; import org.opensearch.sql.common.utils.StringUtils; import org.opensearch.sql.legacy.SQLIntegTestCase; @@ -1288,4 +1289,85 @@ protected JSONObject executeQuery(String query) throws IOException { Response response = client().performRequest(request); return new JSONObject(getResponseBody(response)); } + + @Test + public void testTimestampBracket() throws IOException { + JSONObject result = executeQuery("select {timestamp '2020-09-16 17:30:00'}"); + verifySchema(result, schema("{timestamp '2020-09-16 17:30:00'}", null, "timestamp")); + verifyDataRows(result, rows("2020-09-16 17:30:00")); + + result = executeQuery("select {ts '2020-09-16 17:30:00'}"); + verifySchema(result, schema("{ts '2020-09-16 17:30:00'}", null, "timestamp")); + verifyDataRows(result, rows("2020-09-16 17:30:00")); + + result = executeQuery("select {timestamp '2020-09-16 17:30:00.123'}"); + verifySchema(result, schema("{timestamp '2020-09-16 17:30:00.123'}", null, "timestamp")); + verifyDataRows(result, rows("2020-09-16 17:30:00.123")); + + result = executeQuery("select {ts '2020-09-16 17:30:00.123'}"); + verifySchema(result, schema("{ts '2020-09-16 17:30:00.123'}", null, "timestamp")); + verifyDataRows(result, rows("2020-09-16 17:30:00.123")); + } + + @Test + public void testTimeBracket() throws IOException { + JSONObject result = executeQuery("select {time '17:30:00'}"); + verifySchema(result, schema("{time '17:30:00'}", null, "time")); + verifyDataRows(result, rows("17:30:00")); + + result = executeQuery("select {t '17:30:00'}"); + verifySchema(result, schema("{t '17:30:00'}", null, "time")); + verifyDataRows(result, rows("17:30:00")); + + result = executeQuery("select {time '17:30:00.123'}"); + verifySchema(result, schema("{time '17:30:00.123'}", null, "time")); + verifyDataRows(result, rows("17:30:00.123")); + + result = executeQuery("select {t '17:30:00.123'}"); + verifySchema(result, schema("{t '17:30:00.123'}", null, "time")); + verifyDataRows(result, rows("17:30:00.123")); + } + + @Test + public void testDateBracket() throws IOException { + JSONObject result = executeQuery("select {date '2020-09-16'}"); + verifySchema(result, schema("{date '2020-09-16'}", null, "date")); + verifyDataRows(result, rows("2020-09-16")); + + result = executeQuery("select {d '2020-09-16'}"); + verifySchema(result, schema("{d '2020-09-16'}", null, "date")); + verifyDataRows(result, rows("2020-09-16")); + } + + private void compareBrackets(String query1, String query2, String datetime) throws IOException { + JSONObject result1 = executeQuery("select " + query1 + " '" + datetime + "'"); + JSONObject result2 = executeQuery("select {" + query2 + " '" + datetime + "'}"); + + verifyDataRows(result1, rows(datetime)); + verifyDataRows(result2, rows(datetime)); + } + + @Test + public void testBracketedEquivalent() throws IOException { + compareBrackets("timestamp", "timestamp", "2020-09-16 17:30:00"); + compareBrackets("timestamp", "ts", "2020-09-16 17:30:00"); + compareBrackets("timestamp", "timestamp", "2020-09-16 17:30:00.123"); + compareBrackets("timestamp", "ts", "2020-09-16 17:30:00.123"); + compareBrackets("date", "date", "2020-09-16"); + compareBrackets("date", "d", "2020-09-16"); + compareBrackets("time", "time", "17:30:00"); + compareBrackets("time", "t", "17:30:00"); + } + + @Test + public void testBracketFails() { + assertThrows(ResponseException.class, ()->executeQuery("select {time '2020-09-16'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {t '2020-09-16'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {date '17:30:00'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {d '17:30:00'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {timestamp '2020-09-16'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {ts '2020-09-16'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {timestamp '17:30:00'}")); + assertThrows(ResponseException.class, ()->executeQuery("select {ts '17:30:00'}")); + } } diff --git a/sql/src/main/antlr/OpenSearchSQLParser.g4 b/sql/src/main/antlr/OpenSearchSQLParser.g4 index e68edbbc58..2c3defb9f1 100644 --- a/sql/src/main/antlr/OpenSearchSQLParser.g4 +++ b/sql/src/main/antlr/OpenSearchSQLParser.g4 @@ -185,7 +185,6 @@ constant // Doesn't support the following types for now //| BIT_STRING //| NOT? nullLiteral=(NULL_LITERAL | NULL_SPEC_LITERAL) - //| LEFT_BRACE dateType=(D | T | TS | DATE | TIME | TIMESTAMP) stringLiteral RIGHT_BRACE ; decimalLiteral @@ -227,14 +226,17 @@ datetimeLiteral dateLiteral : DATE date=stringLiteral + | LEFT_BRACE (DATE | D) date=stringLiteral RIGHT_BRACE ; timeLiteral : TIME time=stringLiteral + | LEFT_BRACE (TIME | T) time=stringLiteral RIGHT_BRACE ; timestampLiteral : TIMESTAMP timestamp=stringLiteral + | LEFT_BRACE (TIMESTAMP | TS) timestamp=stringLiteral RIGHT_BRACE ; // Actually, these constants are shortcuts to the corresponding functions diff --git a/sql/src/test/java/org/opensearch/sql/common/antlr/SyntaxParserTestBase.java b/sql/src/test/java/org/opensearch/sql/common/antlr/SyntaxParserTestBase.java index 526dc4e816..63d7666c62 100644 --- a/sql/src/test/java/org/opensearch/sql/common/antlr/SyntaxParserTestBase.java +++ b/sql/src/test/java/org/opensearch/sql/common/antlr/SyntaxParserTestBase.java @@ -1,13 +1,11 @@ package org.opensearch.sql.common.antlr; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.opensearch.sql.sql.antlr.SQLSyntaxParser; /** * A base class for tests for SQL or PPL parser. diff --git a/sql/src/test/java/org/opensearch/sql/sql/antlr/BracketedTimestampTest.java b/sql/src/test/java/org/opensearch/sql/sql/antlr/BracketedTimestampTest.java new file mode 100644 index 0000000000..0f7a284aa7 --- /dev/null +++ b/sql/src/test/java/org/opensearch/sql/sql/antlr/BracketedTimestampTest.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + +package org.opensearch.sql.sql.antlr; + +import org.junit.jupiter.api.Test; + +public class BracketedTimestampTest extends SQLParserTest { + @Test + void date_shortened_test() { + acceptQuery("SELECT {d '2001-05-07'}"); + } + + @Test + void date_test() { + acceptQuery("SELECT {date '2001-05-07'}"); + } + + @Test + void time_shortened_test() { + acceptQuery("SELECT {t '10:11:12'}"); + } + + @Test + void time_test() { + acceptQuery("SELECT {time '10:11:12'}"); + } + + @Test + void timestamp_shortened_test() { + acceptQuery("SELECT {ts '2001-05-07 10:11:12'}"); + } + + @Test + void timestamp_test() { + acceptQuery("SELECT {timestamp '2001-05-07 10:11:12'}"); + } +} diff --git a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLParserTest.java b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLParserTest.java index 7b8b415ee7..3f323725ab 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLParserTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLParserTest.java @@ -1,3 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + package org.opensearch.sql.sql.antlr; import org.opensearch.sql.common.antlr.SyntaxParserTestBase;