Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,8 @@ void PopulateFilterKernels(std::vector<SelectionKernelData>* out) {
{InputType(match::LargeBinaryLike()), plain_filter, BinaryFilterExec},
{InputType(null()), plain_filter, NullFilterExec},
{InputType(Type::FIXED_SIZE_BINARY), plain_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL32), plain_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL64), plain_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL128), plain_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL256), plain_filter, PrimitiveFilterExec},
{InputType(Type::DICTIONARY), plain_filter, DictionaryFilterExec},
Expand All @@ -1116,6 +1118,8 @@ void PopulateFilterKernels(std::vector<SelectionKernelData>* out) {
{InputType(match::LargeBinaryLike()), ree_filter, BinaryFilterExec},
{InputType(null()), ree_filter, NullFilterExec},
{InputType(Type::FIXED_SIZE_BINARY), ree_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL32), ree_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL64), ree_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL128), ree_filter, PrimitiveFilterExec},
{InputType(Type::DECIMAL256), ree_filter, PrimitiveFilterExec},
{InputType(Type::DICTIONARY), ree_filter, DictionaryFilterExec},
Expand Down
2 changes: 2 additions & 0 deletions r/NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ export(date64)
export(decimal)
export(decimal128)
export(decimal256)
export(decimal32)
export(decimal64)
export(default_memory_pool)
export(dictionary)
export(duration)
Expand Down
8 changes: 8 additions & 0 deletions r/R/arrowExports.R

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion r/R/dplyr-funcs-simple.R
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ common_type <- function(exprs) {

cast_or_parse <- function(x, type) {
to_type_id <- type$id
if (to_type_id %in% c(Type[["DECIMAL128"]], Type[["DECIMAL256"]])) {
if (to_type_id %in% c(Type[["DECIMAL32"]], Type[["DECIMAL64"]], Type[["DECIMAL128"]], Type[["DECIMAL256"]])) {
# TODO: determine the minimum size of decimal (or integer) required to
# accommodate x
# We would like to keep calculations on decimal if that's what the data has
Expand Down
2 changes: 1 addition & 1 deletion r/R/dplyr-funcs-type.R
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ register_bindings_type_inspect <- function() {
is.numeric(x) || (inherits(x, "Expression") && x$type_id() %in% Type[c(
"UINT8", "INT8", "UINT16", "INT16", "UINT32", "INT32",
"UINT64", "INT64", "HALF_FLOAT", "FLOAT", "DOUBLE",
"DECIMAL128", "DECIMAL256"
"DECIMAL32", "DECIMAL64", "DECIMAL128", "DECIMAL256"
)])
})
register_binding("base::is.double", function(x) {
Expand Down
14 changes: 10 additions & 4 deletions r/R/enums.R
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,23 @@ Type <- enum("Type::type",
LARGE_BINARY = 35L,
LARGE_LIST = 36L,
INTERVAL_MONTH_DAY_NANO = 37L,
RUN_END_ENCODED = 38L
RUN_END_ENCODED = 38L,
STRING_VIEW = 39L,
BINARY_VIEW = 40L,
LIST_VIEW = 41L,
LARGE_LIST_VIEW = 42L,
Comment on lines +84 to +87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were we just missing these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, went and looked the decimal ones up in the C++ and found a few more missing too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little surprised there weren't / aren't tests that caught this? But maybe I don't totally grok what's up with these

Copy link
Member Author

@thisisnic thisisnic Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know what these enums do, haven't traced it through, but my guess would be that they don't matter as we haven't implemented them in R? Hmm, in which case, maybe I remove? (Can do in a follow-up)

DECIMAL32 = 43L,
DECIMAL64 = 44L
)

TYPES_WITH_NAN <- Type[c("HALF_FLOAT", "FLOAT", "DOUBLE")]
TYPES_NUMERIC <- Type[
c(
"INT8", "UINT8", "INT16", "UINT16", "INT32", "UINT32",
"INT64", "UINT64", "HALF_FLOAT", "FLOAT", "DOUBLE",
"DECIMAL128", "DECIMAL256"
)
]
"DECIMAL32", "DECIMAL64", "DECIMAL128", "DECIMAL256"
)
]

#' @rdname enums
#' @export
Expand Down
26 changes: 25 additions & 1 deletion r/R/type.R
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ DecimalType <- R6Class("DecimalType",
)
)

Decimal32Type <- R6Class("Decimal32Type", inherit = DecimalType)

Decimal64Type <- R6Class("Decimal64Type", inherit = DecimalType)

Decimal128Type <- R6Class("Decimal128Type", inherit = DecimalType)

Decimal256Type <- R6Class("Decimal256Type", inherit = DecimalType)
Expand Down Expand Up @@ -586,11 +590,29 @@ decimal <- function(precision, scale) {

if (args$precision > 38) {
decimal256(args$precision, args$scale)
} else {
} else if (args$precision > 18) {
decimal128(args$precision, args$scale)
} else if (args$precision > 9) {
decimal64(args$precision, args$scale)
} else {
decimal32(args$precision, args$scale)
}
}

#' @rdname data-type
#' @export
decimal32 <- function(precision, scale) {
args <- check_decimal_args(precision, scale)
Decimal32Type__initialize(args$precision, args$scale)
}

#' @rdname data-type
#' @export
decimal64 <- function(precision, scale) {
args <- check_decimal_args(precision, scale)
Decimal64Type__initialize(args$precision, args$scale)
}

#' @rdname data-type
#' @export
decimal128 <- function(precision, scale) {
Expand Down Expand Up @@ -768,6 +790,8 @@ canonical_type_str <- function(type_str) {
time64 = "time64",
null = "null",
timestamp = "timestamp",
decimal32 = "decimal32",
decimal64 = "decimal64",
decimal128 = "decimal128",
decimal256 = "decimal256",
struct = "struct",
Expand Down
6 changes: 6 additions & 0 deletions r/man/data-type.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions r/src/array_to_vector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,12 @@ std::shared_ptr<Converter> Converter::Make(
return std::make_shared<arrow::r::Converter_Int64>(chunked_array);
}

case Type::DECIMAL32:
return std::make_shared<arrow::r::Converter_Decimal<Decimal32Type>>(chunked_array);

case Type::DECIMAL64:
return std::make_shared<arrow::r::Converter_Decimal<Decimal64Type>>(chunked_array);

case Type::DECIMAL128:
return std::make_shared<arrow::r::Converter_Decimal<Decimal128Type>>(chunked_array);

Expand Down
20 changes: 20 additions & 0 deletions r/src/arrowExports.cpp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions r/src/datatype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ const char* r6_class_name<arrow::DataType>::get(
case Type::DURATION:
return "DurationType";

case Type::DECIMAL32:
return "Decimal32Type";
case Type::DECIMAL64:
return "Decimal64Type";
case Type::DECIMAL128:
return "Decimal128Type";
case Type::DECIMAL256:
Expand Down Expand Up @@ -181,6 +185,20 @@ std::shared_ptr<arrow::DataType> Date64__initialize() { return arrow::date64();
// [[arrow::export]]
std::shared_ptr<arrow::DataType> Null__initialize() { return arrow::null(); }

// [[arrow::export]]
std::shared_ptr<arrow::DataType> Decimal32Type__initialize(int32_t precision,
int32_t scale) {
// Use the builder that validates inputs
return ValueOrStop(arrow::Decimal32Type::Make(precision, scale));
}

// [[arrow::export]]
std::shared_ptr<arrow::DataType> Decimal64Type__initialize(int32_t precision,
int32_t scale) {
// Use the builder that validates inputs
return ValueOrStop(arrow::Decimal64Type::Make(precision, scale));
}

// [[arrow::export]]
std::shared_ptr<arrow::DataType> Decimal128Type__initialize(int32_t precision,
int32_t scale) {
Expand Down
30 changes: 29 additions & 1 deletion r/tests/testthat/test-Array.R
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,14 @@ test_that("Array to C-interface", {
})

test_that("Can convert R integer/double to decimal (ARROW-11631)", {
# Check both decimal128 and decimal256
# Check all of decimal32, decimal64, decimal128 and decimal256


decimal32_from_dbl <- arrow_array(c(1, NA_real_), type = decimal32(9, 2))
decimal64_from_dbl <- arrow_array(c(1, NA_real_), type = decimal64(12, 2))
decimal32_from_int <- arrow_array(c(1L, NA_integer_), type = decimal32(9, 2))
decimal64_from_int <- arrow_array(c(1L, NA_integer_), type = decimal64(12, 2))

decimal128_from_dbl <- arrow_array(c(1, NA_real_), type = decimal128(12, 2))
decimal256_from_dbl <- arrow_array(c(1, NA_real_), type = decimal256(12, 2))
decimal128_from_int <- arrow_array(c(1L, NA_integer_), type = decimal128(12, 2))
Expand All @@ -1333,6 +1340,16 @@ test_that("Can convert R integer/double to decimal (ARROW-11631)", {
decimal_from_altrep_dbl <- arrow_array(altrep_dbl, type = decimal128(12, 2))
decimal_from_altrep_int <- arrow_array(altrep_int, type = decimal128(12, 2))

expect_equal(
decimal32_from_dbl,
arrow_array(c(1, NA))$cast(decimal32(9, 2))
)

expect_equal(
decimal64_from_dbl,
arrow_array(c(1, NA))$cast(decimal64(12, 2))
)

expect_equal(
decimal128_from_dbl,
arrow_array(c(1, NA))$cast(decimal128(12, 2))
Expand All @@ -1343,6 +1360,17 @@ test_that("Can convert R integer/double to decimal (ARROW-11631)", {
arrow_array(c(1, NA))$cast(decimal256(12, 2))
)

expect_equal(
decimal32_from_int,
arrow_array(c(1, NA))$cast(decimal32(9, 2))
)

expect_equal(
decimal64_from_int,
arrow_array(c(1, NA))$cast(decimal64(12, 2))
)


expect_equal(
decimal128_from_int,
arrow_array(c(1, NA))$cast(decimal128(12, 2))
Expand Down
1 change: 1 addition & 0 deletions r/tests/testthat/test-chunked-array.R
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ test_that("ChunkedArray supports empty arrays (ARROW-13761)", {
int8(), int16(), int32(), int64(), uint8(), uint16(), uint32(),
uint64(), float32(), float64(), timestamp("ns"), binary(),
large_binary(), fixed_size_binary(32), date32(), date64(),
decimal32(4, 2), decimal64(4, 2),
decimal128(4, 2), decimal256(4, 2),
dictionary(), struct(x = int32())
)
Expand Down
14 changes: 3 additions & 11 deletions r/tests/testthat/test-data-type.R
Original file line number Diff line number Diff line change
Expand Up @@ -474,16 +474,15 @@ test_that("DictionaryType validation", {
})

test_that("decimal type and validation", {
expect_r6_class(decimal(4, 2), "Decimal128Type")
expect_r6_class(decimal(4, 2), "Decimal32Type")
expect_r6_class(decimal(14, 2), "Decimal64Type")
expect_r6_class(decimal(22, 2), "Decimal128Type")
expect_r6_class(decimal(39, 2), "Decimal256Type")

expect_error(decimal("four"), "`precision` must be an integer")
expect_error(decimal(4, "two"), "`scale` must be an integer")
expect_error(decimal(NA, 2), "`precision` must be an integer")
expect_error(decimal(4, NA), "`scale` must be an integer")
# TODO remove precision range tests below once functionality is tested in C++ (ARROW-15162)
expect_error(decimal(0, 2), "Invalid: Decimal precision out of range [1, 38]: 0", fixed = TRUE)
expect_error(decimal(100, 2), "Invalid: Decimal precision out of range [1, 76]: 100", fixed = TRUE)

# decimal() creates either decimal128 or decimal256 based on precision
expect_identical(class(decimal(38, 2)), class(decimal128(38, 2)))
Expand All @@ -497,10 +496,6 @@ test_that("decimal type and validation", {
expect_error(decimal128(4, NA), "`scale` must be an integer")
expect_error(decimal128(3:4, NA), "`precision` must have size 1. not size 2")
expect_error(decimal128(4, 2:3), "`scale` must have size 1. not size 2")
# TODO remove precision range tests below once functionality is tested in C++ (ARROW-15162)
expect_error(decimal128(0, 2), "Invalid: Decimal precision out of range [1, 38]: 0", fixed = TRUE)
expect_error(decimal128(100, 2), "Invalid: Decimal precision out of range [1, 38]: 100", fixed = TRUE)

Comment on lines -500 to -503
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this cleanup, though am I misunderstanding that #30667 is still open? Or maybe this was actually implemented somewhere and we should close that issue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, I confused the JIRA and GitHub numbers. Will double check the C++ code to double check though.

Copy link
Member Author

@thisisnic thisisnic Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were tests added here so we can get rid of our own tests for this, but the decimal() C++ function was deprecated in favour of smallest_decimal() so I'll need to swap that out too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, we don't actually use the C++ decimal() function anyway, so no changes needed. We could look to use smallest_decimal() instead of working out which decimal it should be ourselves in the R code, but I don't think the time it would take to make this change would be worth the effort, though can open a new ticket if you disagree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would mean being able to delete some code in the R package, which is nice — let's make an issue but agreed we don't need to tackle it here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - #46825


expect_r6_class(decimal256(4, 2), "Decimal256Type")

Expand All @@ -510,9 +505,6 @@ test_that("decimal type and validation", {
expect_error(decimal256(4, NA), "`scale` must be an integer")
expect_error(decimal256(3:4, NA), "`precision` must have size 1. not size 2")
expect_error(decimal256(4, 2:3), "`scale` must have size 1. not size 2")
# TODO remove precision range tests below once functionality is tested in C++ (ARROW-15162)
expect_error(decimal256(0, 2), "Invalid: Decimal precision out of range [1, 76]: 0", fixed = TRUE)
expect_error(decimal256(100, 2), "Invalid: Decimal precision out of range [1, 76]: 100", fixed = TRUE)
})

test_that("Binary", {
Expand Down
Loading
Loading