Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions Sources/Arrow/ArrowArrayBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import Foundation

/// A type which builds a type-erased `ArrowArray`.
public protocol AnyArrowArrayBuilder {
/// Returns an unparameterised `ArrowArray`.
/// - Returns: The type-erased Arrow array.
func toAnyArrowArray() throws(ArrowError) -> AnyArrowArray
func appendAny(_ val: Any?)
}
Expand Down Expand Up @@ -73,8 +75,6 @@ extension ArrowArrayBuilderInternal {
self.arrowType.getStride()
}

/// Returns an unparameterised `ArrowArray`.
/// - Returns: The type-erased Arrow array.
public func toAnyArrowArray() throws(ArrowError) -> AnyArrowArray {
try self.finish()
}
Expand Down Expand Up @@ -109,7 +109,7 @@ public class ArrowArrayBuilderBase<
}
}

/// A type which builds an `ArrowArray` with a numeric `ItemType`.
/// An array builder for numeric types.
public class NumberArrayBuilder<ItemType>: ArrowArrayBuilderBase<
FixedBufferBuilder<ItemType>,
FixedArray<ItemType>
Expand All @@ -120,6 +120,7 @@ where ItemType: Numeric, ItemType: BitwiseCopyable {
}
}

/// A `String` array builder.
public class StringArrayBuilder: ArrowArrayBuilderBase<
VariableBufferBuilder<String>,
StringArray
Expand All @@ -130,6 +131,7 @@ public class StringArrayBuilder: ArrowArrayBuilderBase<
}
}

/// A `Data` array builder.
public class BinaryArrayBuilder: ArrowArrayBuilderBase<
VariableBufferBuilder<Data>,
BinaryArray
Expand All @@ -140,6 +142,7 @@ public class BinaryArrayBuilder: ArrowArrayBuilderBase<
}
}

/// A `Bool` array builder.
public class BoolArrayBuilder: ArrowArrayBuilderBase<
BoolBufferBuilder, BoolArray
>
Expand All @@ -149,6 +152,7 @@ public class BoolArrayBuilder: ArrowArrayBuilderBase<
}
}

/// A 32-bit date array builder.
public class Date32ArrayBuilder: ArrowArrayBuilderBase<
Date32BufferBuilder,
Date32Array
Expand All @@ -159,6 +163,7 @@ public class Date32ArrayBuilder: ArrowArrayBuilderBase<
}
}

/// A 64-bit date array builder.
public class Date64ArrayBuilder: ArrowArrayBuilderBase<
Date64BufferBuilder,
Date64Array
Expand All @@ -169,6 +174,7 @@ public class Date64ArrayBuilder: ArrowArrayBuilderBase<
}
}

// A 32-bit elaspsed time builder.
public class Time32ArrayBuilder: ArrowArrayBuilderBase<
FixedBufferBuilder<Time32>,
Time32Array
Expand All @@ -179,6 +185,7 @@ public class Time32ArrayBuilder: ArrowArrayBuilderBase<
}
}

// A 64-bit elaspsed time builder.
public class Time64ArrayBuilder: ArrowArrayBuilderBase<
FixedBufferBuilder<Time64>,
Time64Array
Expand All @@ -189,6 +196,7 @@ public class Time64ArrayBuilder: ArrowArrayBuilderBase<
}
}

// A Timestamp array builder.
public class TimestampArrayBuilder: ArrowArrayBuilderBase<
FixedBufferBuilder<Int64>,
TimestampArray
Expand All @@ -203,6 +211,7 @@ public class TimestampArrayBuilder: ArrowArrayBuilderBase<

// MARK: Struct array builder.

/// Builds an array of structs.
public class StructArrayBuilder: ArrowArrayBuilderBase<
StructBufferBuilder,
NestedArray
Expand Down Expand Up @@ -262,7 +271,9 @@ public class StructArrayBuilder: ArrowArrayBuilderBase<

// MARK: List array builder.

