Copilot commented on code in PR #17357:
URL: https://github.com/apache/pinot/pull/17357#discussion_r2616842180


##########
pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java:
##########
@@ -121,11 +143,207 @@ public BrokerResponse 
executeDMLStatement(SqlNodeAndOptions sqlNodeAndOptions,
     return result;
   }
 
+  /**
+   * Execute metadata (SHOW/USE) statements.
+   */
+  public BrokerResponse executeMetadataStatement(SqlNodeAndOptions 
sqlNodeAndOptions,
+      @Nullable Map<String, String> headers) {
+    BrokerResponseNative result = new BrokerResponseNative();
+    try {
+      ResultTable resultTable =
+          buildMetadataResult(sqlNodeAndOptions.getSqlNode(), 
sqlNodeAndOptions.getOptions(), headers);
+      result.setResultTable(resultTable);
+    } catch (Exception e) {
+      result.addException(new 
QueryProcessingException(QueryErrorCode.QUERY_EXECUTION, e.getMessage()));
+    }
+    return result;
+  }
+
   private MinionClient getMinionClient() {
     // NOTE: using null auth provider here as auth headers injected by caller 
in "executeDMLStatement()"
     if (_helixManager != null) {
       return new MinionClient(getControllerBaseUrl(_helixManager), null);
     }
     return new MinionClient(_controllerUrl, null);
   }
