2023-04-21
DuckDB für Swift
Tristan Celder
Heute freuen wir uns, die DuckDB-API für Swift anzukündigen. Sie ermöglicht es Entwicklerinnen und Entwicklern auf Swift-Plattformen, die volle Leistung von DuckDB über eine native Swift-Schnittstelle zu nutzen – mit Unterstützung für starke Typisierung und Concurrency. Die API gibt es nicht nur auf Apple-Plattformen, sondern auch auf Linux, was neue Möglichkeiten für das wachsende Swift-on-Server-Ökosystem eröffnet.
Was enthalten ist
DuckDB soll schnell, zuverlässig und einfach nutzbar sein – und genau diese Haltung hat auch die neue Swift-API geprägt.
Dieses erste Release unterstützt viele DuckDB-Funktionen direkt, darunter:
- Abfragen über DuckDBs erweiterten SQL-Dialekt: Neben Basis-SQL unterstützt DuckDB beliebige und verschachtelte korrelierte Subqueries, Window-Funktionen, Collations, komplexe Typen (Swift-Arrays und Structs) und mehr.
- Import und Export von JSON-, CSV- und Parquet-Dateien: Neben dem eingebauten, sehr effizienten nativen Dateiformat kann DuckDB JSON, CSV und Parquet einlesen und exportieren.
- Stark typisierte Ergebnissets: DuckDBs stark typisierte Ergebnisse passen natürlich zu Swift. DuckDB-Spalten lassen sich einfach auf ihre nativen Swift-Entsprechungen casten – bereit für die Darstellung mit SwiftUI oder in einem bestehenden TabularData-Workflow.
- Unterstützung für Swift Concurrency: Durch ihre
Sendable-Konformität können viele der zentralen DuckDB-Typen sicher über Concurrency-Kontexte hinweg übergeben werden. Das erleichtert parallele Verarbeitungsabläufe und hält UIs reaktionsfähig.
Nutzung
Um zu zeigen, wie gut DuckDB und Swift zusammenpassen, haben wir ein Beispielprojekt erstellt, das Rohdaten aus dem NASA Exoplanet Archive direkt in DuckDB lädt.
Sie sehen, wie Sie:
- eine DuckDB-In-Memory-Datenbank und eine Connection instanziieren
- eine DuckDB-Tabelle mit dem Inhalt einer entfernten CSV-Datei füllen
- eine DuckDB-Datenbank abfragen und die Ergebnisse für die Darstellung vorbereiten
Zum Schluss präsentieren wir die Analyse mit Apples TabularData Framework und Swift Charts.
DuckDB instanziieren
DuckDB unterstützt dateibasierte und In-Memory-Datenbanken. In diesem Beispiel wollen wir die Ergebnisse der Exoplaneten-Analyse nicht auf die Platte schreiben und wählen daher eine In-Memory-Datenbank.
let database = try Database(store: .inMemory)Abfragen können wir aber noch nicht stellen. Wie bei anderen RDBMS müssen Abfragen über eine Datenbankverbindung laufen. DuckDB unterstützt mehrere Connections pro Datenbank. Das ist etwa für parallele Verarbeitung nützlich. In unserem Projekt brauchen wir nur eine Connection, auf die wir später asynchron zugreifen.
let connection = try database.connect()Schließlich legen wir einen app-spezifischen Typ an, der Datenbank und Connection hält und über den wir später unsere app-spezifischen Abfragen definieren.
import DuckDB
final class ExoplanetStore {
let database: Database let connection: Connection
init(database: Database, connection: Connection) { self.database = database self.connection = connection }}DuckDB mit einer entfernten CSV-Datei füllen
Ein Problem unseres aktuellen ExoplanetStore-Typs: Er enthält noch keine Daten zum Abfragen. Das beheben wir, indem wir die Daten aller bisher entdeckten Exoplaneten aus dem NASA Exoplanet Archive laden.
Es gibt Hunderte von Konfigurationsoptionen für diese Ressource; heute wollen wir Name und Entdeckungsjahr jedes Exoplaneten als CSV. Die Dokumentation liefert den folgenden Endpunkt:
https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name+,+disc_year+from+pscomppars&format=csvSobald die CSV lokal heruntergeladen ist, können wir sie mit folgendem SQL-Befehl als neue Tabelle in unserer DuckDB-In-Memory-Datenbank laden. DuckDBs Befehl read_csv_auto leitet das Tabellenschema automatisch ab, und die Daten stehen sofort zur Analyse bereit.
CREATE TABLE exoplanets AS SELECT * FROM read_csv_auto('downloaded_exoplanets.csv');Das packen wir als asynchrone Factory-Methode auf unserem ExoplanetStore-Typ:
import DuckDBimport Foundation
final class ExoplanetStore {
// Factory method to create and prepare a new ExoplanetStore static func create() async throws -> ExoplanetStore {
// Create our database and connection as described above let database = try Database(store: .inMemory) let connection = try database.connect()
// Download the CSV from the exoplanet archive 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")!)
// Issue our first query to DuckDB try connection.execute(""" CREATE TABLE exoplanets AS ( SELECT * FROM read_csv_auto('\(csvFileURL.path)') ); """)
// Create our pre-populated ExoplanetStore instance return ExoplanetStore( database: database, connection: connection ) }
// Let's make the initializer we defined previously // private. This prevents anyone accidentally instantiating // the store without having pre-loaded our Exoplanet CSV // into the database private init(database: Database, connection: Connection) { // ... }}Die Datenbank abfragen
Jetzt, da die Datenbank gefüllt ist, kann sie analysiert werden. Wir erstellen eine Abfrage, mit der sich die Zahl der pro Jahr entdeckten Exoplaneten als Diagramm darstellen lässt.
SELECT disc_year, count(disc_year) AS CountFROM exoplanetsGROUP BY disc_yearORDER BY disc_year;Die Abfrage von Swift aus an DuckDB zu schicken ist einfach. Wieder nutzen wir eine async-Funktion, damit der Aufrufer während der Ausführung nicht blockiert. Anschließend casten wir die Ergebnispalten mit DuckDBs ResultSet-Methoden cast(to:) auf native Swift-Typen und packen sie in einen DataFrame aus dem TabularData-Framework – bereit für die UI.
...
import TabularData
extension ExoplanetStore {
// Retrieves the number of exoplanets discovered by year func groupedByDiscoveryYear() async throws -> DataFrame {
// Issue the query we described above let result = try connection.query(""" SELECT disc_year, count(disc_year) AS Count FROM exoplanets GROUP BY disc_year ORDER BY disc_year """)
// Cast our DuckDB columns to their native Swift // equivalent types let discoveryYearColumn = result[0].cast(to: Int.self) let countColumn = result[1].cast(to: Int.self)
// Use our DuckDB columns to instantiate TabularData // columns and populate a TabularData DataFrame return DataFrame(columns: [ TabularData.Column(discoveryYearColumn) .eraseToAnyColumn(), TabularData.Column(countColumn) .eraseToAnyColumn(), ]) }}Die Ergebnisse visualisieren
In wenigen Zeilen Code wurde die Datenbank erzeugt, gefüllt und analysiert – jetzt fehlt nur noch die Darstellung.

Und das Gefühl sagt: Das ist erst der Anfang …
Das vollständige Beispielprojekt – einschließlich der SwiftUI-Views und Chart-Definitionen für den Screenshot oben – finden Sie im DuckDB-Swift-Repository. Das lauffähige App-Projekt liegt in Examples/SwiftUI/ExoplanetExplorer.xcodeproj.
Wir ermutigen Sie, den Code anzupassen, das Exoplanet Archive und DuckDB zu erkunden und eigene Entdeckungen zu machen – planetarisch oder anders.
Fazit
In diesem Beitrag haben wir die neue Swift-API für DuckDB vorgestellt und gezeigt, wie schnell Sie mit der Datenanalyse starten können.
Mit DuckDBs Leistung und Analysefähigkeiten und Swifts lebendigem Ökosystem und Plattformsupport war es nie besser, analytische Datensätze in Swift zu erkunden.
Wir sind gespannt, was Sie damit machen. Bei Fragen erreichen Sie uns auf Discord.
Die Swift-API für DuckDB wird über den Swift Package Manager ausgeliefert und liegt in einem neuen Top-Level-Repository unter https://github.com/duckdb/duckdb-swift.