Skip to content

Commit cfd6eb2

Browse files
authored
Add experimental CPP API for ls_recursive. (#4625)
This adds the experimental CPP APIs for ls_recursive, which is currently only supported over S3. --- TYPE: FEATURE DESC: Add experimental CPP API for ls_recursive.
1 parent d017daf commit cfd6eb2

File tree

5 files changed

+443
-13
lines changed

5 files changed

+443
-13
lines changed

test/src/unit-cppapi-vfs.cc

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232

3333
#include <test/support/src/helpers.h>
3434
#include <test/support/tdb_catch.h>
35+
#include "test/support/src/vfs_helpers.h"
3536
#include "tiledb/sm/cpp_api/tiledb"
37+
#include "tiledb/sm/cpp_api/vfs_experimental.h"
3638

3739
#ifdef _WIN32
3840
#include "tiledb/sm/filesystem/path_win.h"
@@ -500,3 +502,187 @@ TEST_CASE(
500502
}
501503
}
502504
}
505+
506+
TEST_CASE("CPP API: VFS ls_recursive filter", "[cppapi][vfs][ls-recursive]") {
507+
using namespace tiledb::test;
508+
S3Test s3_test({10, 100, 0});
509+
if (!s3_test.is_supported()) {
510+
return;
511+
}
512+
auto expected_results = s3_test.expected_results();
513+
514+
vfs_config cfg;
515+
tiledb::Context ctx(tiledb::Config(&cfg.config));
516+
tiledb::VFS vfs(ctx);
517+
518+
tiledb::VFSExperimental::LsObjects ls_objects;
519+
// Predicate filter to apply to ls_recursive.
520+
tiledb::VFSExperimental::LsInclude include;
521+
// Callback to populate ls_objects vector using a filter.
522+
tiledb::VFSExperimental::LsCallback cb = [&](std::string_view path,
523+
uint64_t size) {
524+
if (include(path, size)) {
525+
ls_objects.emplace_back(path, size);
526+
}
527+
return true;
528+
};
529+
530+
SECTION("Default filter (include all)") {
531+
include = [](std::string_view, uint64_t) { return true; };
532+
}
533+
SECTION("Custom filter (include none)") {
534+
include = [](std::string_view, uint64_t) { return false; };
535+
}
536+
537+
bool include_result = true;
538+
SECTION("Custom filter (include half)") {
539+
include = [&include_result](std::string_view, uint64_t) {
540+
include_result = !include_result;
541+
return include_result;
542+
};
543+
}
544+
545+
SECTION("Custom filter (search for test_file_50)") {
546+
include = [](std::string_view object_name, uint64_t) {
547+
return object_name.find("test_file_50") != std::string::npos;
548+
};
549+
}
550+
SECTION("Custom filter (search for test_file_1*)") {
551+
include = [](std::string_view object_name, uint64_t) {
552+
return object_name.find("test_file_1") != std::string::npos;
553+
};
554+
}
555+
SECTION("Custom filter (reject files over 50 bytes)") {
556+
include = [](std::string_view, uint64_t size) { return size <= 50; };
557+
}
558+
559+
// Test collecting results with LsInclude predicate.
560+
auto results = tiledb::VFSExperimental::ls_recursive_filter(
561+
ctx, vfs, s3_test.temp_dir_.to_string(), include);
562+
std::erase_if(expected_results, [&include](const auto& object) {
563+
return !include(object.first, object.second);
564+
});
565+
CHECK(results.size() == expected_results.size());
566+
CHECK(expected_results == results);
567+
568+
// Test collecting results with LsCallback, writing data into ls_objects.
569+
tiledb::VFSExperimental::ls_recursive(
570+
ctx, vfs, s3_test.temp_dir_.to_string(), cb);
571+
CHECK(ls_objects.size() == expected_results.size());
572+
CHECK(expected_results == ls_objects);
573+
}
574+
575+
TEST_CASE("CPP API: Callback stops traversal", "[cppapi][vfs][ls-recursive]") {
576+
using namespace tiledb::test;
577+
S3Test s3_test({10, 50, 15});
578+
if (!s3_test.is_supported()) {
579+
return;
580+
}
581+
auto expected_results = s3_test.expected_results();
582+
583+
vfs_config cfg;
584+
tiledb::Context ctx(tiledb::Config(&cfg.config));
585+
tiledb::VFS vfs(ctx);
586+
587+
tiledb::VFSExperimental::LsObjects ls_objects;
588+
size_t cb_count = GENERATE(1, 10, 11, 50);
589+
auto cb = [&](std::string_view path, uint64_t size) {
590+
// Always emplace to check the callback is not invoked more than `cb_count`.
591+
ls_objects.emplace_back(path, size);
592+
// Signal to stop traversal when we have seen `cb_count` objects.
593+
if (ls_objects.size() == cb_count) {
594+
return false;
595+
}
596+
return true;
597+
};
598+
tiledb::VFSExperimental::ls_recursive(
599+
ctx, vfs, s3_test.temp_dir_.to_string(), cb);
600+
expected_results.resize(cb_count);
601+
CHECK(ls_objects.size() == cb_count);
602+
CHECK(ls_objects == expected_results);
603+
}
604+
605+
TEST_CASE("CPP API: Throwing filter", "[cppapi][vfs][ls-recursive]") {
606+
using namespace tiledb::test;
607+
S3Test s3_test({0});
608+
if (!s3_test.is_supported()) {
609+
return;
610+
}
611+
612+
vfs_config cfg;
613+
tiledb::Context ctx(tiledb::Config(&cfg.config));
614+
tiledb::VFS vfs(ctx);
615+
616+
tiledb::VFSExperimental::LsInclude filter = [](std::string_view,
617+
uint64_t) -> bool {
618+
throw std::runtime_error("Throwing filter");
619+
};
620+
auto path = s3_test.temp_dir_.to_string();
621+
622+
// If the test directory is empty the filter should not throw.
623+
SECTION("Throwing filter with 0 objects should not throw") {
624+
CHECK_NOTHROW(
625+
tiledb::VFSExperimental::ls_recursive_filter(ctx, vfs, path, filter));
626+
CHECK_NOTHROW(
627+
tiledb::VFSExperimental::ls_recursive(ctx, vfs, path, filter));
628+
}
629+
SECTION("Throwing filter with N objects should throw") {
630+
vfs.touch(s3_test.temp_dir_.join_path("test_file").to_string());
631+
CHECK_THROWS_AS(
632+
tiledb::VFSExperimental::ls_recursive_filter(ctx, vfs, path, filter),
633+
std::runtime_error);
634+
CHECK_THROWS_WITH(
635+
tiledb::VFSExperimental::ls_recursive_filter(ctx, vfs, path, filter),
636+
Catch::Matchers::ContainsSubstring("Throwing filter"));
637+
CHECK_THROWS_AS(
638+
tiledb::VFSExperimental::ls_recursive(ctx, vfs, path, filter),
639+
std::runtime_error);
640+
CHECK_THROWS_WITH(
641+
tiledb::VFSExperimental::ls_recursive(ctx, vfs, path, filter),
642+
Catch::Matchers::ContainsSubstring("Throwing filter"));
643+
}
644+
}
645+
646+
TEST_CASE(
647+
"CPP API: CallbackWrapperCPP construction validation",
648+
"[ls-recursive][callback][wrapper]") {
649+
using tiledb::sm::CallbackWrapperCPP;
650+
tiledb::VFSExperimental::LsObjects data;
651+
auto cb = [&](std::string_view, uint64_t) -> bool { return true; };
652+
SECTION("Null callback") {
653+
CHECK_THROWS(CallbackWrapperCPP(nullptr));
654+
}
655+
SECTION("Valid callback") {
656+
CHECK_NOTHROW(CallbackWrapperCPP(cb));
657+
}
658+
}
659+
660+
TEST_CASE(
661+
"CPP API: CallbackWrapperCPP operator() validation",
662+
"[ls-recursive][callback][wrapper]") {
663+
tiledb::VFSExperimental::LsObjects data;
664+
auto cb = [&](std::string_view path, uint64_t object_size) -> bool {
665+
if (object_size > 100) {
666+
// Throw if object size is greater than 100 bytes.
667+
throw std::runtime_error("Throwing callback");
668+
} else if (!path.ends_with(".txt")) {
669+
// Reject non-txt files.
670+
return false;
671+
}
672+
data.emplace_back(path, object_size);
673+
return true;
674+
};
675+
tiledb::sm::CallbackWrapperCPP wrapper(cb);
676+
677+
SECTION("Callback return true accepts object") {
678+
CHECK(wrapper("file.txt", 10) == true);
679+
CHECK(data.size() == 1);
680+
}
681+
SECTION("Callback return false rejects object") {
682+
CHECK(wrapper("some/dir/", 0) == false);
683+
CHECK(data.empty());
684+
}
685+
SECTION("Callback exception is propagated") {
686+
CHECK_THROWS_WITH(wrapper("path", 101) == 0, "Throwing callback");
687+
}
688+
}

