Skip to content

Commit 34a8b86

Browse files
authored
fix: ListingSchemaProvider directory paths (#4788)
- Append trailing slash to table paths if they are directories
1 parent 6c0e5d9 commit 34a8b86

File tree

1 file changed

+54
-2
lines changed

1 file changed

+54
-2
lines changed

datafusion/core/src/catalog/listing_schema.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,23 @@ impl ListingSchemaProvider {
9494
let base = Path::new(self.path.as_ref());
9595
let mut tables = HashSet::new();
9696
for file in entries.iter() {
97+
// The listing will initially be a file. However if we've recursed up to match our base, we know our path is a directory.
98+
let mut is_dir = false;
9799
let mut parent = Path::new(file.location.as_ref());
98100
while let Some(p) = parent.parent() {
99101
if p == base {
100-
tables.insert(parent);
102+
tables.insert(TablePath {
103+
is_dir,
104+
path: parent,
105+
});
101106
}
102107
parent = p;
108+
is_dir = true;
103109
}
104110
}
105111
for table in tables.iter() {
106112
let file_name = table
113+
.path
107114
.file_name()
108115
.ok_or_else(|| {
109116
DataFusionError::Internal("Cannot parse file name!".to_string())
@@ -113,7 +120,7 @@ impl ListingSchemaProvider {
113120
DataFusionError::Internal("Cannot parse file name!".to_string())
114121
})?;
115122
let table_name = file_name.split('.').collect_vec()[0];
116-
let table_path = table.to_str().ok_or_else(|| {
123+
let table_path = table.to_string().ok_or_else(|| {
117124
DataFusionError::Internal("Cannot parse file name!".to_string())
118125
})?;
119126

@@ -197,3 +204,48 @@ impl SchemaProvider for ListingSchemaProvider {
197204
.contains_key(name)
198205
}
199206
}
207+
208+
/// Stores metadata along with a table's path.
209+
/// Primarily whether the path is a directory or not.
210+
#[derive(Eq, PartialEq, Hash, Debug)]
211+
struct TablePath<'a> {
212+
path: &'a Path,
213+
is_dir: bool,
214+
}
215+
216+
impl TablePath<'_> {
217+
/// Format the path with a '/' appended if its a directory.
218+
/// Clients (eg. object_store listing) can and will use the presence of trailing slash as a heuristic
219+
fn to_string(&self) -> Option<String> {
220+
self.path.to_str().map(|path| {
221+
if self.is_dir {
222+
format!("{path}/")
223+
} else {
224+
path.to_string()
225+
}
226+
})
227+
}
228+
}
229+
230+
#[cfg(test)]
231+
mod tests {
232+
use super::*;
233+
234+
#[test]
235+
fn table_path_ends_with_slash_when_is_dir() {
236+
let table_path = TablePath {
237+
path: Path::new("/file"),
238+
is_dir: true,
239+
};
240+
assert!(table_path.to_string().expect("table path").ends_with("/"));
241+
}
242+
243+
#[test]
244+
fn dir_table_path_str_does_not_end_with_slash_when_not_is_dir() {
245+
let table_path = TablePath {
246+
path: Path::new("/file"),
247+
is_dir: false,
248+
};
249+
assert!(!table_path.to_string().expect("table_path").ends_with("/"));
250+
}
251+
}

0 commit comments

Comments
 (0)