This is an automated email from the ASF dual-hosted git repository.

yuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 86e8e24  feat: introduce database level ops for cpp bindings (#286)
86e8e24 is described below

commit 86e8e241bc4101c322811837fe72ddb46342f9cf
Author: yuxia Luo <[email protected]>
AuthorDate: Sun Feb 8 20:21:19 2026 +0800

    feat: introduce database level ops for cpp bindings (#286)
---
 bindings/cpp/BUILD.bazel                |  33 +++++
 bindings/cpp/CMakeLists.txt             |   6 +
 bindings/cpp/examples/admin_example.cpp | 121 +++++++++++++++++++
 bindings/cpp/include/fluss.hpp          |  36 ++++++
 bindings/cpp/src/admin.cpp              | 120 ++++++++++++++++++
 bindings/cpp/src/ffi_converter.hpp      |  25 ++++
 bindings/cpp/src/lib.rs                 | 208 ++++++++++++++++++++++++++++++++
 bindings/cpp/src/types.rs               |  43 +++++++
 8 files changed, 592 insertions(+)

diff --git a/bindings/cpp/BUILD.bazel b/bindings/cpp/BUILD.bazel
index 81d483c..aff8f50 100644
--- a/bindings/cpp/BUILD.bazel
+++ b/bindings/cpp/BUILD.bazel
@@ -340,3 +340,36 @@ cc_binary(
     visibility = ["//visibility:public"],
 )
 
+cc_binary(
+    name = "fluss_cpp_admin_example",
+    srcs = [
+        "examples/admin_example.cpp",
+    ],
+    deps = [":fluss_cpp"],
+    copts = [
+        "-std=c++17",
+    ] + select({
+        ":debug_mode": [
+            "-g3",
+            "-O0",
+            "-ggdb",
+            "-fno-omit-frame-pointer",
+            "-DDEBUG",
+        ],
+        ":fastbuild_mode": [
+            "-g",
+            "-O0",
+        ],
+        ":release_mode": [
+            "-O2",
+            "-DNDEBUG",
+        ],
+    }),
+    linkopts = select({
+        ":debug_mode": ["-g"],
+        ":fastbuild_mode": ["-g"],
+        ":release_mode": [],
+    }),
+    visibility = ["//visibility:public"],
+)
+
diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt
index 93cfc41..ae70842 100644
--- a/bindings/cpp/CMakeLists.txt
+++ b/bindings/cpp/CMakeLists.txt
@@ -102,6 +102,12 @@ target_link_libraries(fluss_cpp_example PRIVATE 
Arrow::arrow_shared)
 target_compile_definitions(fluss_cpp_example PRIVATE ARROW_FOUND)
 target_include_directories(fluss_cpp_example PUBLIC ${CPP_INCLUDE_DIR})
 
+add_executable(fluss_cpp_admin_example examples/admin_example.cpp)
+target_link_libraries(fluss_cpp_admin_example PRIVATE fluss_cpp)
+target_link_libraries(fluss_cpp_admin_example PRIVATE Arrow::arrow_shared)
+target_compile_definitions(fluss_cpp_admin_example PRIVATE ARROW_FOUND)
+target_include_directories(fluss_cpp_admin_example PUBLIC ${CPP_INCLUDE_DIR})
+
 set_target_properties(fluss_cpp
     PROPERTIES ADDITIONAL_CLEAN_FILES ${CARGO_TARGET_DIR}
 )
diff --git a/bindings/cpp/examples/admin_example.cpp 
b/bindings/cpp/examples/admin_example.cpp
new file mode 100644
index 0000000..7b7a333
--- /dev/null
+++ b/bindings/cpp/examples/admin_example.cpp
@@ -0,0 +1,121 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <iostream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "fluss.hpp"
+
+static void check(const char* step, const fluss::Result& r) {
+    if (!r.Ok()) {
+        std::cerr << step << " failed: code=" << r.error_code << " msg=" << 
r.error_message
+                  << std::endl;
+        std::exit(1);
+    }
+}
+
+int main() {
+    const std::string bootstrap = "127.0.0.1:9123";
+    const std::string db_name = "admin_example_db";
+    const std::string table_name = "admin_example_table";
+
+    // 1) Connect and get Admin
+    fluss::Connection conn;
+    check("connect", fluss::Connection::Connect(bootstrap, conn));
+
+    fluss::Admin admin;
+    check("get_admin", conn.GetAdmin(admin));
+
+    // 2) Database operations
+    std::cout << "--- Database operations ---" << std::endl;
+
+    bool exists = false;
+    check("database_exists (before create)", admin.DatabaseExists(db_name, 
exists));
+    std::cout << "Database " << db_name << " exists before create: " << 
(exists ? "yes" : "no")
+              << std::endl;
+
+    fluss::DatabaseDescriptor db_desc;
+    db_desc.comment = "Example database for Admin API";
+    db_desc.properties["owner"] = "admin_example";
+    check("create_database", admin.CreateDatabase(db_name, db_desc, true));
+
+    check("database_exists (after create)", admin.DatabaseExists(db_name, 
exists));
+    std::cout << "Database " << db_name << " exists after create: " << (exists 
? "yes" : "no")
+              << std::endl;
+
+    fluss::DatabaseInfo db_info;
+    check("get_database_info", admin.GetDatabaseInfo(db_name, db_info));
+    std::cout << "Database info: name=" << db_info.database_name
+              << " comment=" << db_info.comment << " created_time=" << 
db_info.created_time
+              << std::endl;
+
+    std::vector<std::string> databases;
+    check("list_databases", admin.ListDatabases(databases));
+    std::cout << "List databases (" << databases.size() << "): ";
+    for (size_t i = 0; i < databases.size(); ++i) {
+        if (i > 0) std::cout << ", ";
+        std::cout << databases[i];
+    }
+    std::cout << std::endl;
+
+    // 3) Table operations in the new database
+    std::cout << "--- Table operations ---" << std::endl;
+
+    fluss::TablePath table_path(db_name, table_name);
+
+    bool table_exists_flag = false;
+    check("table_exists (before create)", admin.TableExists(table_path, 
table_exists_flag));
+    std::cout << "Table " << db_name << "." << table_name
+              << " exists before create: " << (table_exists_flag ? "yes" : 
"no") << std::endl;
+
+    auto schema = fluss::Schema::NewBuilder()
+                      .AddColumn("id", fluss::DataType::Int())
+                      .AddColumn("name", fluss::DataType::String())
+                      .Build();
+    auto descriptor = fluss::TableDescriptor::NewBuilder()
+                          .SetSchema(schema)
+                          .SetBucketCount(1)
+                          .SetComment("admin example table")
+                          .Build();
+
+    check("create_table", admin.CreateTable(table_path, descriptor, true));
+
+    check("table_exists (after create)", admin.TableExists(table_path, 
table_exists_flag));
+    std::cout << "Table exists after create: " << (table_exists_flag ? "yes" : 
"no") << std::endl;
+
+    std::vector<std::string> tables;
+    check("list_tables", admin.ListTables(db_name, tables));
+    std::cout << "List tables in " << db_name << " (" << tables.size() << "): 
";
+    for (size_t i = 0; i < tables.size(); ++i) {
+        if (i > 0) std::cout << ", ";
+        std::cout << tables[i];
+    }
+    std::cout << std::endl;
+
+    // 4) Cleanup: drop table, then drop database
+    std::cout << "--- Cleanup ---" << std::endl;
+    check("drop_table", admin.DropTable(table_path, true));
+    check("drop_database", admin.DropDatabase(db_name, true, true));
+
+    check("database_exists (after drop)", admin.DatabaseExists(db_name, 
exists));
+    std::cout << "Database exists after drop: " << (exists ? "yes" : "no") << 
std::endl;
+
+    std::cout << "Admin example completed successfully." << std::endl;
+    return 0;
+}
diff --git a/bindings/cpp/include/fluss.hpp b/bindings/cpp/include/fluss.hpp
index 6b9d479..50dffae 100644
--- a/bindings/cpp/include/fluss.hpp
+++ b/bindings/cpp/include/fluss.hpp
@@ -709,6 +709,21 @@ struct PartitionInfo {
     std::string partition_name;
 };
 
+/// Descriptor for create_database (optional). Leave comment and properties 
empty for default.
+struct DatabaseDescriptor {
+    std::string comment;
+    std::unordered_map<std::string, std::string> properties;
+};
+
+/// Metadata returned by GetDatabaseInfo.
+struct DatabaseInfo {
+    std::string database_name;
+    std::string comment;
+    std::unordered_map<std::string, std::string> properties;
+    int64_t created_time{0};
+    int64_t modified_time{0};
+};
+
 class AppendWriter;
 class WriteResult;
 class LogScanner;
@@ -773,6 +788,27 @@ class Admin {
                            const std::unordered_map<std::string, std::string>& 
partition_spec,
                            bool ignore_if_exists = false);
 
+    Result DropPartition(const TablePath& table_path,
+                         const std::unordered_map<std::string, std::string>& 
partition_spec,
+                         bool ignore_if_not_exists = false);
+
+    Result CreateDatabase(const std::string& database_name,
+                          const DatabaseDescriptor& descriptor,
+                          bool ignore_if_exists = false);
+
+    Result DropDatabase(const std::string& database_name, bool 
ignore_if_not_exists = false,
+                        bool cascade = true);
+
+    Result ListDatabases(std::vector<std::string>& out);
+
+    Result DatabaseExists(const std::string& database_name, bool& out);
+
+    Result GetDatabaseInfo(const std::string& database_name, DatabaseInfo& 
out);
+
+    Result ListTables(const std::string& database_name, 
std::vector<std::string>& out);
+
+    Result TableExists(const TablePath& table_path, bool& out);
+
    private:
     Result DoListOffsets(const TablePath& table_path, const 
std::vector<int32_t>& bucket_ids,
                          const OffsetQuery& offset_query, 
std::unordered_map<int32_t, int64_t>& out,
diff --git a/bindings/cpp/src/admin.cpp b/bindings/cpp/src/admin.cpp
index 4aed78d..77c95d3 100644
--- a/bindings/cpp/src/admin.cpp
+++ b/bindings/cpp/src/admin.cpp
@@ -204,4 +204,124 @@ Result Admin::CreatePartition(const TablePath& table_path,
     return utils::from_ffi_result(ffi_result);
 }
 
+Result Admin::DropPartition(const TablePath& table_path,
+                            const std::unordered_map<std::string, 
std::string>& partition_spec,
+                            bool ignore_if_not_exists) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_path = utils::to_ffi_table_path(table_path);
+
+    rust::Vec<ffi::FfiPartitionKeyValue> rust_spec;
+    for (const auto& [key, value] : partition_spec) {
+        ffi::FfiPartitionKeyValue kv;
+        kv.key = rust::String(key);
+        kv.value = rust::String(value);
+        rust_spec.push_back(std::move(kv));
+    }
+
+    auto ffi_result =
+        admin_->drop_partition(ffi_path, std::move(rust_spec), 
ignore_if_not_exists);
+    return utils::from_ffi_result(ffi_result);
+}
+
+Result Admin::CreateDatabase(const std::string& database_name,
+                             const DatabaseDescriptor& descriptor,
+                             bool ignore_if_exists) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_desc = utils::to_ffi_database_descriptor(descriptor);
+    auto ffi_result =
+        admin_->create_database(rust::Str(database_name), ffi_desc, 
ignore_if_exists);
+    return utils::from_ffi_result(ffi_result);
+}
+
+Result Admin::DropDatabase(const std::string& database_name, bool 
ignore_if_not_exists,
+                           bool cascade) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_result =
+        admin_->drop_database(rust::Str(database_name), ignore_if_not_exists, 
cascade);
+    return utils::from_ffi_result(ffi_result);
+}
+
+Result Admin::ListDatabases(std::vector<std::string>& out) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_result = admin_->list_databases();
+    auto result = utils::from_ffi_result(ffi_result.result);
+    if (result.Ok()) {
+        out.clear();
+        out.reserve(ffi_result.database_names.size());
+        for (const auto& name : ffi_result.database_names) {
+            out.push_back(std::string(name));
+        }
+    }
+    return result;
+}
+
+Result Admin::DatabaseExists(const std::string& database_name, bool& out) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_result = admin_->database_exists(rust::Str(database_name));
+    auto result = utils::from_ffi_result(ffi_result.result);
+    if (result.Ok()) {
+        out = ffi_result.value;
+    }
+    return result;
+}
+
+Result Admin::GetDatabaseInfo(const std::string& database_name, DatabaseInfo& 
out) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_result = admin_->get_database_info(rust::Str(database_name));
+    auto result = utils::from_ffi_result(ffi_result.result);
+    if (result.Ok()) {
+        out = utils::from_ffi_database_info(ffi_result.database_info);
+    }
+    return result;
+}
+
+Result Admin::ListTables(const std::string& database_name, 
std::vector<std::string>& out) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_result = admin_->list_tables(rust::Str(database_name));
+    auto result = utils::from_ffi_result(ffi_result.result);
+    if (result.Ok()) {
+        out.clear();
+        out.reserve(ffi_result.table_names.size());
+        for (const auto& name : ffi_result.table_names) {
+            out.push_back(std::string(name));
+        }
+    }
+    return result;
+}
+
+Result Admin::TableExists(const TablePath& table_path, bool& out) {
+    if (!Available()) {
+        return utils::make_error(1, "Admin not available");
+    }
+
+    auto ffi_path = utils::to_ffi_table_path(table_path);
+    auto ffi_result = admin_->table_exists(ffi_path);
+    auto result = utils::from_ffi_result(ffi_result.result);
+    if (result.Ok()) {
+        out = ffi_result.value;
+    }
+    return result;
+}
+
 }  // namespace fluss
