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:

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:

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=csv

Sobald 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 DuckDB
import 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 Count
FROM exoplanets
GROUP BY disc_year
ORDER 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.