blob: 67341e40c4275aa46827e7e288db3d90db0b837b [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.Memory;
namespace Apache.Arrow;
public partial class RunEndEncodedArray
{
/// <summary>
/// Builder for <see cref="RunEndEncodedArray"/>.
/// </summary>
/// <typeparam name="TRunEndBuilder">The type of the run ends array builder.</typeparam>
/// <typeparam name="TValueBuilder">The type of the values array builder.</typeparam>
/// <typeparam name="TRunEndArray">The type of the run ends array (must be Int16Array, Int32Array, or Int64Array).</typeparam>
/// <typeparam name="TValueArray">The type of the values array.</typeparam>
/// <typeparam name="TValue">The type of values contained in the values array.</typeparam>
public class Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue>
: IArrowArrayBuilder<TValue, RunEndEncodedArray, Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue>>
where TRunEndBuilder : IArrowArrayBuilder<TRunEndArray>
where TValueBuilder : IArrowArrayBuilder<TValueArray>
where TRunEndArray : IArrowArray
where TValueArray : IArrowArray
{
private readonly IEqualityComparer<TValue> _comparer;
private int _length;
private TValue _lastValue;
private bool _lastValueIsNull;
private bool _hasValue;
/// <summary>
/// Gets the run ends builder.
/// </summary>
public TRunEndBuilder RunEndsBuilder { get; }
/// <summary>
/// Gets the values builder.
/// </summary>
public TValueBuilder ValuesBuilder { get; }
/// <summary>
/// Gets the total logical length of elements appended to this builder.
/// </summary>
public int Length => _length;
/// <summary>
/// Initializes a new instance of the <see cref="Builder{TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue}"/> class.
/// </summary>
/// <param name="runEndsBuilder">The builder to use for run-ends. Must be a builder for an Int16, Int32 or Int64 array.</param>
/// <param name="valuesBuilder">The builder to use for values.</param>
/// <param name="comparer">Optional equality comparer for value run-length grouping.</param>
public Builder(TRunEndBuilder runEndsBuilder, TValueBuilder valuesBuilder, IEqualityComparer<TValue> comparer = null)
{
RunEndsBuilder = runEndsBuilder ?? throw new ArgumentNullException(nameof(runEndsBuilder));
ValuesBuilder = valuesBuilder ?? throw new ArgumentNullException(nameof(valuesBuilder));
_comparer = comparer ?? EqualityComparer<TValue>.Default;
}
/// <summary>
/// Appends a single value to the builder, automatically grouping identical consecutive values into runs.
/// </summary>
/// <param name="value">The value to append.</param>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Append(TValue value)
{
if (value is null)
{
return AppendNull();
}
if (_hasValue)
{
if (!_lastValueIsNull && _comparer.Equals(value, _lastValue))
{
checked
{
_length++;
}
}
else
{
FlushCurrentRun();
StartNewRun(value, isNull: false);
}
}
else
{
StartNewRun(value, isNull: false);
}
return this;
}
/// <summary>
/// Appends a null value to the builder.
/// </summary>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> AppendNull()
{
if (_hasValue)
{
if (_lastValueIsNull)
{
checked
{
_length++;
}
}
else
{
FlushCurrentRun();
StartNewRun(default, isNull: true);
}
}
else
{
StartNewRun(default, isNull: true);
}
return this;
}
/// <summary>
/// Appends a span of values to the builder.
/// </summary>
/// <param name="span">The span of values to append.</param>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Append(ReadOnlySpan<TValue> span)
{
foreach (TValue value in span)
{
Append(value);
}
return this;
}
/// <summary>
/// Appends a sequence of values to the builder.
/// </summary>
/// <param name="values">The sequence of values to append.</param>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> AppendRange(IEnumerable<TValue> values)
{
if (values == null)
{
throw new ArgumentNullException(nameof(values));
}
foreach (TValue value in values)
{
Append(value);
}
return this;
}
/// <summary>
/// Validates the capacity argument. Does not preallocate inner builders to allow doubling growth strategy with REE compression.
/// </summary>
/// <param name="capacity">The capacity to validate.</param>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Reserve(int capacity)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity));
}
return this;
}
/// <summary>
/// Resizing is not supported for RunEndEncodedArray.Builder.
/// </summary>
/// <param name="length">The target length.</param>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Resize(int length)
{
throw new NotSupportedException("Resize is not supported on RunEndEncodedArray.Builder.");
}
/// <summary>
/// Clears the state of the builder and inner builders.
/// </summary>
/// <returns>The builder instance for method chaining.</returns>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Clear()
{
_length = 0;
_lastValue = default;
_lastValueIsNull = false;
_hasValue = false;
ClearBuilder<TRunEndArray, TRunEndBuilder>(RunEndsBuilder);
ClearBuilder<TValueArray, TValueBuilder>(ValuesBuilder);
return this;
}
/// <summary>
/// Swapping elements is not supported for RunEndEncodedArray.Builder.
/// </summary>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Swap(int i, int j)
{
throw new NotSupportedException("Swap is not supported on RunEndEncodedArray.Builder.");
}
/// <summary>
/// Setting elements at specific indices is not supported for RunEndEncodedArray.Builder.
/// </summary>
public Builder<TRunEndBuilder, TValueBuilder, TRunEndArray, TValueArray, TValue> Set(int index, TValue value)
{
throw new NotSupportedException("Set is not supported on RunEndEncodedArray.Builder.");
}
/// <summary>
/// Flushes any pending run and builds the <see cref="RunEndEncodedArray"/>.
/// </summary>
/// <param name="allocator">Optional memory allocator.</param>
/// <returns>The constructed <see cref="RunEndEncodedArray"/>.</returns>
public RunEndEncodedArray Build(MemoryAllocator allocator = default)
{
if (_hasValue)
{
FlushCurrentRun();
}
TRunEndArray runEnds = RunEndsBuilder.Build(allocator);
TValueArray values = ValuesBuilder.Build(allocator);
return new RunEndEncodedArray(runEnds, values);
}
private void StartNewRun(TValue value, bool isNull)
{
_lastValue = value;
_lastValueIsNull = isNull;
_hasValue = true;
checked
{
_length++;
}
}
private void FlushCurrentRun()
{
if (!_hasValue)
{
return;
}
AppendRunEndToRunEndsBuilder(RunEndsBuilder, _length);
if (_lastValueIsNull)
{
AppendNullToValuesBuilder(ValuesBuilder);
}
else
{
AppendValueToValuesBuilder(ValuesBuilder, _lastValue);
}
_hasValue = false;
}
private static void AppendRunEndToRunEndsBuilder(TRunEndBuilder runEndsBuilder, int runEnd)
{
if (runEndsBuilder is Int32Array.Builder b32)
{
b32.Append(runEnd);
}
else if (runEndsBuilder is Int16Array.Builder b16)
{
b16.Append(checked((short)runEnd));
}
else if (runEndsBuilder is Int64Array.Builder b64)
{
b64.Append(runEnd);
}
else if (runEndsBuilder is IArrowArrayBuilder<int, TRunEndArray, IArrowArrayBuilder<TRunEndArray>> bInt)
{
bInt.Append(runEnd);
}
else if (runEndsBuilder is IArrowArrayBuilder<short, TRunEndArray, IArrowArrayBuilder<TRunEndArray>> bShort)
{
bShort.Append(checked((short)runEnd));
}
else if (runEndsBuilder is IArrowArrayBuilder<long, TRunEndArray, IArrowArrayBuilder<TRunEndArray>> bLong)
{
bLong.Append(runEnd);
}
else
{
throw new NotSupportedException($"Run ends builder type '{typeof(TRunEndBuilder).Name}' is not supported.");
}
}
private static void AppendNullToValuesBuilder(TValueBuilder valuesBuilder)
{
if (valuesBuilder is IArrowArrayBuilder<TValueArray, IArrowArrayBuilder<TValueArray>> builder)
{
builder.AppendNull();
}
else if (valuesBuilder is StringArray.Builder sb)
{
sb.AppendNull();
}
else if (valuesBuilder is LargeStringArray.Builder lsb)
{
lsb.AppendNull();
}
else if (valuesBuilder is StringViewArray.Builder svb)
{
svb.AppendNull();
}
else if (valuesBuilder is BinaryArray.Builder bb)
{
bb.AppendNull();
}
else if (valuesBuilder is LargeBinaryArray.Builder lbb)
{
lbb.AppendNull();
}
else if (valuesBuilder is BinaryViewArray.Builder bvb)
{
bvb.AppendNull();
}
else
{
throw new NotSupportedException($"Appending null to values builder type '{typeof(TValueBuilder).Name}' is not supported.");
}
}
private static void AppendValueToValuesBuilder(TValueBuilder valuesBuilder, TValue value)
{
if (valuesBuilder is IArrowArrayBuilder<TValue, TValueArray, IArrowArrayBuilder<TValueArray>> builder)
{
builder.Append(value);
}
else if (valuesBuilder is StringArray.Builder sb && value is string s)
{
sb.Append(s);
}
else if (valuesBuilder is LargeStringArray.Builder lsb && value is string ls)
{
lsb.Append(ls);
}
else if (valuesBuilder is StringViewArray.Builder svb && value is string svs)
{
svb.Append(svs);
}
else if (valuesBuilder is BinaryArray.Builder bb && value is byte[] b)
{
bb.Append((ReadOnlySpan<byte>)b);
}
else if (valuesBuilder is LargeBinaryArray.Builder lbb && value is byte[] lb)
{
lbb.Append((ReadOnlySpan<byte>)lb);
}
else if (valuesBuilder is BinaryViewArray.Builder bvb && value is byte[] bv)
{
bvb.Append((ReadOnlySpan<byte>)bv);
}
else
{
throw new NotSupportedException($"Appending to values builder type '{typeof(TValueBuilder).Name}' with value type '{typeof(TValue).Name}' is not supported.");
}
}
private static void ClearBuilder<TArray, TBuilder>(TBuilder builder)
where TArray : IArrowArray
where TBuilder : IArrowArrayBuilder<TArray>
{
if (builder is IArrowArrayBuilder<TArray, IArrowArrayBuilder<TArray>> b)
{
b.Clear();
}
else if (builder is StringArray.Builder sb)
{
sb.Clear();
}
else if (builder is LargeStringArray.Builder lsb)
{
lsb.Clear();
}
else if (builder is StringViewArray.Builder svb)
{
svb.Clear();
}
else if (builder is BinaryArray.Builder bb)
{
bb.Clear();
}
else if (builder is LargeBinaryArray.Builder lbb)
{
lbb.Clear();
}
else if (builder is BinaryViewArray.Builder bvb)
{
bvb.Clear();
}
else
{
throw new NotSupportedException($"Clearing builder type '{typeof(TBuilder).Name}' is not supported.");
}
}
}
}