tiledb/api/c_api/vfs/test/unit_capi_vfs.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ TEST_CASE(
730730
}
731731

732732
TEST_CASE(
733-
"C API: CallbackWrapper operator() validation",
733+
"C API: CallbackWrapperCAPI operator() validation",
734734
"[ls-recursive][callback][wrapper]") {
735735
tiledb::sm::LsObjects data;
736736
auto cb = [](const char* path,
@@ -748,7 +748,7 @@ TEST_CASE(
748748
ls_data->push_back({{path, path_len}, object_size});
749749
return 1;
750750
};
751-
tiledb::sm::CallbackWrapper wrapper(cb, &data);
751+
tiledb::sm::CallbackWrapperCAPI wrapper(cb, &data);
752752

753753
SECTION("Callback return 1 signals to continue traversal") {
754754
CHECK(wrapper("file.txt", 10) == 1);
@@ -763,21 +763,21 @@ TEST_CASE(
763763
}
764764

765765
TEST_CASE(
766-
"C API: CallbackWrapper construction validation",
766+
"C API: CallbackWrapperCAPI construction validation",
767767
"[ls-recursive][callback][wrapper]") {
768-
using tiledb::sm::CallbackWrapper;
768+
using tiledb::sm::CallbackWrapperCAPI;
769769
tiledb::sm::LsObjects data;
770770
auto cb = [](const char*, size_t, uint64_t, void*) -> int32_t { return 1; };
771771
SECTION("Null callback") {
772-
CHECK_THROWS(CallbackWrapper(nullptr, &data));
772+
CHECK_THROWS(CallbackWrapperCAPI(nullptr, &data));
773773
}
774774
SECTION("Null data") {
775-
CHECK_THROWS(CallbackWrapper(cb, nullptr));
775+
CHECK_THROWS(CallbackWrapperCAPI(cb, nullptr));
776776
}
777777
SECTION("Null callback and data") {
778-
CHECK_THROWS(CallbackWrapper(nullptr, nullptr));
778+
CHECK_THROWS(CallbackWrapperCAPI(nullptr, nullptr));
779779
}
780780
SECTION("Valid callback and data") {
781-
CHECK_NOTHROW(CallbackWrapper(cb, &data));
781+
CHECK_NOTHROW(CallbackWrapperCAPI(cb, &data));
782782
}
783783
}

tiledb/api/c_api/vfs/vfs_api_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ struct tiledb_vfs_handle_t
151151
const tiledb::sm::URI& parent,
152152
tiledb_ls_callback_t cb,
153153
void* data) const {
154-
tiledb::sm::CallbackWrapper wrapper(cb, data);
154+
tiledb::sm::CallbackWrapperCAPI wrapper(cb, data);
155155
vfs_.ls_recursive(parent, wrapper);
156156
}
157157
};

0 commit comments

Comments
 (0)