Skip to content

Commit 8e16274

Browse files
committed
feat: Support STRUCT in ALTREP data frames
1 parent 60f8ac0 commit 8e16274

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

src/relational.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ SEXP result_to_df(duckdb::unique_ptr<duckdb::QueryResult> res) {
4545

4646
// Check if column has names
4747
bool check_has_names(SEXP col, const std::string &col_name) {
48-
if (Rf_getAttrib(col, R_NamesSymbol) != R_NilValue) {
48+
if (Rf_getAttrib(col, R_NamesSymbol) != R_NilValue && !Rf_inherits(col, "data.frame")) {
4949
std::string error_msg = "Can't convert named vectors to relational. Affected column: `" + col_name + "`.";
5050
stop(error_msg.c_str());
5151
return true;

src/reltoaltrep.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ R_altrep_class_t RelToAltrep::string_class;
3737

3838
#if defined(R_HAS_ALTLIST)
3939
R_altrep_class_t RelToAltrep::list_class;
40+
R_altrep_class_t RelToAltrep::struct_class;
4041
#endif
4142

4243
const size_t MAX_SIZE_T = std::numeric_limits<size_t>::max();
@@ -77,6 +78,12 @@ void RelToAltrep::Initialize(DllInfo *dll) {
7778
R_set_altrep_Length_method(list_class, VectorLength);
7879
R_set_altvec_Dataptr_method(list_class, VectorDataptr);
7980
R_set_altlist_Elt_method(list_class, VectorListElt);
81+
82+
struct_class = R_make_altlist_class("reltoaltrep_struct_class", "duckdb", dll);
83+
R_set_altrep_Inspect_method(struct_class, RelInspect);
84+
R_set_altrep_Length_method(struct_class, StructLength);
85+
R_set_altvec_Dataptr_method(struct_class, VectorDataptr);
86+
R_set_altlist_Elt_method(struct_class, VectorListElt);
8087
#endif
8188
}
8289

@@ -340,6 +347,17 @@ SEXP RelToAltrep::VectorStringElt(SEXP x, R_xlen_t i) {
340347
}
341348

342349
#if defined(R_HAS_ALTLIST)
350+
R_xlen_t RelToAltrep::StructLength(SEXP x) {
351+
BEGIN_CPP11
352+
auto const *wrapper = AltrepVectorWrapper::Get(x);
353+
auto const column_index = wrapper->column_index;
354+
auto const &res = wrapper->rel->GetQueryResult();
355+
auto const &type = res->types[column_index];
356+
357+
return static_cast<R_xlen_t>(StructType::GetChildTypes(type).size());
358+
END_CPP11_EX(0)
359+
}
360+
343361
SEXP RelToAltrep::VectorListElt(SEXP x, R_xlen_t i) {
344362
BEGIN_CPP11
345363
return VECTOR_ELT(AltrepVectorWrapper::Get(x)->Vector(), i);
@@ -360,7 +378,11 @@ static R_altrep_class_t LogicalTypeToAltrepType(const LogicalType &type, const d
360378
return RelToAltrep::string_class;
361379
#if defined(R_HAS_ALTLIST)
362380
case VECSXP:
363-
return RelToAltrep::list_class;
381+
if (type.id() == LogicalTypeId::STRUCT) {
382+
return RelToAltrep::struct_class;
383+
} else {
384+
return RelToAltrep::list_class;
385+
}
364386
#endif
365387

366388
default:
@@ -407,6 +429,15 @@ size_t DoubleToSize(double d) {
407429

408430
cpp11::sexp vector_sexp = R_new_altrep(LogicalTypeToAltrepType(col_type, col_name), ptr, R_NilValue);
409431
duckdb_r_decorate(col_type, vector_sexp, duckdb::ConvertOpts());
432+
433+
// Special case: Only STRUCTs have a redundant row names attribute
434+
// Moving this logic into duckdb_r_decorate() would add too much noise elsewhere
435+
if (col_type.id() == LogicalTypeId::STRUCT) {
436+
// FIXME: The exact class of nested columns can be a property
437+
// of the relation object, determined by the data on input
438+
duckdb_r_df_decorate_impl(vector_sexp, row_names_sexp, RStrings::get().dataframe_str);
439+
}
440+
410441
data_frame.push_back(vector_sexp);
411442
}
412443

tests/testthat/test-relational.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,3 +1137,15 @@ test_that("factor", {
11371137

11381138
expect_error(rel_from_df(con, df2), "convert")
11391139
})
1140+
1141+
test_that("data.frame", {
1142+
df1 <- vctrs::new_data_frame(list(a = data.frame(b = 1:3, c = 4:6)))
1143+
rel <- rel_from_df(con, df1)
1144+
expect_equal(rel_to_altrep(rel), df1)
1145+
1146+
df2 <- vctrs::new_data_frame(list(a = structure(data.frame(b = 1:3, c = 4:6), class = c("foo", "data.frame"))))
1147+
rel <- rel_from_df(con, df2, strict = FALSE)
1148+
expect_equal(rel_to_altrep(rel), df1)
1149+
1150+
expect_error(rel_from_df(con, df2), "convert")
1151+
})

0 commit comments

Comments
 (0)