Repository: trinodb/trino (Java)
Date: 2020-01-22 06:35:19+00:00
Task type: Bug Fix

Issue:
Error when querying information schema with empty table_name predicate
```
select * from "hive"."information_schema"."tables" WHERE table_schema='tpch' AND table_name=''
```

Is causing:
```
java.lang.IllegalArgumentException: tableName is empty
	at io.prestosql.spi.connector.SchemaUtil.checkNotEmpty(SchemaUtil.java:28)
	at io.prestosql.spi.connector.SchemaTableName.<init>(SchemaTableName.java:33)
	at io.prestosql.metadata.QualifiedObjectName.asSchemaTableName(QualifiedObjectName.java:75)
	at io.prestosql.metadata.CatalogMetadata.getConnectorId(CatalogMetadata.java:115)
	at io.prestosql.metadata.MetadataManager.getTableHandle(MetadataManager.java:285)
	at io.prestosql.metadata.MetadataManager.listTables(MetadataManager.java:507)
	at io.prestosql.metadata.MetadataListing.listTables(MetadataListing.java:64)
	at io.prestosql.connector.informationschema.InformationSchemaPageSource.addTablesRecords(InformationSchemaPageSource.java:268)
	at io.prestosql.connector.informationschema.InformationSchemaPageSource.buildPages(InformationSchemaPageSource.java:210)
	at io.prestosql.connector.informationschema.InformationSchemaPageSource.getNextPage(InformationSchemaPageSource.java:173)
	at io.prestosql.operator.ScanFilterAndProjectOperator$ConnectorPageSourceToPages.process(ScanFilterAndProjectOperator.java:362)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils.getNextState(WorkProcessorUtils.java:221)
	at io.prestosql.operator.WorkProcessorUtils.access$000(WorkProcessorUtils.java:37)
	at io.prestosql.operator.WorkProcessorUtils$YieldingProcess.process(WorkProcessorUtils.java:181)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:306)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:306)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:306)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils.getNextState(WorkProcessorUtils.java:221)
	at io.prestosql.operator.WorkProcessorUtils.lambda$processStateMonitor$2(WorkProcessorUtils.java:200)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils.lambda$flatten$6(WorkProcessorUtils.java:277)
	at io.prestosql.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:319)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils$3.process(WorkProcessorUtils.java:306)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils.getNextState(WorkProcessorUtils.java:221)
	at io.prestosql.operator.WorkProcessorUtils.lambda$processStateMonitor$2(WorkProcessorUtils.java:200)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorUtils.getNextState(WorkProcessorUtils.java:221)
	at io.prestosql.operator.WorkProcessorUtils.lambda$finishWhen$3(WorkProcessorUtils.java:215)
	at io.prestosql.operator.WorkProcessorUtils$ProcessWorkProcessor.process(WorkProcessorUtils.java:372)
	at io.prestosql.operator.WorkProcessorSourceOperatorAdapter.getOutput(WorkProcessorSourceOperatorAdapter.java:148)
	at io.prestosql.operator.Driver.processInternal(Driver.java:379)
	at io.prestosql.operator.Driver.lambda$processFor$8(Driver.java:283)
	at io.prestosql.operator.Driver.tryWithLock(Driver.java:675)
	at io.prestosql.operator.Driver.processFor(Driver.java:276)
	at io.prestosql.execution.SqlTaskExecution$DriverSplitRunner.processFor(SqlTaskExecution.java:1075)
	at io.prestosql.execution.executor.PrioritizedSplitRunner.process(PrioritizedSplitRunner.java:163)
	at io.prestosql.execution.executor.TaskExecutor$TaskRunner.run(TaskExecutor.java:484)
	at io.prestosql.$gen.Presto_null__testversion____20200116_131217_4.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
```


Issue discussion:
I think this kind of query should evaluate to empty relation during the planning.

Solution patch:
diff --git a/presto-main/src/main/java/io/prestosql/connector/informationschema/InformationSchemaMetadata.java b/presto-main/src/main/java/io/prestosql/connector/informationschema/InformationSchemaMetadata.java
index 1981e3092f90..88829b4f480a 100644
--- a/presto-main/src/main/java/io/prestosql/connector/informationschema/InformationSchemaMetadata.java
+++ b/presto-main/src/main/java/io/prestosql/connector/informationschema/InformationSchemaMetadata.java
@@ -212,7 +212,7 @@ private Set<QualifiedTablePrefix> getPrefixes(ConnectorSession session, Informat
             return ImmutableSet.of();
         }
 
-        Optional<Set<String>> catalogs = filterString(constraint.getSummary(), CATALOG_COLUMN_HANDLE);
+        Optional<Set<String>> catalogs = filterString(constraint.getSummary(), CATALOG_COLUMN_HANDLE).map(this::removeEmptyValues);
         if (catalogs.isPresent() && !catalogs.get().contains(table.getCatalogName())) {
             return ImmutableSet.of();
         }
