agavra commented on code in PR #9765:
URL: https://github.com/apache/pinot/pull/9765#discussion_r1018490355


##########
pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/QueryRunnerTestBase.java:
##########
@@ -198,11 +242,27 @@ private static List<String> 
toH2FieldNamesAndTypes(org.apache.pinot.spi.data.Sch
         case STRING:
           fieldType = "varchar(128)";
           break;
+        case DOUBLE:
+          fieldType = "double";
+          break;
         default:
           throw new UnsupportedOperationException("Unsupported type conversion 
to h2 type: " + dataType);
       }
       fieldNamesAndTypes.add(fieldName + " " + fieldType);
     }
     return fieldNamesAndTypes;
   }
+
+  public static class TestCase {

Review Comment:
   {suggestion - feel free to disagree}
   
   let's create a module named `model` and put all JSON classes in there. Also 
let's not use maps and instead use additional models. For example:
   ```
   class Tables {
     @JsonProperty
     Map<String, TableDefinition> _tables;
   }
   
   class TableDefinition {
     @JsonProperty
     List<ColumnAndType> _columns;
   }
   
   class ColumnAndType {
     @JsonProperty
     String name;
     @JsonProperty
     String type;
   }
   ```
   
   This is a bit of a pain up front but will make maintaining the test 
framework much easier. One example of where this would be cleaner is that we 
could have a rewriter utility that rewrites all table names to be properly 
prefixed.



##########
pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/QueryRunnerTestBase.java:
##########
@@ -70,6 +60,128 @@ public class QueryRunnerTestBase extends QueryTestSet {
   protected Map<ServerInstance, QueryServerEnclosure> _servers = new 
HashMap<>();
   protected GrpcMailboxService _mailboxService;
 
+  // --------------------------------------------------------------------------
+  // QUERY UTILS
+  // --------------------------------------------------------------------------
+  protected List<Object[]> queryRunner(String sql) {
+    QueryPlan queryPlan = _queryEnvironment.planQuery(sql);
+    Map<String, String> requestMetadataMap =
+        ImmutableMap.of("REQUEST_ID", 
String.valueOf(RANDOM_REQUEST_ID_GEN.nextLong()));
+    MailboxReceiveOperator mailboxReceiveOperator = null;
+    for (int stageId : queryPlan.getStageMetadataMap().keySet()) {
+      if (queryPlan.getQueryStageMap().get(stageId) instanceof 
MailboxReceiveNode) {
+        MailboxReceiveNode reduceNode = (MailboxReceiveNode) 
queryPlan.getQueryStageMap().get(stageId);
+        mailboxReceiveOperator = 
QueryDispatcher.createReduceStageOperator(_mailboxService,
+            
queryPlan.getStageMetadataMap().get(reduceNode.getSenderStageId()).getServerInstances(),
+            Long.parseLong(requestMetadataMap.get("REQUEST_ID")), 
reduceNode.getSenderStageId(),
+            reduceNode.getDataSchema(), "localhost", _reducerGrpcPort);
+      } else {
+        for (ServerInstance serverInstance : 
queryPlan.getStageMetadataMap().get(stageId).getServerInstances()) {
+          DistributedStagePlan distributedStagePlan =
+              QueryDispatcher.constructDistributedStagePlan(queryPlan, 
stageId, serverInstance);
+          _servers.get(serverInstance).processQuery(distributedStagePlan, 
requestMetadataMap);
+        }
+      }
+    }
+    Preconditions.checkNotNull(mailboxReceiveOperator);
+    return 
QueryDispatcher.toResultTable(QueryDispatcher.reduceMailboxReceive(mailboxReceiveOperator),
+        queryPlan.getQueryResultFields(), 
queryPlan.getQueryStageMap().get(0).getDataSchema()).getRows();
+  }
+
+  protected List<Object[]> queryH2(String sql)
+      throws Exception {
+    Statement h2statement = 
_h2Connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
ResultSet.CONCUR_READ_ONLY);
+    h2statement.execute(sql);
+    ResultSet h2ResultSet = h2statement.getResultSet();
+    int columnCount = h2ResultSet.getMetaData().getColumnCount();
+    List<Object[]> result = new ArrayList<>();
+    while (h2ResultSet.next()) {
+      Object[] row = new Object[columnCount];
+      for (int i = 0; i < columnCount; i++) {
+        row[i] = h2ResultSet.getObject(i + 1);
+      }
+      result.add(row);
+    }
+    return result;
+  }
+
+  protected void compareRowEquals(List<Object[]> resultRows, List<Object[]> 
expectedRows) {
+    Assert.assertEquals(resultRows.size(), expectedRows.size());
+
+    Comparator<Object> valueComp = (l, r) -> {
+      if (l == null && r == null) {
+        return 0;
+      } else if (l == null) {
+        return -1;
+      } else if (r == null) {
+        return 1;
+      }
+      if (l instanceof Integer) {
+        return Integer.compare((Integer) l, ((Number) r).intValue());
+      } else if (l instanceof Long) {
+        return Long.compare((Long) l, ((Number) r).longValue());
+      } else if (l instanceof Float) {
+        return Float.compare((Float) l, ((Number) r).floatValue());
+      } else if (l instanceof Double) {
+        return Double.compare((Double) l, ((Number) r).doubleValue());
+      } else if (l instanceof String) {
+        return ((String) l).compareTo((String) r);
+      } else if (l instanceof Boolean) {
+        return ((Boolean) l).compareTo((Boolean) r);
+      } else {
+        throw new RuntimeException("non supported type " + l.getClass());
+      }
+    };
+    Comparator<Object[]> rowComp = (l, r) -> {
+      int cmp = 0;
+      for (int i = 0; i < l.length; i++) {
+        cmp = valueComp.compare(l[i], r[i]);
+        if (cmp != 0) {
+          return cmp;
+        }
+      }
+      return 0;
+    };
+    resultRows.sort(rowComp);
+    expectedRows.sort(rowComp);
+    for (int i = 0; i < resultRows.size(); i++) {
+      Object[] resultRow = resultRows.get(i);
+      Object[] expectedRow = expectedRows.get(i);
+      for (int j = 0; j < resultRow.length; j++) {
+        Assert.assertEquals(valueComp.compare(resultRow[j], expectedRow[j]), 0,
+            "Not match at (" + i + "," + j + ")! Expected: " + expectedRow[j] 
+ " Actual: " + resultRow[j]);
+      }
+    }
+  }
+
+  // --------------------------------------------------------------------------
+  // TEST CASES PREP
+  // --------------------------------------------------------------------------
+  protected Schema constructSchema(String schemaName, Map<String, String> 
dataTypes) {
+    Schema.SchemaBuilder builder = new Schema.SchemaBuilder();
+    for (Map.Entry<String, String> dataType : dataTypes.entrySet()) {
+      builder.addSingleValueDimension(dataType.getKey(), 
FieldSpec.DataType.valueOf(dataType.getValue()));
+    }
+    // ts is built in
+    builder.addDateTime("ts", FieldSpec.DataType.LONG, "1:MILLISECONDS:EPOCH", 
"1:HOURS");
+    builder.setSchemaName(schemaName);
+    return builder.build();
+  }
+
+  protected List<GenericRow> toRow(List<List<Object>> value) {
+    List<GenericRow> result = new ArrayList<>(value.size());
+    for (int rowId = 0; rowId < value.size(); rowId++) {
+      GenericRow row = new GenericRow();
+      List<Object> rawRow = value.get(rowId);
+      for (int colId = 0; colId < rawRow.size(); colId++) {
+        row.putValue("col" + colId, rawRow.get(colId));
+      }
+      row.putValue("ts", System.currentTimeMillis());

Review Comment:
   if `ts` is in the row we should use that instead (i.e. make sure we can 
specify it in the json file)



##########
pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryTestExecutor.java:
##########
@@ -0,0 +1,171 @@
+/**
+ * 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.query.runtime.queries;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.pinot.common.datatable.DataTableFactory;
+import org.apache.pinot.core.common.datatable.DataTableBuilderFactory;
+import org.apache.pinot.query.QueryEnvironmentTestBase;
+import org.apache.pinot.query.QueryServerEnclosure;
+import org.apache.pinot.query.mailbox.GrpcMailboxService;
+import org.apache.pinot.query.routing.WorkerInstance;
+import org.apache.pinot.query.runtime.QueryRunnerTestBase;
+import org.apache.pinot.query.service.QueryConfig;
+import org.apache.pinot.query.testutils.MockInstanceDataManagerFactory;
+import org.apache.pinot.query.testutils.QueryTestUtils;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.env.PinotConfiguration;
+import org.apache.pinot.spi.utils.builder.TableNameBuilder;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+public class QueryTestExecutor extends QueryRunnerTestBase {
+  private static final ObjectMapper MAPPER = new ObjectMapper();
+  private static final Pattern TABLE_NAME_REPLACE_PATTERN = 
Pattern.compile("\\{([\\w\\d]+)\\}");
+  private static final String QUERY_TEST_RESOURCE_FOLDER = "queries";
+  private static final List<String> QUERY_TEST_RESOURCE_FILES = 
ImmutableList.of(
+      "BasicQuery.json"
+  );

Review Comment:
   we should just load all test resources dynamically - I can do that in a 
follow up, we don't need to block this PR on it



##########
pinot-query-runtime/src/test/java/org/apache/pinot/query/runtime/queries/QueryTestExecutor.java:
##########
@@ -0,0 +1,171 @@
+/**
+ * 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.query.runtime.queries;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.pinot.common.datatable.DataTableFactory;
+import org.apache.pinot.core.common.datatable.DataTableBuilderFactory;
+import org.apache.pinot.query.QueryEnvironmentTestBase;
+import org.apache.pinot.query.QueryServerEnclosure;
+import org.apache.pinot.query.mailbox.GrpcMailboxService;
+import org.apache.pinot.query.routing.WorkerInstance;
+import org.apache.pinot.query.runtime.QueryRunnerTestBase;
+import org.apache.pinot.query.service.QueryConfig;
+import org.apache.pinot.query.testutils.MockInstanceDataManagerFactory;
+import org.apache.pinot.query.testutils.QueryTestUtils;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.env.PinotConfiguration;
+import org.apache.pinot.spi.utils.builder.TableNameBuilder;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+public class QueryTestExecutor extends QueryRunnerTestBase {
+  private static final ObjectMapper MAPPER = new ObjectMapper();
+  private static final Pattern TABLE_NAME_REPLACE_PATTERN = 
Pattern.compile("\\{([\\w\\d]+)\\}");
+  private static final String QUERY_TEST_RESOURCE_FOLDER = "queries";
+  private static final List<String> QUERY_TEST_RESOURCE_FILES = 
ImmutableList.of(
+      "BasicQuery.json"
+  );
+
+  @BeforeClass
+  public void setUp()
+      throws Exception {
+    DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4);
+    // Setting up mock server factories.
+    MockInstanceDataManagerFactory factory1 = new 
MockInstanceDataManagerFactory("server1");
+    MockInstanceDataManagerFactory factory2 = new 
MockInstanceDataManagerFactory("server2");
+    // Setting up H2 for validation
+    setH2Connection();
+
+    // Scan through all the test cases.
+    for (Map.Entry<String, TestCase> testCaseEntry : 
getTestCases().entrySet()) {

Review Comment:
   why do this in `@BeforeClass` instead of on every test? (e.g. set up the H2 
connection once, but actually register the tables on each test)



##########
pinot-query-runtime/src/test/resources/queries/BasicQuery.json:
##########
@@ -0,0 +1,39 @@
+
+{
+  "basic_test": {
+    "description": "basic test case example",
+    "sql": "SELECT * FROM {tbl}",
+    "tables": {
+      "tbl": { "col1": "STRING", "col2": "INT" }

Review Comment:
   since order matters in the `inputs` part of the model, we should model this 
part as an array of tuples (as suggested in comment above) instead of a map 
since JSON can reorder maps if it wants:
   ```
   "tbl": [{"name":"col1", "type":"STRING}, {"name":"col2", "type":"INT"}]
   ```
   
   Let's commit this first, I think I might be able to write a simple utility 
that allows us to declare tables using SQL syntax and then turn it into what we 
want:
   ```
   "tbl": "col1 STRING, col2 INT"
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to