diff --git a/bindings/cpp/src/ffi_converter.hpp 
b/bindings/cpp/src/ffi_converter.hpp
index 8adcd01..8fc8415 100644
--- a/bindings/cpp/src/ffi_converter.hpp
+++ b/bindings/cpp/src/ffi_converter.hpp
@@ -290,5 +290,30 @@ inline LakeSnapshot from_ffi_lake_snapshot(const 
ffi::FfiLakeSnapshot& ffi_snaps
     return snapshot;
 }
 
+inline ffi::FfiDatabaseDescriptor to_ffi_database_descriptor(
+    const DatabaseDescriptor& desc) {
+    ffi::FfiDatabaseDescriptor ffi_desc;
+    ffi_desc.comment = rust::String(desc.comment);
+    for (const auto& [k, v] : desc.properties) {
+        ffi::HashMapValue kv;
+        kv.key = rust::String(k);
+        kv.value = rust::String(v);
+        ffi_desc.properties.push_back(std::move(kv));
+    }
+    return ffi_desc;
+}
+
+inline DatabaseInfo from_ffi_database_info(const ffi::FfiDatabaseInfo& 
ffi_info) {
+    DatabaseInfo info;
+    info.database_name = std::string(ffi_info.database_name);
+    info.comment = std::string(ffi_info.comment);
+    info.created_time = ffi_info.created_time;
+    info.modified_time = ffi_info.modified_time;
+    for (const auto& prop : ffi_info.properties) {
+        info.properties[std::string(prop.key)] = std::string(prop.value);
+    }
+    return info;
+}
+
 }  // namespace utils
 }  // namespace fluss
