+ * Rule can be applied if query contains multiple count expressions.
+ * " select count(column1), count(column2), count(*) from table "
+ *
*
+ *
* Currently, only parquet group scan has the exact row count and column value count,
* obtained from parquet row group info. This will save the cost to
* scan the whole parquet files.
+ *
*/
-
public class ConvertCountToDirectScan extends Prule {
public static final RelOptRule AGG_ON_PROJ_ON_SCAN = new ConvertCountToDirectScan(
@@ -77,6 +87,8 @@ public class ConvertCountToDirectScan extends Prule {
RelOptHelper.some(DrillAggregateRel.class,
RelOptHelper.any(DrillScanRel.class)), "Agg_on_scan");
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ConvertCountToDirectScan.class);
+
/** Creates a SplunkPushDownRule. */
protected ConvertCountToDirectScan(RelOptRuleOperand rule, String id) {
super(rule, "ConvertCountToDirectScan:" + id);
@@ -85,40 +97,85 @@ protected ConvertCountToDirectScan(RelOptRuleOperand rule, String id) {
@Override
public void onMatch(RelOptRuleCall call) {
final DrillAggregateRel agg = (DrillAggregateRel) call.rel(0);
- final DrillScanRel scan = (DrillScanRel) call.rel(call.rels.length -1);
- final DrillProjectRel proj = call.rels.length == 3 ? (DrillProjectRel) call.rel(1) : null;
+ final DrillScanRel scan = (DrillScanRel) call.rel(call.rels.length - 1);
+ final DrillProjectRel project = call.rels.length == 3 ? (DrillProjectRel) call.rel(1) : null;
final GroupScan oldGrpScan = scan.getGroupScan();
final PlannerSettings settings = PrelUtil.getPlannerSettings(call.getPlanner());
- // Only apply the rule when :
+ // Only apply the rule when:
// 1) scan knows the exact row count in getSize() call,
// 2) No GroupBY key,
- // 3) only one agg function (Check if it's count(*) below).
- // 4) No distinct agg call.
+ // 3) No distinct agg call.
if (!(oldGrpScan.getScanStats(settings).getGroupScanProperty().hasExactRowCount()
&& agg.getGroupCount() == 0
- && agg.getAggCallList().size() == 1
&& !agg.containsDistinctCall())) {
return;
}
- AggregateCall aggCall = agg.getAggCallList().get(0);
+ Map result = collectCounts(settings, agg, scan, project);
+ logger.trace("Calculated the following aggregate counts: ", result);
+ // if could not determine the counts, rule won't be applied
+ if (result.isEmpty()) {
+ return;
+ }
+
+ final RelDataType scanRowType = constructDataType(agg, result.keySet());
+
+ final DynamicPojoRecordReader reader = new DynamicPojoRecordReader<>(
+ buildSchema(scanRowType.getFieldNames()),
+ Collections.singletonList((List) new ArrayList<>(result.values())));
- if (aggCall.getAggregation().getName().equals("COUNT") ) {
+ final ScanStats scanStats = new ScanStats(ScanStats.GroupScanProperty.EXACT_ROW_COUNT, 1, 1, scanRowType.getFieldCount());
+ final GroupScan directScan = new MetadataDirectGroupScan(reader, oldGrpScan.getFiles(), scanStats);
+
+ final ScanPrel newScan = ScanPrel.create(scan,
+ scan.getTraitSet().plus(Prel.DRILL_PHYSICAL).plus(DrillDistributionTrait.SINGLETON), directScan,
+ scanRowType);
+
+ final ProjectPrel newProject = new ProjectPrel(agg.getCluster(), agg.getTraitSet().plus(Prel.DRILL_PHYSICAL)
+ .plus(DrillDistributionTrait.SINGLETON), newScan, prepareFieldExpressions(scanRowType), agg.getRowType());
+
+ call.transformTo(newProject);
+ }
+
+ /**
+ * Collects counts for each aggregation call.
+ * Will return empty result map if was not able to determine count for at least one aggregation call,
+ *
+ * For each aggregate call will determine if count can be calculated. Collects counts only for COUNT function.
+ * For star, not null expressions and implicit columns sets count to total record number.
+ * For other cases obtains counts from group scan operator. Also count can not be calculated for parition columns.
+ *
+ * @param agg aggregate relational expression
+ * @param scan scan relational expression
+ * @param project project relational expression
+ * @return result map where key is count column name, value is count value
+ */
+ private Map collectCounts(PlannerSettings settings, DrillAggregateRel agg, DrillScanRel scan, DrillProjectRel project) {
+ final Set implicitColumnsNames = ColumnExplorer.initImplicitFileColumns(settings.getOptions()).keySet();
+ final GroupScan oldGrpScan = scan.getGroupScan();
+ final long totalRecordCount = oldGrpScan.getScanStats(settings).getRecordCount();
+ final LinkedHashMap result = new LinkedHashMap<>();
+
+ for (int i = 0; i < agg.getAggCallList().size(); i++) {
+ AggregateCall aggCall = agg.getAggCallList().get(i);
+ //for (AggregateCall aggCall : agg.getAggCallList()) {
+ long cnt;
+
+ // rule can be applied only for count function, return empty counts
+ if (!"count".equalsIgnoreCase(aggCall.getAggregation().getName()) ) {
+ return ImmutableMap.of();
+ }
+
+ if (containsStarOrNotNullInput(aggCall, agg)) {
+ cnt = totalRecordCount;
- long cnt = 0;
- // count(*) == > empty arg ==> rowCount
- // count(Not-null-input) ==> rowCount
- if (aggCall.getArgList().isEmpty() ||
- (aggCall.getArgList().size() == 1 &&
- ! agg.getInput().getRowType().getFieldList().get(aggCall.getArgList().get(0).intValue()).getType().isNullable())) {
- cnt = (long) oldGrpScan.getScanStats(settings).getRecordCount();
} else if (aggCall.getArgList().size() == 1) {
- // count(columnName) ==> Agg ( Scan )) ==> columnValueCount
+ // count(columnName) ==> Agg ( Scan )) ==> columnValueCount
int index = aggCall.getArgList().get(0);
- if (proj != null) {
+ if (project != null) {
// project in the middle of Agg and Scan : Only when input of AggCall is a RexInputRef in Project, we find the index of Scan's field.
// For instance,
// Agg - count($0)
@@ -127,67 +184,108 @@ public void onMatch(RelOptRuleCall call) {
// \
// Scan (col1, col2).
// return count of "col2" in Scan's metadata, if found.
-
- if (proj.getProjects().get(index) instanceof RexInputRef) {
- index = ((RexInputRef) proj.getProjects().get(index)).getIndex();
- } else {
- return; // do not apply for all other cases.
+ if (!(project.getProjects().get(index) instanceof RexInputRef)) {
+ return ImmutableMap.of(); // do not apply for all other cases.
}
+
+ index = ((RexInputRef) project.getProjects().get(index)).getIndex();
}
String columnName = scan.getRowType().getFieldNames().get(index).toLowerCase();
- cnt = oldGrpScan.getColumnValueCount(SchemaPath.getSimplePath(columnName));
- if (cnt == GroupScan.NO_COLUMN_STATS) {
- // if column stats are not available don't apply this rule
- return;
+ // for implicit column count will the same as total record count
+ if (implicitColumnsNames.contains(columnName)) {
+ cnt = totalRecordCount;
+ } else {
+ SchemaPath simplePath = SchemaPath.getSimplePath(columnName);
+
+ if (ColumnExplorer.isPartitionColumn(settings.getOptions(), simplePath)) {
+ return ImmutableMap.of();
+ }
+
+ cnt = oldGrpScan.getColumnValueCount(simplePath);
+ if (cnt == GroupScan.NO_COLUMN_STATS) {
+ // if column stats is not available don't apply this rule, return empty counts
+ return ImmutableMap.of();
+ }
}
} else {
- return; // do nothing.
+ return ImmutableMap.of();
}
- RelDataType scanRowType = getCountDirectScanRowType(agg.getCluster().getTypeFactory());
-
- final ScanPrel newScan = ScanPrel.create(scan,
- scan.getTraitSet().plus(Prel.DRILL_PHYSICAL).plus(DrillDistributionTrait.SINGLETON), getCountDirectScan(cnt),
- scanRowType);
-
- List exprs = Lists.newArrayList();
- exprs.add(RexInputRef.of(0, scanRowType));
-
- final ProjectPrel newProj = new ProjectPrel(agg.getCluster(), agg.getTraitSet().plus(Prel.DRILL_PHYSICAL)
- .plus(DrillDistributionTrait.SINGLETON), newScan, exprs, agg.getRowType());
-
- call.transformTo(newProj);
+ String name = "count" + i + "$" + (aggCall.getName() == null ? aggCall.toString() : aggCall.getName());
+ result.put(name, cnt);
}
+ return ImmutableMap.copyOf(result);
}
/**
- * Class to represent the count aggregate result.
+ * Checks if aggregate call contains star or non-null expression:
+ *
+ *
+ * @param aggregateCall aggregate call
+ * @param aggregate aggregate relation expression
+ * @return true of aggregate call contains star or non-null expression
*/
- public static class CountQueryResult {
- public long count;
-
- public CountQueryResult(long cnt) {
- this.count = cnt;
- }
+ private boolean containsStarOrNotNullInput(AggregateCall aggregateCall, DrillAggregateRel aggregate) {
+ return aggregateCall.getArgList().isEmpty() ||
+ (aggregateCall.getArgList().size() == 1 &&
+ !aggregate.getInput().getRowType().getFieldList().get(aggregateCall.getArgList().get(0)).getType().isNullable());
}
- private RelDataType getCountDirectScanRowType(RelDataTypeFactory typeFactory) {
- List fields = Lists.newArrayList();
- fields.add(new RelDataTypeFieldImpl("count", 0, typeFactory.createSqlType(SqlTypeName.BIGINT)));
-
+ /**
+ * For each aggregate call creates field based on its name with bigint type.
+ * Constructs record type for created fields.
+ *
+ * @param aggregateRel aggregate relation expression
+ * @param fieldNames field names
+ * @return record type
+ */
+ private RelDataType constructDataType(DrillAggregateRel aggregateRel, Collection fieldNames) {
+ List fields = new ArrayList<>();
+ Iterator filedNamesIterator = fieldNames.iterator();
+ int fieldIndex = 0;
+ while (filedNamesIterator.hasNext()) {
+ RelDataTypeField field = new RelDataTypeFieldImpl(
+ filedNamesIterator.next(),
+ fieldIndex++,
+ aggregateRel.getCluster().getTypeFactory().createSqlType(SqlTypeName.BIGINT));
+ fields.add(field);
+ }
return new RelRecordType(fields);
}
- private GroupScan getCountDirectScan(long cnt) {
- CountQueryResult res = new CountQueryResult(cnt);
-
- PojoRecordReader reader = new PojoRecordReader(CountQueryResult.class,
- Collections.singleton(res).iterator());
+ /**
+ * Builds schema based on given field names.
+ * Type for each schema is set to long.class.
+ *
+ * @param fieldNames field names
+ * @return schema
+ */
+ private LinkedHashMap> buildSchema(List fieldNames) {
+ LinkedHashMap> schema = new LinkedHashMap<>();
+ for (String fieldName: fieldNames) {
+ schema.put(fieldName, long.class);
+ }
+ return schema;
+ }
- return new DirectGroupScan(reader);
+ /**
+ * For each field creates row expression.
+ *
+ * @param rowType row type
+ * @return list of row expressions
+ */
+ private List prepareFieldExpressions(RelDataType rowType) {
+ List expressions = new ArrayList<>();
+ for (int i = 0; i < rowType.getFieldCount(); i++) {
+ expressions.add(RexInputRef.of(i, rowType));
+ }
+ return expressions;
}
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DirectPlan.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DirectPlan.java
index d40e0d7e5f2..3e1d6c79832 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DirectPlan.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DirectPlan.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -17,9 +17,6 @@
*/
package org.apache.drill.exec.planner.sql;
-import java.util.Collections;
-import java.util.Iterator;
-
import org.apache.drill.common.logical.PlanProperties;
import org.apache.drill.common.logical.PlanProperties.Generator.ResultMode;
import org.apache.drill.common.logical.PlanProperties.PlanPropertiesBuilder;
@@ -33,6 +30,9 @@
import org.apache.drill.exec.store.direct.DirectGroupScan;
import org.apache.drill.exec.store.pojo.PojoRecordReader;
+import java.util.Collections;
+import java.util.List;
+
public class DirectPlan {
static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DirectPlan.class);
@@ -43,12 +43,12 @@ public static PhysicalPlan createDirectPlan(QueryContext context, boolean result
@SuppressWarnings("unchecked")
public static PhysicalPlan createDirectPlan(QueryContext context, T obj){
- Iterator iter = (Iterator) Collections.singleton(obj).iterator();
- return createDirectPlan(context.getCurrentEndpoint(), iter, (Class) obj.getClass());
+ return createDirectPlan(context.getCurrentEndpoint(), Collections.singletonList(obj), (Class) obj.getClass());
}
- public static PhysicalPlan createDirectPlan(DrillbitEndpoint endpoint, Iterator iterator, Class clazz){
- PojoRecordReader reader = new PojoRecordReader(clazz, iterator);
+
+ public static PhysicalPlan createDirectPlan(DrillbitEndpoint endpoint, List records, Class clazz){
+ PojoRecordReader reader = new PojoRecordReader<>(clazz, records);
DirectGroupScan scan = new DirectGroupScan(reader);
Screen screen = new Screen(scan, endpoint);
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/handlers/ShowFileHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/handlers/ShowFileHandler.java
index 5e6af7ce6ee..307b01dd52c 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/handlers/ShowFileHandler.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/handlers/ShowFileHandler.java
@@ -102,6 +102,6 @@ public PhysicalPlan getPlan(SqlNode sqlNode) throws ValidationException, RelConv
fileStatus.getAccessTime(), fileStatus.getModificationTime());
rows.add(result);
}
- return DirectPlan.createDirectPlan(context.getCurrentEndpoint(), rows.iterator(), ShowFilesCommandResult.class);
+ return DirectPlan.createDirectPlan(context.getCurrentEndpoint(), rows, ShowFilesCommandResult.class);
}
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/store/ImplicitColumnExplorer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/store/ColumnExplorer.java
similarity index 79%
rename from exec/java-exec/src/main/java/org/apache/drill/exec/store/ImplicitColumnExplorer.java
rename to exec/java-exec/src/main/java/org/apache/drill/exec/store/ColumnExplorer.java
index 42ff82728be..ccd622b93e2 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/store/ImplicitColumnExplorer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/store/ColumnExplorer.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -35,7 +35,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class ImplicitColumnExplorer {
+public class ColumnExplorer {
private final String partitionDesignator;
private final List columns;
@@ -51,7 +51,7 @@ public class ImplicitColumnExplorer {
* between actual table columns, partition columns and implicit file columns.
* Also populates map with implicit columns names as keys and their values
*/
- public ImplicitColumnExplorer(FragmentContext context, List columns) {
+ public ColumnExplorer(FragmentContext context, List columns) {
this(context.getOptions(), columns);
}
@@ -60,7 +60,7 @@ public ImplicitColumnExplorer(FragmentContext context, List columns)
* between actual table columns, partition columns and implicit file columns.
* Also populates map with implicit columns names as keys and their values
*/
- public ImplicitColumnExplorer(OptionManager optionManager, List columns) {
+ public ColumnExplorer(OptionManager optionManager, List columns) {
this.partitionDesignator = optionManager.getOption(ExecConstants.FILESYSTEM_PARTITION_COLUMN_LABEL).string_val;
this.columns = columns;
this.isStarQuery = columns != null && AbstractRecordReader.isStarQuery(columns);
@@ -86,6 +86,32 @@ public static Map initImplicitFileColumns(OptionMan
return map;
}
+ /**
+ * Checks if given column is partition or not.
+ *
+ * @param optionManager options
+ * @param column column
+ * @return true if given column is partition, false otherwise
+ */
+ public static boolean isPartitionColumn(OptionManager optionManager, SchemaPath column){
+ String partitionDesignator = optionManager.getOption(ExecConstants.FILESYSTEM_PARTITION_COLUMN_LABEL).string_val;
+ String path = column.getAsUnescapedPath();
+ return isPartitionColumn(partitionDesignator, path);
+ }
+
+ /**
+ * Checks if given column is partition or not.
+ *
+ * @param partitionDesignator partition designator
+ * @param path column path
+ * @return true if given column is partition, false otherwise
+ */
+ public static boolean isPartitionColumn(String partitionDesignator, String path){
+ Pattern pattern = Pattern.compile(String.format("%s[0-9]+", partitionDesignator));
+ Matcher matcher = pattern.matcher(path);
+ return matcher.matches();
+ }
+
/**
* Compares selection root and actual file path to determine partition columns values.
* Adds implicit file columns according to columns list.
@@ -132,6 +158,24 @@ public List getTableColumns() {
return tableColumns;
}
+ /**
+ * Checks if current column selection contains partition columns.
+ *
+ * @return true if partition columns are present, false otherwise
+ */
+ public boolean containsPartitionColumns() {
+ return !selectedPartitionColumns.isEmpty();
+ }
+
+ /**
+ * Checks if current column selection contains implicit columns.
+ *
+ * @return true if implicit columns are present, false otherwise
+ */
+ public boolean containsImplicitColumns() {
+ return !selectedImplicitColumns.isEmpty();
+ }
+
/**
* If it is not star query, sorts out columns into three categories:
* 1. table columns
@@ -142,11 +186,9 @@ private void init() {
if (isStarQuery) {
selectedImplicitColumns.putAll(allImplicitColumns);
} else {
- Pattern pattern = Pattern.compile(String.format("%s[0-9]+", partitionDesignator));
for (SchemaPath column : columns) {
String path = column.getAsUnescapedPath();
- Matcher m = pattern.matcher(path);
- if (m.matches()) {
+ if (isPartitionColumn(partitionDesignator, path)) {
selectedPartitionColumns.add(Integer.parseInt(path.substring(partitionDesignator.length())));
} else if (allImplicitColumns.get(path) != null) {
selectedImplicitColumns.put(path, allImplicitColumns.get(path));
@@ -169,7 +211,7 @@ public enum ImplicitFileColumns {
FQN (ExecConstants.IMPLICIT_FQN_COLUMN_LABEL) {
@Override
public String getValue(Path path) {
- return path.toString();
+ return path.toUri().getPath();
}
},
@@ -179,7 +221,7 @@ public String getValue(Path path) {
FILEPATH (ExecConstants.IMPLICIT_FILEPATH_COLUMN_LABEL) {
@Override
public String getValue(Path path) {
- return path.getParent().toString();
+ return path.getParent().toUri().getPath();
}
},
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/store/dfs/easy/EasyFormatPlugin.java b/exec/java-exec/src/main/java/org/apache/drill/exec/store/dfs/easy/EasyFormatPlugin.java
index 776d806bea9..1f7bce937b0 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/store/dfs/easy/EasyFormatPlugin.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/store/dfs/easy/EasyFormatPlugin.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -41,7 +41,7 @@
import org.apache.drill.exec.record.CloseableRecordBatch;
import org.apache.drill.exec.record.RecordBatch;
import org.apache.drill.exec.server.DrillbitContext;
-import org.apache.drill.exec.store.ImplicitColumnExplorer;
+import org.apache.drill.exec.store.ColumnExplorer;
import org.apache.drill.exec.store.RecordReader;
import org.apache.drill.exec.store.RecordWriter;
import org.apache.drill.exec.store.StoragePluginOptimizerRule;
@@ -52,7 +52,6 @@
import org.apache.drill.exec.store.dfs.FormatPlugin;
import org.apache.drill.exec.store.schedule.CompleteFileWork;
import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.Path;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
@@ -128,7 +127,7 @@ public abstract RecordReader getRecordReader(FragmentContext context, DrillFileS
@SuppressWarnings("resource")
CloseableRecordBatch getReaderBatch(FragmentContext context, EasySubScan scan) throws ExecutionSetupException {
- final ImplicitColumnExplorer columnExplorer = new ImplicitColumnExplorer(context, scan.getColumns());
+ final ColumnExplorer columnExplorer = new ColumnExplorer(context, scan.getColumns());
if (!columnExplorer.isStarQuery()) {
scan = new EasySubScan(scan.getUserName(), scan.getWorkUnits(), scan.getFormatPlugin(),
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/DirectGroupScan.java b/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/DirectGroupScan.java
index a4b2fadf38f..67b2e5ce68f 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/DirectGroupScan.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/DirectGroupScan.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -33,10 +33,9 @@
@JsonTypeName("direct-scan")
public class DirectGroupScan extends AbstractGroupScan {
-// private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DirectGroupScan.class);
- private final RecordReader reader;
- private final ScanStats stats;
+ protected final RecordReader reader;
+ protected final ScanStats stats;
public DirectGroupScan(RecordReader reader) {
this(reader, ScanStats.TRIVIAL_TABLE);
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/MetadataDirectGroupScan.java b/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/MetadataDirectGroupScan.java
new file mode 100644
index 00000000000..505d68e78be
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/store/direct/MetadataDirectGroupScan.java
@@ -0,0 +1,86 @@
+/*
+ * 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.drill.exec.store.direct;
+
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import org.apache.drill.common.exceptions.ExecutionSetupException;
+import org.apache.drill.common.expression.SchemaPath;
+import org.apache.drill.exec.physical.base.GroupScan;
+import org.apache.drill.exec.physical.base.PhysicalOperator;
+import org.apache.drill.exec.physical.base.ScanStats;
+import org.apache.drill.exec.store.RecordReader;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Represents direct scan based on metadata information.
+ * For example, for parquet files it can be obtained from parquet footer (total row count)
+ * or from parquet metadata files (column counts).
+ * Contains reader, statistics and list of scanned files if present.
+ */
+@JsonTypeName("metadata-direct-scan")
+public class MetadataDirectGroupScan extends DirectGroupScan {
+
+ private final Collection files;
+
+ public MetadataDirectGroupScan(RecordReader reader, Collection files) {
+ super(reader);
+ this.files = files;
+ }
+
+ public MetadataDirectGroupScan(RecordReader reader, Collection files, ScanStats stats) {
+ super(reader, stats);
+ this.files = files;
+ }
+
+ @Override
+ public PhysicalOperator getNewWithChildren(List children) throws ExecutionSetupException {
+ assert children == null || children.isEmpty();
+ return new MetadataDirectGroupScan(reader, files, stats);
+ }
+
+ @Override
+ public GroupScan clone(List columns) {
+ return this;
+ }
+
+ /**
+ *
+ * Returns string representation of group scan data.
+ * Includes list of files if present.
+ *