Skip to content

Commit

Permalink
apacheGH-38061: [C#] Implement Duration support (apache#38062)
Browse files Browse the repository at this point in the history
### What changes are included in this PR?

Complete support for the Duration array type in the C# implementation.

### Are these changes tested?

Yes.

### Are there any user-facing changes?

The Duration array type is now supported in the C# library. This also
does some slight refactoring of classes which could impact edge cases of
user scenarios.
* Closes: apache#38061
  • Loading branch information
CurtHagenlocher authored and dgreiss committed Feb 17, 2024
1 parent 7dd2a6b commit 7f89ea7
Show file tree
Hide file tree
Showing 27 changed files with 465 additions and 66 deletions.
18 changes: 4 additions & 14 deletions csharp/src/Apache.Arrow/Arrays/ArrayDataTypeComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using Apache.Arrow.Types;

namespace Apache.Arrow
Expand All @@ -22,8 +21,7 @@ internal sealed class ArrayDataTypeComparer :
IArrowTypeVisitor<TimestampType>,
IArrowTypeVisitor<Date32Type>,
IArrowTypeVisitor<Date64Type>,
IArrowTypeVisitor<Time32Type>,
IArrowTypeVisitor<Time64Type>,
IArrowTypeVisitor<TimeBasedType>,
IArrowTypeVisitor<FixedSizeBinaryType>,
IArrowTypeVisitor<ListType>,
IArrowTypeVisitor<FixedSizeListType>,
Expand Down Expand Up @@ -69,18 +67,10 @@ public void Visit(Date64Type actualType)
}
}

public void Visit(Time32Type actualType)
public void Visit(TimeBasedType actualType)
{
if (_expectedType is Time32Type expectedType
&& expectedType.Unit == actualType.Unit)
{
_dataTypeMatch = true;
}
}