diff --git a/bindings/cpp/src/lib.rs b/bindings/cpp/src/lib.rs
index 235d282..5f3e7e9 100644
--- a/bindings/cpp/src/lib.rs
+++ b/bindings/cpp/src/lib.rs
@@ -202,6 +202,39 @@ mod ffi {
         partition_infos: Vec<FfiPartitionInfo>,
     }
 
+    struct FfiDatabaseDescriptor {
+        comment: String,
+        properties: Vec<HashMapValue>,
+    }
+
+    struct FfiDatabaseInfo {
+        database_name: String,
+        comment: String,
+        properties: Vec<HashMapValue>,
+        created_time: i64,
+        modified_time: i64,
+    }
+
+    struct FfiDatabaseInfoResult {
+        result: FfiResult,
+        database_info: FfiDatabaseInfo,
+    }
+
+    struct FfiListDatabasesResult {
+        result: FfiResult,
+        database_names: Vec<String>,
+    }
+
+    struct FfiListTablesResult {
+        result: FfiResult,
+        table_names: Vec<String>,
+    }
+
+    struct FfiBoolResult {
+        result: FfiResult,
+        value: bool,
+    }
+
     extern "Rust" {
         type Connection;
         type Admin;
@@ -257,6 +290,29 @@ mod ffi {
             partition_spec: Vec<FfiPartitionKeyValue>,
             ignore_if_exists: bool,
         ) -> FfiResult;
+        fn drop_partition(
+            self: &Admin,
+            table_path: &FfiTablePath,
+            partition_spec: Vec<FfiPartitionKeyValue>,
+            ignore_if_not_exists: bool,
+        ) -> FfiResult;
+        fn create_database(
+            self: &Admin,
+            database_name: &str,
+            descriptor: &FfiDatabaseDescriptor,
+            ignore_if_exists: bool,
+        ) -> FfiResult;
+        fn drop_database(
+            self: &Admin,
+            database_name: &str,
+            ignore_if_not_exists: bool,
+            cascade: bool,
+        ) -> FfiResult;
+        fn list_databases(self: &Admin) -> FfiListDatabasesResult;
+        fn database_exists(self: &Admin, database_name: &str) -> FfiBoolResult;
+        fn get_database_info(self: &Admin, database_name: &str) -> 
FfiDatabaseInfoResult;
+        fn list_tables(self: &Admin, database_name: &str) -> 
FfiListTablesResult;
+        fn table_exists(self: &Admin, table_path: &FfiTablePath) -> 
FfiBoolResult;
 
         // Table
         unsafe fn delete_table(table: *mut Table);
@@ -661,6 +717,158 @@ impl Admin {
             Err(e) => err_result(1, e.to_string()),
         }
     }
