[
https://issues.apache.org/jira/browse/FLINK-38774?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
abjfh updated FLINK-38774:
--------------------------
Description:
Problem
Reading a partitioned table of the filesystem connector fails when the table
path contains a hidden directory at partition depth. Flink discovers partitions
by walking the table path recursively
(PartitionPathUtils.searchPartSpecAndPaths -> getFileStatusRecurse ->
listStatusRecursively). Hidden directories are meant to be ignored, but the
recursion descends into them: a non-hidden child of a hidden directory - for
example {{job-123}} under the {{_temporary}} staging directory of the streaming
file sink - is collected at the expected partition depth with an empty
partition spec, and FileSystemTableSource.toFullLinkedPartSpec then rejects it.
The table stays unreadable until the directory is removed; writing into the
table is not affected.
Reported in FLINK-31975 against Flink 1.16.0 with a filesystem table on S3; the
recursion is unchanged on master.
Error
{code:java}
Flink SQL> select * from country_page_view;
[ERROR] Could not execute SQL statement. Reason:
org.apache.flink.table.api.TableException: Partition keys are: [date, country],
incomplete partition spec: {}
{code}
Root cause
listStatusRecursively lists with fs.listStatus(dir) at every level, which
returns hidden entries as well:
{code:java}
if (fileStatus.isDir()) {
for (FileStatus stat : fs.listStatus(fileStatus.getPath())) {
listStatusRecursively(fs, stat, level + 1, expectLevel, results);
}
}
{code}
Hidden entries are filtered only on the collected result in
searchPartSpecAndPaths:
{code:java}
for (FileStatus part : generatedParts) {
// ignore hidden file
if (isHiddenFile(part)) {
continue;
}
ret.add(new Tuple2<>(extractPartitionSpecFromPath(part.getPath()),
part.getPath()));
}
{code}
isHiddenFile checks the name of the entry itself, not that of its ancestors.
For {{_temporary/job-123}} the hidden directory {{_temporary}} is dropped at
the leaf, but {{job-123}} is not hidden by name and reaches the result with an
empty partition spec - the spec that toFullLinkedPartSpec rejects.
The same method has two further defects: fs.listStatus may return null
(listStatusWithoutHidden handles that and returns null), which makes the
for-each throw a NullPointerException; and the unfiltered listing is walked
although hidden entries are never wanted.
Reproduction
Place a hidden staging subtree next to a real partition and read the table:
{code}
path/date=2019-8-30/country=China/ <- real partition
path/_temporary/job-123/ <- hidden staging directory
{code}
PartitionPathUtils.searchPartSpecAndPaths(fs, path, 2) then returns two
entries: the real partition, and {{job-123}} with an empty partition spec. With
hidden directories skipped at every level, only the real partition is returned.
Proposed fix
Reuse the existing listStatusWithoutHidden helper at every recursion level and
keep its null contract, so that an absent listing ends the recursion instead of
failing:
{code:java}
if (fileStatus.isDir()) {
FileStatus[] children = listStatusWithoutHidden(fs, fileStatus.getPath());
if (children == null) {
return;
}
for (FileStatus stat : children) {
listStatusRecursively(fs, stat, level + 1, expectLevel, results);
}
}
{code}
Verification
The linked pull request adds a regression test to PartitionPathUtilsTest: a
temporary directory holding the real partition {{date=2019-8-30/country=China}}
and the hidden {{_temporary/job-123}} subtree, asserting that
searchPartSpecAndPaths returns the real partition only; on the unfixed code the
hidden child is returned as a second entry with an empty spec. The change is
confined to PartitionPathUtils.listStatusRecursively, so paths without hidden
entries keep their current behaviour.
Related
Supersedes FLINK-31975 (default catalog failed to retrieve partition Spec),
which reports the same failure on 1.16.0. Pull requests: #27314 (closed by the
stale bot, no technical objection) and #28913 (current).
was:# Since the original issue:FLINK-31975 is too old, I created a new issue
to fix it.
Summary: Filesystem connector: "incomplete partition spec" when
partition discovery descends into hidden directories (was: Fixed incorrect
handling of hidden directories when obtaining partition structure)
> Filesystem connector: "incomplete partition spec" when partition discovery
> descends into hidden directories
> -----------------------------------------------------------------------------------------------------------
>
> Key: FLINK-38774
> URL: https://issues.apache.org/jira/browse/FLINK-38774
> Project: Flink
> Issue Type: Bug
> Components: Connectors / FileSystem
> Affects Versions: 1.16.0, 2.1.1
> Reporter: abjfh
> Priority: Major
> Labels: bug, pull-request-available
> Original Estimate: 24h
> Remaining Estimate: 24h
>
> Problem
> Reading a partitioned table of the filesystem connector fails when the table
> path contains a hidden directory at partition depth. Flink discovers
> partitions by walking the table path recursively
> (PartitionPathUtils.searchPartSpecAndPaths -> getFileStatusRecurse ->
> listStatusRecursively). Hidden directories are meant to be ignored, but the
> recursion descends into them: a non-hidden child of a hidden directory - for
> example {{job-123}} under the {{_temporary}} staging directory of the
> streaming file sink - is collected at the expected partition depth with an
> empty partition spec, and FileSystemTableSource.toFullLinkedPartSpec then
> rejects it. The table stays unreadable until the directory is removed;
> writing into the table is not affected.
> Reported in FLINK-31975 against Flink 1.16.0 with a filesystem table on S3;
> the recursion is unchanged on master.
> Error
> {code:java}
> Flink SQL> select * from country_page_view;
> [ERROR] Could not execute SQL statement. Reason:
> org.apache.flink.table.api.TableException: Partition keys are: [date,
> country], incomplete partition spec: {}
> {code}
> Root cause
> listStatusRecursively lists with fs.listStatus(dir) at every level, which
> returns hidden entries as well:
> {code:java}
> if (fileStatus.isDir()) {
> for (FileStatus stat : fs.listStatus(fileStatus.getPath())) {
> listStatusRecursively(fs, stat, level + 1, expectLevel, results);
> }
> }
> {code}
> Hidden entries are filtered only on the collected result in
> searchPartSpecAndPaths:
> {code:java}
> for (FileStatus part : generatedParts) {
> // ignore hidden file
> if (isHiddenFile(part)) {
> continue;
> }
> ret.add(new Tuple2<>(extractPartitionSpecFromPath(part.getPath()),
> part.getPath()));
> }
> {code}
> isHiddenFile checks the name of the entry itself, not that of its ancestors.
> For {{_temporary/job-123}} the hidden directory {{_temporary}} is dropped at
> the leaf, but {{job-123}} is not hidden by name and reaches the result with
> an empty partition spec - the spec that toFullLinkedPartSpec rejects.
> The same method has two further defects: fs.listStatus may return null
> (listStatusWithoutHidden handles that and returns null), which makes the
> for-each throw a NullPointerException; and the unfiltered listing is walked
> although hidden entries are never wanted.
> Reproduction
> Place a hidden staging subtree next to a real partition and read the table:
> {code}
> path/date=2019-8-30/country=China/ <- real partition
> path/_temporary/job-123/ <- hidden staging directory
> {code}
> PartitionPathUtils.searchPartSpecAndPaths(fs, path, 2) then returns two
> entries: the real partition, and {{job-123}} with an empty partition spec.
> With hidden directories skipped at every level, only the real partition is
> returned.
> Proposed fix
> Reuse the existing listStatusWithoutHidden helper at every recursion level
> and keep its null contract, so that an absent listing ends the recursion
> instead of failing:
> {code:java}
> if (fileStatus.isDir()) {
> FileStatus[] children = listStatusWithoutHidden(fs, fileStatus.getPath());
> if (children == null) {
> return;
> }
> for (FileStatus stat : children) {
> listStatusRecursively(fs, stat, level + 1, expectLevel, results);
> }
> }
> {code}
> Verification
> The linked pull request adds a regression test to PartitionPathUtilsTest: a
> temporary directory holding the real partition
> {{date=2019-8-30/country=China}} and the hidden {{_temporary/job-123}}
> subtree, asserting that searchPartSpecAndPaths returns the real partition
> only; on the unfixed code the hidden child is returned as a second entry with
> an empty spec. The change is confined to
> PartitionPathUtils.listStatusRecursively, so paths without hidden entries
> keep their current behaviour.
> Related
> Supersedes FLINK-31975 (default catalog failed to retrieve partition Spec),
> which reports the same failure on 1.16.0. Pull requests: #27314 (closed by
> the stale bot, no technical objection) and #28913 (current).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)