public void Visit(Time64Type actualType)
{
if (_expectedType is Time64Type expectedType
if (_expectedType.TypeId == actualType.TypeId
&& _expectedType is TimeBasedType expectedType
&& expectedType.Unit == actualType.Unit)
{
_dataTypeMatch = true;
Expand Down
2 changes: 2 additions & 0 deletions csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ internal static IArrowArrayBuilder<IArrowArray, IArrowArrayBuilder<IArrowArray>>
return new Time32Array.Builder(dataType as Time32Type);
case ArrowTypeId.Time64:
return new Time64Array.Builder(dataType as Time64Type);
case ArrowTypeId.Duration:
return new DurationArray.Builder(dataType as DurationType);
case ArrowTypeId.List:
return new ListArray.Builder(dataType as ListType);
case ArrowTypeId.FixedSizeList:
Expand Down
2 changes: 2 additions & 0 deletions csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public static IArrowArray BuildArray(ArrayData data)
return new Time32Array(data);
case ArrowTypeId.Time64:
return new Time64Array(data);
case ArrowTypeId.Duration:
return new DurationArray(data);
case ArrowTypeId.Decimal128:
return new Decimal128Array(data);
case ArrowTypeId.Decimal256:
Expand Down
84 changes: 84 additions & 0 deletions csharp/src/Apache.Arrow/Arrays/DurationArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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 Apache.Arrow.Types;

namespace Apache.Arrow
{
public class DurationArray : PrimitiveArray<long>
{
public class Builder : PrimitiveArrayBuilder<long, DurationArray, Builder>
{
public DurationType DataType { get; }

public Builder(DurationType dataType)
{
DataType = dataType;
}

protected override DurationArray Build(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset) =>
new DurationArray(DataType, valueBuffer, nullBitmapBuffer, length, nullCount, offset);

/// <summary>
/// Append a duration in the form of a <see cref="TimeSpan"/> object to the array.
/// </summary>
/// <param name="value">TimeSpan to add.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder Append(TimeSpan value)
{
Append(DataType.Unit.ConvertFromTicks(value.Ticks));
return this;
}

/// <summary>
/// Append a duration in the form of a <see cref="TimeSpan"/> object to the array.
/// </summary>
/// <param name="value">TimeSpan to add.</param>
/// <returns>Returns the builder (for fluent-style composition).</returns>
public Builder Append(TimeSpan? value) =>
(value == null) ? AppendNull() : Append(value.Value);
}

public DurationArray(
DurationType type,
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset)
: this(new ArrayData(type, length, nullCount, offset,
new[] { nullBitmapBuffer, valueBuffer }))
{ }

public DurationArray(ArrayData data)
: base(data)
{
data.EnsureDataType(ArrowTypeId.Duration);
}

public DurationType DataType => (DurationType)this.Data.DataType;

public TimeSpan? GetTimeSpan(int index)
{
if (index < 0 || index >= Length)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return IsValid(index) ? new TimeSpan(DataType.Unit.ConvertToTicks(Values[index])) : null;
}

public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
}
}
19 changes: 2 additions & 17 deletions csharp/src/Apache.Arrow/Arrays/Time64Array.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ namespace Apache.Arrow
/// </summary>
public class Time64Array : PrimitiveArray<long>
{
private const long TicksPerMicrosecond = 10;
private const long NanosecondsPerTick = 100;

/// <summary>
/// The <see cref="Builder"/> class can be used to fluently build <see cref="Time64Array"/> objects.
/// </summary>
Expand Down Expand Up @@ -62,13 +59,7 @@ public Builder(Time64Type type)
#if NET6_0_OR_GREATER
protected override long Convert(TimeOnly time)
{
var unit = ((TimeBuilder)InnerBuilder).DataType.Unit;
return unit switch
{
TimeUnit.Microsecond => (long)(time.Ticks / TicksPerMicrosecond),
TimeUnit.Nanosecond => (long)(time.Ticks * NanosecondsPerTick),
_ => throw new InvalidDataException($"Unsupported time unit for Time32Type: {unit}")
};
return ((TimeBuilder)InnerBuilder).DataType.Unit.ConvertFromTicks(time.Ticks);
}
#endif
}
Expand Down Expand Up @@ -153,13 +144,7 @@ public Time64Array(ArrayData data)
return null;
}

var unit = ((Time64Type)Data.DataType).Unit;
return unit switch
{
TimeUnit.Microsecond => new TimeOnly(value.Value * TicksPerMicrosecond),
TimeUnit.Nanosecond => new TimeOnly(value.Value / NanosecondsPerTick),
_ => throw new InvalidDataException($"Unsupported time unit for Time64Type: {unit}")
};
return new TimeOnly(((Time64Type)Data.DataType).Unit.ConvertToTicks(value.Value));
}
#endif
}
Expand Down
3 changes: 3 additions & 0 deletions csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ private static string GetFormat(IArrowType datatype)
case Time64Type timeType:
// Same prefix as Time32, but allowed time units are different.
return String.Format("tt{0}", FormatTimeUnit(timeType.Unit));
// Duration
case DurationType durationType:
return String.Format("tD{0}", FormatTimeUnit(durationType.Unit));
// Timestamp
case TimestampType timestampType:
return String.Format("ts{0}:{1}", FormatTimeUnit(timestampType.Unit), timestampType.Timezone);
Expand Down
5 changes: 4 additions & 1 deletion csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,10 @@ public ArrowType GetAsType()
"ttm" => TimeType.Millisecond,
"ttu" => TimeType.Microsecond,
"ttn" => TimeType.Nanosecond,
// TODO: duration not yet implemented
"tDs" => DurationType.Second,
"tDm" => DurationType.Millisecond,
"tDu" => DurationType.Microsecond,
"tDn" => DurationType.Nanosecond,
"tiM" => IntervalType.YearMonth,
"tiD" => IntervalType.DayTime,
//"tin" => IntervalType.MonthDayNanosecond, // Not yet implemented
Expand Down
2 changes: 2 additions & 0 deletions csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ internal class ArrowRecordBatchFlatBufferBuilder :
IArrowArrayVisitor<Date64Array>,
IArrowArrayVisitor<Time32Array>,
IArrowArrayVisitor<Time64Array>,
IArrowArrayVisitor<DurationArray>,
IArrowArrayVisitor<ListArray>,
IArrowArrayVisitor<FixedSizeListArray>,
IArrowArrayVisitor<StringArray>,
Expand Down Expand Up @@ -104,6 +105,7 @@ public ArrowRecordBatchFlatBufferBuilder()
public void Visit(Date64Array array) => CreateBuffers(array);
public void Visit(Time32Array array) => CreateBuffers(array);
public void Visit(Time64Array array) => CreateBuffers(array);
public void Visit(DurationArray array) => CreateBuffers(array);

public void Visit(ListArray array)
{
Expand Down
8 changes: 8 additions & 0 deletions csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class TypeVisitor :
IArrowTypeVisitor<Date64Type>,
IArrowTypeVisitor<Time32Type>,
IArrowTypeVisitor<Time64Type>,
IArrowTypeVisitor<DurationType>,
IArrowTypeVisitor<BinaryType>,
IArrowTypeVisitor<TimestampType>,
IArrowTypeVisitor<ListType>,
Expand Down Expand Up @@ -188,6 +189,13 @@ public void Visit(Time64Type type)
Flatbuf.Time.CreateTime(Builder, ToFlatBuffer(type.Unit), 64));
}

public void Visit(DurationType type)
{
Result = FieldType.Build(
Flatbuf.Type.Duration,
Flatbuf.Duration.CreateDuration(Builder, ToFlatBuffer(type.Unit)));
}