+
+    fn drop_partition(
+        &self,
+        table_path: &ffi::FfiTablePath,
+        partition_spec: Vec<ffi::FfiPartitionKeyValue>,
+        ignore_if_not_exists: bool,
+    ) -> ffi::FfiResult {
+        let path = fcore::metadata::TablePath::new(
+            table_path.database_name.clone(),
+            table_path.table_name.clone(),
+        );
+        let spec_map: std::collections::HashMap<String, String> = 
partition_spec
+            .into_iter()
+            .map(|kv| (kv.key, kv.value))
+            .collect();
+        let partition_spec = fcore::metadata::PartitionSpec::new(spec_map);
+
+        let result = RUNTIME.block_on(async {
+            self.inner
+                .drop_partition(&path, &partition_spec, ignore_if_not_exists)
+                .await
+        });
+
+        match result {
+            Ok(_) => ok_result(),
+            Err(e) => err_result(1, e.to_string()),
+        }
+    }
+
+    fn create_database(
+        &self,
+        database_name: &str,
+        descriptor: &ffi::FfiDatabaseDescriptor,
+        ignore_if_exists: bool,
+    ) -> ffi::FfiResult {
+        let descriptor_opt = 
types::ffi_database_descriptor_to_core(descriptor);
+
+        let result = RUNTIME.block_on(async {
+            self.inner
+                .create_database(database_name, ignore_if_exists, 
descriptor_opt.as_ref())
+                .await
+        });
+
+        match result {
+            Ok(_) => ok_result(),
+            Err(e) => err_result(1, e.to_string()),
+        }
+    }
+
+    fn drop_database(
+        &self,
+        database_name: &str,
+        ignore_if_not_exists: bool,
+        cascade: bool,
+    ) -> ffi::FfiResult {
+        let result = RUNTIME.block_on(async {
+            self.inner
+                .drop_database(database_name, ignore_if_not_exists, cascade)
+                .await
+        });
+
+        match result {
+            Ok(_) => ok_result(),
+            Err(e) => err_result(1, e.to_string()),
+        }
+    }
+
+    fn list_databases(&self) -> ffi::FfiListDatabasesResult {
+        let result = RUNTIME.block_on(async { 
self.inner.list_databases().await });
+
+        match result {
+            Ok(names) => ffi::FfiListDatabasesResult {
+                result: ok_result(),
+                database_names: names,
+            },
+            Err(e) => ffi::FfiListDatabasesResult {
+                result: err_result(1, e.to_string()),
+                database_names: vec![],
+            },
+        }
+    }
+
+    fn database_exists(&self, database_name: &str) -> ffi::FfiBoolResult {
+        let result = RUNTIME.block_on(async { 
self.inner.database_exists(database_name).await });
+
+        match result {
+            Ok(exists) => ffi::FfiBoolResult {
+                result: ok_result(),
+                value: exists,
+            },
+            Err(e) => ffi::FfiBoolResult {
+                result: err_result(1, e.to_string()),
+                value: false,
+            },
+        }
+    }
+
+    fn get_database_info(&self, database_name: &str) -> 
ffi::FfiDatabaseInfoResult {
+        let result = RUNTIME.block_on(async { 
self.inner.get_database_info(database_name).await });
+
+        match result {
+            Ok(info) => ffi::FfiDatabaseInfoResult {
+                result: ok_result(),
+                database_info: types::core_database_info_to_ffi(&info),
+            },
+            Err(e) => ffi::FfiDatabaseInfoResult {
+                result: err_result(1, e.to_string()),
+                database_info: ffi::FfiDatabaseInfo {
+                    database_name: String::new(),
+                    comment: String::new(),
+                    properties: vec![],
+                    created_time: 0,
+                    modified_time: 0,
+                },
+            },
+        }
+    }
+
+    fn list_tables(&self, database_name: &str) -> ffi::FfiListTablesResult {
+        let result = RUNTIME.block_on(async { 
self.inner.list_tables(database_name).await });
+
+        match result {
+            Ok(names) => ffi::FfiListTablesResult {
+                result: ok_result(),
+                table_names: names,
+            },
+            Err(e) => ffi::FfiListTablesResult {
+                result: err_result(1, e.to_string()),
+                table_names: vec![],
+            },
+        }
+    }
+
+    fn table_exists(&self, table_path: &ffi::FfiTablePath) -> 
ffi::FfiBoolResult {
+        let path = fcore::metadata::TablePath::new(
+            table_path.database_name.clone(),
+            table_path.table_name.clone(),
+        );
+
+        let result = RUNTIME.block_on(async { 
self.inner.table_exists(&path).await });
+
+        match result {
+            Ok(exists) => ffi::FfiBoolResult {
+                result: ok_result(),
+                value: exists,
+            },
+            Err(e) => ffi::FfiBoolResult {
+                result: err_result(1, e.to_string()),
+                value: false,
+            },
+        }
+    }
 }
 
 // Table implementation