@@ -220,7 +220,7 @@ private Set<QualifiedTablePrefix> getPrefixes(ConnectorSession session, Informat
         InformationSchemaTable informationSchemaTable = table.getTable();
         Set<QualifiedTablePrefix> prefixes = calculatePrefixesWithSchemaName(session, constraint.getSummary(), constraint.predicate());
         Set<QualifiedTablePrefix> tablePrefixes = calculatePrefixesWithTableName(informationSchemaTable, session, prefixes, constraint.getSummary(), constraint.predicate());
-        // in case of high number of prefixes it is better to populate all data and then filter
+
         if (tablePrefixes.size() <= MAX_PREFIXES_COUNT) {
             prefixes = tablePrefixes;
         }
@@ -242,7 +242,7 @@ private Set<QualifiedTablePrefix> calculatePrefixesWithSchemaName(
             TupleDomain<ColumnHandle> constraint,
             Optional<Predicate<Map<ColumnHandle, NullableValue>>> predicate)
     {
-        Optional<Set<String>> schemas = filterString(constraint, SCHEMA_COLUMN_HANDLE);
+        Optional<Set<String>> schemas = filterString(constraint, SCHEMA_COLUMN_HANDLE).map(this::removeEmptyValues);
         if (schemas.isPresent()) {
             return schemas.get().stream()
                     .filter(this::isLowerCase)
@@ -270,7 +270,7 @@ private Set<QualifiedTablePrefix> calculatePrefixesWithTableName(
     {
         Session session = ((FullConnectorSession) connectorSession).getSession();
 
-        Optional<Set<String>> tables = filterString(constraint, TABLE_NAME_COLUMN_HANDLE);
+        Optional<Set<String>> tables = filterString(constraint, TABLE_NAME_COLUMN_HANDLE).map(this::removeEmptyValues);
         if (tables.isPresent()) {
             return prefixes.stream()
                     .peek(prefix -> verify(!prefix.asQualifiedObjectName().isPresent()))
@@ -364,4 +364,11 @@ private boolean isLowerCase(String value)
     {
         return value.toLowerCase(ENGLISH).equals(value);
     }
+
+    private Set<String> removeEmptyValues(Set<String> values)
+    {
+        return values.stream()
+                .filter(value -> !value.isEmpty())
+                .collect(toImmutableSet());
+    }
 }


Test patch:
diff --git a/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java b/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java
index 38a4bd7445ee..5f256f3da796 100644
--- a/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java
+++ b/presto-main/src/test/java/io/prestosql/metadata/TestInformationSchemaMetadata.java
@@ -57,6 +57,7 @@
 import static java.util.Arrays.stream;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 public class TestInformationSchemaMetadata
 {
@@ -217,6 +218,35 @@ public void testInformationSchemaPredicatePushdownOnCatalogWiseTables()
         assertFalse(result.isPresent());
     }
 
+    @Test
+    public void testInformationSchemaPredicatePushdownForEmptyNames()
+    {
+        assertApplyFiterReturnsEmptyPrefixes(
+                new SchemaTableName("information_schema", "tables"),
+                ImmutableMap.of(
+                        new InformationSchemaColumnHandle("table_name"), new NullableValue(VARCHAR, Slices.utf8Slice(""))));
+
+        assertApplyFiterReturnsEmptyPrefixes(
+                new SchemaTableName("information_schema", "tables"),
+                ImmutableMap.of(
+                        new InformationSchemaColumnHandle("table_schema"), new NullableValue(VARCHAR, Slices.utf8Slice(""))));
+    }
+
+    private void assertApplyFiterReturnsEmptyPrefixes(SchemaTableName schemaTableName, Map<ColumnHandle, NullableValue> constraint)
+    {
+        TransactionId transactionId = transactionManager.beginTransaction(false);
+        ConnectorSession session = createNewSession(transactionId);
+        ConnectorMetadata metadata = new InformationSchemaMetadata("test_catalog", this.metadata);
+
+        InformationSchemaTableHandle tableHandle = (InformationSchemaTableHandle) metadata.getTableHandle(session, schemaTableName);
+
+        assertTrue(metadata.applyFilter(session, tableHandle, new Constraint(TupleDomain.fromFixedValues(constraint)))
+                .map(ConstraintApplicationResult::getHandle)
+                .map(InformationSchemaTableHandle.class::cast)
+                .orElseThrow(AssertionError::new)
+                .getPrefixes().isEmpty());
+    }
+
     private static boolean testConstraint(Map<ColumnHandle, NullableValue> bindings)
     {
         // test_schema has a table named "another_table" and we filter that out in this predicate
diff --git a/presto-tests/src/test/java/io/prestosql/tests/TestInformationSchemaConnector.java b/presto-tests/src/test/java/io/prestosql/tests/TestInformationSchemaConnector.java
index ed1c8243a1a7..ae42c3827218 100644
--- a/presto-tests/src/test/java/io/prestosql/tests/TestInformationSchemaConnector.java
+++ b/presto-tests/src/test/java/io/prestosql/tests/TestInformationSchemaConnector.java
@@ -194,6 +194,13 @@ public void testMetadataCalls()
                         .withGetColumnsCount(10000));
     }
 
+    @Test
+    public void testInformationForEmptyNames()
+    {
+        assertQuery("SELECT count(*) FROM test_catalog.information_schema.tables WHERE table_schema='tpch' AND table_name=''", "VALUES 0");
+        assertQuery("SELECT count(*) FROM test_catalog.information_schema.tables WHERE table_schema='' AND table_name='table'", "VALUES 0");
+    }
+
     @Override
     protected DistributedQueryRunner createQueryRunner()
             throws Exception