Skip to content

Commit

Permalink
apacheGH-38316: [C#] Implement interval types (apache#39043)
Browse files Browse the repository at this point in the history
### What changes are included in this PR?

Changes required to support the three interval types in the C# implementation.

### Are these changes tested?

Yes.

### Are there any user-facing changes?

Adds new classes for interval support.
* Closes: apache#38316
* Closes: apache#29431

Lead-authored-by: Curt Hagenlocher <[email protected]>
Co-authored-by: Platob <[email protected]>
Signed-off-by: Curt Hagenlocher <[email protected]>
  • Loading branch information
2 people authored and clayburn committed Jan 23, 2024
1 parent 532e691 commit 0a614bd
Show file tree
Hide file tree
Showing 22 changed files with 883 additions and 37 deletions.
12 changes: 11 additions & 1 deletion csharp/src/Apache.Arrow/Arrays/ArrayDataTypeComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ internal sealed class ArrayDataTypeComparer :
IArrowTypeVisitor<FixedSizeListType>,
IArrowTypeVisitor<StructType>,
IArrowTypeVisitor<UnionType>,
IArrowTypeVisitor<MapType>
IArrowTypeVisitor<MapType>,
IArrowTypeVisitor<IntervalType>
{
private readonly IArrowType _expectedType;
private bool _dataTypeMatch;
Expand Down Expand Up @@ -133,6 +134,15 @@ public void Visit(MapType actualType)
}
}

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

private static bool CompareNested(NestedType expectedType, NestedType actualType)
{
if (expectedType.Fields.Count != actualType.Fields.Count)
Expand Down
1 change: 1 addition & 0 deletions csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public static IArrowArray BuildArray(ArrayData data)
case ArrowTypeId.FixedSizeList:
return new FixedSizeListArray(data);
case ArrowTypeId.Interval:
return IntervalArray.Create(data);
default:
throw new NotSupportedException($"An ArrowArray cannot be built for type {data.DataType.TypeId}.");
}
Expand Down
140 changes: 140 additions & 0 deletions csharp/src/Apache.Arrow/Arrays/IntervalArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// 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.Scalars;
using Apache.Arrow.Types;

