blob: b4c1c5c007e4762d72a4f3f5f69d1afc445cb8ca [file] [log] [blame]
// 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.
#define R_NO_REMAP
#include <R.h>
#include <Rinternals.h>
#include <stdlib.h>
#include <string.h>
#include "altrep.h"
#include "array.h"
#include "convert.h"
#include "nanoarrow.h"
#include "util.h"
#ifdef HAS_ALTREP
// This file defines all ALTREP classes used to speed up conversion
// from an arrow_array to an R vector. Currently only string and
// large string arrays are converted to ALTREP.
//
// All ALTREP classes follow some common patterns:
//
// - R_altrep_data1() holds an external pointer to a struct RConverter.
// - R_altrep_data2() holds the materialized version of the vector.
// - When materialization happens, we set R_altrep_data1() to R_NilValue
// to ensure we don't hold on to any more resources than needed.
static R_xlen_t nanoarrow_altrep_length(SEXP altrep_sexp) {
SEXP converter_xptr = R_altrep_data1(altrep_sexp);
if (converter_xptr == R_NilValue) {
return Rf_xlength(R_altrep_data2(altrep_sexp));
}
struct RConverter* converter = (struct RConverter*)R_ExternalPtrAddr(converter_xptr);
return converter->array_view.array->length;
}
static Rboolean nanoarrow_altrep_inspect(SEXP altrep_sexp, int pre, int deep, int pvec,
void (*inspect_subtree)(SEXP, int, int, int)) {
SEXP converter_xptr = R_altrep_data1(altrep_sexp);
const char* materialized = "";
if (converter_xptr == R_NilValue) {
materialized = "materialized ";
}
R_xlen_t len = nanoarrow_altrep_length(altrep_sexp);
const char* class_name = nanoarrow_altrep_class(altrep_sexp);
Rprintf("<%s%s[%ld]>\n", materialized, class_name, (long)len);
return TRUE;
}
static SEXP nanoarrow_altstring_elt(SEXP altrep_sexp, R_xlen_t i) {
SEXP converter_xptr = R_altrep_data1(altrep_sexp);
if (converter_xptr == R_NilValue) {
return STRING_ELT(R_altrep_data2(altrep_sexp), i);
}
struct RConverter* converter = (struct RConverter*)R_ExternalPtrAddr(converter_xptr);
if (ArrowArrayViewIsNull(&converter->array_view, i)) {
return NA_STRING;
}
struct ArrowStringView item = ArrowArrayViewGetStringUnsafe(&converter->array_view, i);
return Rf_mkCharLenCE(item.data, item.size_bytes, CE_UTF8);
}
static SEXP nanoarrow_altstring_materialize(SEXP altrep_sexp) {
SEXP converter_xptr = R_altrep_data1(altrep_sexp);
if (converter_xptr == R_NilValue) {
return R_altrep_data2(altrep_sexp);
}
if (nanoarrow_converter_materialize_all(converter_xptr) != NANOARROW_OK) {
Rf_error("Error materializing altstring");
}
if (nanoarrow_converter_finalize(converter_xptr) != NANOARROW_OK) {
Rf_error("Error finalizing materialized altstring");
}
SEXP result_sexp = PROTECT(nanoarrow_converter_release_result(converter_xptr));
R_set_altrep_data2(altrep_sexp, result_sexp);
R_set_altrep_data1(altrep_sexp, R_NilValue);
UNPROTECT(1);
return result_sexp;
}
static void* nanoarrow_altrep_dataptr(SEXP altrep_sexp, Rboolean writable) {
return DATAPTR(nanoarrow_altstring_materialize(altrep_sexp));
}
static const void* nanoarrow_altrep_dataptr_or_null(SEXP altrep_sexp) {
SEXP converter_xptr = R_altrep_data1(altrep_sexp);
if (converter_xptr == R_NilValue) {
return DATAPTR_OR_NULL(R_altrep_data2(altrep_sexp));
}
return NULL;
}
static R_altrep_class_t nanoarrow_altrep_chr_cls;
#endif
static void register_nanoarrow_altstring(DllInfo* info) {
#ifdef HAS_ALTREP
nanoarrow_altrep_chr_cls =
R_make_altstring_class("nanoarrow::altrep_chr", "nanoarrow", info);
R_set_altrep_Length_method(nanoarrow_altrep_chr_cls, &nanoarrow_altrep_length);
R_set_altrep_Inspect_method(nanoarrow_altrep_chr_cls, &nanoarrow_altrep_inspect);
R_set_altvec_Dataptr_or_null_method(nanoarrow_altrep_chr_cls,
&nanoarrow_altrep_dataptr_or_null);
R_set_altvec_Dataptr_method(nanoarrow_altrep_chr_cls, &nanoarrow_altrep_dataptr);
R_set_altstring_Elt_method(nanoarrow_altrep_chr_cls, &nanoarrow_altstring_elt);
// Notes about other available methods:
//
// - The no_na method never seems to get called (anyNA() doesn't seem to
// use it)
// - Because set_Elt is not defined, SET_STRING_ELT() will modify the
// technically modify the materialized value. The object has been marked
// immutable but in the case of a string this is fine because we materialize
// when this happens (via Dataptr).
// - It may be beneficial to implement the Extract_subset method to defer string
// conversion even longer since this is expensive compared to rearranging integer
// indices.
// - The duplicate method may be useful because it's used when setting attributes
// or unclassing the vector.
#endif
}
void register_nanoarrow_altrep(DllInfo* info) { register_nanoarrow_altstring(info); }
SEXP nanoarrow_c_make_altrep_chr(SEXP array_xptr) {
#ifdef HAS_ALTREP
SEXP schema_xptr = array_xptr_get_schema(array_xptr);
// Create the converter
SEXP converter_xptr = PROTECT(nanoarrow_converter_from_type(VECTOR_TYPE_CHR));
if (nanoarrow_converter_set_schema(converter_xptr, schema_xptr) != NANOARROW_OK) {
nanoarrow_converter_stop(converter_xptr);
}
struct RConverter* converter = (struct RConverter*)R_ExternalPtrAddr(converter_xptr);
switch (converter->array_view.storage_type) {
case NANOARROW_TYPE_NA:
case NANOARROW_TYPE_STRING:
case NANOARROW_TYPE_LARGE_STRING:
break;
default:
UNPROTECT(1);
return R_NilValue;
}
// Ensure the array that we're attaching to this ALTREP object does not keep its
// parent struct alive unnecessarily (i.e., a user can select only a few columns
// and the memory for the unused columns will be released).
SEXP array_xptr_independent = PROTECT(array_xptr_ensure_independent(array_xptr));
if (nanoarrow_converter_set_array(converter_xptr, array_xptr_independent) !=
NANOARROW_OK) {
nanoarrow_converter_stop(converter_xptr);
}
Rf_setAttrib(converter_xptr, R_ClassSymbol, nanoarrow_cls_altrep_chr);
SEXP out = PROTECT(R_new_altrep(nanoarrow_altrep_chr_cls, converter_xptr, R_NilValue));
MARK_NOT_MUTABLE(out);
UNPROTECT(3);
return out;
#else
return R_NilValue;
#endif
}
SEXP nanoarrow_c_is_altrep(SEXP x_sexp) {
return Rf_ScalarLogical(is_nanoarrow_altrep(x_sexp));
}
SEXP nanoarrow_c_altrep_is_materialized(SEXP x_sexp) {
const char* class_name = nanoarrow_altrep_class(x_sexp);
if (class_name == NULL || strncmp(class_name, "nanoarrow::", 11) != 0) {
return Rf_ScalarLogical(NA_LOGICAL);
} else {
return Rf_ScalarLogical(R_altrep_data1(x_sexp) == R_NilValue);
}
}
SEXP nanoarrow_c_altrep_force_materialize(SEXP x_sexp, SEXP recursive_sexp) {
// The recursive flag lets a developer/user force materialization of any
// string columns in a data.frame that came from nanoarrow.
if (Rf_inherits(x_sexp, "data.frame") && LOGICAL(recursive_sexp)[0]) {
int n_materialized = 0;
for (R_xlen_t i = 0; i < Rf_xlength(x_sexp); i++) {
SEXP n_materialized_sexp = PROTECT(
nanoarrow_c_altrep_force_materialize(VECTOR_ELT(x_sexp, i), recursive_sexp));
n_materialized += INTEGER(n_materialized_sexp)[0];
UNPROTECT(1);
}
return Rf_ScalarInteger(n_materialized);
}
const char* class_name = nanoarrow_altrep_class(x_sexp);
if (class_name && strcmp(class_name, "nanoarrow::altrep_chr") == 0) {
// Force materialization even if already materialized (the method
// should be safe to call more than once as written here)
int already_materialized = R_altrep_data1(x_sexp) == R_NilValue;
nanoarrow_altstring_materialize(x_sexp);
return Rf_ScalarInteger(!already_materialized);
} else {
return Rf_ScalarInteger(0);
}
}