blob: 2dcea00043ebbca283c67c37109d0a1a06035ab2 [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.
using System;
using System.Collections.Generic;
using Apache.Arrow;
using Apache.Arrow.Scalars.Variant;
namespace Apache.Arrow.Operations.Shredding
{
/// <summary>
/// Reader for a single row of a shredded-array slot. The underlying storage
/// is a list of element groups (each a <c>{value, typed_value}</c> struct).
/// </summary>
public ref struct ShreddedArray
{
private readonly ShredSchema _schema;
private readonly ReadOnlySpan<byte> _metadata;
// The typed_value list (elements are {value, typed_value} structs). May be null.
private readonly ListArray _list;
// The residual binary column at the array level (for unshredded arrays). May be null.
private readonly IArrowArray _residual;
private readonly int _row;
internal ShreddedArray(
ShredSchema schema,
ReadOnlySpan<byte> metadata,
ListArray list,
IArrowArray residual,
int row)
{
_schema = schema;
_metadata = metadata;
_list = list;
_residual = residual;
_row = row;
}
/// <summary>
/// True when the typed list is populated at this row (the array is stored
/// element-by-element in the shredded column).
/// </summary>
public bool IsTypedList => _list != null && !_list.IsNull(_row);
/// <summary>
/// The number of shredded elements at this row. Only valid when
/// <see cref="IsTypedList"/> is true.
/// </summary>
/// <exception cref="InvalidOperationException">If the array is stored as a residual.</exception>
public int ElementCount
{
get
{
if (!IsTypedList)
{
throw new InvalidOperationException(
"Array at this row is stored as a residual (not a typed list). " +
"Use TryGetResidualReader and iterate via VariantArrayReader.");
}
return _list.ValueOffsets[_row + 1] - _list.ValueOffsets[_row];
}
}
/// <summary>
/// Gets a <see cref="ShreddedVariant"/> reader for the element at position
/// <paramref name="index"/>. Only valid when <see cref="IsTypedList"/> is true.
/// </summary>
public ShreddedVariant GetElement(int index)
{
if (!IsTypedList)
{
throw new InvalidOperationException(
"Array at this row is stored as a residual (not a typed list).");
}
int start = _list.ValueOffsets[_row];
int end = _list.ValueOffsets[_row + 1];
if ((uint)index >= (uint)(end - start))
{
throw new ArgumentOutOfRangeException(nameof(index));
}
StructArray elementGroup = (StructArray)_list.Values;
return ShreddingHelpers.BuildSlot(_schema.ArrayElement, _metadata, elementGroup, start + index);
}
/// <summary>
/// If the array is stored as a residual at this row (not shredded), returns
/// a <see cref="VariantReader"/> over the residual bytes. Callers can then
/// inspect the array via <c>VariantArrayReader</c>.
/// </summary>
public bool TryGetResidualReader(out VariantReader reader)
{
if (_residual == null || _residual.IsNull(_row))
{
reader = default;
return false;
}
ReadOnlySpan<byte> bytes = ((BinaryArray)_residual).GetBytes(_row, out _);
reader = new VariantReader(_metadata, bytes);
return true;
}
/// <summary>
/// Materializes the array into a <see cref="VariantValue"/>. If the typed
/// list is null at this row, falls back to the residual binary (the array
/// was stored unshredded for this row). When neither is populated, the
/// slot encodes a variant null — consistent with <see cref="ShreddedObject"/>
/// and <see cref="ShreddedVariant"/>.
/// </summary>
public VariantValue ToVariantValue()
{
if (_list != null && !_list.IsNull(_row))
{
int start = _list.ValueOffsets[_row];
int end = _list.ValueOffsets[_row + 1];
int count = end - start;
StructArray elementGroup = (StructArray)_list.Values;
List<VariantValue> elements = new List<VariantValue>(count);
for (int i = start; i < end; i++)
{
// For array elements, a both-null slot encodes a variant null
// (arrays cannot contain "missing"). ShreddedVariant.ToVariantValue
// already returns VariantValue.Null for a missing slot.
ShreddedVariant slot = ShreddingHelpers.BuildSlot(_schema.ArrayElement, _metadata, elementGroup, i);
elements.Add(slot.ToVariantValue());
}
return VariantValue.FromArray(elements);
}
// No typed list at this row — decode from the residual if present,
// otherwise the slot is variant null.
if (_residual == null || _residual.IsNull(_row))
{
return VariantValue.Null;
}
BinaryArray residualBinary = (BinaryArray)_residual;
ReadOnlySpan<byte> bytes = residualBinary.GetBytes(_row, out _);
return new VariantReader(_metadata, bytes).ToVariantValue();
}
}
}