public void Visit(StructType type)
{
Flatbuf.Struct_.StartStruct_(Builder);
Expand Down
3 changes: 3 additions & 0 deletions csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ private static Types.IArrowType GetFieldArrowType(Flatbuf.Field field, Field[] c
Types.TimeUnit unit = timestampTypeMetadata.Unit.ToArrow();
string timezone = timestampTypeMetadata.Timezone;
return new Types.TimestampType(unit, timezone);
case Flatbuf.Type.Duration:
Flatbuf.Duration durationMeta = field.Type<Flatbuf.Duration>().Value;
return DurationType.FromTimeUnit(durationMeta.Unit.ToArrow());
case Flatbuf.Type.Interval:
Flatbuf.Interval intervalMetadata = field.Type<Flatbuf.Interval>().Value;
return Types.IntervalType.FromIntervalUnit(intervalMetadata.Unit.ToArrow());
Expand Down
2 changes: 2 additions & 0 deletions csharp/src/Apache.Arrow/RecordBatch.Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public Time32Array Time32(Time32Type type, Action<Time32Array.Builder> action) =
public Time64Array Time64(Time64Type type, Action<Time64Array.Builder> action) =>
Build<Time64Array, Time64Array.Builder>(
new Time64Array.Builder(type), action);
public DurationArray Duration(DurationType type, Action<DurationArray.Builder> action) =>
Build<DurationArray, DurationArray.Builder>(new DurationArray.Builder(type), action);
public BinaryArray Binary(Action<BinaryArray.Builder> action) => Build<BinaryArray, BinaryArray.Builder>(new BinaryArray.Builder(), action);
public StringArray String(Action<StringArray.Builder> action) => Build<StringArray, StringArray.Builder>(new StringArray.Builder(), action);
public TimestampArray Timestamp(Action<TimestampArray.Builder> action) => Build<TimestampArray, TimestampArray.Builder>(new TimestampArray.Builder(), action);
Expand Down
42 changes: 42 additions & 0 deletions csharp/src/Apache.Arrow/Types/DurationType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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.

namespace Apache.Arrow.Types
{
public sealed class DurationType : TimeBasedType
{
public static readonly DurationType Second = new DurationType(TimeUnit.Second);
public static readonly DurationType Millisecond = new DurationType(TimeUnit.Millisecond);
public static readonly DurationType Microsecond = new DurationType(TimeUnit.Microsecond);
public static readonly DurationType Nanosecond = new DurationType(TimeUnit.Nanosecond);
private static readonly DurationType[] _types = new DurationType[] { Second, Millisecond, Microsecond, Nanosecond };

private DurationType(TimeUnit unit)
: base(unit)
{
}

public override ArrowTypeId TypeId => ArrowTypeId.Duration;
public override string Name => "duration";
public override int BitWidth => 64;

public static DurationType FromTimeUnit(TimeUnit unit)
{
return _types[(int)unit];
}

public override void Accept(IArrowTypeVisitor visitor) => Accept(this, visitor);
}
}
1 change: 1 addition & 0 deletions csharp/src/Apache.Arrow/Types/IArrowType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public enum ArrowTypeId
Dictionary,
Map,
FixedSizeList,
Duration,
}

public interface IArrowType
Expand Down
27 changes: 27 additions & 0 deletions csharp/src/Apache.Arrow/Types/TimeBasedType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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.

namespace Apache.Arrow.Types
{
public abstract class TimeBasedType : FixedWidthType
{
public TimeUnit Unit { get; }

protected TimeBasedType(TimeUnit unit)
{
Unit = unit;
}
}
}
15 changes: 2 additions & 13 deletions csharp/src/Apache.Arrow/Types/TimeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.


namespace Apache.Arrow.Types
{
public enum TimeUnit
{
Second,
Millisecond,
Microsecond,
Nanosecond
}

public abstract class TimeType: FixedWidthType
public abstract class TimeType : TimeBasedType
{
public static readonly Time32Type Second = new Time32Type(TimeUnit.Second);
public static readonly Time32Type Millisecond = new Time32Type(TimeUnit.Millisecond);
public static readonly Time64Type Microsecond = new Time64Type(TimeUnit.Microsecond);
public static readonly Time64Type Nanosecond = new Time64Type(TimeUnit.Nanosecond);
private static readonly TimeType[] _types = new TimeType[] { Second, Millisecond, Microsecond, Nanosecond };

public TimeUnit Unit { get; }

protected TimeType(TimeUnit unit)
: base(unit)
{
Unit = unit;
}

public static TimeType FromTimeUnit(TimeUnit unit)
Expand Down
Loading

0 comments on commit 7f89ea7

Please sign in to comment.