+
+  private ResultTable buildMetadataResult(SqlNode sqlNode, Map<String, String> 
options,
+      @Nullable Map<String, String> headers)
+      throws Exception {
+    if (sqlNode instanceof SqlShowDatabases) {
+      return toSingleStringColumnResult("databaseName", 
fetchDatabases(headers));
+    }
+    if (sqlNode instanceof SqlShowTables) {
+      SqlShowTables showTables = (SqlShowTables) sqlNode;
+      String database = resolveDatabase(showTables.getDatabaseName(), options, 
headers);
+      List<String> tables = stripDatabasePrefix(fetchTables(database, 
headers), database);
+      return toSingleStringColumnResult("tableName", tables);
+    }
+    if (sqlNode instanceof SqlShowSchemas) {
+      SqlShowSchemas showSchemas = (SqlShowSchemas) sqlNode;
+      String database = resolveDatabase(null, options, headers);
+      List<String> schemas = stripDatabasePrefix(fetchSchemas(database, 
headers), database);
+      String likePattern = getLikePattern(showSchemas.getLikePattern());
+      schemas = applyLikeFilter(schemas, likePattern);
+      return toSingleStringColumnResult("schemaName", schemas);
+    }
+    throw new UnsupportedOperationException("Unsupported METADATA SqlKind - " 
+ sqlNode.getKind());
+  }
+
+  private ResultTable toSingleStringColumnResult(String columnName, 
List<String> values) {
+    DataSchema dataSchema = new DataSchema(new String[]{columnName}, new 
ColumnDataType[]{ColumnDataType.STRING});
+    List<Object[]> rows = values.stream().map(value -> new 
Object[]{value}).collect(Collectors.toList());
+    return new ResultTable(dataSchema, rows);
+  }
+
+  private String resolveDatabase(@Nullable SqlIdentifier explicitDatabase, 
Map<String, String> options,
+      @Nullable Map<String, String> headers)
+      throws DatabaseConflictException {
+    String databaseFromSql = explicitDatabase != null ? 
explicitDatabase.toString() : null;
+    String databaseFromOptions = options.get(CommonConstants.DATABASE);
+    String databaseFromHeaders = getHeaderValue(headers, 
CommonConstants.DATABASE);
+
+    if (databaseFromSql != null) {
+      if (databaseFromOptions != null && 
!databaseFromSql.equalsIgnoreCase(databaseFromOptions)) {
+        throw new DatabaseConflictException(
+            "Database name '" + databaseFromSql + "' from statement does not 
match database name '"
+                + databaseFromOptions + "' from query options and database 
name '" + databaseFromHeaders
+                + "' from request header");
+      }
+      if (databaseFromHeaders != null && 
!databaseFromSql.equalsIgnoreCase(databaseFromHeaders)) {
+        throw new DatabaseConflictException(
+            "Database name '" + databaseFromSql + "' from statement does not 
match database name '"
+                + databaseFromHeaders + "' from request header and database 
name '" + databaseFromOptions
+                + "' from query options");
+      }
+      return databaseFromSql;
+    }
+
+    if (databaseFromOptions != null && databaseFromHeaders != null
+        && !databaseFromOptions.equalsIgnoreCase(databaseFromHeaders)) {
+      throw new DatabaseConflictException("Database name '" + 
databaseFromHeaders + "' from request header does not "
+          + "match database name '" + databaseFromOptions + "' from query 
options");
+    }
+
+    return databaseFromOptions != null ? databaseFromOptions
+        : databaseFromHeaders != null ? databaseFromHeaders : 
CommonConstants.DEFAULT_DATABASE;
+  }
+
+  private List<String> stripDatabasePrefix(List<String> names, String 
database) {
+    if (names.isEmpty()) {
+      return names;
+    }
+    return names.stream()
+        .map(name -> DatabaseUtils.isPartOfDatabase(name, database)
+            ? DatabaseUtils.removeDatabasePrefix(name, database)
+            : name)
+        .collect(Collectors.toList());
+  }
+
+  private List<String> applyLikeFilter(List<String> values, @Nullable String 
likePattern) {
+    if (StringUtils.isEmpty(likePattern)) {
+      return values;
+    }
+    Pattern pattern = buildLikeRegex(likePattern);
+    return values.stream().filter(value -> 
pattern.matcher(value).matches()).collect(Collectors.toList());
+  }
+
+  private Pattern buildLikeRegex(String likePattern) {
+    StringBuilder regex = new StringBuilder();
+    boolean escaped = false;
+    for (char c : likePattern.toCharArray()) {
+      if (escaped) {
+        regex.append(Pattern.quote(String.valueOf(c)));
+        escaped = false;
+      } else if (c == '\\') {
+        escaped = true;
+      } else if (c == '%') {
+        regex.append(".*");
+      } else if (c == '_') {
+        regex.append('.');
+      } else {
+        regex.append(Pattern.quote(String.valueOf(c)));
+      }
+    }
+    return Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE);
+  }
+
+  private String getLikePattern(@Nullable SqlLiteral likeLiteral) {
+    return likeLiteral == null ? null : likeLiteral.toValue();
+  }
+
+  private List<String> fetchDatabases(@Nullable Map<String, String> headers)
+      throws IOException {
+    String response = sendGetRequest("/databases", headers);
+    JsonNode jsonNode = JsonUtils.stringToJsonNode(response);
+    List<String> databases = new ArrayList<>();
+    jsonNode.forEach(node -> databases.add(node.asText()));
+    return databases;
+  }
+
+  private List<String> fetchTables(String database, @Nullable Map<String, 
String> headers)
+      throws IOException {
+    Map<String, String> requestHeaders = new HashMap<>();
+    if (headers != null) {
+      requestHeaders.putAll(headers);
+    }
+    requestHeaders.put(CommonConstants.DATABASE, database);
+    String response = sendGetRequest("/tables", requestHeaders);
+    JsonNode jsonNode = JsonUtils.stringToJsonNode(response);
+    JsonNode tablesNode = jsonNode.has("tables") ? jsonNode.get("tables") : 
jsonNode;
+    List<String> tables = new ArrayList<>();
+    tablesNode.forEach(node -> tables.add(node.asText()));
+    return tables;
+  }
+
+  private List<String> fetchSchemas(String database, @Nullable Map<String, 
String> headers)
+      throws IOException {
+    Map<String, String> requestHeaders = new HashMap<>();
+    if (headers != null) {
+      requestHeaders.putAll(headers);
+    }
+    requestHeaders.put(CommonConstants.DATABASE, database);
+    String response = sendGetRequest("/schemas", requestHeaders);
+    JsonNode jsonNode = JsonUtils.stringToJsonNode(response);
+    List<String> schemas = new ArrayList<>();
+    jsonNode.forEach(node -> schemas.add(node.asText()));
+    return schemas;
+  }
+
+  private String sendGetRequest(String path, @Nullable Map<String, String> 
headers)
+      throws IOException {
+    String baseUrl = _helixManager != null ? 
getControllerBaseUrl(_helixManager) : _controllerUrl;
+    if (baseUrl == null) {
+      throw new IOException("Controller URL is not configured for metadata 
query");
+    }
+    String urlString = baseUrl.endsWith("/") ? baseUrl.substring(0, 
baseUrl.length() - 1) + path : baseUrl + path;

Review Comment:
   The URL construction logic could be simplified using a utility method or 
standard URL building approach. Consider extracting this to a helper method 
like `buildUrl(baseUrl, path)` to improve readability and reusability.



##########
pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java:
##########
@@ -121,11 +143,207 @@ public BrokerResponse 
executeDMLStatement(SqlNodeAndOptions sqlNodeAndOptions,
     return result;
   }
 
+  /**
+   * Execute metadata (SHOW/USE) statements.
+   */
+  public BrokerResponse executeMetadataStatement(SqlNodeAndOptions 
sqlNodeAndOptions,
+      @Nullable Map<String, String> headers) {
+    BrokerResponseNative result = new BrokerResponseNative();
+    try {
+      ResultTable resultTable =
+          buildMetadataResult(sqlNodeAndOptions.getSqlNode(), 
sqlNodeAndOptions.getOptions(), headers);
+      result.setResultTable(resultTable);
+    } catch (Exception e) {
+      result.addException(new 
QueryProcessingException(QueryErrorCode.QUERY_EXECUTION, e.getMessage()));
+    }
+    return result;
+  }
+
   private MinionClient getMinionClient() {
     // NOTE: using null auth provider here as auth headers injected by caller 
in "executeDMLStatement()"
     if (_helixManager != null) {
       return new MinionClient(getControllerBaseUrl(_helixManager), null);
     }
     return new MinionClient(_controllerUrl, null);
   }
+
+  private ResultTable buildMetadataResult(SqlNode sqlNode, Map<String, String> 
options,
+      @Nullable Map<String, String> headers)
+      throws Exception {
+    if (sqlNode instanceof SqlShowDatabases) {
+      return toSingleStringColumnResult("databaseName", 
fetchDatabases(headers));
+    }
+    if (sqlNode instanceof SqlShowTables) {
+      SqlShowTables showTables = (SqlShowTables) sqlNode;
+      String database = resolveDatabase(showTables.getDatabaseName(), options, 
headers);
+      List<String> tables = stripDatabasePrefix(fetchTables(database, 
headers), database);
+      return toSingleStringColumnResult("tableName", tables);
+    }
+    if (sqlNode instanceof SqlShowSchemas) {
+      SqlShowSchemas showSchemas = (SqlShowSchemas) sqlNode;
+      String database = resolveDatabase(null, options, headers);
+      List<String> schemas = stripDatabasePrefix(fetchSchemas(database, 
headers), database);
+      String likePattern = getLikePattern(showSchemas.getLikePattern());
+      schemas = applyLikeFilter(schemas, likePattern);
+      return toSingleStringColumnResult("schemaName", schemas);
+    }
+    throw new UnsupportedOperationException("Unsupported METADATA SqlKind - " 
+ sqlNode.getKind());
+  }
+
+  private ResultTable toSingleStringColumnResult(String columnName, 
List<String> values) {
+    DataSchema dataSchema = new DataSchema(new String[]{columnName}, new 
ColumnDataType[]{ColumnDataType.STRING});
+    List<Object[]> rows = values.stream().map(value -> new 
Object[]{value}).collect(Collectors.toList());
+    return new ResultTable(dataSchema, rows);
+  }
+
+  private String resolveDatabase(@Nullable SqlIdentifier explicitDatabase, 
Map<String, String> options,
+      @Nullable Map<String, String> headers)
+      throws DatabaseConflictException {
+    String databaseFromSql = explicitDatabase != null ? 
explicitDatabase.toString() : null;
+    String databaseFromOptions = options.get(CommonConstants.DATABASE);
+    String databaseFromHeaders = getHeaderValue(headers, 
CommonConstants.DATABASE);
+
+    if (databaseFromSql != null) {
+      if (databaseFromOptions != null && 
!databaseFromSql.equalsIgnoreCase(databaseFromOptions)) {
+        throw new DatabaseConflictException(
+            "Database name '" + databaseFromSql + "' from statement does not 
match database name '"
+                + databaseFromOptions + "' from query options and database 
name '" + databaseFromHeaders
+                + "' from request header");
+      }
+      if (databaseFromHeaders != null && 
!databaseFromSql.equalsIgnoreCase(databaseFromHeaders)) {
+        throw new DatabaseConflictException(
+            "Database name '" + databaseFromSql + "' from statement does not 
match database name '"
+                + databaseFromHeaders + "' from request header and database 
name '" + databaseFromOptions
+                + "' from query options");

Review Comment:
   Similar to the previous error message, the sources mentioned in the error 
message ('from request header and database name from query options') are in 
reversed order compared to the comparison logic (checking against headers 
first, then mentioning options). This inconsistency makes the error message 
potentially confusing.
   ```suggestion
                   + databaseFromOptions + "' from query options and database 
name '" + databaseFromHeaders
                   + "' from request header");
   ```



##########
pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java:
##########
@@ -121,11 +143,207 @@ public BrokerResponse 
executeDMLStatement(SqlNodeAndOptions sqlNodeAndOptions,
     return result;
   }
 
+  /**
+   * Execute metadata (SHOW/USE) statements.
+   */
+  public BrokerResponse executeMetadataStatement(SqlNodeAndOptions 
sqlNodeAndOptions,
+      @Nullable Map<String, String> headers) {
+    BrokerResponseNative result = new BrokerResponseNative();
+    try {
+      ResultTable resultTable =
+          buildMetadataResult(sqlNodeAndOptions.getSqlNode(), 
sqlNodeAndOptions.getOptions(), headers);
+      result.setResultTable(resultTable);
+    } catch (Exception e) {
+      result.addException(new 
QueryProcessingException(QueryErrorCode.QUERY_EXECUTION, e.getMessage()));
+    }
+    return result;
+  }
+
   private MinionClient getMinionClient() {
     // NOTE: using null auth provider here as auth headers injected by caller 
in "executeDMLStatement()"
     if (_helixManager != null) {
       return new MinionClient(getControllerBaseUrl(_helixManager), null);
     }
     return new MinionClient(_controllerUrl, null);
   }
+
+  private ResultTable buildMetadataResult(SqlNode sqlNode, Map<String, String> 
options,
+      @Nullable Map<String, String> headers)
+      throws Exception {
+    if (sqlNode instanceof SqlShowDatabases) {
+      return toSingleStringColumnResult("databaseName", 
fetchDatabases(headers));
+    }
+    if (sqlNode instanceof SqlShowTables) {
+      SqlShowTables showTables = (SqlShowTables) sqlNode;
+      String database = resolveDatabase(showTables.getDatabaseName(), options, 
headers);
+      List<String> tables = stripDatabasePrefix(fetchTables(database, 
headers), database);
+      return toSingleStringColumnResult("tableName", tables);
+    }
+    if (sqlNode instanceof SqlShowSchemas) {
+      SqlShowSchemas showSchemas = (SqlShowSchemas) sqlNode;
+      String database = resolveDatabase(null, options, headers);
+      List<String> schemas = stripDatabasePrefix(fetchSchemas(database, 
headers), database);
+      String likePattern = getLikePattern(showSchemas.getLikePattern());
+      schemas = applyLikeFilter(schemas, likePattern);
+      return toSingleStringColumnResult("schemaName", schemas);
+    }
+    throw new UnsupportedOperationException("Unsupported METADATA SqlKind - " 
+ sqlNode.getKind());
+  }
+
+  private ResultTable toSingleStringColumnResult(String columnName, 
List<String> values) {
+    DataSchema dataSchema = new DataSchema(new String[]{columnName}, new 
ColumnDataType[]{ColumnDataType.STRING});
+    List<Object[]> rows = values.stream().map(value -> new 
Object[]{value}).collect(Collectors.toList());
+    return new ResultTable(dataSchema, rows);
+  }
+
+  private String resolveDatabase(@Nullable SqlIdentifier explicitDatabase, 
Map<String, String> options,
+      @Nullable Map<String, String> headers)
+      throws DatabaseConflictException {
+    String databaseFromSql = explicitDatabase != null ? 
explicitDatabase.toString() : null;
+    String databaseFromOptions = options.get(CommonConstants.DATABASE);
+    String databaseFromHeaders = getHeaderValue(headers, 
CommonConstants.DATABASE);
+
+    if (databaseFromSql != null) {
+      if (databaseFromOptions != null && 
!databaseFromSql.equalsIgnoreCase(databaseFromOptions)) {
+        throw new DatabaseConflictException(
+            "Database name '" + databaseFromSql + "' from statement does not 
match database name '"
+                + databaseFromOptions + "' from query options and database 
name '" + databaseFromHeaders
+                + "' from request header");

Review Comment:
   The error message mentions 'database name from request header' but 
`databaseFromOptions` is referenced before `databaseFromHeaders` in the 
concatenation. The order in the message should match the comparison logic or be 
clarified to avoid confusion about which sources are being compared.
   ```suggestion
                   + databaseFromHeaders + "' from request header and database 
name '" + databaseFromOptions
                   + "' from query options");
   ```



##########
pinot-common/src/main/codegen/config.fmpp:
##########
@@ -44,6 +44,9 @@ data: {
     keywords: [
       "FILE"
       "ARCHIVE"
+      "DATABASES"
+      "TABLES"
+      "SCHEMAS"

Review Comment:
   The keywords "DATABASES", "TABLES", and "SCHEMAS" are defined twice in this 
configuration file (lines 47-49 and lines 76-78). This duplication could lead 
to maintenance issues if these keywords need to be updated in the future.



-- 
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