This is an automated email from the ASF dual-hosted git repository.
gortiz 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 2ac1db8d01 Fix error message on database context mismatch in v1 query
(#12699)
2ac1db8d01 is described below
commit 2ac1db8d01cafdfd869d3dfb9f18c5277c2ad45f
Author: Shounak kulkarni <[email protected]>
AuthorDate: Mon Mar 25 13:14:55 2024 +0500
Fix error message on database context mismatch in v1 query (#12699)
* Fix error message on database context mismatch in v1 query
* Handle database context conflicts gracefully
* test fix
* test fix
---
.../requesthandler/BaseBrokerRequestHandler.java | 14 ++++--
.../MultiStageBrokerRequestHandler.java | 6 +++
.../apache/pinot/common/utils/DatabaseUtils.java | 50 ++++++++++++++++------
.../pinot/common/utils/DatabaseUtilsTest.java | 3 +-
.../api/resources/PinotQueryResource.java | 9 ++--
.../tests/MultiStageEngineIntegrationTest.java | 14 +++---
.../spi/exception/DatabaseConflictException.java | 25 +++++++++++
7 files changed, 94 insertions(+), 27 deletions(-)
diff --git
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java
index fa30f3c5bf..31bae2215a 100644
---
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java
+++
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java
@@ -95,6 +95,7 @@ import org.apache.pinot.spi.env.PinotConfiguration;
import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListener;
import
org.apache.pinot.spi.eventlistener.query.PinotBrokerQueryEventListenerFactory;
import org.apache.pinot.spi.exception.BadQueryRequestException;
+import org.apache.pinot.spi.exception.DatabaseConflictException;
import org.apache.pinot.spi.trace.RequestContext;
import org.apache.pinot.spi.trace.Tracing;
import org.apache.pinot.spi.utils.BigDecimalUtils;
@@ -375,9 +376,16 @@ public abstract class BaseBrokerRequestHandler implements
BrokerRequestHandler {
}
boolean ignoreCase = _tableCache.isIgnoreCase();
- String tableName =
-
getActualTableName(DatabaseUtils.translateTableName(dataSource.getTableName(),
httpHeaders, ignoreCase),
- _tableCache);
+ String tableName;
+ try {
+ tableName = getActualTableName(
+ DatabaseUtils.translateTableName(dataSource.getTableName(),
httpHeaders, ignoreCase), _tableCache);
+ } catch (DatabaseConflictException e) {
+ LOGGER.info("{}. Request {}: {}", e.getMessage(), requestId, query);
+
_brokerMetrics.addMeteredGlobalValue(BrokerMeter.QUERY_VALIDATION_EXCEPTIONS,
1);
+
requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE);
+ return new
BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR,
e));
+ }
dataSource.setTableName(tableName);
String rawTableName = TableNameBuilder.extractRawTableName(tableName);
requestContext.setTableName(rawTableName);
diff --git
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
index 7f856b6a54..ee9f6cf19a 100644
---
a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
+++
b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java
@@ -71,6 +71,7 @@ import org.apache.pinot.query.type.TypeFactory;
import org.apache.pinot.query.type.TypeSystem;
import org.apache.pinot.spi.env.PinotConfiguration;
import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListener;
+import org.apache.pinot.spi.exception.DatabaseConflictException;
import org.apache.pinot.spi.trace.RequestContext;
import org.apache.pinot.spi.utils.CommonConstants;
import org.apache.pinot.spi.utils.builder.TableNameBuilder;
@@ -146,6 +147,11 @@ public class MultiStageBrokerRequestHandler extends
BaseBrokerRequestHandler {
queryPlanResult = queryEnvironment.planQuery(query,
sqlNodeAndOptions, requestId);
break;
}
+ } catch (DatabaseConflictException e) {
+ LOGGER.info("{}. Request {}: {}", e.getMessage(), requestId, query);
+
_brokerMetrics.addMeteredGlobalValue(BrokerMeter.QUERY_VALIDATION_EXCEPTIONS,
1);
+ requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE);
+ return new
BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR,
e));
} catch (WebApplicationException e) {
throw e;
} catch (RuntimeException e) {
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/utils/DatabaseUtils.java
b/pinot-common/src/main/java/org/apache/pinot/common/utils/DatabaseUtils.java
index 6913c409a6..28c9235cd9 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/utils/DatabaseUtils.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/utils/DatabaseUtils.java
@@ -24,6 +24,7 @@ import java.util.Objects;
import javax.annotation.Nullable;
import javax.ws.rs.core.HttpHeaders;
import org.apache.commons.lang3.StringUtils;
+import org.apache.pinot.spi.exception.DatabaseConflictException;
import org.apache.pinot.spi.utils.CommonConstants;
@@ -37,8 +38,10 @@ public class DatabaseUtils {
* @param databaseName database name
* @param ignoreCase whether to ignore case when comparing passed in
database name against table name prefix if both
* exist. For 'default' database, always compare it
ignoring case.
- * @return translated table name. Throws {@link IllegalArgumentException} if
{@code tableName} contains
- * more than 1 dot or if {@code tableName} has database prefix, and it does
not match with {@code databaseName}
+ * @return translated table name.
+ * <br>Throws {@link IllegalArgumentException} if {@code tableName} contains
more than 1 dot
+ * <br>Throws {@link DatabaseConflictException} if {@code tableName} has
database prefix,
+ * and it does not match with {@code databaseName}
*/
public static String translateTableName(String tableName, @Nullable String
databaseName, boolean ignoreCase) {
Preconditions.checkArgument(StringUtils.isNotEmpty(tableName),
"'tableName' cannot be null or empty");
@@ -53,11 +56,11 @@ public class DatabaseUtils {
case 2:
Preconditions.checkArgument(!tableSplit[1].isEmpty(), "Invalid table
name '%s'", tableName);
String databasePrefix = tableSplit[0];
- Preconditions.checkArgument(
- StringUtils.isEmpty(databaseName) || (!ignoreCase &&
databaseName.equals(databasePrefix)) || (ignoreCase
- && databaseName.equalsIgnoreCase(databasePrefix)),
- "Database name '%s' from table prefix does not match database name
'%s' from header", databasePrefix,
- databaseName);
+ if (!StringUtils.isEmpty(databaseName) && (ignoreCase ||
!databaseName.equals(databasePrefix))
+ && (!ignoreCase ||
!databaseName.equalsIgnoreCase(databasePrefix))) {
+ throw new DatabaseConflictException("Database name '" +
databasePrefix
+ + "' from table prefix does not match database name '" +
databaseName + "' from header");
+ }
// skip database name prefix if it's a 'default' database
return
databasePrefix.equalsIgnoreCase(CommonConstants.DEFAULT_DATABASE) ?
tableSplit[1] : tableName;
default:
@@ -66,6 +69,15 @@ public class DatabaseUtils {
}
}
+ /**
+ * Construct the fully qualified table name i.e. {databaseName}.{tableName}
from given table name and database name
+ * @param tableName table/schema name
+ * @param databaseName database name
+ * @return translated table name.
+ * <br>Throws {@link IllegalArgumentException} if {@code tableName} contains
more than 1 dot
+ * <br>Throws {@link DatabaseConflictException} if {@code tableName} has
database prefix,
+ * and it does not match with {@code databaseName}
+ */
public static String translateTableName(String tableName, @Nullable String
databaseName) {
return translateTableName(tableName, databaseName, false);
}
@@ -76,13 +88,24 @@ public class DatabaseUtils {
* @param headers http headers
* @param ignoreCase whether to ignore case when comparing database name in
headers against table name prefix if both
* exist. For 'default' database, always compare it
ignoring case.
- * @return translated table name. Throws {@link IllegalStateException} if
{@code tableName} contains more than 1 dot
- * or if {@code tableName} has database prefix, and it does not match with
the 'database' header
+ * @return translated table name.
+ * <br>Throws {@link IllegalArgumentException} if {@code tableName} contains
more than 1 dot
+ * <br>Throws {@link DatabaseConflictException} if {@code tableName} has
database prefix,
+ * and it does not match with the 'database' header
*/
public static String translateTableName(String tableName, HttpHeaders
headers, boolean ignoreCase) {
return translateTableName(tableName,
headers.getHeaderString(CommonConstants.DATABASE), ignoreCase);
}
+ /**
+ * Utility to get fully qualified table name i.e. {databaseName}.{tableName}
from given table name and http headers
+ * @param tableName table/schema name
+ * @param headers http headers
+ * @return translated table name.
+ * <br>Throws {@link IllegalArgumentException} if {@code tableName} contains
more than 1 dot
+ * <br>Throws {@link DatabaseConflictException} if {@code tableName} has
database prefix,
+ * and it does not match with the 'database' header
+ */
public static String translateTableName(String tableName, HttpHeaders
headers) {
return translateTableName(tableName, headers, false);
}
@@ -117,15 +140,16 @@ public class DatabaseUtils {
* @param headers http headers from request
* @return extracted database name.
* <br>If database context is not provided at all return {@link
CommonConstants#DEFAULT_DATABASE}.
- * <br>If queryOptions and headers have conflicting database context an
{@link IllegalArgumentException} is thrown.
+ * <br>If queryOptions and headers have conflicting database context an
{@link DatabaseConflictException} is thrown.
*/
public static String extractDatabaseFromQueryRequest(
@Nullable Map<String, String> queryOptions, @Nullable HttpHeaders
headers) {
String databaseFromOptions = queryOptions == null ? null :
queryOptions.get(CommonConstants.DATABASE);
String databaseFromHeaders = headers == null ? null :
headers.getHeaderString(CommonConstants.DATABASE);
- if (databaseFromHeaders != null && databaseFromOptions != null) {
-
Preconditions.checkArgument(databaseFromOptions.equals(databaseFromHeaders),
"Database context mismatch : "
- + "from headers %s, from query options %s", databaseFromHeaders,
databaseFromOptions);
+ if (databaseFromHeaders != null && databaseFromOptions != null
+ && !databaseFromOptions.equals(databaseFromHeaders)) {
+ throw new DatabaseConflictException("Database context mismatch : from
headers '" + databaseFromHeaders
+ + "', from query options '" + databaseFromOptions + "'");
}
String database = databaseFromHeaders != null ? databaseFromHeaders :
databaseFromOptions;
return Objects.requireNonNullElse(database,
CommonConstants.DEFAULT_DATABASE);
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/utils/DatabaseUtilsTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/utils/DatabaseUtilsTest.java
index edbd618702..5036641619 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/utils/DatabaseUtilsTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/utils/DatabaseUtilsTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.pinot.common.utils;
+import org.apache.pinot.spi.exception.DatabaseConflictException;
import org.apache.pinot.spi.utils.CommonConstants;
import org.testng.annotations.Test;
@@ -72,7 +73,7 @@ public class DatabaseUtilsTest {
try {
DatabaseUtils.translateTableName(tableName, databaseName);
fail();
- } catch (IllegalArgumentException ignored) {
+ } catch (IllegalArgumentException | DatabaseConflictException ignored) {
return;
}
}
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
index b9f13a53f4..3d8a3bb263 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotQueryResource.java
@@ -73,6 +73,7 @@ import org.apache.pinot.query.catalog.PinotCatalog;
import org.apache.pinot.query.type.TypeFactory;
import org.apache.pinot.query.type.TypeSystem;
import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.exception.DatabaseConflictException;
import org.apache.pinot.spi.utils.CommonConstants;
import
org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey;
import org.apache.pinot.spi.utils.JsonUtils;
@@ -253,13 +254,15 @@ public class PinotQueryResource {
if (queryOptions != null) {
queryOptionsMap.putAll(RequestUtils.getOptionsFromString(queryOptions));
}
- String database =
DatabaseUtils.extractDatabaseFromQueryRequest(queryOptionsMap, httpHeaders);
+ String database = null;
try {
String inputTableName =
sqlNode != null ?
RequestUtils.getTableNames(CalciteSqlParser.compileSqlNodeToPinotQuery(sqlNode)).iterator()
.next() :
CalciteSqlCompiler.compileToBrokerRequest(query).getQuerySource().getTableName();
- tableName = _pinotHelixResourceManager.getActualTableName(inputTableName,
- httpHeaders.getHeaderString(CommonConstants.DATABASE));
+ database =
DatabaseUtils.extractDatabaseFromQueryRequest(queryOptionsMap, httpHeaders);
+ tableName =
_pinotHelixResourceManager.getActualTableName(inputTableName, database);
+ } catch (DatabaseConflictException e) {
+ return
QueryException.getException(QueryException.QUERY_VALIDATION_ERROR,
e).toString();
} catch (Exception e) {
LOGGER.error("Caught exception while compiling query: {}", query, e);
try {
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/MultiStageEngineIntegrationTest.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/MultiStageEngineIntegrationTest.java
index 0f5c5f611e..33825ef727 100644
---
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/MultiStageEngineIntegrationTest.java
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/MultiStageEngineIntegrationTest.java
@@ -849,7 +849,7 @@ public class MultiStageEngineIntegrationTest extends
BaseClusterIntegrationTestS
// Using renamed column "ActualElapsedTime_2" to ensure that the same
table is not being queried.
// custom database check. Database context passed only as table prefix.
Will
JsonNode result = getQueryResultForDBTest("ActualElapsedTime_2",
TABLE_NAME_WITH_DATABASE, null, null);
- checkQueryPlanningErrorForDBTest(result);
+ checkQueryPlanningErrorForDBTest(result,
QueryException.QUERY_PLANNING_ERROR_CODE);
}
@Test
@@ -891,7 +891,7 @@ public class MultiStageEngineIntegrationTest extends
BaseClusterIntegrationTestS
throws Exception {
JsonNode result = getQueryResultForDBTest("ActualElapsedTime",
TABLE_NAME_WITH_DATABASE, DEFAULT_DATABASE_NAME,
null);
- checkQueryPlanningErrorForDBTest(result);
+ checkQueryPlanningErrorForDBTest(result,
QueryException.QUERY_PLANNING_ERROR_CODE);
}
@Test
@@ -899,7 +899,7 @@ public class MultiStageEngineIntegrationTest extends
BaseClusterIntegrationTestS
throws Exception {
JsonNode result = getQueryResultForDBTest("ActualElapsedTime",
TABLE_NAME_WITH_DATABASE, null,
Collections.singletonMap(CommonConstants.DATABASE,
DEFAULT_DATABASE_NAME));
- checkQueryPlanningErrorForDBTest(result);
+ checkQueryPlanningErrorForDBTest(result,
QueryException.QUERY_PLANNING_ERROR_CODE);
}
@Test
@@ -907,7 +907,7 @@ public class MultiStageEngineIntegrationTest extends
BaseClusterIntegrationTestS
throws Exception {
JsonNode result = getQueryResultForDBTest("ActualElapsedTime",
TABLE_NAME_WITH_DATABASE, DATABASE_NAME,
Collections.singletonMap(CommonConstants.DATABASE,
DEFAULT_DATABASE_NAME));
- checkQueryPlanningErrorForDBTest(result);
+ checkQueryPlanningErrorForDBTest(result,
QueryException.QUERY_VALIDATION_ERROR_CODE);
}
@Test
@@ -918,7 +918,7 @@ public class MultiStageEngineIntegrationTest extends
BaseClusterIntegrationTestS
+ " Carrier FROM " + TABLE_NAME_WITH_DATABASE + " GROUP BY Carrier) AS
tb2 "
+ "ON tb1.Carrier = tb2.Carrier; ";
JsonNode result = postQuery(query);
- checkQueryPlanningErrorForDBTest(result);
+ checkQueryPlanningErrorForDBTest(result,
QueryException.QUERY_PLANNING_ERROR_CODE);
}
private void checkQueryResultForDBTest(String column, String tableName)
@@ -946,9 +946,9 @@ public class MultiStageEngineIntegrationTest extends
BaseClusterIntegrationTestS
assertEquals(result, expectedValue);
}
- private void checkQueryPlanningErrorForDBTest(JsonNode queryResult) {
+ private void checkQueryPlanningErrorForDBTest(JsonNode queryResult, int
errorCode) {
long result =
queryResult.get("exceptions").get(0).get("errorCode").asInt();
- assertEquals(result, QueryException.QUERY_PLANNING_ERROR_CODE);
+ assertEquals(result, errorCode);
}
private JsonNode getQueryResultForDBTest(String column, String tableName,
@Nullable String database,
diff --git
a/pinot-spi/src/main/java/org/apache/pinot/spi/exception/DatabaseConflictException.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/exception/DatabaseConflictException.java
new file mode 100644
index 0000000000..37c0b82bd4
--- /dev/null
+++
b/pinot-spi/src/main/java/org/apache/pinot/spi/exception/DatabaseConflictException.java
@@ -0,0 +1,25 @@
+/**
+ * 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.spi.exception;
+
+public class DatabaseConflictException extends BadQueryRequestException {
+ public DatabaseConflictException(String message) {
+ super(message);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]