namespace Apache.Arrow
{
internal static class IntervalArray
{
internal static IArrowArray Create(ArrayData data) => ((IntervalType)data.DataType).Unit switch
{
IntervalUnit.YearMonth => new YearMonthIntervalArray(data),
IntervalUnit.DayTime => new DayTimeIntervalArray(data),
IntervalUnit.MonthDayNanosecond => new MonthDayNanosecondIntervalArray(data),
_ => throw new InvalidOperationException($"Unsupported interval unit {((IntervalType)data.DataType).Unit}"),
};
}

public abstract class IntervalArray<T> : PrimitiveArray<T>
where T : struct
{
protected IntervalArray(ArrayData data)
: base(data)
{
data.EnsureBufferCount(2);
data.EnsureDataType(ArrowTypeId.Interval);
}

public IntervalType Type => (IntervalType)Data.DataType;

public IntervalUnit Unit => Type.Unit;

internal static IArrowArray Create(ArrayData data) => ((IntervalType)data.DataType).Unit switch
{
IntervalUnit.YearMonth => new YearMonthIntervalArray(data),
IntervalUnit.DayTime => new DayTimeIntervalArray(data),
IntervalUnit.MonthDayNanosecond => new MonthDayNanosecondIntervalArray(data),
_ => throw new InvalidOperationException($"Unsupported interval unit {((IntervalType)data.DataType).Unit}"),
};

internal static void ValidateUnit(IntervalUnit expected, IntervalUnit actual)
{
if (expected != actual)
{
throw new ArgumentException(
$"Specified interval unit <{actual}> does not match expected unit <{expected}>",
"Unit");
}
}
}

public sealed class YearMonthIntervalArray : IntervalArray<YearMonthInterval>
{
public class Builder : PrimitiveArrayBuilder<YearMonthInterval, YearMonthIntervalArray, Builder>
{
protected override YearMonthIntervalArray Build(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset) =>
new YearMonthIntervalArray(valueBuffer, nullBitmapBuffer, length, nullCount, offset);
}

public YearMonthIntervalArray(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset)
: this(new ArrayData(IntervalType.YearMonth, length, nullCount, offset,
new[] { nullBitmapBuffer, valueBuffer }))
{ }

public YearMonthIntervalArray(ArrayData data) : base(data)
{
ValidateUnit(IntervalUnit.YearMonth, Unit);
}

public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
}

public sealed class DayTimeIntervalArray : IntervalArray<DayTimeInterval>
{
public class Builder : PrimitiveArrayBuilder<DayTimeInterval, DayTimeIntervalArray, Builder>
{
protected override DayTimeIntervalArray Build(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset) =>
new DayTimeIntervalArray(valueBuffer, nullBitmapBuffer, length, nullCount, offset);
}

public DayTimeIntervalArray(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset)
: this(new ArrayData(IntervalType.DayTime, length, nullCount, offset,
new[] { nullBitmapBuffer, valueBuffer }))
{ }

public DayTimeIntervalArray(ArrayData data) : base(data)
{
ValidateUnit(IntervalUnit.DayTime, Unit);
}

public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
}

public sealed class MonthDayNanosecondIntervalArray : IntervalArray<MonthDayNanosecondInterval>
{
public class Builder : PrimitiveArrayBuilder<MonthDayNanosecondInterval, MonthDayNanosecondIntervalArray, Builder>
{
protected override MonthDayNanosecondIntervalArray Build(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset) =>
new MonthDayNanosecondIntervalArray(valueBuffer, nullBitmapBuffer, length, nullCount, offset);
}

public MonthDayNanosecondIntervalArray(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset)
: this(new ArrayData(IntervalType.MonthDayNanosecond, length, nullCount, offset,
new[] { nullBitmapBuffer, valueBuffer }))
{ }

public MonthDayNanosecondIntervalArray(ArrayData data) : base(data)
{
ValidateUnit(IntervalUnit.MonthDayNanosecond, Unit);
}

public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
}
}
9 changes: 9 additions & 0 deletions csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ private static string GetFormat(IArrowType datatype)
// Timestamp
case TimestampType timestampType:
return String.Format("ts{0}:{1}", FormatTimeUnit(timestampType.Unit), timestampType.Timezone);
// Interval
case IntervalType intervalType:
return intervalType.Unit switch
{
IntervalUnit.YearMonth => "tiM",
IntervalUnit.DayTime => "tiD",
IntervalUnit.MonthDayNanosecond => "tin",
_ => throw new InvalidDataException($"Unsupported interval unit for export: {intervalType.Unit}"),
};
// Nested
case ListType _: return "+l";
case FixedSizeListType fixedListType:
Expand Down
2 changes: 1 addition & 1 deletion csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public ArrowType GetAsType()
"tDn" => DurationType.Nanosecond,
"tiM" => IntervalType.YearMonth,
"tiD" => IntervalType.DayTime,
//"tin" => IntervalType.MonthDayNanosecond, // Not yet implemented
"tin" => IntervalType.MonthDayNanosecond,
_ => throw new NotSupportedException("Data type is not yet supported in import.")
};
}
Expand Down
2 changes: 2 additions & 0 deletions csharp/src/Apache.Arrow/Extensions/FlatbufExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public static Types.IntervalUnit ToArrow(this Flatbuf.IntervalUnit unit)
return Types.IntervalUnit.DayTime;
case Flatbuf.IntervalUnit.YEAR_MONTH:
return Types.IntervalUnit.YearMonth;
case Flatbuf.IntervalUnit.MONTH_DAY_NANO:
return Types.IntervalUnit.MonthDayNanosecond;
default:
throw new ArgumentException($"Unexpected Flatbuf IntervalUnit", nameof(unit));
}
Expand Down
6 changes: 6 additions & 0 deletions csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ internal class ArrowRecordBatchFlatBufferBuilder :
IArrowArrayVisitor<Time32Array>,
IArrowArrayVisitor<Time64Array>,
IArrowArrayVisitor<DurationArray>,
IArrowArrayVisitor<YearMonthIntervalArray>,
IArrowArrayVisitor<DayTimeIntervalArray>,
IArrowArrayVisitor<MonthDayNanosecondIntervalArray>,
IArrowArrayVisitor<ListArray>,
IArrowArrayVisitor<FixedSizeListArray>,
IArrowArrayVisitor<StringArray>,
Expand Down Expand Up @@ -106,6 +109,9 @@ public ArrowRecordBatchFlatBufferBuilder()
public void Visit(Time32Array array) => CreateBuffers(array);
public void Visit(Time64Array array) => CreateBuffers(array);
public void Visit(DurationArray array) => CreateBuffers(array);
public void Visit(YearMonthIntervalArray array) => CreateBuffers(array);
public void Visit(DayTimeIntervalArray array) => CreateBuffers(array);
public void Visit(MonthDayNanosecondIntervalArray array) => CreateBuffers(array);

