blob: 51aedd0f467dde0742d3a69a19f4d75ba5b0b261 [file]
# 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.
# Shared logical values belong in the facade; ArrowCore keeps wire storage.
_decimalstorage(t::AC.DecimalType) =
t.bits == 32 ? Int32 :
t.bits == 64 ? Int64 : t.bits == 128 ? Int128 : DataDecimals.Int256
_shareddecimal(t::AC.DecimalType) = 0 <= t.scale <= t.precision <= 76
_decimalhost(t::AC.DecimalType) =
DataDecimals.Decimal{t.precision,t.scale,_decimalstorage(t)}
_rawdeclaredbasetype(t::AC.DecimalType) =
t.bits == 32 ? Int32 : t.bits == 64 ? Int64 : Vector{UInt8}
_rawdeclaredbasetype(t::AC.IntervalType) =
t.unit == AC.YEAR_MONTH ? Int32 :
t.unit == AC.DAY_TIME ? NamedTuple{(:days, :millis),Tuple{Int32,Int32}} :
NamedTuple{(:months, :days, :nanos),Tuple{Int32,Int32,Int64}}
function _bytescoefficient(::Type{T}, x::AbstractVector{UInt8}) where {T}
U = unsigned(T)
u = zero(U)
for b in Iterators.reverse(x)
u = (u << 8) | U(b)
end
return reinterpret(T, u)
end
"""
One shared-decimal scalar conversion, specialized on the host and storage
types: the coefficient loop, the bound power, and the range check must all
run unboxed, or a wide (128/256-bit) coefficient boxes on every operation.
"""
function _shareddecimalscalar(::Type{D}, ::Type{T}, precision::Int, x)::D where {D,T}
u =
x isa Integer ? T(x) :
x isa AbstractVector{UInt8} ? _bytescoefficient(T, x) :
throw(ArgumentError("unsupported decimal storage value"))
bound = T(10)^precision
-bound < u < bound ||
throw(ArgumentError("decimal coefficient exceeds declared precision"))
return reinterpret(D, u)
end
function _postconvert(t::AC.DecimalType, col, budget=nothing)
_shareddecimal(t) || return col
D = _decimalhost(t)
T = _decimalstorage(t)
precision = Int(t.precision)
# The per-element call into the specialized converter is dynamic, so
# each element leaves a few transient boxes behind even over a
# concretely typed storage column.
budget === nothing || _charge!(
budget,
AC.checked_mul(Int64(length(col)), Int64(64)),
"facade decimal conversion temporaries",
)
return _mapcol(D, x -> _shareddecimalscalar(D, T, precision, x), col, budget)
end
function _postconvert(t::AC.IntervalType, col, budget=nothing)
return _mapcol(
Durations.Duration,
x ->
t.unit == AC.YEAR_MONTH ? Durations.Duration(x, 0, 0) :
t.unit == AC.DAY_TIME ?
Durations.Duration(0, x.days, Int64(x.millis) * 1_000_000) :
Durations.Duration(x.months, x.days, x.nanos),
col,
budget,
)
end
function _shareddecimalraw(t::AC.DecimalType, x::DataDecimals.AbstractDecimal)
DataDecimals.scale(x) == t.scale ||
throw(ArgumentError("decimal scale does not match the Arrow schema"))
u = _decimalstorage(t)(DataDecimals.unscaled(x))
bound = _decimalstorage(t)(10)^t.precision
-bound < u < bound ||
throw(ArgumentError("decimal coefficient exceeds declared precision"))
t.bits == 32 && return Int32(u)
t.bits == 64 && return Int64(u)
return UInt8[(u >> (8 * i)) & 0xff for i = 0:(t.bits รท 8 - 1)]
end
function _sharedintervalraw(t::AC.IntervalType, x::Durations.Duration)
if t.unit == AC.YEAR_MONTH
iszero(x.days) && iszero(x.nanoseconds) ||
throw(ArgumentError("year-month interval cannot hold days or nanoseconds"))
return x.months
elseif t.unit == AC.DAY_TIME
iszero(x.months) && iszero(rem(x.nanoseconds, 1_000_000)) || throw(
ArgumentError("day-time interval requires zero months and exact milliseconds"),
)
return (days=x.days, millis=Int32(div(x.nanoseconds, 1_000_000)))
end
return (months=x.months, days=x.days, nanos=x.nanoseconds)
end
function _constructsharedpart(name, v, ::Type{D}) where {D<:DataDecimals.Decimal}
isconcretetype(D) || throw(
ArgumentError(
"an empty decimal column needs a concrete precision, scale, and storage type",
),
)
P, S, T = D.parameters
t = AC.DecimalType(P, S, 8sizeof(T))
return _retaineddecimal(AC.Field(name, t; nullable=Missing <: eltype(v)), v)
end
function _constructsharedpart(name, v, ::Type{Durations.Duration})
t = AC.IntervalType(AC.MONTH_DAY_NANO)
return _retainedinterval(AC.Field(name, t; nullable=Missing <: eltype(v)), v)
end