/// A type which can build an `NestedArray`containing exactly `ItemType`.
/// Builds a `NestedArray`containing lists of `ItemType`.
///
/// Both lists and items in lists are nullablie.
public class ListArrayBuilder: ArrowArrayBuilderBase<
ListBufferBuilder,
NestedArray
Expand All @@ -271,18 +282,17 @@ public class ListArrayBuilder: ArrowArrayBuilderBase<
let valueBuilder: any AnyArrowArrayBuilder

public override init(_ elementType: ArrowType) throws(ArrowError) {

guard case .list(let field) = elementType else {
throw .invalid("Expected a field with type .list")
}

self.valueBuilder = try ArrowArrayBuilders.loadBuilder(
arrowType: field.type
)
try super.init(elementType)
}

// Overrides the default
// Overrides the protocol extension.
// Swift currently provides no marker for this.
public func append(_ values: [Any?]?) {
self.bufferBuilder.append(values)
if let vals = values {
Expand Down
5 changes: 3 additions & 2 deletions Sources/Arrow/ArrowBuffer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2025 The Apache Software Foundation
// Copyright 2025 The Columnar-Swift Contributors
// Copyright 2025 The Columnar Swift Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -24,7 +24,8 @@ public class ArrowBuffer {
let isMemoryOwner: Bool

init(
length: UInt, capacity: UInt, rawPointer: UnsafeMutableRawPointer,
length: UInt, capacity: UInt,
rawPointer: UnsafeMutableRawPointer,
isMemoryOwner: Bool = true
) {
self.length = length
Expand Down
3 changes: 2 additions & 1 deletion Sources/Arrow/ArrowSchema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

import Foundation

public struct ArrowSchema: Sendable {
// Note this is a reference type to reduce copying.
public final class ArrowSchema: Sendable {
public let fields: [ArrowField]
public let fieldLookup: [String: Int]
init(_ fields: [ArrowField]) {
Expand Down
18 changes: 18 additions & 0 deletions Sources/Arrow/ArrowTable.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2025 The Apache Software Foundation
// Copyright 2025 The Columnar Swift Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -244,6 +245,12 @@ public class RecordBatch {
return self
}

/// Add a column the `RecordBatch` builder.
/// - Parameters:
/// - field: The field describing the array.
/// - arrowArray: The array to add to the reocrd batch.
/// - Returns: The `RecordBatch.Builder` with the array appended and the field added to
/// the schema.
@discardableResult
public func addColumn(
_ field: ArrowField,
Expand All @@ -263,6 +270,17 @@ public class RecordBatch {
}
}
}
// Check nullability matches actual data
let schema = self.schemaBuilder.finish()
for (index, field) in schema.fields.enumerated() {
let column = columns[index]
if !field.isNullable && column.nullCount > 0 {
return .failure(
.invalid(
"non-nullable column '\(field.name)' contains \(column.nullCount) null values."
))
}
}
return .success(
RecordBatch(self.schemaBuilder.finish(), columns: self.columns)
)
Expand Down
1 change: 1 addition & 0 deletions Sources/Arrow/ArrowType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public indirect enum ArrowType: Codable, Sendable, Equatable {
/// of binary data in total.
case binary
/// Opaque binary data of fixed size.
///
/// Enum parameter specifies the number of bytes per value.
case fixedSizeBinary(Int32)
/// Opaque binary data of variable length and 64-bit offsets.
Expand Down
17 changes: 17 additions & 0 deletions Tests/ArrowTests/RecordBatchTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,21 @@ struct RecordBatchTests {
throw error
}
}

// Ensure that invalid record batches can't be built.
@Test func schemaNullabilityChecked() throws {
let stringBuilder = try ArrowArrayBuilders.loadStringArrayBuilder()
stringBuilder.append("test10")
stringBuilder.append(nil)
stringBuilder.append("test33")
let array = try stringBuilder.finish()

let field = ArrowField(name: "col1", dataType: .utf8, isNullable: false)
let result = RecordBatch.Builder()
.addColumn(field, arrowArray: array)
.finish()
if case .success(_) = result {
Issue.record("Record batch should have rejected null data.")
}
}
}