public void Visit(ListArray array)
{
Expand Down
19 changes: 19 additions & 0 deletions csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class TypeVisitor :
IArrowTypeVisitor<Time32Type>,
IArrowTypeVisitor<Time64Type>,
IArrowTypeVisitor<DurationType>,
IArrowTypeVisitor<IntervalType>,
IArrowTypeVisitor<BinaryType>,
IArrowTypeVisitor<TimestampType>,
IArrowTypeVisitor<ListType>,
Expand Down Expand Up @@ -196,6 +197,13 @@ public void Visit(DurationType type)
Flatbuf.Duration.CreateDuration(Builder, ToFlatBuffer(type.Unit)));
}

public void Visit(IntervalType type)
{
Result = FieldType.Build(
Flatbuf.Type.Interval,
Flatbuf.Interval.CreateInterval(Builder, ToFlatBuffer(type.Unit)));
}

public void Visit(StructType type)
{
Flatbuf.Struct_.StartStruct_(Builder);
Expand Down Expand Up @@ -307,5 +315,16 @@ private static Flatbuf.UnionMode ToFlatBuffer(Types.UnionMode mode)
_ => throw new ArgumentException($"unsupported union mode <{mode}>", nameof(mode)),
};
}

private static Flatbuf.IntervalUnit ToFlatBuffer(Types.IntervalUnit unit)
{
return unit switch
{
Types.IntervalUnit.YearMonth => Flatbuf.IntervalUnit.YEAR_MONTH,
Types.IntervalUnit.DayTime => Flatbuf.IntervalUnit.DAY_TIME,
Types.IntervalUnit.MonthDayNanosecond => Flatbuf.IntervalUnit.MONTH_DAY_NANO,
_ => throw new ArgumentException($"unsupported interval unit <{unit}>", nameof(unit))
}; ;
}
}
}
63 changes: 63 additions & 0 deletions csharp/src/Apache.Arrow/Scalars/DayTimeInterval.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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.Runtime.InteropServices;

