diff --git a/extension b/extension index 371e60889..988456458 160000 --- a/extension +++ b/extension @@ -1 +1 @@ -Subproject commit 371e60889597bfc96759e57ad945af06635874fc +Subproject commit 988456458e64b26cc3c4bb3663fe2e2f2f21d347 diff --git a/src/binder/bind/bind_file_scan.cpp b/src/binder/bind/bind_file_scan.cpp index d8b041642..8297f1f5c 100644 --- a/src/binder/bind/bind_file_scan.cpp +++ b/src/binder/bind/bind_file_scan.cpp @@ -45,7 +45,8 @@ FileTypeInfo Binder::bindFileTypeInfo(const std::vector& filePaths) return expectedFileType; } -std::vector Binder::bindFilePaths(const std::vector& filePaths) const { +std::vector Binder::bindFilePaths(const std::vector& filePaths, + bool passThroughOnNoMatch) const { std::vector boundFilePaths; for (auto& filePath : filePaths) { // This is a temporary workaround because we use duckdb to read from iceberg/delta/azure. @@ -64,6 +65,13 @@ std::vector Binder::bindFilePaths(const std::vector& f auto globbedFilePaths = VirtualFileSystem::GetUnsafe(*clientContext)->glob(clientContext, filePath); if (globbedFilePaths.empty()) { + if (passThroughOnNoMatch) { + // The format's scan function owns the interpretation of the scan source, so + // leave the source unmodified for the extension to resolve (e.g. as a + // REST catalog table identifier) and to produce its own error if invalid. + boundFilePaths.push_back(filePath); + continue; + } throw BinderException{ std::format("No file found that matches the pattern: {}.", filePath)}; } @@ -124,18 +132,30 @@ std::unique_ptr Binder::bindFileScanSource(const BaseScanSo const options_t& options, const std::vector& columnNames, const std::vector& columnTypes) { auto fileSource = scanSource.constPtrCast(); - auto filePaths = bindFilePaths(fileSource->filePaths); auto boundOptions = bindParsingOptions(options); + auto formatOption = boundOptions.find(FileScanInfo::FILE_FORMAT_OPTION_NAME); + // An explicitly declared format that is not one of the built-in file types is provided by + // an extension (e.g. 'iceberg', 'delta'). The _SCAN table function registered by + // that extension owns the interpretation of the scan source: it may be a filesystem path, + // but it can also be a logical identifier (e.g. an Iceberg REST catalog table name) that + // must not be validated against the filesystem. When globbing finds no match, the source + // is therefore passed through verbatim so the extension can resolve it and produce its + // own error if invalid. + const bool extensionFormat = + formatOption != boundOptions.end() && + FileTypeUtils::fromString(formatOption->second.toString()) == FileType::UNKNOWN; + auto filePaths = + bindFilePaths(fileSource->filePaths, extensionFormat /* passThroughOnNoMatch */); FileTypeInfo fileTypeInfo; - if (boundOptions.contains(FileScanInfo::FILE_FORMAT_OPTION_NAME)) { - auto fileFormat = boundOptions.at(FileScanInfo::FILE_FORMAT_OPTION_NAME).toString(); + if (formatOption != boundOptions.end()) { + auto fileFormat = formatOption->second.toString(); fileTypeInfo = FileTypeInfo{FileTypeUtils::fromString(fileFormat), fileFormat}; } else { fileTypeInfo = bindFileTypeInfo(filePaths); } // If we defined a certain FileType, we have to ensure the path is a file, not something else - // (e.g. an existed directory) + // (e.g. an existed directory). Extension-provided formats interpret the source themselves. if (fileTypeInfo.fileType != FileType::UNKNOWN) { for (const auto& filePath : filePaths) { if (!LocalFileSystem::fileExists(filePath) && LocalFileSystem::isLocalPath(filePath)) { diff --git a/src/include/binder/binder.h b/src/include/binder/binder.h index e487eb63b..11038f09c 100644 --- a/src/include/binder/binder.h +++ b/src/include/binder/binder.h @@ -188,7 +188,8 @@ class Binder { common::case_insensitive_map_t bindParsingOptions( const parser::options_t& parsingOptions); common::FileTypeInfo bindFileTypeInfo(const std::vector& filePaths) const; - std::vector bindFilePaths(const std::vector& filePaths) const; + std::vector bindFilePaths(const std::vector& filePaths, + bool passThroughOnNoMatch = false) const; /*** bind query ***/ std::unique_ptr bindQuery(const parser::Statement& statement);