Skip to content

Commit 2ed8281

Browse files
committed
apacheGH-37938: [Swift] initial impl of C Data interface
1 parent 79799e5 commit 2ed8281

26 files changed

+1199
-46
lines changed

ci/docker/ubuntu-swift.dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18-
FROM swift:5.7.3
18+
FROM swift:5.9.0
1919

2020
# Go is needed for generating test data
2121
RUN apt-get update -y -q && \

dev/release/rat_exclude_files.txt

+1
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,4 @@ r/tools/nixlibs-allowlist.txt
150150
ruby/red-arrow/.yardopts
151151
.github/pull_request_template.md
152152
swift/data-generator/swift-datagen/go.sum
153+
swift/CDataWGo/go.sum

swift/.swiftlint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ included:
2020
- Arrow/Tests
2121
- ArrowFlight/Sources
2222
- ArrowFlight/Tests
23+
- CDataWGo/Sources/go-swift
2324
excluded:
2425
- Arrow/Sources/Arrow/File_generated.swift
2526
- Arrow/Sources/Arrow/Message_generated.swift

swift/Arrow/Package.swift

+13-4
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,27 @@ let package = Package(
3636
// and therefore doesn't include the unaligned buffer swift changes.
3737
// This can be changed back to using the tag once a new version of
3838
// flatbuffers has been released.
39-
.package(url: "https://github.com/google/flatbuffers.git", branch: "master")
39+
.package(url: "https://github.com/google/flatbuffers.git", branch: "master"),
40+
.package(
41+
url: "https://github.com/apple/swift-atomics.git",
42+
.upToNextMajor(from: "1.2.0") // or `.upToNextMinor
43+
)
4044
],
4145
targets: [
4246
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
4347
// Targets can depend on other targets in this package, and on products in packages this package depends on.
48+
.target(
49+
name: "ArrowC", // your C/C++ library's name
50+
path: "Sources/ArrowC" // your path to the C/C++ library
51+
),
4452
.target(
4553
name: "Arrow",
46-
dependencies: [
47-
.product(name: "FlatBuffers", package: "flatbuffers")
54+
dependencies: ["ArrowC",
55+
.product(name: "FlatBuffers", package: "flatbuffers"),
56+
.product(name: "Atomics", package: "swift-atomics")
4857
]),
4958
.testTarget(
5059
name: "ArrowTests",
51-
dependencies: ["Arrow"]),
60+
dependencies: ["Arrow", "ArrowC"]),
5261
]
5362
)

swift/Arrow/Sources/Arrow/ArrowArray.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,24 @@
1717

1818
import Foundation
1919

