|
33 | 33 | #include <yql/essentials/sql/v1/format/sql_format.h>
|
34 | 34 |
|
35 | 35 | #include <library/cpp/containers/stack_vector/stack_vec.h>
|
| 36 | +#include <library/cpp/json/json_reader.h> |
36 | 37 | #include <library/cpp/regex/pcre/regexp.h>
|
37 | 38 | #include <library/cpp/string_utils/quote/quote.h>
|
38 | 39 |
|
|
55 | 56 | #include <google/protobuf/text_format.h>
|
56 | 57 |
|
57 | 58 | #include <format>
|
| 59 | +#include <ranges> |
58 | 60 |
|
59 | 61 | namespace NYdb::NBackup {
|
60 | 62 |
|
@@ -788,6 +790,131 @@ void BackupReplication(
|
788 | 790 | BackupPermissions(driver, dbPath, fsBackupFolder);
|
789 | 791 | }
|
790 | 792 |
|
| 793 | +namespace { |
| 794 | + |
| 795 | +Ydb::Table::DescribeExternalDataSourceResult DescribeExternalDataSource(TDriver driver, const TString& path) { |
| 796 | + NTable::TTableClient client(driver); |
| 797 | + Ydb::Table::DescribeExternalDataSourceResult description; |
| 798 | + auto status = client.RetryOperationSync([&](NTable::TSession session) { |
| 799 | + auto result = session.DescribeExternalDataSource(path).ExtractValueSync(); |
| 800 | + if (result.IsSuccess()) { |
| 801 | + description = TProtoAccessor::GetProto(result.GetExternalDataSourceDescription()); |
| 802 | + } |
| 803 | + return result; |
| 804 | + }); |
| 805 | + VerifyStatus(status, "describe external data source"); |
| 806 | + return description; |
| 807 | +} |
| 808 | + |
| 809 | +std::string ToString(std::string_view key, std::string_view value) { |
| 810 | + // indented to follow the default YQL formatting |
| 811 | + return std::format(R"( {} = "{}")", key, value); |
| 812 | +} |
| 813 | + |
| 814 | +namespace NExternalDataSource { |
| 815 | + |
| 816 | + std::string PropertyToString(const std::pair<TProtoStringType, TProtoStringType>& property) { |
| 817 | + const auto& [key, value] = property; |
| 818 | + return ToString(key, value); |
| 819 | + } |
| 820 | + |
| 821 | +} |
| 822 | + |
| 823 | +TString BuildCreateExternalDataSourceQuery(const Ydb::Table::DescribeExternalDataSourceResult& description) { |
| 824 | + return std::format( |
| 825 | + "CREATE EXTERNAL DATA SOURCE IF NOT EXISTS `{}` WITH (\n{},\n{}{}\n);", |
| 826 | + description.self().name().c_str(), |
| 827 | + ToString("SOURCE_TYPE", description.source_type()), |
| 828 | + ToString("LOCATION", description.location()), |
| 829 | + description.properties().empty() |
| 830 | + ? "" |
| 831 | + : std::string(",\n") + |
| 832 | + JoinSeq(",\n", std::views::transform(description.properties(), NExternalDataSource::PropertyToString)).c_str() |
| 833 | + ); |
| 834 | +} |
| 835 | + |
| 836 | +} |
| 837 | + |
| 838 | +void BackupExternalDataSource(TDriver driver, const TString& dbPath, const TFsPath& fsBackupFolder) { |
| 839 | + Y_ENSURE(!dbPath.empty()); |
| 840 | + LOG_I("Backup external data source " << dbPath.Quote() << " to " << fsBackupFolder.GetPath().Quote()); |
| 841 | + |
| 842 | + const auto description = DescribeExternalDataSource(driver, dbPath); |
| 843 | + const auto creationQuery = BuildCreateExternalDataSourceQuery(description); |
| 844 | + |
| 845 | + WriteCreationQueryToFile(creationQuery, fsBackupFolder, NDump::NFiles::CreateExternalDataSource()); |
| 846 | + BackupPermissions(driver, dbPath, fsBackupFolder); |
| 847 | +} |
| 848 | + |
| 849 | +namespace { |
| 850 | + |
| 851 | +Ydb::Table::DescribeExternalTableResult DescribeExternalTable(TDriver driver, const TString& path) { |
| 852 | + NTable::TTableClient client(driver); |
| 853 | + Ydb::Table::DescribeExternalTableResult description; |
| 854 | + auto status = client.RetryOperationSync([&](NTable::TSession session) { |
| 855 | + auto result = session.DescribeExternalTable(path).ExtractValueSync(); |
| 856 | + if (result.IsSuccess()) { |
| 857 | + description = TProtoAccessor::GetProto(result.GetExternalTableDescription()); |
| 858 | + } |
| 859 | + return result; |
| 860 | + }); |
| 861 | + VerifyStatus(status, "describe external table"); |
| 862 | + return description; |
| 863 | +} |
| 864 | + |
| 865 | +namespace NExternalTable { |
| 866 | + |
| 867 | + std::string PropertyToString(const std::pair<TProtoStringType, TProtoStringType>& property) { |
| 868 | + const auto& [key, json] = property; |
| 869 | + const auto items = NJson::ReadJsonFastTree(json).GetArray(); |
| 870 | + Y_ENSURE(!items.empty(), "Empty items for an external table property: " << key); |
| 871 | + if (items.size() == 1) { |
| 872 | + return ToString(key, items.front().GetString()); |
| 873 | + } else { |
| 874 | + return ToString(key, std::format("[{}]", JoinSeq(", ", items).c_str())); |
| 875 | + } |
| 876 | + } |
| 877 | + |
| 878 | +} |
| 879 | + |
| 880 | +std::string ColumnToString(const Ydb::Table::ColumnMeta& column) { |
| 881 | + const auto& type = column.type(); |
| 882 | + const bool notNull = !type.has_optional_type() || (type.has_pg_type() && column.not_null()); |
| 883 | + return std::format( |
| 884 | + " {} {}{}", |
| 885 | + column.name().c_str(), |
| 886 | + TType(type).ToString(), |
| 887 | + notNull ? " NOT NULL" : "" |
| 888 | + ); |
| 889 | +} |
| 890 | + |
| 891 | +TString BuildCreateExternalTableQuery(const Ydb::Table::DescribeExternalTableResult& description) { |
| 892 | + return std::format( |
| 893 | + "CREATE EXTERNAL TABLE IF NOT EXISTS `{}` (\n{}\n) WITH (\n{},\n{}{}\n);", |
| 894 | + description.self().name().c_str(), |
| 895 | + JoinSeq(",\n", std::views::transform(description.columns(), ColumnToString)).c_str(), |
| 896 | + ToString("DATA_SOURCE", description.data_source_path()), |
| 897 | + ToString("LOCATION", description.location()), |
| 898 | + description.content().empty() |
| 899 | + ? "" |
| 900 | + : std::string(",\n") + |
| 901 | + JoinSeq(",\n", std::views::transform(description.content(), NExternalTable::PropertyToString)).c_str() |
| 902 | + ); |
| 903 | +} |
| 904 | + |
| 905 | +} |
| 906 | + |
| 907 | +void BackupExternalTable(TDriver driver, const TString& dbPath, const TFsPath& fsBackupFolder) { |
| 908 | + Y_ENSURE(!dbPath.empty()); |
| 909 | + LOG_I("Backup external table " << dbPath.Quote() << " to " << fsBackupFolder.GetPath().Quote()); |
| 910 | + |
| 911 | + const auto description = DescribeExternalTable(driver, dbPath); |
| 912 | + const auto creationQuery = BuildCreateExternalTableQuery(description); |
| 913 | + |
| 914 | + WriteCreationQueryToFile(creationQuery, fsBackupFolder, NDump::NFiles::CreateExternalTable()); |
| 915 | + BackupPermissions(driver, dbPath, fsBackupFolder); |
| 916 | +} |
| 917 | + |
791 | 918 | void CreateClusterDirectory(const TDriver& driver, const TString& path, bool rootBackupDir = false) {
|
792 | 919 | if (rootBackupDir) {
|
793 | 920 | LOG_I("Create temporary directory " << path.Quote() << " in database");
|
@@ -837,11 +964,11 @@ void BackupFolderImpl(TDriver driver, const TString& database, const TString& db
|
837 | 964 | bool schemaOnly, bool useConsistentCopyTable, bool avoidCopy, bool preservePoolKinds, bool ordered,
|
838 | 965 | NYql::TIssues& issues
|
839 | 966 | ) {
|
840 |
| - TFile(folderPath.Child(NDump::NFiles::Incomplete().FileName), CreateAlways); |
| 967 | + TFile(folderPath.Child(NDump::NFiles::Incomplete().FileName), CreateAlways).Close(); |
841 | 968 |
|
842 | 969 | TMap<TString, TAsyncStatus> copiedTablesStatuses;
|
843 | 970 | TVector<NTable::TCopyItem> tablesToCopy;
|
844 |
| - // Copy all tables to temporal folder |
| 971 | + // Copy all tables to temporal folder and backup other scheme objects along the way. |
845 | 972 | {
|
846 | 973 | TDbIterator<ETraverseType::Preordering> dbIt(driver, dbPrefix);
|
847 | 974 | while (dbIt) {
|
@@ -884,6 +1011,12 @@ void BackupFolderImpl(TDriver driver, const TString& database, const TString& db
|
884 | 1011 | if (dbIt.IsReplication()) {
|
885 | 1012 | BackupReplication(driver, database, dbIt.GetTraverseRoot(), dbIt.GetRelPath(), childFolderPath);
|
886 | 1013 | }
|
| 1014 | + if (dbIt.IsExternalDataSource()) { |
| 1015 | + BackupExternalDataSource(driver, dbIt.GetFullPath(), childFolderPath); |
| 1016 | + } |
| 1017 | + if (dbIt.IsExternalTable()) { |
| 1018 | + BackupExternalTable(driver, dbIt.GetFullPath(), childFolderPath); |
| 1019 | + } |
887 | 1020 | dbIt.Next();
|
888 | 1021 | }
|
889 | 1022 | }
|
@@ -1040,7 +1173,7 @@ TAdmins FindAdmins(TDriver driver, const TString& dbPath) {
|
1040 | 1173 | struct TBackupDatabaseSettings {
|
1041 | 1174 | bool WithRegularUsers = false;
|
1042 | 1175 | bool WithContent = false;
|
1043 |
| - TString TemporalBackupPostfix; |
| 1176 | + TString TemporalBackupPostfix = ""; |
1044 | 1177 | };
|
1045 | 1178 |
|
1046 | 1179 | void BackupUsers(TDriver driver, const TString& dbPath, const TFsPath& folderPath, const THashSet<TString>& filter = {}) {
|
@@ -1165,7 +1298,7 @@ void BackupDatabaseImpl(TDriver driver, const TString& dbPath, const TFsPath& fo
|
1165 | 1298 | Ydb::Cms::CreateDatabaseRequest proto;
|
1166 | 1299 | status.SerializeTo(proto);
|
1167 | 1300 | WriteProtoToFile(proto, folderPath, NDump::NFiles::Database());
|
1168 |
| - |
| 1301 | + |
1169 | 1302 | if (!settings.WithRegularUsers) {
|
1170 | 1303 | TAdmins admins = FindAdmins(driver, dbPath);
|
1171 | 1304 | BackupUsers(driver, dbPath, folderPath, admins.UserSids);
|
@@ -1308,7 +1441,7 @@ void BackupDatabase(const TDriver& driver, const TString& database, TFsPath fold
|
1308 | 1441 |
|
1309 | 1442 | try {
|
1310 | 1443 | NYql::TIssues issues;
|
1311 |
| - TFile(folderPath.Child(NDump::NFiles::Incomplete().FileName), CreateAlways); |
| 1444 | + TFile(folderPath.Child(NDump::NFiles::Incomplete().FileName), CreateAlways).Close(); |
1312 | 1445 |
|
1313 | 1446 | BackupDatabaseImpl(driver, database, folderPath, {
|
1314 | 1447 | .WithRegularUsers = true,
|
@@ -1339,7 +1472,7 @@ void BackupCluster(const TDriver& driver, TFsPath folderPath) {
|
1339 | 1472 |
|
1340 | 1473 | try {
|
1341 | 1474 | NYql::TIssues issues;
|
1342 |
| - TFile(folderPath.Child(NDump::NFiles::Incomplete().FileName), CreateAlways); |
| 1475 | + TFile(folderPath.Child(NDump::NFiles::Incomplete().FileName), CreateAlways).Close(); |
1343 | 1476 |
|
1344 | 1477 | BackupClusterRoot(driver, folderPath);
|
1345 | 1478 | auto databases = ListDatabases(driver);
|
|
0 commit comments