diff --git a/bindings/cpp/src/types.rs b/bindings/cpp/src/types.rs
index 05d3d6a..65b9b04 100644
--- a/bindings/cpp/src/types.rs
+++ b/bindings/cpp/src/types.rs
@@ -250,6 +250,49 @@ pub fn empty_table_info() -> ffi::FfiTableInfo {
     }
 }
 
+/// Convert FFI database descriptor to core. Returns None if descriptor is 
effectively empty
+/// (no comment and no properties), so create_database can pass Option::None 
to core.
+pub fn ffi_database_descriptor_to_core(
+    d: &ffi::FfiDatabaseDescriptor,
+) -> Option<fcore::metadata::DatabaseDescriptor> {
+    if d.comment.is_empty() && d.properties.is_empty() {
+        return None;
+    }
+    let mut builder = fcore::metadata::DatabaseDescriptor::builder();
+    if !d.comment.is_empty() {
+        builder = builder.comment(&d.comment);
+    }
+    if !d.properties.is_empty() {
+        let props: std::collections::HashMap<String, String> = d
+            .properties
+            .iter()
+            .map(|kv| (kv.key.clone(), kv.value.clone()))
+            .collect();
+        builder = builder.custom_properties(props);
+    }
+    Some(builder.build())
+}
+
+/// Convert core DatabaseInfo to FFI.
+pub fn core_database_info_to_ffi(info: &fcore::metadata::DatabaseInfo) -> 
ffi::FfiDatabaseInfo {
+    let desc = info.database_descriptor();
+    let properties: Vec<ffi::HashMapValue> = desc
+        .custom_properties()
+        .iter()
+        .map(|(k, v)| ffi::HashMapValue {
+            key: k.clone(),
+            value: v.clone(),
+        })
+        .collect();
+    ffi::FfiDatabaseInfo {
+        database_name: info.database_name().to_string(),
+        comment: desc.comment().unwrap_or("").to_string(),
+        properties,
+        created_time: info.created_time(),
+        modified_time: info.modified_time(),
+    }
+}
+
 /// Look up decimal (precision, scale) from schema for column `idx`.
 fn get_decimal_type(idx: usize, schema: Option<&fcore::metadata::Schema>) -> 
Result<(u32, u32)> {
     let col = schema

Reply via email to