Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/ducklake_extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ static void LoadInternal(ExtensionLoader &loader) {
DuckLakeCleanupOrphanedFilesFunction cleanup_orphaned_files;
loader.RegisterFunction(cleanup_orphaned_files);

DuckLakeStaticBackupFunction backup;
loader.RegisterFunction(backup);

DuckLakeExpireSnapshotsFunction expire_snapshots;
loader.RegisterFunction(expire_snapshots);

Expand Down
1 change: 1 addition & 0 deletions src/functions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(
ducklake_options.cpp
ducklake_table_changes.cpp
ducklake_table_info.cpp
ducklake_static_backup.cpp
ducklake_table_insertions.cpp)
set(ALL_OBJECT_FILES
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:ducklake_functions>
Expand Down
95 changes: 95 additions & 0 deletions src/functions/ducklake_static_backup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "functions/ducklake_table_functions.hpp"
#include "duckdb/common/types/uuid.hpp"
#include "duckdb/main/attached_database.hpp"
#include "duckdb/main/database_manager.hpp"
#include "storage/ducklake_catalog.hpp"
#include "storage/ducklake_transaction.hpp"

namespace duckdb {

struct BackupBindData : public TableFunctionData {

explicit BackupBindData(Catalog &catalog) : catalog(catalog) {
}

Catalog &catalog;
string backup_location;
};

static unique_ptr<FunctionData> DuckLakeStaticBackupBind(ClientContext &context, TableFunctionBindInput &input,
vector<LogicalType> &return_types, vector<string> &names) {
auto &catalog = BaseMetadataFunction::GetCatalog(context, input.inputs[0]);
auto result = make_uniq<BackupBindData>(catalog);

auto &ducklake_catalog = reinterpret_cast<DuckLakeCatalog &>(catalog);
string backup_location = ducklake_catalog.GetStaticBackup();

if (backup_location.empty()) {
throw InvalidInputException("static_backup not specified as attach option");
}

result->backup_location = backup_location;

return_types.emplace_back(LogicalType::VARCHAR);
names.emplace_back("errors");

return std::move(result);
}

struct DuckLakeBackupData : public GlobalTableFunctionState {
DuckLakeBackupData() : offset(0), executed(false) {
}

idx_t offset;
bool executed;
};

unique_ptr<GlobalTableFunctionState> DuckLakeStaticBackupInit(ClientContext &context, TableFunctionInitInput &input) {
auto result = make_uniq<DuckLakeBackupData>();
return std::move(result);
}

void DuckLakeStaticBackupExecute(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) {
auto &data = data_p.bind_data->Cast<BackupBindData>();
auto &state = data_p.global_state->Cast<DuckLakeBackupData>();

if (!state.executed) {
auto &transaction = DuckLakeTransaction::Get(context, data.catalog);

auto tmp_uuid = "ducklake_backup_file." + UUID::ToString(UUID::GenerateRandomUUID());

auto &fs = FileSystem::GetFileSystem(context);

if (fs.FileExists(tmp_uuid) || fs.FileExists(tmp_uuid + ".wal")) {
throw BinderException(
"Temporary file \"%s\" is already in use, please cleanup files in the form \"ducklake_backup_file.*\"",
tmp_uuid);
}

auto result = transaction.Query(
string("") + "ATTACH IF NOT EXISTS '" + tmp_uuid +
"' AS {METADATA_CATALOG_NAME_IDENTIFIER_BACKUP} (STORAGE_VERSION 'v1.4.0');" +
"COPY FROM DATABASE {METADATA_CATALOG_NAME_IDENTIFIER} TO {METADATA_CATALOG_NAME_IDENTIFIER_BACKUP};" +
"DETACH {METADATA_CATALOG_NAME_IDENTIFIER_BACKUP};" + "COPY (SELECT content FROM read_blob('" + tmp_uuid +
"')) TO '" + data.backup_location + "' (FORMAT BLOB);" + "COPY (SELECT content FROM read_blob('" +
tmp_uuid + ".wal')) TO '" + data.backup_location + ".wal' (FORMAT BLOB);" + "");

fs.TryRemoveFile(tmp_uuid);
fs.TryRemoveFile(tmp_uuid + ".wal");

if (result->HasError()) {
auto &error_obj = result->GetErrorObject();
error_obj.Throw("Failed to attach temp backup");
}
state.executed = true;
}
idx_t count = 0;
output.SetCardinality(count);
}

DuckLakeStaticBackupFunction::DuckLakeStaticBackupFunction()
: TableFunction("ducklake_static_backup", {LogicalType::VARCHAR}, DuckLakeStaticBackupExecute,
DuckLakeStaticBackupBind, DuckLakeStaticBackupInit) {
}

} // namespace duckdb
1 change: 1 addition & 0 deletions src/include/common/ducklake_options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct DuckLakeOptions {
string metadata_path;
string metadata_schema;
string data_path;
string static_backup;
bool override_data_path = false;
AccessMode access_mode = AccessMode::AUTOMATIC;
DuckLakeEncryption encryption = DuckLakeEncryption::AUTOMATIC;
Expand Down
5 changes: 5 additions & 0 deletions src/include/functions/ducklake_table_functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,9 @@ class DuckLakeAddDataFilesFunction : public TableFunction {
DuckLakeAddDataFilesFunction();
};

class DuckLakeStaticBackupFunction : public TableFunction {
public:
DuckLakeStaticBackupFunction();
};

} // namespace duckdb
4 changes: 4 additions & 0 deletions src/include/storage/ducklake_catalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ class DuckLakeCatalog : public Catalog {

string GetDataPath();

string GetStaticBackup() const {
return options.static_backup;
}

bool SupportsTimeTravel() const override {
return true;
}
Expand Down
2 changes: 2 additions & 0 deletions src/storage/ducklake_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ static void HandleDuckLakeOption(DuckLakeOptions &options, const string &option,
options.override_data_path = value.GetValue<bool>();
} else if (lcase == "metadata_schema") {
options.metadata_schema = value.ToString();
} else if (lcase == "static_backup") {
options.static_backup = value.ToString();
} else if (lcase == "metadata_catalog") {
options.metadata_database = value.ToString();
} else if (lcase == "metadata_path") {
Expand Down
3 changes: 3 additions & 0 deletions src/storage/ducklake_transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,8 @@ void DuckLakeTransaction::DeleteInlinedData(const DuckLakeInlinedTableInfo &inli
unique_ptr<QueryResult> DuckLakeTransaction::Query(string query) {
auto &connection = GetConnection();
auto catalog_identifier = DuckLakeUtil::SQLIdentifierToString(ducklake_catalog.MetadataDatabaseName());
auto catalog_identifier_backup =
DuckLakeUtil::SQLIdentifierToString(ducklake_catalog.MetadataDatabaseName() + "_backup");
auto catalog_literal = DuckLakeUtil::SQLLiteralToString(ducklake_catalog.MetadataDatabaseName());
auto schema_identifier = DuckLakeUtil::SQLIdentifierToString(ducklake_catalog.MetadataSchemaName());
auto schema_identifier_escaped = StringUtil::Replace(schema_identifier, "'", "''");
Expand All @@ -1464,6 +1466,7 @@ unique_ptr<QueryResult> DuckLakeTransaction::Query(string query) {

query = StringUtil::Replace(query, "{METADATA_CATALOG_NAME_LITERAL}", catalog_literal);
query = StringUtil::Replace(query, "{METADATA_CATALOG_NAME_IDENTIFIER}", catalog_identifier);
query = StringUtil::Replace(query, "{METADATA_CATALOG_NAME_IDENTIFIER_BACKUP}", catalog_identifier_backup);
query = StringUtil::Replace(query, "{METADATA_SCHEMA_NAME_LITERAL}", schema_literal);
query = StringUtil::Replace(query, "{METADATA_CATALOG}", catalog_identifier + "." + schema_identifier);
query = StringUtil::Replace(query, "{METADATA_SCHEMA_ESCAPED}", schema_identifier_escaped);
Expand Down
Loading