namespace Apache.Arrow.Scalars
{
[StructLayout(LayoutKind.Explicit)]
public struct DayTimeInterval : IEquatable<DayTimeInterval>
{
[FieldOffset(0)]
public readonly int Days;

[FieldOffset(4)]
public readonly int Milliseconds;

public DayTimeInterval(int days, int milliseconds)
{
Days = days;
Milliseconds = milliseconds;
}

public override bool Equals(object obj) => obj switch
{
DayTimeInterval interval => Equals(interval),
_ => false,
};

public override int GetHashCode() => unchecked((17 + Days) * 23 + Milliseconds);

public bool Equals(DayTimeInterval interval)
{
return this.Days == interval.Days && this.Milliseconds == interval.Milliseconds;
}

public static DateTime operator +(DateTime dateTime, DayTimeInterval interval)
=> dateTime.AddDays(interval.Days).AddMilliseconds(interval.Milliseconds);
public static DateTime operator +(DayTimeInterval interval, DateTime dateTime)
=> dateTime.AddDays(interval.Days).AddMilliseconds(interval.Milliseconds);
public static DateTime operator -(DateTime dateTime, DayTimeInterval interval)
=> dateTime.AddDays(-interval.Days).AddMilliseconds(-interval.Milliseconds);

public static DateTimeOffset operator +(DateTimeOffset dateTime, DayTimeInterval interval)
=> dateTime.AddDays(interval.Days).AddMilliseconds(interval.Milliseconds);
public static DateTimeOffset operator +(DayTimeInterval interval, DateTimeOffset dateTime)
=> dateTime.AddDays(interval.Days).AddMilliseconds(interval.Milliseconds);
public static DateTimeOffset operator -(DateTimeOffset dateTime, DayTimeInterval interval)
=> dateTime.AddDays(-interval.Days).AddMilliseconds(-interval.Milliseconds);
}
}
67 changes: 67 additions & 0 deletions csharp/src/Apache.Arrow/Scalars/MonthDayNanosecondInterval.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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.Runtime.InteropServices;

namespace Apache.Arrow.Scalars
{
[StructLayout(LayoutKind.Explicit)]
public struct MonthDayNanosecondInterval : IEquatable<MonthDayNanosecondInterval>
{
[FieldOffset(0)]
public readonly int Months;

[FieldOffset(4)]
public readonly int Days;

[FieldOffset(8)]
public readonly long Nanoseconds;

public MonthDayNanosecondInterval(int months, int days, long nanoseconds)
{
Months = months;
Days = days;
Nanoseconds = nanoseconds;
}

public override bool Equals(object obj) => obj switch
{
MonthDayNanosecondInterval interval => Equals(interval),
_ => false,
};

public override int GetHashCode() => unchecked(((17 + Months) * 23 + Days) * 23 + (int)Nanoseconds);

public bool Equals(MonthDayNanosecondInterval interval)
{
return this.Months == interval.Months && this.Days == interval.Days && this.Nanoseconds == interval.Nanoseconds;
}

public static DateTime operator +(DateTime dateTime, MonthDayNanosecondInterval interval)
=> dateTime.AddMonths(interval.Months).AddDays(interval.Days).AddTicks(interval.Nanoseconds / 100);
public static DateTime operator +(MonthDayNanosecondInterval interval, DateTime dateTime)
=> dateTime.AddMonths(interval.Months).AddDays(interval.Days).AddTicks(interval.Nanoseconds / 100);
public static DateTime operator -(DateTime dateTime, MonthDayNanosecondInterval interval)
=> dateTime.AddMonths(-interval.Months).AddDays(-interval.Days).AddTicks(-interval.Nanoseconds / 100);

public static DateTimeOffset operator +(DateTimeOffset dateTime, MonthDayNanosecondInterval interval)
=> dateTime.AddMonths(interval.Months).AddDays(interval.Days).AddTicks(interval.Nanoseconds / 100);
public static DateTimeOffset operator +(MonthDayNanosecondInterval interval, DateTimeOffset dateTime)
=> dateTime.AddMonths(interval.Months).AddDays(interval.Days).AddTicks(interval.Nanoseconds / 100);
public static DateTimeOffset operator -(DateTimeOffset dateTime, MonthDayNanosecondInterval interval)
=> dateTime.AddMonths(-interval.Months).AddDays(-interval.Days).AddTicks(-interval.Nanoseconds / 100);
}
}
Loading

0 comments on commit 0a614bd

Please sign in to comment.