diff --git a/src/duckdb/src/common/file_system.cpp b/src/duckdb/src/common/file_system.cpp index 0709ae66e..6827d4a0f 100644 --- a/src/duckdb/src/common/file_system.cpp +++ b/src/duckdb/src/common/file_system.cpp @@ -575,6 +575,12 @@ bool FileSystem::TryRemoveFile(const string &filename, optional_ptr return false; } +void FileSystem::RemoveFiles(const vector &filenames, optional_ptr opener) { + for (const auto &filename : filenames) { + TryRemoveFile(filename, opener); + } +} + void FileSystem::FileSync(FileHandle &handle) { throw NotImplementedException("%s: FileSync is not implemented!", GetName()); } @@ -677,6 +683,10 @@ unique_ptr FileSystem::OpenCompressedFile(QueryContext context, uniq throw NotImplementedException("%s: OpenCompressedFile is not implemented!", GetName()); } +bool FileSystem::IsLocalFileSystem() const { + return false; +} + bool FileSystem::OnDiskFile(FileHandle &handle) { throw NotImplementedException("%s: OnDiskFile is not implemented!", GetName()); } diff --git a/src/duckdb/src/common/virtual_file_system.cpp b/src/duckdb/src/common/virtual_file_system.cpp index 5bf07f797..271c928b0 100644 --- a/src/duckdb/src/common/virtual_file_system.cpp +++ b/src/duckdb/src/common/virtual_file_system.cpp @@ -15,6 +15,14 @@ VirtualFileSystem::VirtualFileSystem(unique_ptr &&inner) : default_f VirtualFileSystem::RegisterSubSystem(FileCompressionType::GZIP, make_uniq()); } +FileSystem &VirtualFileSystem::GetDefaultFileSystem() { + auto &fs = *default_fs; + if (SubSystemIsDisabled(fs.GetName())) { + throw PermissionException("File system %s has been disabled by configuration", fs.GetName()); + } + return fs; +} + unique_ptr VirtualFileSystem::OpenFileExtended(const OpenFileInfo &file, FileOpenFlags flags, optional_ptr opener) { @@ -136,6 +144,17 @@ bool VirtualFileSystem::TryRemoveFile(const string &filename, optional_ptr &filenames, optional_ptr opener) { + reference_map_t> files_by_fs; + for (const auto &filename : filenames) { + auto &fs = FindFileSystem(filename); + files_by_fs[fs].push_back(filename); + } + for (auto &entry : files_by_fs) { + entry.first.get().RemoveFiles(entry.second, opener); + } +} + string VirtualFileSystem::PathSeparator(const string &path) { return FindFileSystem(path).PathSeparator(path); } diff --git a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp index 5958a9885..5b8665f5c 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp @@ -412,9 +412,7 @@ void CheckDirectory(FileSystem &fs, const string &file_path, CopyOverwriteMode o return; } if (overwrite_mode == CopyOverwriteMode::COPY_OVERWRITE) { - for (auto &file : file_list) { - fs.RemoveFile(file); - } + fs.RemoveFiles(file_list); } else { throw IOException("Directory \"%s\" is not empty! Enable OVERWRITE option to overwrite files", file_path); } diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 4cf88fffb..4688c28a8 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "5-dev98" +#define DUCKDB_PATCH_VERSION "5-dev101" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 4 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 1 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v1.4.5-dev98" +#define DUCKDB_VERSION "v1.4.5-dev101" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "3b4213a993" +#define DUCKDB_SOURCE_ID "ca5f01efef" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/include/duckdb/common/file_system.hpp b/src/duckdb/src/include/duckdb/common/file_system.hpp index 32929a459..6abc9caab 100644 --- a/src/duckdb/src/include/duckdb/common/file_system.hpp +++ b/src/duckdb/src/include/duckdb/common/file_system.hpp @@ -128,6 +128,7 @@ class FileSystem { public: DUCKDB_API static FileSystem &GetFileSystem(ClientContext &context); DUCKDB_API static FileSystem &GetFileSystem(DatabaseInstance &db); + DUCKDB_API static FileSystem &GetLocal(DatabaseInstance &db); DUCKDB_API static FileSystem &Get(AttachedDatabase &db); DUCKDB_API virtual unique_ptr OpenFile(const string &path, FileOpenFlags flags, @@ -191,6 +192,8 @@ class FileSystem { DUCKDB_API virtual void RemoveFile(const string &filename, optional_ptr opener = nullptr); //! Remvoe a file from disk if it exists - if it does not exist, return false DUCKDB_API virtual bool TryRemoveFile(const string &filename, optional_ptr opener = nullptr); + //! Remove multiple files from disk - does not error if any file does not exist + DUCKDB_API virtual void RemoveFiles(const vector &filenames, optional_ptr opener = nullptr); //! Sync a file handle to disk DUCKDB_API virtual void FileSync(FileHandle &handle); //! Sets the working directory @@ -274,6 +277,9 @@ class FileSystem { //! Create a LocalFileSystem. DUCKDB_API static unique_ptr CreateLocal(); + //! Whether this is a LocalFileSystem instance. + DUCKDB_API virtual bool IsLocalFileSystem() const; + //! Return the name of the filesytem. Used for forming diagnosis messages. DUCKDB_API virtual std::string GetName() const = 0; diff --git a/src/duckdb/src/include/duckdb/common/local_file_system.hpp b/src/duckdb/src/include/duckdb/common/local_file_system.hpp index 354886e50..658b21089 100644 --- a/src/duckdb/src/include/duckdb/common/local_file_system.hpp +++ b/src/duckdb/src/include/duckdb/common/local_file_system.hpp @@ -88,6 +88,10 @@ class LocalFileSystem : public FileSystem { return "LocalFileSystem"; } + bool IsLocalFileSystem() const override { + return true; + } + //! Returns the last Win32 error, in string format. Returns an empty string if there is no error, or on non-Windows //! systems. static std::string GetLastErrorAsString(); diff --git a/src/duckdb/src/include/duckdb/common/opener_file_system.hpp b/src/duckdb/src/include/duckdb/common/opener_file_system.hpp index 7c2da009a..340c3fa6a 100644 --- a/src/duckdb/src/include/duckdb/common/opener_file_system.hpp +++ b/src/duckdb/src/include/duckdb/common/opener_file_system.hpp @@ -110,6 +110,34 @@ class OpenerFileSystem : public FileSystem { return FileSystem::ExpandPath(path, GetOpener()); } + bool DirectoryExists(const string &directory) { + return DirectoryExists(directory, nullptr); + } + + void CreateDirectory(const string &directory) { + CreateDirectory(directory, nullptr); + } + void RemoveDirectory(const string &directory) { + RemoveDirectory(directory, nullptr); + } + void MoveFile(const string &source, const string &target) { + MoveFile(source, target, nullptr); + } + bool FileExists(const string &filename) { + return FileExists(filename, nullptr); + } + bool IsPipe(const string &filename) { + return IsPipe(filename, nullptr); + } + void RemoveFile(const string &filename) { + RemoveFile(filename, nullptr); + } + bool TryRemoveFile(const string &filename) { + return TryRemoveFile(filename, nullptr); + } + void RemoveFiles(const vector &filenames) { + RemoveFiles(filenames, nullptr); + } bool FileExists(const string &filename, optional_ptr opener) override { VerifyNoOpener(opener); VerifyCanAccessFile(filename); @@ -132,6 +160,14 @@ class OpenerFileSystem : public FileSystem { return GetFileSystem().TryRemoveFile(filename, GetOpener()); } + void RemoveFiles(const vector &filenames, optional_ptr opener) override { + VerifyNoOpener(opener); + for (const auto &filename : filenames) { + VerifyCanAccessFile(filename); + } + GetFileSystem().RemoveFiles(filenames, GetOpener()); + } + string PathSeparator(const string &path) override { return GetFileSystem().PathSeparator(path); } diff --git a/src/duckdb/src/include/duckdb/common/virtual_file_system.hpp b/src/duckdb/src/include/duckdb/common/virtual_file_system.hpp index 07c49b541..5e702ac71 100644 --- a/src/duckdb/src/include/duckdb/common/virtual_file_system.hpp +++ b/src/duckdb/src/include/duckdb/common/virtual_file_system.hpp @@ -48,6 +48,7 @@ class VirtualFileSystem : public FileSystem { bool IsPipe(const string &filename, optional_ptr opener) override; void RemoveFile(const string &filename, optional_ptr opener) override; bool TryRemoveFile(const string &filename, optional_ptr opener) override; + void RemoveFiles(const vector &filenames, optional_ptr opener) override; vector Glob(const string &path, FileOpener *opener = nullptr) override; @@ -61,6 +62,8 @@ class VirtualFileSystem : public FileSystem { vector ListSubSystems() override; + FileSystem &GetDefaultFileSystem(); + std::string GetName() const override; void SetDisabledFileSystems(const vector &names) override; diff --git a/src/duckdb/src/include/duckdb/main/database.hpp b/src/duckdb/src/include/duckdb/main/database.hpp index 7e43de600..1e962665f 100644 --- a/src/duckdb/src/include/duckdb/main/database.hpp +++ b/src/duckdb/src/include/duckdb/main/database.hpp @@ -17,6 +17,7 @@ #include "duckdb/main/extension_manager.hpp" namespace duckdb { +class LocalDatabaseFileSystem; class BufferManager; class DatabaseManager; class StorageManager; @@ -50,6 +51,7 @@ class DatabaseInstance : public enable_shared_from_this { DUCKDB_API const BufferManager &GetBufferManager() const; DUCKDB_API DatabaseManager &GetDatabaseManager(); DUCKDB_API FileSystem &GetFileSystem(); + DUCKDB_API FileSystem &GetLocalFileSystem(); DUCKDB_API ExternalFileCache &GetExternalFileCache(); DUCKDB_API TaskScheduler &GetScheduler(); DUCKDB_API ObjectCache &GetObjectCache(); @@ -90,6 +92,7 @@ class DatabaseInstance : public enable_shared_from_this { unique_ptr extension_manager; ValidChecker db_validity; unique_ptr db_file_system; + unique_ptr local_db_file_system; unique_ptr log_manager; unique_ptr external_file_cache; diff --git a/src/duckdb/src/include/duckdb/main/database_file_opener.hpp b/src/duckdb/src/include/duckdb/main/database_file_opener.hpp index 7eacbbcfe..8786eb5e3 100644 --- a/src/duckdb/src/include/duckdb/main/database_file_opener.hpp +++ b/src/duckdb/src/include/duckdb/main/database_file_opener.hpp @@ -12,6 +12,7 @@ #include "duckdb/common/opener_file_system.hpp" #include "duckdb/main/config.hpp" #include "duckdb/main/database.hpp" +#include "duckdb/common/local_file_system.hpp" #include "duckdb/logging/log_manager.hpp" namespace duckdb { @@ -63,4 +64,20 @@ class DatabaseFileSystem : public OpenerFileSystem { mutable DatabaseFileOpener database_opener; }; +class LocalDatabaseFileSystem : public OpenerFileSystem { +public: + explicit LocalDatabaseFileSystem(DatabaseInstance &db_p); + + FileSystem &GetFileSystem() const override; + optional_ptr GetOpener() const override { + return &database_opener; + } + +private: + DatabaseInstance &db; + unique_ptr owned_file_system; + FileSystem &local_fs; + mutable DatabaseFileOpener database_opener; +}; + } // namespace duckdb diff --git a/src/duckdb/src/main/database.cpp b/src/duckdb/src/main/database.cpp index 8d23656aa..c0df26fcf 100644 --- a/src/duckdb/src/main/database.cpp +++ b/src/duckdb/src/main/database.cpp @@ -2,6 +2,7 @@ #include "duckdb/catalog/catalog.hpp" #include "duckdb/common/virtual_file_system.hpp" +#include "duckdb/common/local_file_system.hpp" #include "duckdb/execution/index/index_type_set.hpp" #include "duckdb/execution/operator/helper/physical_set.hpp" #include "duckdb/function/cast/cast_function_set.hpp" @@ -126,6 +127,10 @@ FileSystem &FileSystem::GetFileSystem(DatabaseInstance &db) { return db.GetFileSystem(); } +FileSystem &FileSystem::GetLocal(DatabaseInstance &db) { + return db.GetLocalFileSystem(); +} + FileSystem &FileSystem::Get(AttachedDatabase &db) { return FileSystem::GetFileSystem(db.GetDatabase()); } @@ -278,6 +283,7 @@ void DatabaseInstance::Initialize(const char *database_path, DBConfig *user_conf create_api_v1 = CreateAPIv1Wrapper; db_file_system = make_uniq(*this); + local_db_file_system = make_uniq(*this); db_manager = make_uniq(*this); if (config.buffer_manager) { buffer_manager = config.buffer_manager; @@ -380,6 +386,32 @@ FileSystem &DatabaseInstance::GetFileSystem() { return *db_file_system; } +FileSystem &DatabaseInstance::GetLocalFileSystem() { + return *local_db_file_system; +} + +static FileSystem &ResolveLocalFileSystem(DatabaseInstance &db, unique_ptr &owned) { + auto &vfs = static_cast(*db.config.file_system); + auto &default_fs = vfs.GetDefaultFileSystem(); + if (default_fs.IsLocalFileSystem()) { + return default_fs; + } + owned = make_uniq(); + return *owned; +} + +LocalDatabaseFileSystem::LocalDatabaseFileSystem(DatabaseInstance &db_p) + : db(db_p), local_fs(ResolveLocalFileSystem(db_p, owned_file_system)), database_opener(db_p) { +} + +FileSystem &LocalDatabaseFileSystem::GetFileSystem() const { + auto &vfs = static_cast(*db.config.file_system); + if (vfs.SubSystemIsDisabled(local_fs.GetName())) { + throw PermissionException("File system %s has been disabled by configuration", local_fs.GetName()); + } + return local_fs; +} + ExternalFileCache &DatabaseInstance::GetExternalFileCache() { return *external_file_cache; } diff --git a/src/duckdb/src/main/extension/extension_helper.cpp b/src/duckdb/src/main/extension/extension_helper.cpp index 74add5379..d897342d2 100644 --- a/src/duckdb/src/main/extension/extension_helper.cpp +++ b/src/duckdb/src/main/extension/extension_helper.cpp @@ -2,6 +2,8 @@ #include "duckdb/common/file_system.hpp" #include "duckdb/common/serializer/binary_deserializer.hpp" +#include "duckdb/common/local_file_system.hpp" +#include "duckdb/main/database_file_opener.hpp" #include "duckdb/common/serializer/buffered_file_reader.hpp" #include "duckdb/common/string_util.hpp" #include "duckdb/common/windows.hpp" @@ -387,17 +389,17 @@ void ExtensionHelper::AutoLoadExtension(DatabaseInstance &db, const string &exte } auto &dbconfig = DBConfig::GetConfig(db); try { - auto fs = FileSystem::CreateLocal(); + auto &fs = FileSystem::GetLocal(db); #ifndef DUCKDB_WASM if (dbconfig.options.autoinstall_known_extensions) { auto repository_url = GetAutoInstallExtensionsRepository(dbconfig.options); auto autoinstall_repo = ExtensionRepository::GetRepositoryByUrl(repository_url); ExtensionInstallOptions options; options.repository = autoinstall_repo; - ExtensionHelper::InstallExtension(db, *fs, extension_name, options); + ExtensionHelper::InstallExtension(db, fs, extension_name, options); } #endif - ExtensionHelper::LoadExternalExtension(db, *fs, extension_name); + ExtensionHelper::LoadExternalExtension(db, fs, extension_name); DUCKDB_LOG_INFO(db, "Loaded extension '%s'", extension_name); } catch (std::exception &e) { ErrorData error(e); diff --git a/src/duckdb/src/main/extension/extension_install.cpp b/src/duckdb/src/main/extension/extension_install.cpp index d0996d952..300ee8214 100644 --- a/src/duckdb/src/main/extension/extension_install.cpp +++ b/src/duckdb/src/main/extension/extension_install.cpp @@ -2,6 +2,7 @@ #include "duckdb/common/gzip_file_system.hpp" #include "duckdb/common/http_util.hpp" #include "duckdb/common/local_file_system.hpp" +#include "duckdb/main/database_file_opener.hpp" #include "duckdb/common/serializer/binary_serializer.hpp" #include "duckdb/common/string_util.hpp" #include "duckdb/common/types/uuid.hpp" @@ -373,11 +374,11 @@ static unique_ptr InstallFromHttpUrl(DatabaseInstance &db, optional_ptr context) { unique_ptr install_info; { - auto fs = FileSystem::CreateLocal(); - if (fs->FileExists(local_extension_path + ".info")) { + auto &fs = FileSystem::GetLocal(db); + if (fs.FileExists(local_extension_path + ".info")) { try { install_info = - ExtensionInstallInfo::TryReadInfoFile(*fs, local_extension_path + ".info", extension_name); + ExtensionInstallInfo::TryReadInfoFile(fs, local_extension_path + ".info", extension_name); } catch (...) { if (!options.force_install) { // We are going to rewrite the file anyhow, so this is fine @@ -450,8 +451,8 @@ static unique_ptr InstallFromHttpUrl(DatabaseInstance &db, } QueryContext query_context(context); - auto fs = FileSystem::CreateLocal(); - WriteExtensionFiles(query_context, *fs, temp_path, local_extension_path, (void *)decompressed_body.data(), + auto &fs = FileSystem::GetLocal(db); + WriteExtensionFiles(query_context, fs, temp_path, local_extension_path, (void *)decompressed_body.data(), decompressed_body.size(), info, db.config); return make_uniq(info); @@ -548,15 +549,15 @@ unique_ptr ExtensionHelper::InstallExtensionInternal(Datab // Install extension from local, direct url if (ExtensionHelper::IsFullPath(extension) && !IsHTTP(extension)) { - LocalFileSystem local_fs; + auto &local_fs = FileSystem::GetLocal(db); return DirectInstallExtension(db, local_fs, extension, temp_path, extension, local_extension_path, options, context); } // Install extension from local url based on a repository (Note that this will install it as a local file) if (options.repository && !IsHTTP(options.repository->path)) { - LocalFileSystem local_fs; - return InstallFromRepository(db, fs, extension, extension_name, temp_path, local_extension_path, options, + auto &local_fs = FileSystem::GetLocal(db); + return InstallFromRepository(db, local_fs, extension, extension_name, temp_path, local_extension_path, options, context); } diff --git a/src/duckdb/src/main/secret/secret_manager.cpp b/src/duckdb/src/main/secret/secret_manager.cpp index 8788b595b..898817dd1 100644 --- a/src/duckdb/src/main/secret/secret_manager.cpp +++ b/src/duckdb/src/main/secret/secret_manager.cpp @@ -3,6 +3,7 @@ #include "duckdb/catalog/catalog_entry.hpp" #include "duckdb/common/common.hpp" #include "duckdb/common/file_system.hpp" +#include "duckdb/main/database_file_opener.hpp" #include "duckdb/common/local_file_system.hpp" #include "duckdb/common/mutex.hpp" #include "duckdb/common/serializer/binary_deserializer.hpp" @@ -43,7 +44,7 @@ void SecretManager::Initialize(DatabaseInstance &db) { lock_guard lck(manager_lock); // Construct default path - LocalFileSystem fs; + auto &fs = FileSystem::GetLocal(db); config.default_secret_path = fs.GetHomeDirectory(); vector path_components = {".duckdb", "stored_secrets"}; for (auto &path_ele : path_components) { @@ -653,7 +654,7 @@ unique_ptr DefaultSecretGenerator::CreateDefaultEntryInternal(cons return nullptr; } - LocalFileSystem fs; + auto &fs = FileSystem::GetLocal(catalog.GetDatabase()); string base_secret_path = secret_manager.PersistentSecretPath(); string secret_path = fs.JoinPath(base_secret_path, entry_name + ".duckdb_secret"); diff --git a/src/duckdb/src/main/secret/secret_storage.cpp b/src/duckdb/src/main/secret/secret_storage.cpp index a014d13f3..5264a4b43 100644 --- a/src/duckdb/src/main/secret/secret_storage.cpp +++ b/src/duckdb/src/main/secret/secret_storage.cpp @@ -1,6 +1,7 @@ #include "duckdb/common/common.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/common/local_file_system.hpp" +#include "duckdb/main/database_file_opener.hpp" #include "duckdb/common/mutex.hpp" #include "duckdb/common/serializer/binary_serializer.hpp" #include "duckdb/common/serializer/buffered_file_reader.hpp" @@ -137,7 +138,7 @@ LocalFileSecretStorage::LocalFileSecretStorage(SecretManager &manager, DatabaseI persistent = true; // Check existence of persistent secret dir - LocalFileSystem fs; + auto &fs = FileSystem::GetLocal(db); if (fs.DirectoryExists(secret_path)) { fs.ListFiles(secret_path, [&](const string &fname, bool is_dir) { string full_path = fs.JoinPath(secret_path, fname); @@ -186,7 +187,7 @@ static void WriteSecretFileToDisk(FileSystem &fs, const string &path, const Base } void LocalFileSecretStorage::WriteSecret(const BaseSecret &secret, OnCreateConflict on_conflict) { - LocalFileSystem fs; + auto &fs = FileSystem::GetLocal(db); // We may need to create the secret dir here if the directory was not present during LocalFileSecretStorage // construction @@ -230,7 +231,7 @@ void LocalFileSecretStorage::WriteSecret(const BaseSecret &secret, OnCreateConfl } void LocalFileSecretStorage::RemoveSecret(const string &secret, OnEntryNotFound on_entry_not_found) { - LocalFileSystem fs; + auto &fs = FileSystem::GetLocal(db); string file = fs.JoinPath(secret_path, secret + ".duckdb_secret"); persistent_secrets.erase(secret); try { diff --git a/src/duckdb/src/storage/temporary_file_manager.cpp b/src/duckdb/src/storage/temporary_file_manager.cpp index b8ab5a7b0..dc805da84 100644 --- a/src/duckdb/src/storage/temporary_file_manager.cpp +++ b/src/duckdb/src/storage/temporary_file_manager.cpp @@ -762,9 +762,12 @@ TemporaryDirectoryHandle::~TemporaryDirectoryHandle() { // we want to remove all files in the directory fs.RemoveDirectory(temp_directory); } else { + vector full_path_files_to_delete; + full_path_files_to_delete.reserve(files_to_delete.size()); for (auto &file : files_to_delete) { - fs.RemoveFile(fs.JoinPath(temp_directory, file)); + full_path_files_to_delete.push_back(fs.JoinPath(temp_directory, file)); } + fs.RemoveFiles(full_path_files_to_delete); } } }