20-
public class ArrowArrayHolder {
20+
public protocol ArrowArrayHolder {
21+
var type: ArrowType {get}
22+
var length: UInt {get}
23+
var nullCount: UInt {get}
24+
var array: Any {get}
25+
var getBufferData: () -> [Data] {get}
26+
var getBufferDataSizes: () -> [Int] {get}
27+
var getArrowColumn: (ArrowField, [ArrowArrayHolder]) throws -> ArrowColumn {get}
28+
}
29+
30+
public class ArrowArrayHolderImpl: ArrowArrayHolder {
2131
public let type: ArrowType
2232
public let length: UInt
2333
public let nullCount: UInt
2434
public let array: Any
2535
public let getBufferData: () -> [Data]
2636
public let getBufferDataSizes: () -> [Int]
27-
private let getArrowColumn: (ArrowField, [ArrowArrayHolder]) throws -> ArrowColumn
37+
public let getArrowColumn: (ArrowField, [ArrowArrayHolder]) throws -> ArrowColumn
2838
public init<T>(_ arrowArray: ArrowArray<T>) {
2939
self.array = arrowArray
3040
self.length = arrowArray.length
@@ -60,16 +70,6 @@ public class ArrowArrayHolder {
6070
return ArrowColumn(field, chunked: ChunkedArrayHolder(try ChunkedArray<T>(arrays)))
6171
}
6272
}
63-
64-
public static func makeArrowColumn(_ field: ArrowField,
65-
holders: [ArrowArrayHolder]
66-
) -> Result<ArrowColumn, ArrowError> {
67-
do {
68-
return .success(try holders[0].getArrowColumn(field, holders))
69-
} catch {
70-
return .failure(.runtimeError("\(error)"))
71-
}
72-
}
7373
}
7474

7575
public class ArrowArray<T>: AsString {

swift/Arrow/Sources/Arrow/ArrowBuffer.swift

+13-2
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,33 @@ public class ArrowBuffer {
2323
fileprivate(set) var length: UInt
2424
let capacity: UInt
2525
let rawPointer: UnsafeMutableRawPointer
26+
let isMemoryOwner: Bool
2627

27-
init(length: UInt, capacity: UInt, rawPointer: UnsafeMutableRawPointer) {
28+
init(length: UInt, capacity: UInt, rawPointer: UnsafeMutableRawPointer, isMemoryOwner: Bool = true) {
2829
self.length = length
2930
self.capacity = capacity
3031
self.rawPointer = rawPointer
32+
self.isMemoryOwner = isMemoryOwner
3133
}
3234

3335
deinit {
34-
self.rawPointer.deallocate()
36+
if isMemoryOwner {
37+
self.rawPointer.deallocate()
38+
}
3539
}
3640

3741
func append(to data: inout Data) {
3842
let ptr = UnsafePointer(rawPointer.assumingMemoryBound(to: UInt8.self))
3943
data.append(ptr, count: Int(capacity))
4044
}
4145

46+
static func createEmptyBuffer() -> ArrowBuffer {
47+
return ArrowBuffer(
48+
length: 0,
49+
capacity: 0,
50+
rawPointer: UnsafeMutableRawPointer.allocate(byteCount: 0, alignment: .zero))
51+
}
52+
4253
static func createBuffer(_ data: [UInt8], length: UInt) -> ArrowBuffer {
4354
let byteCount = UInt(data.count)
4455
let capacity = alignTo64(byteCount)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import Foundation
19+
import ArrowC
20+
import Atomics
21+
22+
extension String {
23+
var cstring: UnsafePointer<CChar> {
24+
(self as NSString).cString(using: String.Encoding.utf8.rawValue)!
25+
}
26+
}
27+
28+
// The memory used by UnsafeAtomic is not automatically
29+
// reclaimed. Since this value is initialized once
30+
// and used until the program/app is closed it's
31+
// memory will be released on program/app exit
32+
let exportDataCounter: UnsafeAtomic<Int> = .create(0)
33+
34+
public class ArrowCExporter {
35+
private class ExportData {
36+
let id: Int
37+
init() {
38+
id = exportDataCounter.loadThenWrappingIncrement(ordering: .relaxed)
39+
ArrowCExporter.exportedData[id] = self
40+
}
41+
}
42+
43+
private class ExportSchema: ExportData {
44+
public let arrowTypeName: UnsafePointer<CChar>
45+
public let name: UnsafePointer<CChar>
46+
private let arrowType: ArrowType
47+
init(_ arrowType: ArrowType, name: String = "") throws {
48+
self.arrowType = arrowType
49+
self.arrowTypeName = try arrowType.cDataFormatId.cstring
50+
self.name = name.cstring
51+
super.init()
52+
}
53+
}
54+
55+
private class ExportArray: ExportData {
56+
private let arrowData: ArrowData
57+
private(set) var data = [UnsafeRawPointer?]()
58+
private(set) var buffers: UnsafeMutablePointer<UnsafeRawPointer?>
59+
init(_ arrowData: ArrowData) {
60+
// keep a reference to the ArrowData
61+
// obj so the memory doesn't get
62+
// deallocated
63+
self.arrowData = arrowData
64+
for arrowBuffer in arrowData.buffers {
65+
data.append(arrowBuffer.rawPointer)
66+
}
67+
68+
self.buffers = UnsafeMutablePointer(mutating: data)
69+
super.init()
70+
}
71+
}
72+
73+
private static var exportedData = [Int: ExportData]()
74+
public init() {}
75+
76+
public func exportType(_ cSchema: inout ArrowC.ArrowSchema, arrowType: ArrowType, name: String = "") ->
77+
Result<Bool, ArrowError> {
78+
do {
79+
let exportSchema = try ExportSchema(arrowType, name: name)
80+
cSchema.format = exportSchema.arrowTypeName
81+
cSchema.name = exportSchema.name
82+
cSchema.private_data =
83+
UnsafeMutableRawPointer(mutating: UnsafeRawPointer(bitPattern: exportSchema.id))
84+
cSchema.release = {(data: UnsafeMutablePointer<ArrowC.ArrowSchema>?) in
85+
let arraySchema = data!.pointee
86+
let exportId = Int(bitPattern: arraySchema.private_data)
87+
guard ArrowCExporter.exportedData[exportId] != nil else {
88+
fatalError("Export schema not found with id \(exportId)")
89+
}
90+
91+
// the data associated with this exportSchema object
92+
// which includes the C strings for the format and name
93+
// be deallocated upon removal
94+
ArrowCExporter.exportedData.removeValue(forKey: exportId)
95+
ArrowC.ArrowSwiftClearReleaseSchema(data)
96+
}
97+
} catch {
98+
return .failure(.unknownError("\(error)"))
99+
}
100+
return .success(true)
101+
}
102+
103+
public func exportField(_ schema: inout ArrowC.ArrowSchema, field: ArrowField) ->
104+
Result<Bool, ArrowError> {
105+
return exportType(&schema, arrowType: field.type, name: field.name)
106+
}
107+
108+
public func exportArray(_ cArray: inout ArrowC.ArrowArray, arrowData: ArrowData) {
109+
let exportArray = ExportArray(arrowData)
110+
cArray.buffers = exportArray.buffers
111+
cArray.length = Int64(arrowData.length)
112+
cArray.null_count = Int64(arrowData.nullCount)
113+
cArray.n_buffers = Int64(arrowData.buffers.count)
114+
cArray.n_children = 0
115+
cArray.children = nil
116+
cArray.dictionary = nil
117+
cArray.private_data =
118+
UnsafeMutableRawPointer(mutating: UnsafeRawPointer(bitPattern: exportArray.id))
119+
cArray.release = {(data: UnsafeMutablePointer<ArrowC.ArrowArray>?) in
120+
let arrayData = data!.pointee
121+
let exportId = Int(bitPattern: arrayData.private_data)
122+
guard ArrowCExporter.exportedData[exportId] != nil else {
123+
fatalError("Export data not found with id \(exportId)")
124+
}
125+
126+
// the data associated with this exportArray object
127+
// which includes the entire arrowData object
128+
// and the buffers UnsafeMutablePointer[] will
129+
// be deallocated upon removal
130+
ArrowCExporter.exportedData.removeValue(forKey: exportId)
131+
ArrowC.ArrowSwiftClearReleaseArray(data)
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)