This is an automated email from the ASF dual-hosted git repository.
godfrey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new 5f2d088a271 [FLINK-27988][table-planner] Let HiveTableSource extend
from SupportStatisticsReport
5f2d088a271 is described below
commit 5f2d088a2713ced5c6ce072db92f4378f73bc739
Author: zhengyunhong.zyh <[email protected]>
AuthorDate: Mon Jun 27 19:58:01 2022 +0800
[FLINK-27988][table-planner] Let HiveTableSource extend from
SupportStatisticsReport
This closes #20084
---
.../file/table/FileSystemTableSource.java | 29 +-
.../flink/connectors/hive/HiveSourceBuilder.java | 2 +-
.../flink/connectors/hive/HiveTableSource.java | 112 +++++-
.../connectors/hive/read/HiveInputFormat.java | 6 +-
.../hive/HiveTableSourceStatisticsReportTest.java | 418 +++++++++++++++++++++
.../flink/formats/csv/CsvFileFormatFactory.java | 56 +--
.../csv/util/CsvFormatStatisticsReportUtil.java | 89 +++++
.../CsvFormatFilesystemStatisticsReportTest.java | 74 +---
.../formats/csv/CsvFormatStatisticsReportTest.java | 92 +++--
.../flink/orc/OrcColumnarRowInputFormat.java | 14 +-
.../org/apache/flink/orc/OrcFileFormatFactory.java | 203 +---------
.../orc/util/OrcFormatStatisticsReportUtil.java | 220 +++++++++++
.../parquet/ParquetColumnarRowInputFormat.java | 13 +-
.../formats/parquet/ParquetFileFormatFactory.java | 268 +------------
.../parquet/ParquetVectorizedInputFormat.java | 4 +-
.../utils/ParquetFormatStatisticsReportUtil.java | 307 +++++++++++++++
.../PushPartitionIntoTableSourceScanRule.java | 2 +-
.../file/table/FileSystemStatisticsReportTest.java | 148 ++++----
.../formats/testcsv/TestCsvFormatFactory.java | 6 +
.../planner/utils/StatisticsReportTestBase.java | 2 +-
20 files changed, 1372 insertions(+), 693 deletions(-)
diff --git
a/flink-connectors/flink-connector-files/src/main/java/org/apache/flink/connector/file/table/FileSystemTableSource.java
b/flink-connectors/flink-connector-files/src/main/java/org/apache/flink/connector/file/table/FileSystemTableSource.java
index 3532372e2e6..7a59323e955 100644
---
a/flink-connectors/flink-connector-files/src/main/java/org/apache/flink/connector/file/table/FileSystemTableSource.java
+++
b/flink-connectors/flink-connector-files/src/main/java/org/apache/flink/connector/file/table/FileSystemTableSource.java
@@ -55,6 +55,9 @@ import org.apache.flink.table.plan.stats.TableStats;
import org.apache.flink.table.types.DataType;
import org.apache.flink.table.utils.PartitionPathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import javax.annotation.Nullable;
import java.io.Serializable;
@@ -86,6 +89,8 @@ public class FileSystemTableSource extends
AbstractFileSystemTable
SupportsReadingMetadata,
SupportsStatisticReport {
+ private static final Logger LOG =
LoggerFactory.getLogger(FileSystemTableSource.class);
+
@Nullable private final DecodingFormat<BulkFormat<RowData,
FileSourceSplit>> bulkReaderFormat;
@Nullable private final DecodingFormat<DeserializationSchema<RowData>>
deserializationFormat;
@@ -359,15 +364,31 @@ public class FileSystemTableSource extends
AbstractFileSystemTable
splits.stream().map(FileSourceSplit::path).collect(Collectors.toList());
if (bulkReaderFormat instanceof
FileBasedStatisticsReportableInputFormat) {
- return ((FileBasedStatisticsReportableInputFormat)
bulkReaderFormat)
- .reportStatistics(files, producedDataType);
+ TableStats tableStats =
+ ((FileBasedStatisticsReportableInputFormat)
bulkReaderFormat)
+ .reportStatistics(files, producedDataType);
+ if (tableStats.equals(TableStats.UNKNOWN)) {
+ return tableStats;
+ }
+ return limit == null
+ ? tableStats
+ : new TableStats(Math.min(limit,
tableStats.getRowCount()));
} else if (deserializationFormat instanceof
FileBasedStatisticsReportableInputFormat) {
- return ((FileBasedStatisticsReportableInputFormat)
deserializationFormat)
- .reportStatistics(files, producedDataType);
+ TableStats tableStats =
+ ((FileBasedStatisticsReportableInputFormat)
deserializationFormat)
+ .reportStatistics(files, producedDataType);
+ if (tableStats.equals(TableStats.UNKNOWN)) {
+ return tableStats;
+ }
+ return limit == null
+ ? tableStats
+ : new TableStats(Math.min(limit,
tableStats.getRowCount()));
} else {
return TableStats.UNKNOWN;
}
} catch (Exception e) {
+ LOG.warn(
+ "Reporting statistics failed for file system table source:
{}", e.getMessage());
return TableStats.UNKNOWN;
}
}
diff --git
a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveSourceBuilder.java
b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveSourceBuilder.java
index 75b6e741533..4afff3b181e 100644
---
a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveSourceBuilder.java
+++
b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveSourceBuilder.java
@@ -315,7 +315,7 @@ public class HiveSourceBuilder {
return (RowType)
producedSchema.toRowDataType().bridgedTo(RowData.class).getLogicalType();
}
- private BulkFormat<RowData, HiveSourceSplit> createDefaultBulkFormat() {
+ protected BulkFormat<RowData, HiveSourceSplit> createDefaultBulkFormat() {
return LimitableBulkFormat.create(
new HiveInputFormat(
new JobConfWrapper(jobConf),
diff --git
a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveTableSource.java
b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveTableSource.java
index cc5d63962c8..cc407efcec0 100644
---
a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveTableSource.java
+++
b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/HiveTableSource.java
@@ -25,11 +25,18 @@ import
org.apache.flink.api.common.typeutils.base.LongSerializer;
import org.apache.flink.api.common.typeutils.base.StringSerializer;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.ReadableConfig;
+import org.apache.flink.connector.file.src.FileSourceSplit;
+import org.apache.flink.connector.file.src.reader.BulkFormat;
import org.apache.flink.connector.file.table.ContinuousPartitionFetcher;
import org.apache.flink.connector.file.table.DefaultPartTimeExtractor;
+import org.apache.flink.connector.file.table.FileSystemConnectorOptions;
import org.apache.flink.connectors.hive.read.HiveContinuousPartitionContext;
import org.apache.flink.connectors.hive.read.HivePartitionFetcherContextBase;
+import org.apache.flink.connectors.hive.read.HiveSourceSplit;
import org.apache.flink.connectors.hive.util.HivePartitionUtils;
+import org.apache.flink.core.fs.Path;
+import
org.apache.flink.formats.parquet.utils.ParquetFormatStatisticsReportUtil;
+import org.apache.flink.orc.util.OrcFormatStatisticsReportUtil;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
@@ -41,14 +48,17 @@ import
org.apache.flink.table.catalog.hive.client.HiveShimLoader;
import org.apache.flink.table.catalog.hive.factories.HiveCatalogFactoryOptions;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.ProviderContext;
+import
org.apache.flink.table.connector.format.FileBasedStatisticsReportableInputFormat;
import org.apache.flink.table.connector.source.DataStreamScanProvider;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.connector.source.ScanTableSource;
import org.apache.flink.table.connector.source.abilities.SupportsLimitPushDown;
import
org.apache.flink.table.connector.source.abilities.SupportsPartitionPushDown;
import
org.apache.flink.table.connector.source.abilities.SupportsProjectionPushDown;
+import
org.apache.flink.table.connector.source.abilities.SupportsStatisticReport;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.TimestampData;
+import org.apache.flink.table.plan.stats.TableStats;
import org.apache.flink.table.types.DataType;
import org.apache.flink.util.Preconditions;
@@ -56,6 +66,8 @@ import
org.apache.hadoop.hive.metastore.api.NoSuchObjectException;
import org.apache.hadoop.hive.metastore.api.Partition;
import org.apache.hadoop.mapred.JobConf;
import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
@@ -64,6 +76,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import static
org.apache.flink.connector.file.table.FileSystemConnectorOptions.PARTITION_TIME_EXTRACTOR_TIMESTAMP_FORMATTER;
import static
org.apache.flink.connectors.hive.HiveOptions.STREAMING_SOURCE_CONSUME_START_OFFSET;
@@ -75,8 +88,10 @@ public class HiveTableSource
implements ScanTableSource,
SupportsPartitionPushDown,
SupportsProjectionPushDown,
- SupportsLimitPushDown {
+ SupportsLimitPushDown,
+ SupportsStatisticReport {
+ private static final Logger LOG =
LoggerFactory.getLogger(HiveTableSource.class);
private static final String HIVE_TRANSFORMATION = "hive";
protected final JobConf jobConf;
@@ -261,6 +276,101 @@ public class HiveTableSource
return source;
}
+ @Override
+ public TableStats reportStatistics() {
+ try {
+ // only support BOUNDED source
+ if (isStreamingSource()) {
+ return TableStats.UNKNOWN;
+ }
+ if
(flinkConf.get(FileSystemConnectorOptions.SOURCE_REPORT_STATISTICS)
+ != FileSystemConnectorOptions.FileStatisticsType.ALL) {
+ return TableStats.UNKNOWN;
+ }
+
+ HiveSourceBuilder sourceBuilder =
+ new HiveSourceBuilder(jobConf, flinkConf, tablePath,
hiveVersion, catalogTable)
+ .setProjectedFields(projectedFields);
+ int threadNum =
+
flinkConf.get(HiveOptions.TABLE_EXEC_HIVE_LOAD_PARTITION_SPLITS_THREAD_NUM);
+ List<HiveTablePartition> hivePartitionsToRead =
+ getAllPartitions(
+ jobConf,
+ hiveVersion,
+ tablePath,
+ catalogTable.getPartitionKeys(),
+ remainingPartitions);
+ BulkFormat<RowData, HiveSourceSplit> defaultBulkFormat =
+ sourceBuilder.createDefaultBulkFormat();
+ List<HiveSourceSplit> inputSplits =
+ HiveSourceFileEnumerator.createInputSplits(
+ 0, hivePartitionsToRead, threadNum, jobConf);
+ if (inputSplits.size() != 0) {
+ TableStats tableStats;
+ if (defaultBulkFormat instanceof
FileBasedStatisticsReportableInputFormat) {
+ // If HiveInputFormat's variable useMapRedReader is false,
Flink will use hadoop
+ // mapRed record to read data.
+ tableStats =
+ ((FileBasedStatisticsReportableInputFormat)
defaultBulkFormat)
+ .reportStatistics(
+ inputSplits.stream()
+ .map(FileSourceSplit::path)
+
.collect(Collectors.toList()),
+
catalogTable.getSchema().toRowDataType());
+ } else {
+ // If HiveInputFormat's variable useMapRedReader is true,
Hive using MapRed
+ // InputFormat to read data.
+ tableStats =
+ getMapRedInputFormatStatistics(
+ inputSplits,
catalogTable.getSchema().toRowDataType());
+ }
+ if (limit == null) {
+ // If no limit push down, return recompute table stats.
+ return tableStats;
+ } else {
+ // If table have limit push down, return new table stats
without table column
+ // stats.
+ long newRowCount = Math.min(limit,
tableStats.getRowCount());
+ return new TableStats(newRowCount);
+ }
+ } else {
+ return new TableStats(0);
+ }
+
+ } catch (Exception e) {
+ LOG.warn("Reporting statistics failed for hive table source: {}",
e.getMessage());
+ return TableStats.UNKNOWN;
+ }
+ }
+
+ private TableStats getMapRedInputFormatStatistics(
+ List<HiveSourceSplit> inputSplits, DataType producedDataType) {
+ // TODO now we assume that one hive external table has only one
storage file format
+ String serializationLib =
+ inputSplits
+ .get(0)
+ .getHiveTablePartition()
+ .getStorageDescriptor()
+ .getSerdeInfo()
+ .getSerializationLib()
+ .toLowerCase();
+ List<Path> files =
+
inputSplits.stream().map(FileSourceSplit::path).collect(Collectors.toList());
+ // Now we only support Parquet, Orc formats.
+ if (serializationLib.contains("parquet")) {
+ return ParquetFormatStatisticsReportUtil.getTableStatistics(
+ files, producedDataType, jobConf,
hiveVersion.startsWith("3"));
+ } else if (serializationLib.contains("orc")) {
+ return OrcFormatStatisticsReportUtil.getTableStatistics(
+ files, producedDataType, jobConf);
+ } else {
+ // Now, only support Orc and Parquet Formats.
+ LOG.info(
+ "Now for hive table source, reporting statistics only
support Orc and Parquet formats.");
+ return TableStats.UNKNOWN;
+ }
+ }
+
/** PartitionFetcher.Context for {@link ContinuousPartitionFetcher}. */
@SuppressWarnings("unchecked")
public static class HiveContinuousPartitionFetcherContext<T extends
Comparable<T>>
diff --git
a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/read/HiveInputFormat.java
b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/read/HiveInputFormat.java
index 3b93bdf413e..d88ba944c67 100644
---
a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/read/HiveInputFormat.java
+++
b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/connectors/hive/read/HiveInputFormat.java
@@ -135,7 +135,7 @@ public class HiveInputFormat implements BulkFormat<RowData,
HiveSourceSplit> {
private BulkFormat<RowData, ? super HiveSourceSplit>
createBulkFormatForSplit(
HiveSourceSplit split) {
if (!useMapRedReader &&
useParquetVectorizedRead(split.getHiveTablePartition())) {
- LOG.debug(String.format("Use native parquet reader for %s.",
split.toString()));
+ LOG.debug(String.format("Use native parquet reader for %s.",
split));
return ParquetColumnarRowInputFormat.createPartitionedFormat(
jobConfWrapper.conf(),
producedRowType,
@@ -146,7 +146,7 @@ public class HiveInputFormat implements BulkFormat<RowData,
HiveSourceSplit> {
hiveVersion.startsWith("3"),
false);
} else if (!useMapRedReader &&
useOrcVectorizedRead(split.getHiveTablePartition())) {
- LOG.debug(String.format("Use native orc reader for %s.",
split.toString()));
+ LOG.debug(String.format("Use native orc reader for %s.", split));
return createOrcFormat();
} else {
if (useMapRedReader) {
@@ -157,7 +157,7 @@ public class HiveInputFormat implements BulkFormat<RowData,
HiveSourceSplit> {
LOG.debug(
String.format(
"Use MapReduce RecordReader reader because the
conditions of vectorized read are not met for %s.",
- split.toString()));
+ split));
}
return new HiveMapRedBulkFormat();
}
diff --git
a/flink-connectors/flink-connector-hive/src/test/java/org/apache/flink/connectors/hive/HiveTableSourceStatisticsReportTest.java
b/flink-connectors/flink-connector-hive/src/test/java/org/apache/flink/connectors/hive/HiveTableSourceStatisticsReportTest.java
new file mode 100644
index 00000000000..4917d50bda3
--- /dev/null
+++
b/flink-connectors/flink-connector-hive/src/test/java/org/apache/flink/connectors/hive/HiveTableSourceStatisticsReportTest.java
@@ -0,0 +1,418 @@
+/*
+ * 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.flink.connectors.hive;
+
+import org.apache.flink.table.api.SqlDialect;
+import org.apache.flink.table.catalog.hive.HiveCatalog;
+import org.apache.flink.table.catalog.hive.HiveTestUtils;
+import org.apache.flink.table.plan.stats.ColumnStats;
+import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.planner.plan.stats.FlinkStatistic;
+import org.apache.flink.table.planner.utils.StatisticsReportTestBase;
+import org.apache.flink.table.types.DataType;
+import org.apache.flink.table.utils.DateTimeUtils;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test for statistics functionality in {@link HiveTableSource}. */
+public class HiveTableSourceStatisticsReportTest extends
StatisticsReportTestBase {
+
+ private static HiveCatalog hiveCatalog;
+ private static final String catalogName = "hive";
+ private static final String dbName = "db1";
+ private static final String sourceTable = "sourceTable";
+
+ @BeforeEach
+ public void setup(@TempDir File file) throws Exception {
+ super.setup(file);
+ hiveCatalog = HiveTestUtils.createHiveCatalog();
+ hiveCatalog.open();
+
+ tEnv.getConfig().setSqlDialect(SqlDialect.HIVE);
+ tEnv.registerCatalog(catalogName, hiveCatalog);
+ tEnv.useCatalog(catalogName);
+ tEnv.executeSql(String.format("create database %s", dbName));
+
+ tEnv.executeSql(
+ String.format(
+ "create table %s.%s.%s ( %s )",
+ catalogName,
+ dbName,
+ sourceTable,
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+
+ DataType dataType =
+ tEnv.from(String.format("%s.%s.%s", catalogName, dbName,
sourceTable))
+ .getResolvedSchema()
+ .toPhysicalRowDataType();
+ tEnv.fromValues(dataType, getData())
+ .executeInsert(String.format("%s.%s.%s", catalogName, dbName,
sourceTable))
+ .await();
+ }
+
+ @AfterEach
+ public void after() {
+ super.after();
+ if (null != hiveCatalog) {
+ hiveCatalog.close();
+ }
+ }
+
+ @Override
+ protected String[] properties() {
+ return new String[0];
+ }
+
+ @Test
+ public void testMapRedCsvFormatHiveTableSourceStatisticsReport() {
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ String.format("select * from %s.%s.%s", catalogName,
dbName, sourceTable));
+ assertThat(statistic.getTableStats()).isEqualTo(TableStats.UNKNOWN);
+ }
+
+ @Test
+ public void testFlinkOrcFormatHiveTableSourceStatisticsReport() throws
Exception {
+ tEnv.executeSql(
+ String.format(
+ "create table hive.db1.orcTable ( %s ) stored as orc",
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+ tEnv.executeSql(
+ String.format(
+ "insert into hive.db1.orcTable select * from
%s.%s.%s",
+ catalogName, dbName, sourceTable))
+ .await();
+
+ // Hive to read Orc file
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ "select * from hive.db1.orcTable where f_smallint >
100");
+ assertHiveTableOrcFormatTableStatsEquals(statistic.getTableStats(), 3,
1L);
+ }
+
+ @Test
+ public void testFlinkParquetFormatHiveTableSourceStatisticsReport() throws
Exception {
+ tEnv.executeSql(
+ String.format(
+ "create table hive.db1.parquetTable ( %s ) stored as
parquet",
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+ tEnv.executeSql(
+ String.format(
+ "insert into hive.db1.parquetTable select *
from %s.%s.%s",
+ catalogName, dbName, sourceTable))
+ .await();
+
+ // Hive to read Parquet file
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ "select * from hive.db1.parquetTable where f_smallint
> 100");
+
assertHiveTableParquetFormatTableStatsEquals(statistic.getTableStats(), 3, 1L);
+ }
+
+ @Test
+ public void testMapRedOrcFormatHiveTableSourceStatisticsReport() throws
Exception {
+ // Use mapRed parquet format.
+
tEnv.getConfig().set(HiveOptions.TABLE_EXEC_HIVE_FALLBACK_MAPRED_READER, true);
+ tEnv.executeSql(
+ String.format(
+ "create table hive.db1.orcTable ( %s ) stored as orc",
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+ tEnv.executeSql(
+ String.format(
+ "insert into hive.db1.orcTable select * from
%s.%s.%s",
+ catalogName, dbName, sourceTable))
+ .await();
+
+ // Hive to read Orc file
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ "select * from hive.db1.orcTable where f_smallint >
100");
+ assertHiveTableOrcFormatTableStatsEquals(statistic.getTableStats(), 3,
1L);
+ }
+
+ @Test
+ public void testMapRedParquetFormatHiveTableSourceStatisticsReport()
throws Exception {
+ // Use mapRed parquet format.
+
tEnv.getConfig().set(HiveOptions.TABLE_EXEC_HIVE_FALLBACK_MAPRED_READER, true);
+ tEnv.executeSql(
+ String.format(
+ "create table hive.db1.parquetTable ( %s ) stored as
parquet",
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+ tEnv.executeSql(
+ String.format(
+ "insert into hive.db1.parquetTable select *
from %s.%s.%s",
+ catalogName, dbName, sourceTable))
+ .await();
+
+ // Hive to read Parquet file.
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ "select * from hive.db1.parquetTable where f_smallint
> 100");
+
assertHiveTableParquetFormatTableStatsEquals(statistic.getTableStats(), 3, 1L);
+ }
+
+ @Test
+ public void testHiveTableSourceWithLimitPushDown() {
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ String.format(
+ "select * from %s.%s.%s limit 1",
+ catalogName, dbName, sourceTable));
+ assertThat(statistic.getTableStats()).isEqualTo(new TableStats(1));
+ }
+
+ @Test
+ public void testHiveTableSourceWithoutData() {
+ tEnv.executeSql(
+ String.format(
+ "create table hive.db1.orcTable ( %s ) stored as orc",
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ String.format("select * from %s.%s.%s", catalogName,
dbName, "orcTable"));
+ assertThat(statistic.getTableStats()).isEqualTo(new TableStats(0));
+ }
+
+ @Test
+ public void testHiveTableSourceWithoutDataWithLimitPushDown() {
+ tEnv.executeSql(
+ String.format(
+ "create table hive.db1.orcTable ( %s ) stored as orc",
+ String.join(", ",
ddlTypesMapToStringList(ddlTypesMap()))));
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan(
+ String.format(
+ "select * from %s.%s.%s limit 1", catalogName,
dbName, "orcTable"));
+ assertThat(statistic.getTableStats()).isEqualTo(new TableStats(0));
+ }
+
+ @Override
+ protected Map<String, String> ddlTypesMap() {
+ // hive table ddl now don't support type: TIMESTAMP(3), TIMESTAMP(9),
TIMESTAMP WITHOUT TIME
+ // ZONE, TIMESTAMP WITH LOCAL TIME ZONE AND ROW. So we remove these
types.
+ Map<String, String> ddlTypesMap = super.ddlTypesMap();
+ String timestampTypeName = ddlTypesMap.remove("timestamp(3)");
+ ddlTypesMap.remove("timestamp(9)");
+ ddlTypesMap.remove("timestamp without time zone");
+ ddlTypesMap.remove("timestamp with local time zone");
+ String binaryTypeName = ddlTypesMap.remove("binary(1)");
+ ddlTypesMap.remove("varbinary(1)");
+ ddlTypesMap.remove("time");
+ String rowName = ddlTypesMap.remove("row<col1 string, col2 int>");
+ ddlTypesMap.put("timestamp", timestampTypeName);
+ ddlTypesMap.put("binary", binaryTypeName);
+ ddlTypesMap.put("STRUCT<col1 : string, col2 : int>", rowName);
+
+ return ddlTypesMap;
+ }
+
+ @Override
+ protected Map<String, List<Object>> getDataMap() {
+ // hive table ddl now don't support type: TIMESTAMP(3), TIMESTAMP(9),
TIMESTAMP WITHOUT TIME
+ // ZONE, TIMESTAMP WITH LOCAL TIME ZONE AND ROW. So we remove these
types related data.
+ Map<String, List<Object>> dataMap = super.getDataMap();
+ List<Object> timestampData = dataMap.remove("timestamp(3)");
+ dataMap.remove("timestamp(9)");
+ dataMap.remove("timestamp without time zone");
+ dataMap.remove("timestamp with local time zone");
+ List<Object> binaryData = dataMap.remove("binary(1)");
+ dataMap.remove("varbinary(1)");
+ dataMap.remove("time");
+ List<Object> rowData = dataMap.remove("row<col1 string, col2 int>");
+ dataMap.put("timestamp", timestampData);
+ dataMap.put("binary", binaryData);
+ dataMap.put("row<col1 string, col2 int>", rowData);
+
+ return dataMap;
+ }
+
+ private static void assertHiveTableOrcFormatTableStatsEquals(
+ TableStats tableStats, int expectedRowCount, long nullCount) {
+ Map<String, ColumnStats> expectedColumnStatsMap = new HashMap<>();
+ expectedColumnStatsMap.put(
+ "f_boolean", new
ColumnStats.Builder().setNullCount(nullCount).build());
+ expectedColumnStatsMap.put(
+ "f_tinyint",
+ new
ColumnStats.Builder().setMax(3L).setMin(1L).setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_smallint",
+ new
ColumnStats.Builder().setMax(128L).setMin(100L).setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_int",
+ new ColumnStats.Builder()
+ .setMax(45536L)
+ .setMin(31000L)
+ .setNullCount(nullCount)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_bigint",
+ new ColumnStats.Builder()
+ .setMax(1238123899121L)
+ .setMin(1238123899000L)
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_float",
+ new ColumnStats.Builder()
+ .setMax(33.33300018310547D)
+ .setMin(33.31100082397461D)
+ .setNullCount(nullCount)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_double",
+ new
ColumnStats.Builder().setMax(10.1D).setMin(1.1D).setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_string",
+ new
ColumnStats.Builder().setMax("def").setMin("abcd").setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_decimal5",
+ new ColumnStats.Builder()
+ .setMax(new BigDecimal("223.45"))
+ .setMin(new BigDecimal("123.45"))
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_decimal14",
+ new ColumnStats.Builder()
+ .setMax(new BigDecimal("123333333355.33"))
+ .setMin(new BigDecimal("123333333333.33"))
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_decimal38",
+ new ColumnStats.Builder()
+ .setMax(new
BigDecimal("123433343334333433343334333433343334.34"))
+ .setMin(new
BigDecimal("123433343334333433343334333433343334.33"))
+ .setNullCount(nullCount)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_date",
+ new ColumnStats.Builder()
+ .setMax(Date.valueOf("1990-10-16"))
+ .setMin(Date.valueOf("1990-10-14"))
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_timestamp3",
+ new ColumnStats.Builder()
+ .setMax(
+ DateTimeUtils.parseTimestampData("1990-10-16
12:12:43.123", 3)
+ .toTimestamp())
+ .setMin(
+ DateTimeUtils.parseTimestampData("1990-10-14
12:12:43.123", 3)
+ .toTimestamp())
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put("f_binary", null);
+ expectedColumnStatsMap.put("f_array", null);
+ expectedColumnStatsMap.put("f_map", null);
+ expectedColumnStatsMap.put("f_row", null);
+
+ assertThat(tableStats).isEqualTo(new TableStats(expectedRowCount,
expectedColumnStatsMap));
+ }
+
+ private static void assertHiveTableParquetFormatTableStatsEquals(
+ TableStats tableStats, int expectedRowCount, long nullCount) {
+ Map<String, ColumnStats> expectedColumnStatsMap = new HashMap<>();
+ expectedColumnStatsMap.put(
+ "f_boolean", new
ColumnStats.Builder().setNullCount(nullCount).build());
+ expectedColumnStatsMap.put(
+ "f_tinyint",
+ new
ColumnStats.Builder().setMax(3).setMin(1).setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_smallint",
+ new
ColumnStats.Builder().setMax(128).setMin(100).setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_int",
+ new ColumnStats.Builder()
+ .setMax(45536)
+ .setMin(31000)
+ .setNullCount(nullCount)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_bigint",
+ new ColumnStats.Builder()
+ .setMax(1238123899121L)
+ .setMin(1238123899000L)
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_float",
+ new ColumnStats.Builder()
+ .setMax(33.333F)
+ .setMin(33.311F)
+ .setNullCount(nullCount)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_double",
+ new
ColumnStats.Builder().setMax(10.1D).setMin(1.1D).setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_string",
+ new
ColumnStats.Builder().setMax("def").setMin("abcd").setNullCount(0L).build());
+ expectedColumnStatsMap.put(
+ "f_decimal5",
+ new ColumnStats.Builder()
+ .setMax(new BigDecimal("223.45"))
+ .setMin(new BigDecimal("123.45"))
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_decimal14",
+ new ColumnStats.Builder()
+ .setMax(new BigDecimal("123333333355.33"))
+ .setMin(new BigDecimal("123333333333.33"))
+ .setNullCount(0L)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_decimal38",
+ new ColumnStats.Builder()
+ .setMax(new
BigDecimal("123433343334333433343334333433343334.34"))
+ .setMin(new
BigDecimal("123433343334333433343334333433343334.33"))
+ .setNullCount(nullCount)
+ .build());
+ expectedColumnStatsMap.put(
+ "f_date",
+ new ColumnStats.Builder()
+ .setMax(Date.valueOf("1990-10-16"))
+ .setMin(Date.valueOf("1990-10-14"))
+ .setNullCount(0L)
+ .build());
+ // Now parquet store timestamp as type int96, and int96 now not
support statistics, so
+ // timestamp not support statistics now.
+ expectedColumnStatsMap.put("f_timestamp3", null);
+
+ expectedColumnStatsMap.put("f_binary", new
ColumnStats.Builder().setNullCount(0L).build());
+ expectedColumnStatsMap.put("f_array", null);
+ expectedColumnStatsMap.put("f_map", null);
+ expectedColumnStatsMap.put("f_row", null);
+ assertThat(tableStats).isEqualTo(new TableStats(expectedRowCount,
expectedColumnStatsMap));
+ }
+}
diff --git
a/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/CsvFileFormatFactory.java
b/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/CsvFileFormatFactory.java
index 2054c182a01..391e1c49f20 100644
---
a/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/CsvFileFormatFactory.java
+++
b/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/CsvFileFormatFactory.java
@@ -30,11 +30,10 @@ import
org.apache.flink.connector.file.src.reader.BulkFormat;
import org.apache.flink.connector.file.table.factories.BulkReaderFormatFactory;
import org.apache.flink.connector.file.table.factories.BulkWriterFormatFactory;
import org.apache.flink.connector.file.table.format.BulkDecodingFormat;
-import org.apache.flink.core.fs.FileStatus;
-import org.apache.flink.core.fs.FileSystem;
import org.apache.flink.core.fs.Path;
import org.apache.flink.formats.common.Converter;
import
org.apache.flink.formats.csv.RowDataToCsvConverters.RowDataToCsvConverter;
+import org.apache.flink.formats.csv.util.CsvFormatStatisticsReportUtil;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.Projection;
import org.apache.flink.table.connector.format.EncodingFormat;
@@ -55,11 +54,6 @@ import
org.apache.flink.shaded.jackson2.com.fasterxml.jackson.dataformat.csv.Csv
import org.apache.commons.lang3.StringEscapeUtils;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -154,53 +148,7 @@ public class CsvFileFormatFactory implements
BulkReaderFormatFactory, BulkWriter
@Override
public TableStats reportStatistics(List<Path> files, DataType
producedDataType) {
- // For Csv format, it's a heavy operation to obtain accurate
statistics by scanning all
- // files. So, We obtain the estimated statistics by sampling, the
specific way is to
- // sample the first 100 lines and calculate their row size, then
compare row size with
- // total file size to get the estimated row count.
- final int totalSampleLineCnt = 100;
- try {
- long totalFileSize = 0;
- int sampledRowCnt = 0;
- long sampledRowSize = 0;
- for (Path file : files) {
- FileSystem fs = FileSystem.get(file.toUri());
- FileStatus status = fs.getFileStatus(file);
- totalFileSize += status.getLen();
-
- // sample the line size
- if (sampledRowCnt < totalSampleLineCnt) {
- try (InputStreamReader isr =
- new InputStreamReader(
- Files.newInputStream(
- new
File(file.toUri()).toPath()));
- BufferedReader br = new BufferedReader(isr)) {
- String line;
- while (sampledRowCnt < totalSampleLineCnt
- && (line = br.readLine()) != null) {
- sampledRowCnt += 1;
- sampledRowSize +=
-
(line.getBytes(StandardCharsets.UTF_8).length + 1);
- }
- }
- }
- }
-
- // If line break is "\r\n", br.readLine() will ignore '\n'
which make sampledRowSize
- // smaller than totalFileSize. This will influence test result.
- if (sampledRowCnt < totalSampleLineCnt) {
- sampledRowSize = totalFileSize;
- }
- if (sampledRowSize == 0) {
- return TableStats.UNKNOWN;
- }
-
- int realSampledLineCnt = Math.min(totalSampleLineCnt,
sampledRowCnt);
- long estimatedRowCount = totalFileSize * realSampledLineCnt /
sampledRowSize;
- return new TableStats(estimatedRowCount);
- } catch (Exception e) {
- return TableStats.UNKNOWN;
- }
+ return CsvFormatStatisticsReportUtil.getTableStatistics(files);
}
}
diff --git
a/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/util/CsvFormatStatisticsReportUtil.java
b/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/util/CsvFormatStatisticsReportUtil.java
new file mode 100644
index 00000000000..bb0fa317acc
--- /dev/null
+++
b/flink-formats/flink-csv/src/main/java/org/apache/flink/formats/csv/util/CsvFormatStatisticsReportUtil.java
@@ -0,0 +1,89 @@
+/*
+ * 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.flink.formats.csv.util;
+
+import org.apache.flink.core.fs.FileStatus;
+import org.apache.flink.core.fs.FileSystem;
+import org.apache.flink.core.fs.Path;
+import org.apache.flink.table.plan.stats.TableStats;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
+
+/** Utils for Csv format statistics report. */
+public class CsvFormatStatisticsReportUtil {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(CsvFormatStatisticsReportUtil.class);
+
+ public static TableStats getTableStatistics(List<Path> files) {
+ // For Csv format, it's a heavy operation to obtain accurate
statistics by scanning all
+ // files. So, We obtain the estimated statistics by sampling, the
specific way is to
+ // sample the first 100 lines and calculate their row size, then
compare row size with
+ // total file size to get the estimated row count.
+ final int totalSampleLineCnt = 100;
+ try {
+ long totalFileSize = 0;
+ int sampledRowCnt = 0;
+ long sampledRowSize = 0;
+ for (Path file : files) {
+ FileSystem fs = FileSystem.get(file.toUri());
+ FileStatus status = fs.getFileStatus(file);
+ totalFileSize += status.getLen();
+
+ // sample the line size
+ if (sampledRowCnt < totalSampleLineCnt) {
+ try (InputStreamReader isr =
+ new InputStreamReader(
+ Files.newInputStream(new
File(file.toUri()).toPath()));
+ BufferedReader br = new BufferedReader(isr)) {
+ String line;
+ while (sampledRowCnt < totalSampleLineCnt
+ && (line = br.readLine()) != null) {
+ sampledRowCnt += 1;
+ sampledRowSize +=
(line.getBytes(StandardCharsets.UTF_8).length + 1);
+ }
+ }
+ }
+ }
+
+ // If line break is "\r\n", br.readLine() will ignore '\n' which
make sampledRowSize
+ // smaller than totalFileSize. This will influence test result.
+ if (sampledRowCnt < totalSampleLineCnt) {
+ sampledRowSize = totalFileSize;
+ }
+ if (sampledRowSize == 0) {
+ return TableStats.UNKNOWN;
+ }
+
+ int realSampledLineCnt = Math.min(totalSampleLineCnt,
sampledRowCnt);
+ long estimatedRowCount = totalFileSize * realSampledLineCnt /
sampledRowSize;
+ return new TableStats(estimatedRowCount);
+ } catch (Exception e) {
+ LOG.warn("Reporting statistics failed for Csv format: {}",
e.getMessage());
+ return TableStats.UNKNOWN;
+ }
+ }
+}
diff --git
a/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatFilesystemStatisticsReportTest.java
b/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatFilesystemStatisticsReportTest.java
index 476319d6ce4..72ecbeeabbc 100644
---
a/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatFilesystemStatisticsReportTest.java
+++
b/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatFilesystemStatisticsReportTest.java
@@ -18,90 +18,36 @@
package org.apache.flink.formats.csv;
-import org.apache.flink.table.api.TableConfig;
-import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.plan.stats.TableStats;
-import org.apache.flink.table.planner.plan.schema.TableSourceTable;
import org.apache.flink.table.planner.plan.stats.FlinkStatistic;
-import org.apache.flink.table.planner.utils.BatchTableTestUtil;
-import org.apache.flink.table.planner.utils.TableTestBase;
-import org.apache.flink.table.planner.utils.TableTestUtil;
-import org.apache.flink.util.Preconditions;
+import org.apache.flink.table.types.DataType;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelVisitor;
-import org.apache.calcite.rel.core.TableScan;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for statistics functionality in {@link CsvFormatFactory} in the case
of file system source.
*/
-public class CsvFormatFilesystemStatisticsReportTest extends TableTestBase {
- private BatchTableTestUtil util;
- private TableEnvironment tEnv;
- @TempDir private static File path;
+public class CsvFormatFilesystemStatisticsReportTest extends
CsvFormatStatisticsReportTest {
@BeforeEach
- public void setup() throws IOException {
- util = batchTestUtil(TableConfig.getDefault());
- tEnv = util.getTableEnv();
- String pathName = writeData(path, Arrays.asList("1,1,hi", "2,1,hello",
"3,2,hello world"));
-
- String ddl =
- String.format(
- "CREATE TABLE sourceTable (\n"
- + " a bigint,\n"
- + " b int,\n"
- + " c varchar\n"
- + ") with (\n"
- + " 'connector' = 'filesystem',"
- + " 'format' = 'csv',"
- + " 'path' = '%s')",
- pathName);
- tEnv.executeSql(ddl);
+ public void setup(@TempDir File file) throws Exception {
+ super.setup(file);
}
@Test
- public void testCsvFileSystemStatisticsReport() {
+ public void testCsvFileSystemStatisticsReport()
+ throws ExecutionException, InterruptedException {
+ // insert data and get statistics by get plan.
+ DataType dataType =
tEnv.from("sourceTable").getResolvedSchema().toPhysicalRowDataType();
+ tEnv.fromValues(dataType,
getData()).executeInsert("sourceTable").await();
FlinkStatistic statistic = getStatisticsFromOptimizedPlan("select *
from sourceTable");
assertThat(statistic.getTableStats()).isEqualTo(new TableStats(3));
}
-
- private String writeData(File path, List<String> data) throws IOException {
- String file = path.getAbsolutePath() + "/00-00.tmp";
- Files.write(new File(file).toPath(), String.join("\n",
data).getBytes());
- return file;
- }
-
- private FlinkStatistic getStatisticsFromOptimizedPlan(String sql) {
- RelNode relNode = TableTestUtil.toRelNode(tEnv.sqlQuery(sql));
- RelNode optimized = util.getPlanner().optimize(relNode);
- FlinkStatisticVisitor visitor = new FlinkStatisticVisitor();
- visitor.go(optimized);
- return visitor.result;
- }
-
- private static class FlinkStatisticVisitor extends RelVisitor {
- private FlinkStatistic result = null;
-
- @Override
- public void visit(RelNode node, int ordinal, RelNode parent) {
- if (node instanceof TableScan) {
- Preconditions.checkArgument(result == null);
- TableSourceTable table = (TableSourceTable) node.getTable();
- result = table.getStatistic();
- }
- super.visit(node, ordinal, parent);
- }
- }
}
diff --git
a/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatStatisticsReportTest.java
b/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatStatisticsReportTest.java
index 6a166e2d0b2..a96f938dc7f 100644
---
a/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatStatisticsReportTest.java
+++
b/flink-formats/flink-csv/src/test/java/org/apache/flink/formats/csv/CsvFormatStatisticsReportTest.java
@@ -21,9 +21,12 @@ package org.apache.flink.formats.csv;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.core.fs.Path;
import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.planner.utils.StatisticsReportTestBase;
+import org.apache.flink.table.types.DataType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
@@ -33,61 +36,58 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/** Test for statistics functionality in {@link CsvFormatFactory}. */
-public class CsvFormatStatisticsReportTest {
+public class CsvFormatStatisticsReportTest extends StatisticsReportTestBase {
private static CsvFileFormatFactory.CsvBulkDecodingFormat
csvBulkDecodingFormat;
@BeforeEach
- public void setup() {
+ public void setup(@TempDir File file) throws Exception {
+ super.setup(file);
+ createFileSystemSource();
Configuration configuration = new Configuration();
csvBulkDecodingFormat = new
CsvFileFormatFactory.CsvBulkDecodingFormat(configuration);
}
- @Test
- public void testCsvFormatStatsReportWithSingleFile() throws IOException {
- String fileContent =
- "#description of the data\n"
- + "header1|header2|header3|\n"
- + "this is|1|2.0|\n"
- + "//a comment\n"
- + "a test|3|4.0|\n"
- + "#next|5|6.0|\n";
-
- Path tempFile = createTempFile(fileContent);
+ @Override
+ protected String[] properties() {
+ List<String> ret = new ArrayList<>();
+ ret.add("'format' = 'csv'");
+ return ret.toArray(new String[0]);
+ }
+ @Test
+ public void testCsvFormatStatsReportWithSingleFile() throws Exception {
+ // insert data and get statistics.
+ DataType dataType =
tEnv.from("sourceTable").getResolvedSchema().toPhysicalRowDataType();
+ tEnv.fromValues(dataType,
getData()).executeInsert("sourceTable").await();
+ assertThat(folder.listFiles()).isNotNull().hasSize(1);
+ File[] files = folder.listFiles();
+ assert files != null;
TableStats tableStats =
-
csvBulkDecodingFormat.reportStatistics(Collections.singletonList(tempFile),
null);
- assertThat(tableStats).isEqualTo(new TableStats(6));
+ csvBulkDecodingFormat.reportStatistics(
+ Collections.singletonList(new
Path(files[0].toURI().toString())), null);
+ assertThat(tableStats).isEqualTo(new TableStats(3));
}
@Test
- public void testCsvFormatStatsReportWithMultiFile() throws IOException {
- String fileContent1 =
- "#description of the data\r\n"
- + "header1|header2|header3|\r\n"
- + "this is|1|2.0|\r\n"
- + "//a comment\r\n"
- + "a test|3|4.0|\r\n"
- + "#next|5|6.0|\r\n";
- String fileContent2 =
- "#description of the data\r\n"
- + "header1|header2|header3|\r\n"
- + "this is|1|2.0|\r\n"
- + "//a comment\r\n"
- + "a test|3|4.0|\r\n"
- + "#next|5|6.0|\r\n";
- Path tempFile1 = createTempFile(fileContent1);
- Path tempFile2 = createTempFile(fileContent2);
- List<Path> files = new ArrayList<>();
- files.add(tempFile1);
- files.add(tempFile2);
-
- TableStats tableStats = csvBulkDecodingFormat.reportStatistics(files,
null);
- assertThat(tableStats).isEqualTo(new TableStats(12));
+ public void testCsvFormatStatsReportWithMultiFile() throws Exception {
+ // insert data and get statistics.
+ DataType dataType =
tEnv.from("sourceTable").getResolvedSchema().toPhysicalRowDataType();
+ tEnv.fromValues(dataType,
getData()).executeInsert("sourceTable").await();
+ tEnv.fromValues(dataType,
getData()).executeInsert("sourceTable").await();
+ assertThat(folder.listFiles()).isNotNull().hasSize(2);
+ File[] files = folder.listFiles();
+ List<Path> paths = new ArrayList<>();
+ assert files != null;
+ paths.add(new Path(files[0].toURI().toString()));
+ paths.add(new Path(files[1].toURI().toString()));
+ TableStats tableStats = csvBulkDecodingFormat.reportStatistics(paths,
null);
+ assertThat(tableStats).isEqualTo(new TableStats(6));
}
@Test
@@ -109,6 +109,22 @@ public class CsvFormatStatisticsReportTest {
assertThat(tableStats).isEqualTo(TableStats.UNKNOWN);
}
+ @Override
+ protected Map<String, String> ddlTypesMap() {
+ // now Csv format don't support type MAP, so we remove this type.
+ Map<String, String> ddlTypes = super.ddlTypesMap();
+ ddlTypes.remove("map<string, int>");
+ return ddlTypes;
+ }
+
+ @Override
+ protected Map<String, List<Object>> getDataMap() {
+ // now Csv format don't support type MAP, so we remove data belong to
this type.
+ Map<String, List<Object>> dataMap = super.getDataMap();
+ dataMap.remove("map<string, int>");
+ return dataMap;
+ }
+
private static Path createTempFile(String content) throws IOException {
File tempFile = File.createTempFile("test_contents", "tmp");
tempFile.deleteOnExit();
diff --git
a/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcColumnarRowInputFormat.java
b/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcColumnarRowInputFormat.java
index d719a1c6daa..d7963db1102 100644
---
a/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcColumnarRowInputFormat.java
+++
b/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcColumnarRowInputFormat.java
@@ -23,13 +23,18 @@ import org.apache.flink.connector.file.src.FileSourceSplit;
import org.apache.flink.connector.file.src.util.Pool;
import org.apache.flink.connector.file.table.ColumnarRowIterator;
import org.apache.flink.connector.file.table.PartitionFieldExtractor;
+import org.apache.flink.core.fs.Path;
import org.apache.flink.orc.shim.OrcShim;
+import org.apache.flink.orc.util.OrcFormatStatisticsReportUtil;
import org.apache.flink.orc.vector.ColumnBatchFactory;
import org.apache.flink.orc.vector.OrcVectorizedBatchWrapper;
+import
org.apache.flink.table.connector.format.FileBasedStatisticsReportableInputFormat;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.columnar.ColumnarRowData;
import org.apache.flink.table.data.columnar.vector.ColumnVector;
import org.apache.flink.table.data.columnar.vector.VectorizedColumnBatch;
+import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.types.DataType;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RowType;
@@ -56,7 +61,8 @@ import static
org.apache.flink.orc.vector.AbstractOrcColumnVector.createFlinkVec
* different and types of extra fields need to be added.
*/
public class OrcColumnarRowInputFormat<BatchT, SplitT extends FileSourceSplit>
- extends AbstractOrcFileInputFormat<RowData, BatchT, SplitT> {
+ extends AbstractOrcFileInputFormat<RowData, BatchT, SplitT>
+ implements FileBasedStatisticsReportableInputFormat {
private static final long serialVersionUID = 1L;
@@ -94,6 +100,12 @@ public class OrcColumnarRowInputFormat<BatchT, SplitT
extends FileSourceSplit>
return this.producedTypeInfo;
}
+ @Override
+ public TableStats reportStatistics(List<Path> files, DataType
producedDataType) {
+ return OrcFormatStatisticsReportUtil.getTableStatistics(
+ files, producedDataType,
hadoopConfigWrapper.getHadoopConfig());
+ }
+
// ------------------------------------------------------------------------
/** One batch of ORC columnar vectors and Flink column vectors. */
diff --git
a/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcFileFormatFactory.java
b/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcFileFormatFactory.java
index 02057fd66be..5eb37873265 100644
---
a/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcFileFormatFactory.java
+++
b/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/OrcFileFormatFactory.java
@@ -29,6 +29,7 @@ import
org.apache.flink.connector.file.table.factories.BulkWriterFormatFactory;
import org.apache.flink.connector.file.table.format.BulkDecodingFormat;
import org.apache.flink.core.fs.Path;
import org.apache.flink.orc.shim.OrcShim;
+import org.apache.flink.orc.util.OrcFormatStatisticsReportUtil;
import org.apache.flink.orc.vector.RowDataVectorizer;
import org.apache.flink.orc.writer.OrcBulkWriterFactory;
import org.apache.flink.table.connector.ChangelogMode;
@@ -43,34 +44,18 @@ import
org.apache.flink.table.data.columnar.vector.VectorizedColumnBatch;
import org.apache.flink.table.expressions.Expression;
import org.apache.flink.table.expressions.ResolvedExpression;
import org.apache.flink.table.factories.DynamicTableFactory;
-import org.apache.flink.table.plan.stats.ColumnStats;
import org.apache.flink.table.plan.stats.TableStats;
import org.apache.flink.table.types.DataType;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RowType;
import org.apache.hadoop.conf.Configuration;
-import org.apache.orc.ColumnStatistics;
-import org.apache.orc.DateColumnStatistics;
-import org.apache.orc.DecimalColumnStatistics;
-import org.apache.orc.DoubleColumnStatistics;
-import org.apache.orc.IntegerColumnStatistics;
-import org.apache.orc.OrcConf;
-import org.apache.orc.OrcFile;
-import org.apache.orc.Reader;
-import org.apache.orc.StringColumnStatistics;
-import org.apache.orc.TimestampColumnStatistics;
import org.apache.orc.TypeDescription;
-import org.apache.orc.impl.ColumnStatisticsImpl;
-import java.io.IOException;
-import java.sql.Date;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -103,6 +88,13 @@ public class OrcFileFormatFactory implements
BulkReaderFormatFactory, BulkWriter
return orcProperties;
}
+ private static Configuration getOrcConfiguration(ReadableConfig
formatOptions) {
+ Properties properties = getOrcProperties(formatOptions);
+ Configuration hadoopConf = new Configuration();
+ properties.forEach((k, v) -> hadoopConf.set(k.toString(),
v.toString()));
+ return hadoopConf;
+ }
+
@Override
public BulkDecodingFormat<RowData> createDecodingFormat(
DynamicTableFactory.Context context, ReadableConfig formatOptions)
{
@@ -165,13 +157,9 @@ public class OrcFileFormatFactory implements
BulkReaderFormatFactory, BulkWriter
}
}
- Properties properties = getOrcProperties(formatOptions);
- Configuration conf = new Configuration();
- properties.forEach((k, v) -> conf.set(k.toString(), v.toString()));
-
return OrcColumnarRowInputFormat.createPartitionedFormat(
OrcShim.defaultShim(),
- conf,
+ getOrcConfiguration(formatOptions),
(RowType) producedDataType.getLogicalType(),
Collections.emptyList(),
null,
@@ -193,177 +181,8 @@ public class OrcFileFormatFactory implements
BulkReaderFormatFactory, BulkWriter
@Override
public TableStats reportStatistics(List<Path> files, DataType
producedDataType) {
- try {
- Properties properties = getOrcProperties(formatOptions);
- Configuration hadoopConfig = new Configuration();
- properties.forEach((k, v) -> hadoopConfig.set(k.toString(),
v.toString()));
-
- long rowCount = 0;
- Map<String, ColumnStatistics> columnStatisticsMap = new
HashMap<>();
- RowType producedRowType = (RowType)
producedDataType.getLogicalType();
- for (Path file : files) {
- rowCount +=
- updateStatistics(
- hadoopConfig, file, columnStatisticsMap,
producedRowType);
- }
-
- Map<String, ColumnStats> columnStatsMap =
- convertToColumnStats(rowCount, columnStatisticsMap,
producedRowType);
-
- return new TableStats(rowCount, columnStatsMap);
- } catch (Exception e) {
- return TableStats.UNKNOWN;
- }
- }
-
- private long updateStatistics(
- Configuration hadoopConf,
- Path file,
- Map<String, ColumnStatistics> columnStatisticsMap,
- RowType producedRowType)
- throws IOException {
- org.apache.hadoop.fs.Path path = new
org.apache.hadoop.fs.Path(file.toUri());
- Reader reader =
- OrcFile.createReader(
- path,
- OrcFile.readerOptions(hadoopConf)
-
.maxLength(OrcConf.MAX_FILE_LENGTH.getLong(hadoopConf)));
- ColumnStatistics[] statistics = reader.getStatistics();
- TypeDescription schema = reader.getSchema();
- List<String> fieldNames = schema.getFieldNames();
- List<TypeDescription> columnTypes = schema.getChildren();
- for (String column : producedRowType.getFieldNames()) {
- int fieldIdx = fieldNames.indexOf(column);
- if (fieldIdx >= 0) {
- int colId = columnTypes.get(fieldIdx).getId();
- ColumnStatistics statistic = statistics[colId];
- updateStatistics(statistic, column, columnStatisticsMap);
- }
- }
-
- return reader.getNumberOfRows();
- }
-
- private void updateStatistics(
- ColumnStatistics statistic,
- String column,
- Map<String, ColumnStatistics> columnStatisticsMap) {
- ColumnStatistics previousStatistics =
columnStatisticsMap.get(column);
- if (previousStatistics == null) {
- columnStatisticsMap.put(column, statistic);
- } else {
- if (previousStatistics instanceof ColumnStatisticsImpl) {
- ((ColumnStatisticsImpl) previousStatistics)
- .merge((ColumnStatisticsImpl) statistic);
- }
- }
- }
-
- private Map<String, ColumnStats> convertToColumnStats(
- long totalRowCount,
- Map<String, ColumnStatistics> columnStatisticsMap,
- RowType logicalType) {
- Map<String, ColumnStats> columnStatsMap = new HashMap<>();
- for (String column : logicalType.getFieldNames()) {
- ColumnStatistics columnStatistics =
columnStatisticsMap.get(column);
- if (columnStatistics == null) {
- continue;
- }
- ColumnStats columnStats =
- convertToColumnStats(
- totalRowCount,
-
logicalType.getTypeAt(logicalType.getFieldIndex(column)),
- columnStatistics);
- columnStatsMap.put(column, columnStats);
- }
-
- return columnStatsMap;
- }
-
- private ColumnStats convertToColumnStats(
- long totalRowCount, LogicalType logicalType, ColumnStatistics
columnStatistics) {
- ColumnStats.Builder builder =
- new
ColumnStats.Builder().setNdv(null).setAvgLen(null).setMaxLen(null);
- if (!columnStatistics.hasNull()) {
- builder.setNullCount(0L);
- } else {
- builder.setNullCount(totalRowCount -
columnStatistics.getNumberOfValues());
- }
-
- // For complex types: ROW, ARRAY, MAP. The returned statistics
have wrong null count
- // value, so now complex types stats return null.
- switch (logicalType.getTypeRoot()) {
- case BOOLEAN:
- break;
- case TINYINT:
- case SMALLINT:
- case INTEGER:
- case BIGINT:
- if (columnStatistics instanceof IntegerColumnStatistics) {
- builder.setMax(((IntegerColumnStatistics)
columnStatistics).getMaximum())
- .setMin(((IntegerColumnStatistics)
columnStatistics).getMinimum());
- break;
- } else {
- return null;
- }
- case FLOAT:
- case DOUBLE:
- if (columnStatistics instanceof DoubleColumnStatistics) {
- builder.setMax(((DoubleColumnStatistics)
columnStatistics).getMaximum())
- .setMin(((DoubleColumnStatistics)
columnStatistics).getMinimum());
- break;
- } else {
- return null;
- }
- case CHAR:
- case VARCHAR:
- if (columnStatistics instanceof StringColumnStatistics) {
- builder.setMax(((StringColumnStatistics)
columnStatistics).getMaximum())
- .setMin(((StringColumnStatistics)
columnStatistics).getMinimum());
- break;
- } else {
- return null;
- }
- case DATE:
- if (columnStatistics instanceof DateColumnStatistics) {
- Date maximum =
- (Date) ((DateColumnStatistics)
columnStatistics).getMaximum();
- Date minimum =
- (Date) ((DateColumnStatistics)
columnStatistics).getMinimum();
- builder.setMax(maximum).setMin(minimum);
- break;
- } else {
- return null;
- }
- case TIMESTAMP_WITHOUT_TIME_ZONE:
- case TIMESTAMP_WITH_TIME_ZONE:
- if (columnStatistics instanceof TimestampColumnStatistics)
{
- builder.setMax(((TimestampColumnStatistics)
columnStatistics).getMaximum())
- .setMin(
- ((TimestampColumnStatistics)
columnStatistics)
- .getMinimum());
- break;
- } else {
- return null;
- }
- case DECIMAL:
- if (columnStatistics instanceof DecimalColumnStatistics) {
- builder.setMax(
- ((DecimalColumnStatistics)
columnStatistics)
- .getMaximum()
- .bigDecimalValue())
- .setMin(
- ((DecimalColumnStatistics)
columnStatistics)
- .getMinimum()
- .bigDecimalValue());
- break;
- } else {
- return null;
- }
- default:
- return null;
- }
- return builder.build();
+ return OrcFormatStatisticsReportUtil.getTableStatistics(
+ files, producedDataType,
getOrcConfiguration(formatOptions));
}
}
}
diff --git
a/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/util/OrcFormatStatisticsReportUtil.java
b/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/util/OrcFormatStatisticsReportUtil.java
new file mode 100644
index 00000000000..a4abfd63ad2
--- /dev/null
+++
b/flink-formats/flink-orc/src/main/java/org/apache/flink/orc/util/OrcFormatStatisticsReportUtil.java
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.orc.util;
+
+import org.apache.flink.core.fs.Path;
+import org.apache.flink.table.plan.stats.ColumnStats;
+import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.types.DataType;
+import org.apache.flink.table.types.logical.LogicalType;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.orc.ColumnStatistics;
+import org.apache.orc.DateColumnStatistics;
+import org.apache.orc.DecimalColumnStatistics;
+import org.apache.orc.DoubleColumnStatistics;
+import org.apache.orc.IntegerColumnStatistics;
+import org.apache.orc.OrcConf;
+import org.apache.orc.OrcFile;
+import org.apache.orc.Reader;
+import org.apache.orc.StringColumnStatistics;
+import org.apache.orc.TimestampColumnStatistics;
+import org.apache.orc.TypeDescription;
+import org.apache.orc.impl.ColumnStatisticsImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.sql.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Utils for Orc format statistics report. */
+public class OrcFormatStatisticsReportUtil {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OrcFormatStatisticsReportUtil.class);
+
+ public static TableStats getTableStatistics(
+ List<Path> files, DataType producedDataType, Configuration
hadoopConfig) {
+ try {
+ long rowCount = 0;
+ Map<String, ColumnStatistics> columnStatisticsMap = new
HashMap<>();
+ RowType producedRowType = (RowType)
producedDataType.getLogicalType();
+ for (Path file : files) {
+ rowCount +=
+ updateStatistics(hadoopConfig, file,
columnStatisticsMap, producedRowType);
+ }
+
+ Map<String, ColumnStats> columnStatsMap =
+ convertToColumnStats(rowCount, columnStatisticsMap,
producedRowType);
+
+ return new TableStats(rowCount, columnStatsMap);
+ } catch (Exception e) {
+ LOG.warn("Reporting statistics failed for Orc format: {}",
e.getMessage());
+ return TableStats.UNKNOWN;
+ }
+ }
+
+ private static long updateStatistics(
+ Configuration hadoopConf,
+ Path file,
+ Map<String, ColumnStatistics> columnStatisticsMap,
+ RowType producedRowType)
+ throws IOException {
+ org.apache.hadoop.fs.Path path = new
org.apache.hadoop.fs.Path(file.toUri());
+ Reader reader =
+ OrcFile.createReader(
+ path,
+ OrcFile.readerOptions(hadoopConf)
+
.maxLength(OrcConf.MAX_FILE_LENGTH.getLong(hadoopConf)));
+ ColumnStatistics[] statistics = reader.getStatistics();
+ TypeDescription schema = reader.getSchema();
+ List<String> fieldNames = schema.getFieldNames();
+ List<TypeDescription> columnTypes = schema.getChildren();
+ for (String column : producedRowType.getFieldNames()) {
+ int fieldIdx = fieldNames.indexOf(column);
+ if (fieldIdx >= 0) {
+ int colId = columnTypes.get(fieldIdx).getId();
+ ColumnStatistics statistic = statistics[colId];
+ updateStatistics(statistic, column, columnStatisticsMap);
+ }
+ }
+
+ return reader.getNumberOfRows();
+ }
+
+ private static void updateStatistics(
+ ColumnStatistics statistic,
+ String column,
+ Map<String, ColumnStatistics> columnStatisticsMap) {
+ ColumnStatistics previousStatistics = columnStatisticsMap.get(column);
+ if (previousStatistics == null) {
+ columnStatisticsMap.put(column, statistic);
+ } else {
+ if (previousStatistics instanceof ColumnStatisticsImpl) {
+ ((ColumnStatisticsImpl)
previousStatistics).merge((ColumnStatisticsImpl) statistic);
+ }
+ }
+ }
+
+ private static Map<String, ColumnStats> convertToColumnStats(
+ long totalRowCount,
+ Map<String, ColumnStatistics> columnStatisticsMap,
+ RowType logicalType) {
+ Map<String, ColumnStats> columnStatsMap = new HashMap<>();
+ for (String column : logicalType.getFieldNames()) {
+ ColumnStatistics columnStatistics =
columnStatisticsMap.get(column);
+ if (columnStatistics == null) {
+ continue;
+ }
+ ColumnStats columnStats =
+ convertToColumnStats(
+ totalRowCount,
+
logicalType.getTypeAt(logicalType.getFieldIndex(column)),
+ columnStatistics);
+ columnStatsMap.put(column, columnStats);
+ }
+
+ return columnStatsMap;
+ }
+
+ private static ColumnStats convertToColumnStats(
+ long totalRowCount, LogicalType logicalType, ColumnStatistics
columnStatistics) {
+ ColumnStats.Builder builder =
+ new
ColumnStats.Builder().setNdv(null).setAvgLen(null).setMaxLen(null);
+ if (!columnStatistics.hasNull()) {
+ builder.setNullCount(0L);
+ } else {
+ builder.setNullCount(totalRowCount -
columnStatistics.getNumberOfValues());
+ }
+
+ // For complex types: ROW, ARRAY, MAP. The returned statistics have
wrong null count
+ // value, so now complex types stats return null.
+ switch (logicalType.getTypeRoot()) {
+ case BOOLEAN:
+ break;
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ if (columnStatistics instanceof IntegerColumnStatistics) {
+ builder.setMax(((IntegerColumnStatistics)
columnStatistics).getMaximum())
+ .setMin(((IntegerColumnStatistics)
columnStatistics).getMinimum());
+ break;
+ } else {
+ return null;
+ }
+ case FLOAT:
+ case DOUBLE:
+ if (columnStatistics instanceof DoubleColumnStatistics) {
+ builder.setMax(((DoubleColumnStatistics)
columnStatistics).getMaximum())
+ .setMin(((DoubleColumnStatistics)
columnStatistics).getMinimum());
+ break;
+ } else {
+ return null;
+ }
+ case CHAR:
+ case VARCHAR:
+ if (columnStatistics instanceof StringColumnStatistics) {
+ builder.setMax(((StringColumnStatistics)
columnStatistics).getMaximum())
+ .setMin(((StringColumnStatistics)
columnStatistics).getMinimum());
+ break;
+ } else {
+ return null;
+ }
+ case DATE:
+ if (columnStatistics instanceof DateColumnStatistics) {
+ Date maximum = (Date) ((DateColumnStatistics)
columnStatistics).getMaximum();
+ Date minimum = (Date) ((DateColumnStatistics)
columnStatistics).getMinimum();
+ builder.setMax(maximum).setMin(minimum);
+ break;
+ } else {
+ return null;
+ }
+ case TIMESTAMP_WITHOUT_TIME_ZONE:
+ case TIMESTAMP_WITH_TIME_ZONE:
+ if (columnStatistics instanceof TimestampColumnStatistics) {
+ builder.setMax(((TimestampColumnStatistics)
columnStatistics).getMaximum())
+ .setMin(((TimestampColumnStatistics)
columnStatistics).getMinimum());
+ break;
+ } else {
+ return null;
+ }
+ case DECIMAL:
+ if (columnStatistics instanceof DecimalColumnStatistics) {
+ builder.setMax(
+ ((DecimalColumnStatistics)
columnStatistics)
+ .getMaximum()
+ .bigDecimalValue())
+ .setMin(
+ ((DecimalColumnStatistics)
columnStatistics)
+ .getMinimum()
+ .bigDecimalValue());
+ break;
+ } else {
+ return null;
+ }
+ default:
+ return null;
+ }
+ return builder.build();
+ }
+}
diff --git
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetColumnarRowInputFormat.java
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetColumnarRowInputFormat.java
index 5caa2cb773c..85550adac35 100644
---
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetColumnarRowInputFormat.java
+++
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetColumnarRowInputFormat.java
@@ -24,13 +24,17 @@ import org.apache.flink.connector.file.src.util.Pool;
import org.apache.flink.connector.file.table.ColumnarRowIterator;
import org.apache.flink.connector.file.table.PartitionFieldExtractor;
import org.apache.flink.core.fs.Path;
+import
org.apache.flink.formats.parquet.utils.ParquetFormatStatisticsReportUtil;
import org.apache.flink.formats.parquet.utils.SerializableConfiguration;
import org.apache.flink.formats.parquet.vector.ColumnBatchFactory;
+import
org.apache.flink.table.connector.format.FileBasedStatisticsReportableInputFormat;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.columnar.ColumnarRowData;
import org.apache.flink.table.data.columnar.vector.ColumnVector;
import org.apache.flink.table.data.columnar.vector.VectorizedColumnBatch;
import
org.apache.flink.table.data.columnar.vector.writable.WritableColumnVector;
+import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.types.DataType;
import org.apache.flink.table.types.logical.RowType;
import org.apache.hadoop.conf.Configuration;
@@ -45,7 +49,8 @@ import static
org.apache.flink.formats.parquet.vector.ParquetSplitReaderUtil.cre
* ColumnarRowData} to provide a row view of column batch.
*/
public class ParquetColumnarRowInputFormat<SplitT extends FileSourceSplit>
- extends ParquetVectorizedInputFormat<RowData, SplitT> {
+ extends ParquetVectorizedInputFormat<RowData, SplitT>
+ implements FileBasedStatisticsReportableInputFormat {
private static final long serialVersionUID = 1L;
@@ -117,6 +122,12 @@ public class ParquetColumnarRowInputFormat<SplitT extends
FileSourceSplit>
return producedTypeInfo;
}
+ @Override
+ public TableStats reportStatistics(List<Path> files, DataType
producedDataType) {
+ return ParquetFormatStatisticsReportUtil.getTableStatistics(
+ files, producedDataType, hadoopConfig.conf(), isUtcTimestamp);
+ }
+
private static class ColumnarRowReaderBatch extends
ParquetReaderBatch<RowData> {
private final ColumnarRowIterator result;
diff --git
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetFileFormatFactory.java
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetFileFormatFactory.java
index d401da5a8f9..14f257899c1 100644
---
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetFileFormatFactory.java
+++
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetFileFormatFactory.java
@@ -29,7 +29,7 @@ import
org.apache.flink.connector.file.table.factories.BulkWriterFormatFactory;
import org.apache.flink.connector.file.table.format.BulkDecodingFormat;
import org.apache.flink.core.fs.Path;
import org.apache.flink.formats.parquet.row.ParquetRowDataBuilder;
-import org.apache.flink.formats.parquet.vector.reader.TimestampColumnReader;
+import
org.apache.flink.formats.parquet.utils.ParquetFormatStatisticsReportUtil;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.Projection;
import org.apache.flink.table.connector.format.EncodingFormat;
@@ -38,48 +38,19 @@ import
org.apache.flink.table.connector.format.ProjectableDecodingFormat;
import org.apache.flink.table.connector.sink.DynamicTableSink;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.data.RowData;
-import org.apache.flink.table.data.TimestampData;
import org.apache.flink.table.data.columnar.vector.VectorizedColumnBatch;
import org.apache.flink.table.factories.DynamicTableFactory;
-import org.apache.flink.table.plan.stats.ColumnStats;
import org.apache.flink.table.plan.stats.TableStats;
import org.apache.flink.table.types.DataType;
-import org.apache.flink.table.types.logical.DecimalType;
-import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RowType;
-import org.apache.flink.table.utils.DateTimeUtils;
import org.apache.hadoop.conf.Configuration;
-import org.apache.parquet.Preconditions;
-import org.apache.parquet.column.statistics.BinaryStatistics;
-import org.apache.parquet.column.statistics.DoubleStatistics;
-import org.apache.parquet.column.statistics.FloatStatistics;
-import org.apache.parquet.column.statistics.IntStatistics;
-import org.apache.parquet.column.statistics.LongStatistics;
-import org.apache.parquet.column.statistics.Statistics;
-import org.apache.parquet.hadoop.ParquetFileReader;
-import org.apache.parquet.hadoop.metadata.BlockMetaData;
-import org.apache.parquet.hadoop.metadata.ParquetMetadata;
-import org.apache.parquet.io.api.Binary;
-import org.apache.parquet.schema.MessageType;
-import org.apache.parquet.schema.Type;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.sql.Date;
-import java.sql.Time;
-import java.sql.Timestamp;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Properties;
import java.util.Set;
-import java.util.stream.Collectors;
import static org.apache.flink.configuration.ConfigOptions.key;
@@ -185,238 +156,11 @@ public class ParquetFileFormatFactory implements
BulkReaderFormatFactory, BulkWr
@Override
public TableStats reportStatistics(List<Path> files, DataType
producedDataType) {
- try {
- Configuration hadoopConfig =
getParquetConfiguration(formatOptions);
- Map<String, Statistics<?>> columnStatisticsMap = new
HashMap<>();
- RowType producedRowType = (RowType)
producedDataType.getLogicalType();
- long rowCount = 0;
- for (Path file : files) {
- rowCount += updateStatistics(hadoopConfig, file,
columnStatisticsMap);
- }
- Map<String, ColumnStats> columnStatsMap =
- convertToColumnStats(columnStatisticsMap,
producedRowType);
- return new TableStats(rowCount, columnStatsMap);
- } catch (Exception e) {
- return TableStats.UNKNOWN;
- }
- }
-
- private Map<String, ColumnStats> convertToColumnStats(
- Map<String, Statistics<?>> columnStatisticsMap, RowType
producedRowType) {
- Map<String, ColumnStats> columnStatMap = new HashMap<>();
- for (String column : producedRowType.getFieldNames()) {
- Statistics<?> statistics = columnStatisticsMap.get(column);
- if (statistics == null) {
- continue;
- }
- ColumnStats columnStats =
- convertToColumnStats(
-
producedRowType.getTypeAt(producedRowType.getFieldIndex(column)),
- statistics);
- columnStatMap.put(column, columnStats);
- }
- return columnStatMap;
- }
-
- private ColumnStats convertToColumnStats(
- LogicalType logicalType, Statistics<?> statistics) {
- ColumnStats.Builder builder =
- new
ColumnStats.Builder().setNullCount(statistics.getNumNulls());
-
- // For complex types: ROW, ARRAY, MAP. The returned statistics
have wrong null count
- // value, so now complex types stats return null.
- switch (logicalType.getTypeRoot()) {
- case BOOLEAN:
- case BINARY:
- case VARBINARY:
- break;
- case TINYINT:
- case SMALLINT:
- case INTEGER:
- case BIGINT:
- if (statistics instanceof IntStatistics) {
- builder.setMin(((IntStatistics) statistics).getMin())
- .setMax(((IntStatistics) statistics).getMax());
- } else if (statistics instanceof LongStatistics) {
- builder.setMin(((LongStatistics) statistics).getMin())
- .setMax(((LongStatistics)
statistics).getMax());
- } else {
- return null;
- }
- break;
- case DOUBLE:
- if (statistics instanceof DoubleStatistics) {
- builder.setMin(((DoubleStatistics)
statistics).getMin())
- .setMax(((DoubleStatistics)
statistics).getMax());
- break;
- } else {
- return null;
- }
- case FLOAT:
- if (statistics instanceof FloatStatistics) {
- builder.setMin(((FloatStatistics) statistics).getMin())
- .setMax(((FloatStatistics)
statistics).getMax());
- break;
- } else {
- return null;
- }
- case DATE:
- if (statistics instanceof IntStatistics) {
- Date min =
- Date.valueOf(
- DateTimeUtils.formatDate(
- ((IntStatistics)
statistics).getMin()));
- Date max =
- Date.valueOf(
- DateTimeUtils.formatDate(
- ((IntStatistics)
statistics).getMax()));
- builder.setMin(min).setMax(max);
- break;
- } else {
- return null;
- }
- case TIME_WITHOUT_TIME_ZONE:
- if (statistics instanceof IntStatistics) {
- Time min =
- Time.valueOf(
- DateTimeUtils.toLocalTime(
- ((IntStatistics)
statistics).getMin()));
- Time max =
- Time.valueOf(
- DateTimeUtils.toLocalTime(
- ((IntStatistics)
statistics).getMax()));
- builder.setMin(min).setMax(max);
- break;
- } else {
- return null;
- }
- case CHAR:
- case VARCHAR:
- if (statistics instanceof BinaryStatistics) {
- Binary min = ((BinaryStatistics)
statistics).genericGetMin();
- Binary max = ((BinaryStatistics)
statistics).genericGetMax();
- if (min != null) {
- builder.setMin(min.toStringUsingUTF8());
- } else {
- builder.setMin(null);
- }
- if (max != null) {
- builder.setMax(max.toStringUsingUTF8());
- } else {
- builder.setMax(null);
- }
- break;
- } else {
- return null;
- }
- case TIMESTAMP_WITHOUT_TIME_ZONE:
- case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
- case TIMESTAMP_WITH_TIME_ZONE:
- if (statistics instanceof LongStatistics) {
- builder.setMin(new Timestamp(((LongStatistics)
statistics).getMin()))
- .setMax(new Timestamp(((LongStatistics)
statistics).getMax()));
- } else if (statistics instanceof BinaryStatistics) {
- Binary min = ((BinaryStatistics)
statistics).genericGetMin();
- Binary max = ((BinaryStatistics)
statistics).genericGetMax();
- if (min != null) {
- builder.setMin(binaryToTimestamp(min,
formatOptions.get(UTC_TIMEZONE)));
- } else {
- builder.setMin(null);
- }
- if (max != null) {
- builder.setMax(binaryToTimestamp(max,
formatOptions.get(UTC_TIMEZONE)));
- } else {
- builder.setMax(null);
- }
- } else {
- return null;
- }
- break;
- case DECIMAL:
- if (statistics instanceof IntStatistics) {
- builder.setMin(BigDecimal.valueOf(((IntStatistics)
statistics).getMin()))
- .setMax(BigDecimal.valueOf(((IntStatistics)
statistics).getMax()));
- } else if (statistics instanceof LongStatistics) {
- builder.setMin(BigDecimal.valueOf(((LongStatistics)
statistics).getMin()))
- .setMax(BigDecimal.valueOf(((LongStatistics)
statistics).getMax()));
- } else if (statistics instanceof BinaryStatistics) {
- Binary min = ((BinaryStatistics)
statistics).genericGetMin();
- Binary max = ((BinaryStatistics)
statistics).genericGetMax();
- if (min != null) {
- builder.setMin(
- binaryToDecimal(min, ((DecimalType)
logicalType).getScale()));
- } else {
- builder.setMin(null);
- }
- if (max != null) {
- builder.setMax(
- binaryToDecimal(max, ((DecimalType)
logicalType).getScale()));
- } else {
- builder.setMax(null);
- }
- } else {
- return null;
- }
- break;
- default:
- return null;
- }
- return builder.build();
- }
-
- private long updateStatistics(
- Configuration hadoopConfig,
- Path file,
- Map<String, Statistics<?>> columnStatisticsMap)
- throws IOException {
- org.apache.hadoop.fs.Path hadoopPath = new
org.apache.hadoop.fs.Path(file.toUri());
- ParquetMetadata metadata =
ParquetFileReader.readFooter(hadoopConfig, hadoopPath);
- MessageType schema = metadata.getFileMetaData().getSchema();
- List<String> columns =
- schema.asGroupType().getFields().stream()
- .map(Type::getName)
- .collect(Collectors.toList());
- List<BlockMetaData> blocks = metadata.getBlocks();
- long rowCount = 0;
- for (BlockMetaData block : blocks) {
- rowCount += block.getRowCount();
- for (int i = 0; i < columns.size(); ++i) {
- updateStatistics(
- block.getColumns().get(i).getStatistics(),
- columns.get(i),
- columnStatisticsMap);
- }
- }
- return rowCount;
- }
-
- private void updateStatistics(
- Statistics<?> statistics,
- String column,
- Map<String, Statistics<?>> columnStatisticsMap) {
- Statistics<?> previousStatistics = columnStatisticsMap.get(column);
- if (previousStatistics == null) {
- columnStatisticsMap.put(column, statistics);
- } else {
- previousStatistics.mergeStatistics(statistics);
- }
- }
-
- private static BigDecimal binaryToDecimal(Binary decimal, int scale) {
- BigInteger bigInteger = new BigInteger(decimal.getBytesUnsafe());
- return new BigDecimal(bigInteger, scale);
- }
-
- private static Timestamp binaryToTimestamp(Binary timestamp, boolean
utcTimestamp) {
- Preconditions.checkArgument(timestamp.length() == 12, "Must be 12
bytes");
- ByteBuffer buf = timestamp.toByteBuffer();
- buf.order(ByteOrder.LITTLE_ENDIAN);
- long timeOfDayNanos = buf.getLong();
- int julianDay = buf.getInt();
-
- TimestampData timestampData =
- TimestampColumnReader.int96ToTimestamp(utcTimestamp,
timeOfDayNanos, julianDay);
- return timestampData.toTimestamp();
+ return ParquetFormatStatisticsReportUtil.getTableStatistics(
+ files,
+ producedDataType,
+ getParquetConfiguration(formatOptions),
+ formatOptions.get(UTC_TIMEZONE));
}
}
}
diff --git
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetVectorizedInputFormat.java
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetVectorizedInputFormat.java
index a6394589fac..0d8654ddb11 100644
---
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetVectorizedInputFormat.java
+++
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetVectorizedInputFormat.java
@@ -81,12 +81,12 @@ public abstract class ParquetVectorizedInputFormat<T,
SplitT extends FileSourceS
private static final Logger LOG =
LoggerFactory.getLogger(ParquetVectorizedInputFormat.class);
private static final long serialVersionUID = 1L;
- private final SerializableConfiguration hadoopConfig;
+ protected final SerializableConfiguration hadoopConfig;
private final String[] projectedFields;
private final LogicalType[] projectedTypes;
private final ColumnBatchFactory<SplitT> batchFactory;
private final int batchSize;
- private final boolean isUtcTimestamp;
+ protected final boolean isUtcTimestamp;
private final boolean isCaseSensitive;
private final Set<Integer> unknownFieldsIndices = new HashSet<>();
diff --git
a/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/utils/ParquetFormatStatisticsReportUtil.java
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/utils/ParquetFormatStatisticsReportUtil.java
new file mode 100644
index 00000000000..d426e2a29b3
--- /dev/null
+++
b/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/utils/ParquetFormatStatisticsReportUtil.java
@@ -0,0 +1,307 @@
+/*
+ * 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.flink.formats.parquet.utils;
+
+import org.apache.flink.core.fs.Path;
+import org.apache.flink.formats.parquet.vector.reader.TimestampColumnReader;
+import org.apache.flink.table.data.TimestampData;
+import org.apache.flink.table.plan.stats.ColumnStats;
+import org.apache.flink.table.plan.stats.TableStats;
+import org.apache.flink.table.types.DataType;
+import org.apache.flink.table.types.logical.DecimalType;
+import org.apache.flink.table.types.logical.LogicalType;
+import org.apache.flink.table.types.logical.RowType;
+import org.apache.flink.table.utils.DateTimeUtils;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.parquet.Preconditions;
+import org.apache.parquet.column.statistics.BinaryStatistics;
+import org.apache.parquet.column.statistics.DoubleStatistics;
+import org.apache.parquet.column.statistics.FloatStatistics;
+import org.apache.parquet.column.statistics.IntStatistics;
+import org.apache.parquet.column.statistics.LongStatistics;
+import org.apache.parquet.column.statistics.Statistics;
+import org.apache.parquet.hadoop.ParquetFileReader;
+import org.apache.parquet.hadoop.metadata.BlockMetaData;
+import org.apache.parquet.hadoop.metadata.ParquetMetadata;
+import org.apache.parquet.io.api.Binary;
+import org.apache.parquet.schema.MessageType;
+import org.apache.parquet.schema.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Utils for Parquet format statistics report. */
+public class ParquetFormatStatisticsReportUtil {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(ParquetFormatStatisticsReportUtil.class);
+
+ public static TableStats getTableStatistics(
+ List<Path> files,
+ DataType producedDataType,
+ Configuration hadoopConfig,
+ boolean isUtcTimestamp) {
+ try {
+ Map<String, Statistics<?>> columnStatisticsMap = new HashMap<>();
+ RowType producedRowType = (RowType)
producedDataType.getLogicalType();
+ long rowCount = 0;
+ for (Path file : files) {
+ rowCount += updateStatistics(hadoopConfig, file,
columnStatisticsMap);
+ }
+ Map<String, ColumnStats> columnStatsMap =
+ convertToColumnStats(columnStatisticsMap, producedRowType,
isUtcTimestamp);
+ return new TableStats(rowCount, columnStatsMap);
+ } catch (Exception e) {
+ LOG.warn("Reporting statistics failed for Parquet format: {}",
e.getMessage());
+ return TableStats.UNKNOWN;
+ }
+ }
+
+ private static Map<String, ColumnStats> convertToColumnStats(
+ Map<String, Statistics<?>> columnStatisticsMap,
+ RowType producedRowType,
+ boolean isUtcTimestamp) {
+ Map<String, ColumnStats> columnStatMap = new HashMap<>();
+ for (String column : producedRowType.getFieldNames()) {
+ Statistics<?> statistics = columnStatisticsMap.get(column);
+ if (statistics == null) {
+ continue;
+ }
+ ColumnStats columnStats =
+ convertToColumnStats(
+
producedRowType.getTypeAt(producedRowType.getFieldIndex(column)),
+ statistics,
+ isUtcTimestamp);
+ columnStatMap.put(column, columnStats);
+ }
+ return columnStatMap;
+ }
+
+ private static ColumnStats convertToColumnStats(
+ LogicalType logicalType, Statistics<?> statistics, boolean
isUtcTimestamp) {
+ ColumnStats.Builder builder =
+ new
ColumnStats.Builder().setNullCount(statistics.getNumNulls());
+
+ // For complex types: ROW, ARRAY, MAP. The returned statistics have
wrong null count
+ // value, so now complex types stats return null.
+ switch (logicalType.getTypeRoot()) {
+ case BOOLEAN:
+ case BINARY:
+ case VARBINARY:
+ break;
+ case TINYINT:
+ case SMALLINT:
+ case INTEGER:
+ case BIGINT:
+ if (statistics instanceof IntStatistics) {
+ builder.setMin(((IntStatistics) statistics).getMin())
+ .setMax(((IntStatistics) statistics).getMax());
+ } else if (statistics instanceof LongStatistics) {
+ builder.setMin(((LongStatistics) statistics).getMin())
+ .setMax(((LongStatistics) statistics).getMax());
+ } else {
+ return null;
+ }
+ break;
+ case DOUBLE:
+ if (statistics instanceof DoubleStatistics) {
+ builder.setMin(((DoubleStatistics) statistics).getMin())
+ .setMax(((DoubleStatistics) statistics).getMax());
+ break;
+ } else {
+ return null;
+ }
+ case FLOAT:
+ if (statistics instanceof FloatStatistics) {
+ builder.setMin(((FloatStatistics) statistics).getMin())
+ .setMax(((FloatStatistics) statistics).getMax());
+ break;
+ } else {
+ return null;
+ }
+ case DATE:
+ if (statistics instanceof IntStatistics) {
+ Date min =
+ Date.valueOf(
+ DateTimeUtils.formatDate(
+ ((IntStatistics)
statistics).getMin()));
+ Date max =
+ Date.valueOf(
+ DateTimeUtils.formatDate(
+ ((IntStatistics)
statistics).getMax()));
+ builder.setMin(min).setMax(max);
+ break;
+ } else {
+ return null;
+ }
+ case TIME_WITHOUT_TIME_ZONE:
+ if (statistics instanceof IntStatistics) {
+ Time min =
+ Time.valueOf(
+ DateTimeUtils.toLocalTime(
+ ((IntStatistics)
statistics).getMin()));
+ Time max =
+ Time.valueOf(
+ DateTimeUtils.toLocalTime(
+ ((IntStatistics)
statistics).getMax()));
+ builder.setMin(min).setMax(max);
+ break;
+ } else {
+ return null;
+ }
+ case CHAR:
+ case VARCHAR:
+ if (statistics instanceof BinaryStatistics) {
+ Binary min = ((BinaryStatistics)
statistics).genericGetMin();
+ Binary max = ((BinaryStatistics)
statistics).genericGetMax();
+ if (min != null) {
+ builder.setMin(min.toStringUsingUTF8());
+ } else {
+ builder.setMin(null);
+ }
+ if (max != null) {
+ builder.setMax(max.toStringUsingUTF8());
+ } else {
+ builder.setMax(null);
+ }
+ break;
+ } else {
+ return null;
+ }
+ case TIMESTAMP_WITHOUT_TIME_ZONE:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ case TIMESTAMP_WITH_TIME_ZONE:
+ if (statistics instanceof LongStatistics) {
+ builder.setMin(new Timestamp(((LongStatistics)
statistics).getMin()))
+ .setMax(new Timestamp(((LongStatistics)
statistics).getMax()));
+ } else if (statistics instanceof BinaryStatistics) {
+ Binary min = ((BinaryStatistics)
statistics).genericGetMin();
+ Binary max = ((BinaryStatistics)
statistics).genericGetMax();
+ if (min != null) {
+ builder.setMin(binaryToTimestamp(min, isUtcTimestamp));
+ } else {
+ builder.setMin(null);
+ }
+ if (max != null) {
+ builder.setMax(binaryToTimestamp(max, isUtcTimestamp));
+ } else {
+ builder.setMax(null);
+ }
+ } else {
+ return null;
+ }
+ break;
+ case DECIMAL:
+ if (statistics instanceof IntStatistics) {
+ builder.setMin(BigDecimal.valueOf(((IntStatistics)
statistics).getMin()))
+ .setMax(BigDecimal.valueOf(((IntStatistics)
statistics).getMax()));
+ } else if (statistics instanceof LongStatistics) {
+ builder.setMin(BigDecimal.valueOf(((LongStatistics)
statistics).getMin()))
+ .setMax(BigDecimal.valueOf(((LongStatistics)
statistics).getMax()));
+ } else if (statistics instanceof BinaryStatistics) {
+ Binary min = ((BinaryStatistics)
statistics).genericGetMin();
+ Binary max = ((BinaryStatistics)
statistics).genericGetMax();
+ if (min != null) {
+ builder.setMin(
+ binaryToDecimal(min, ((DecimalType)
logicalType).getScale()));
+ } else {
+ builder.setMin(null);
+ }
+ if (max != null) {
+ builder.setMax(
+ binaryToDecimal(max, ((DecimalType)
logicalType).getScale()));
+ } else {
+ builder.setMax(null);
+ }
+ } else {
+ return null;
+ }
+ break;
+ default:
+ return null;
+ }
+ return builder.build();
+ }
+
+ private static long updateStatistics(
+ Configuration hadoopConfig, Path file, Map<String, Statistics<?>>
columnStatisticsMap)
+ throws IOException {
+ org.apache.hadoop.fs.Path hadoopPath = new
org.apache.hadoop.fs.Path(file.toUri());
+ ParquetMetadata metadata = ParquetFileReader.readFooter(hadoopConfig,
hadoopPath);
+ MessageType schema = metadata.getFileMetaData().getSchema();
+ List<String> columns =
+ schema.asGroupType().getFields().stream()
+ .map(Type::getName)
+ .collect(Collectors.toList());
+ List<BlockMetaData> blocks = metadata.getBlocks();
+ long rowCount = 0;
+ for (BlockMetaData block : blocks) {
+ rowCount += block.getRowCount();
+ for (int i = 0; i < columns.size(); ++i) {
+ updateStatistics(
+ block.getColumns().get(i).getStatistics(),
+ columns.get(i),
+ columnStatisticsMap);
+ }
+ }
+ return rowCount;
+ }
+
+ private static void updateStatistics(
+ Statistics<?> statistics,
+ String column,
+ Map<String, Statistics<?>> columnStatisticsMap) {
+ Statistics<?> previousStatistics = columnStatisticsMap.get(column);
+ if (previousStatistics == null) {
+ columnStatisticsMap.put(column, statistics);
+ } else {
+ previousStatistics.mergeStatistics(statistics);
+ }
+ }
+
+ private static BigDecimal binaryToDecimal(Binary decimal, int scale) {
+ BigInteger bigInteger = new BigInteger(decimal.getBytesUnsafe());
+ return new BigDecimal(bigInteger, scale);
+ }
+
+ private static Timestamp binaryToTimestamp(Binary timestamp, boolean
utcTimestamp) {
+ Preconditions.checkArgument(timestamp.length() == 12, "Must be 12
bytes");
+ ByteBuffer buf = timestamp.toByteBuffer();
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ long timeOfDayNanos = buf.getLong();
+ int julianDay = buf.getInt();
+
+ TimestampData timestampData =
+ TimestampColumnReader.int96ToTimestamp(utcTimestamp,
timeOfDayNanos, julianDay);
+ return timestampData.toTimestamp();
+ }
+}
diff --git
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/PushPartitionIntoTableSourceScanRule.java
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/PushPartitionIntoTableSourceScanRule.java
index 2bcb97ddd66..7c0fe1e302e 100644
---
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/PushPartitionIntoTableSourceScanRule.java
+++
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/logical/PushPartitionIntoTableSourceScanRule.java
@@ -185,7 +185,7 @@ public class PushPartitionIntoTableSourceScanRule extends
RelOptRule {
TableSourceTable newTableSourceTable =
tableSourceTable.copy(
dynamicTableSource,
- // the statistics will be updated in
FlinkCollectStatisticsProgram
+ // The statistics will be updated in
FlinkRecomputeStatisticsProgram.
tableSourceTable.getStatistic(),
new SourceAbilitySpec[] {partitionPushDownSpec});
LogicalTableScan newScan =
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/connector/file/table/FileSystemStatisticsReportTest.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/connector/file/table/FileSystemStatisticsReportTest.java
index f53e361fb26..9bb5c207207 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/connector/file/table/FileSystemStatisticsReportTest.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/connector/file/table/FileSystemStatisticsReportTest.java
@@ -18,29 +18,18 @@
package org.apache.flink.connector.file.table;
-import org.apache.flink.table.api.TableConfig;
-import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.config.OptimizerConfigOptions;
import org.apache.flink.table.catalog.CatalogPartitionImpl;
import org.apache.flink.table.catalog.CatalogPartitionSpec;
import org.apache.flink.table.catalog.ObjectPath;
-import org.apache.flink.table.catalog.exceptions.PartitionNotExistException;
-import org.apache.flink.table.catalog.exceptions.TableNotExistException;
import org.apache.flink.table.catalog.stats.CatalogTableStatistics;
import org.apache.flink.table.plan.stats.TableStats;
-import org.apache.flink.table.planner.plan.optimize.program.FlinkBatchProgram;
-import org.apache.flink.table.planner.plan.schema.TableSourceTable;
import org.apache.flink.table.planner.plan.stats.FlinkStatistic;
-import org.apache.flink.table.planner.utils.BatchTableTestUtil;
-import org.apache.flink.table.planner.utils.TableTestBase;
-import org.apache.flink.table.planner.utils.TableTestUtil;
-import org.apache.flink.util.Preconditions;
+import org.apache.flink.table.planner.utils.StatisticsReportTestBase;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelVisitor;
-import org.apache.calcite.rel.core.TableScan;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
@@ -53,19 +42,14 @@ import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/** Test for statistics functionality in {@link FileSystemTableSource}. */
-public class FileSystemStatisticsReportTest extends TableTestBase {
-
- private BatchTableTestUtil util;
- private TableEnvironment tEnv;
-
- @Before
- public void setup() throws Exception {
- util = batchTestUtil(TableConfig.getDefault());
- util.buildBatchProgram(FlinkBatchProgram.JOIN_REORDER());
- tEnv = util.getTableEnv();
- String path1 = tempFolder().newFile().getAbsolutePath();
- writeData(new File(path1), Arrays.asList("1,1,hi", "2,1,hello",
"3,2,hello world"));
-
+public class FileSystemStatisticsReportTest extends StatisticsReportTestBase {
+
+ @BeforeEach
+ public void setup(@TempDir File file) throws Exception {
+ super.setup(file);
+ String filePath1 =
+ createFileAndWriteData(
+ file, "00-00.tmp", Arrays.asList("1,1,hi",
"2,1,hello", "3,2,hello world"));
String ddl1 =
String.format(
"CREATE TABLE NonPartTable (\n"
@@ -76,12 +60,13 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
+ " 'connector' = 'filesystem',"
+ " 'format' = 'testcsv',"
+ " 'path' = '%s')",
- path1);
+ filePath1);
tEnv.executeSql(ddl1);
- String path2 = tempFolder().newFolder().getAbsolutePath();
- writeData(new File(path2, "b=1"), Arrays.asList("1,1,hi",
"2,1,hello"));
- writeData(new File(path2, "b=2"), Arrays.asList("3,2,hello world"));
+ File partitionDataPath = new File(file, "partitionData");
+ partitionDataPath.mkdirs();
+ writeData(new File(partitionDataPath, "b=1"), Arrays.asList("1,1,hi",
"2,1,hello"));
+ writeData(new File(partitionDataPath, "b=2"),
Collections.singletonList("3,2,hello world"));
String ddl2 =
String.format(
"CREATE TABLE PartTable (\n"
@@ -92,25 +77,26 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
+ " 'connector' = 'filesystem',"
+ " 'format' = 'testcsv',"
+ " 'path' = '%s')",
- path2);
+ partitionDataPath.toURI());
tEnv.executeSql(ddl2);
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.createPartition(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"1")),
new CatalogPartitionImpl(new HashMap<>(), ""),
false);
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.createPartition(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"2")),
new CatalogPartitionImpl(new HashMap<>(), ""),
false);
- String path3 = tempFolder().newFile().getAbsolutePath();
- writeData(new File(path1), Arrays.asList("1,1,hi", "2,1,hello",
"3,2,hello world"));
+ String filePath2 =
+ createFileAndWriteData(
+ file, "00-01.tmp", Arrays.asList("1,1,hi",
"2,1,hello", "3,2,hello world"));
String ddl3 =
String.format(
@@ -123,8 +109,34 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
+ " 'format' = 'testcsv',"
+ " 'source.report-statistics' = 'NONE',"
+ " 'path' = '%s')",
- path3);
+ filePath2);
tEnv.executeSql(ddl3);
+
+ String emptyPath = createFileAndWriteData(file, "00-02.tmp",
Collections.emptyList());
+ String ddl4 =
+ String.format(
+ "CREATE TABLE emptyTable (\n"
+ + " a bigint,\n"
+ + " b int,\n"
+ + " c varchar\n"
+ + ") with (\n"
+ + " 'connector' = 'filesystem',"
+ + " 'format' = 'testcsv',"
+ + " 'path' = '%s')",
+ emptyPath);
+ tEnv.executeSql(ddl4);
+ }
+
+ @Override
+ protected String[] properties() {
+ return new String[0];
+ }
+
+ private String createFileAndWriteData(File path, String fileName,
List<String> data)
+ throws IOException {
+ String file = path.getAbsolutePath() + "/" + fileName;
+ Files.write(new File(file).toPath(), String.join("\n",
data).getBytes());
+ return file;
}
private void writeData(File file, List<String> data) throws IOException {
@@ -134,7 +146,7 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
@Test
public void testCatalogStatisticsExist() throws Exception {
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterTableStatistics(
new ObjectPath(tEnv.getCurrentDatabase(),
"NonPartTable"),
new CatalogTableStatistics(10L, 1, 100L, 100L),
@@ -158,9 +170,9 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
}
@Test
- public void testFilterPushDownAndCatalogStatisticsExist() throws
TableNotExistException {
+ public void testFilterPushDownAndCatalogStatisticsExist() throws Exception
{
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterTableStatistics(
new ObjectPath(tEnv.getCurrentDatabase(),
"NonPartTable"),
new CatalogTableStatistics(10L, 1, 100L, 100L),
@@ -190,17 +202,23 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
}
@Test
- public void testNoPartitionPushDownAndCatalogStatisticsExist()
- throws PartitionNotExistException {
+ public void testLimitPushDownAndCatalogStatisticsDoNotExist() {
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan("select * from NonPartTable
limit 1");
+ assertThat(statistic.getTableStats()).isEqualTo(new TableStats(1));
+ }
+
+ @Test
+ public void testNoPartitionPushDownAndCatalogStatisticsExist() throws
Exception {
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterPartitionStatistics(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"1")),
new CatalogTableStatistics(6L, 1, 100L, 100L),
false);
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterPartitionStatistics(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"2")),
@@ -222,16 +240,16 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
}
@Test
- public void testPartitionPushDownAndCatalogStatisticsExist() throws
PartitionNotExistException {
+ public void testPartitionPushDownAndCatalogStatisticsExist() throws
Exception {
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterPartitionStatistics(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"1")),
new CatalogTableStatistics(6L, 1, 100L, 100L),
false);
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterPartitionStatistics(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"2")),
@@ -246,14 +264,14 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
@Test
public void testFilterPartitionPushDownPushDownAndCatalogStatisticsExist()
throws Exception {
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterPartitionStatistics(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"1")),
new CatalogTableStatistics(6L, 1, 100L, 100L),
false);
tEnv.getCatalog(tEnv.getCurrentCatalog())
- .get()
+ .orElseThrow(Exception::new)
.alterPartitionStatistics(
new ObjectPath(tEnv.getCurrentDatabase(), "PartTable"),
new CatalogPartitionSpec(Collections.singletonMap("b",
"2")),
@@ -283,25 +301,19 @@ public class FileSystemStatisticsReportTest extends
TableTestBase {
assertThat(statistic.getTableStats()).isEqualTo(TableStats.UNKNOWN);
}
- private FlinkStatistic getStatisticsFromOptimizedPlan(String sql) {
- RelNode relNode = TableTestUtil.toRelNode(tEnv.sqlQuery(sql));
- RelNode optimized = util.getPlanner().optimize(relNode);
- FlinkStatisticVisitor visitor = new FlinkStatisticVisitor();
- visitor.go(optimized);
- return visitor.result;
+ @Test
+ public void testFileSystemSourceWithoutData() {
+ FlinkStatistic statistic = getStatisticsFromOptimizedPlan("select *
from emptyTable");
+ assertThat(statistic.getTableStats()).isEqualTo(TableStats.UNKNOWN);
}
- private static class FlinkStatisticVisitor extends RelVisitor {
- private FlinkStatistic result = null;
-
- @Override
- public void visit(RelNode node, int ordinal, RelNode parent) {
- if (node instanceof TableScan) {
- Preconditions.checkArgument(result == null);
- TableSourceTable table = (TableSourceTable) node.getTable();
- result = table.getStatistic();
- }
- super.visit(node, ordinal, parent);
- }
+ @Test
+ public void testFileSystemSourceWithoutDataWithLimitPushDown() {
+ // TODO for source support limit push down and query have limit
condition, In
+ // PushLimitIntoTableSourceScanRule will give stats a new rowCount
value even if this table
+ // have no data.
+ FlinkStatistic statistic =
+ getStatisticsFromOptimizedPlan("select * from emptyTable limit
1");
+ assertThat(statistic.getTableStats()).isEqualTo(new TableStats(1));
}
}
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/formats/testcsv/TestCsvFormatFactory.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/formats/testcsv/TestCsvFormatFactory.java
index 45c64b9fced..2755760b293 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/formats/testcsv/TestCsvFormatFactory.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/formats/testcsv/TestCsvFormatFactory.java
@@ -41,6 +41,9 @@ import org.apache.flink.table.plan.stats.TableStats;
import
org.apache.flink.table.runtime.connector.source.ScanRuntimeProviderContext;
import org.apache.flink.table.types.DataType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
@@ -59,6 +62,8 @@ import java.util.Set;
public class TestCsvFormatFactory
implements DeserializationFormatFactory, SerializationFormatFactory {
+ private static final Logger LOG =
LoggerFactory.getLogger(TestCsvFormatFactory.class);
+
public static final String IDENTIFIER = "testcsv";
@Override
@@ -173,6 +178,7 @@ public class TestCsvFormatFactory
long estimatedRowCount = totalFileSize * realSampledLineCnt /
sampledRowSize;
return new TableStats(estimatedRowCount);
} catch (Exception e) {
+ LOG.warn("Reporting statistics failed for Csv format: {}",
e.getMessage());
return TableStats.UNKNOWN;
}
}
diff --git
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/utils/StatisticsReportTestBase.java
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/utils/StatisticsReportTestBase.java
index 764d9689242..d27c739ddd5 100644
---
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/utils/StatisticsReportTestBase.java
+++
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/utils/StatisticsReportTestBase.java
@@ -294,7 +294,7 @@ public abstract class StatisticsReportTestBase extends
TestLogger {
}
}
- private static String[] ddlTypesMapToStringList(Map<String, String>
ddlTypesMap) {
+ protected static String[] ddlTypesMapToStringList(Map<String, String>
ddlTypesMap) {
String[] types = new String[ddlTypesMap.size()];
int i = 0;
for (Map.Entry<String, String> entry : ddlTypesMap.entrySet()) {