Swift 클라이언트
Swift 클라이언트
DuckDB에는 Swift 클라이언트가 있어요. 애플리케이션 전용 타입을 만들고, CSV를 로드하고, 쿼리를 실행하는 전체 흐름을 예시와 함께 살펴볼게요.
출처: 문서
본문
DuckDB는 인메모리와 영속 데이터베이스 둘 다 지원해요.
인메모리 데이터베이스로 작업하려면:
let database = try Database(store: .inMemory)
영속 데이터베이스로 작업하려면:
let database = try Database(store: .file(at: "test.db"))
쿼리는 데이터베이스 연결을 통해 발행할 수 있어요.
let connection = try database.connect()
DuckDB는 데이터베이스당 여러 연결을 지원해요.
애플리케이션 예시
이 페이지의 나머지는 우리의 발표 포스트의 예시에 기반하며, NASA의 Exoplanet Archive의 원시 데이터를 직접 DuckDB에 로드한 것을 사용해요.
애플리케이션 전용 타입 만들기
먼저 데이터베이스와 연결을 담고, 나중에 앱 전용 쿼리를 정의하게 될 애플리케이션 전용 타입을 만들어요.
import DuckDB
final class ExoplanetStore {
let database: Database
let connection: Connection
init(database: Database, connection: Connection) {
self.database = database
self.connection = connection
}
}
CSV 파일 로드
NASA의 Exoplanet Archive에서 데이터를 로드해요.
wget https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name+,+disc_year+from+pscomppars&format=csv -O downloaded_exoplanets.csv
CSV를 로컬에 다운로드한 뒤, 다음 SQL 명령어로 DuckDB에 새 테이블로 로드할 수 있어요.
CREATE TABLE exoplanets AS
SELECT * FROM read_csv('downloaded_exoplanets.csv');
이것을 ExoplanetStore 타입의 새 비동기 팩토리 메서드로 패키징해 볼게요.
import DuckDB
import Foundation
final class ExoplanetStore {
// 새 ExoplanetStore를 만들고 준비하는 팩토리 메서드
static func create() async throws -> ExoplanetStore {
// 위에서 설명한 대로 데이터베이스와 연결 생성
let database = try Database(store: .inMemory)
let connection = try database.connect()
// exoplanet archive에서 CSV 다운로드
let (csvFileURL, _) = try await URLSession.shared.download(
from: URL(string: "https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name+,+disc_year+from+pscomppars&format=csv")!)
// DuckDB에 첫 쿼리 발행
try connection.execute("""
CREATE TABLE exoplanets AS
SELECT * FROM read_csv('\(csvFileURL.path)');
""")
// 미리 채워진 ExoplanetStore 인스턴스 생성
return ExoplanetStore(
database: database,
connection: connection
)
}
// 앞서 정의한 이니셜라이저를
// private로 만들자. 이렇게 하면 Exoplanet CSV를
// 미리 로드하지 않고 store를 실수로 인스턴스화하는
// 것을 방지할 수 있어요
private init(database: Database, connection: Connection) {
...
}
}
데이터베이스 쿼리
다음 예시는 비동기 함수를 통해 Swift 안에서 DuckDB를 쿼리해요. 즉 쿼리가 실행되는 동안 호출자가 블록되지 않아요. 그런 다음 DuckDB의 ResultSet cast(to:) 메서드 계열을 사용해 결과 컬럼을 Swift 네이티브 타입으로 캐스팅하고, 마지막으로 TabularData 프레임워크의 DataFrame으로 감쌉니다.
...
import TabularData
extension ExoplanetStore {
// 연도별로 발견된 외계행성 수를 가져옴
func groupedByDiscoveryYear() async throws -> DataFrame {
// 위에서 설명한 쿼리 발행
let result = try connection.query("""
SELECT disc_year, count(disc_year) AS Count
FROM exoplanets
GROUP BY disc_year
ORDER BY disc_year
""")
// DuckDB 컬럼을 Swift 네이티브
// 동등 타입으로 캐스팅
let discoveryYearColumn = result[0].cast(to: Int.self)
let countColumn = result[1].cast(to: Int.self)
// DuckDB 컬럼으로 TabularData
// 컬럼을 만들고 TabularData DataFrame을 채움
return DataFrame(columns: [
TabularData.Column(discoveryYearColumn).eraseToAnyColumn(),
TabularData.Column(countColumn).eraseToAnyColumn(),
])
}
}
전체 프로젝트
전체 예시 프로젝트를 보려면 DuckDB Swift 저장소를 clone하고 Examples/SwiftUI/ExoplanetExplorer.xcodeproj에 있는 실행 가능한 앱 프로젝트를 열어보세요.
더 알아보기 (Learn more)
- 전체 예시 프로젝트는 DuckDB Swift 저장소의 ExoplanetExplorer 앱을 참고해 주세요.