should be it

This commit is contained in:
2025-10-24 19:21:19 -05:00
parent a4b23fc57c
commit f09560c7b1
14047 changed files with 3161551 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
/*:
[Home](1.%20Welcome)
# Welcome to DuckDB
## Introduction
DuckDB is a high-performance analytical database system. It is designed to be
fast, reliable and easy to use. DuckDB provides a rich SQL dialect, with
support far beyond basic SQL. DuckDB supports arbitrary and nested correlated
subqueries, window functions, collations, complex types (arrays, structs), and
more. For more information on the goals of DuckDB, please refer to the [Why
DuckDB page](https://duckdb.org/why_duckdb) on our website.
## How to use these playgrounds
Each playground is a live code example that you can run and edit to familiarise
yourself with the DuckDB Swift API. Playgrounds should run automatically, but
if your environment is set-up to run manually, press the play button in the
debug window to get started.
> For playgrounds in this series to work, it's important to access them via the
> `DuckDB.xcworkspace` file otherwise Xcode Playgrounds will be unable to find
> the DuckDB Swift module.
## Table of Contents
1. [Welcome to DuckDB](1.%20Welcome) (This page)
2. [Getting Started](2.%20Getting%20Started)
3. [Visualizing Data](3.%20Visualizing%20Data)
*/

View File

@@ -0,0 +1,162 @@
/*:
[Home](1.%20Welcome)
# Getting Started
## Introduction
In this first playground, we'll show you how you can use DuckDB with SwiftUI
to view a table of data. The task is simple, but it will demonstrate many of
the steps you'll need to interact successfully with DuckDB.
> For this playground to work, it's important to access it via the
> `DuckDB.xcworkspace` file otherwise Xcode Playgrounds will be unable to find
> the DuckDB Swift module.
You'll see how to:
- Create an in-memory database
- Create a connection to that database
- Query the database through the connection
We'll also be showing how DuckDB can be used to not only query internal tables,
but also any CSV or JSON files that you may have downloaded to your file system.
So let's get started.
*/
PlaygroundPage.current.needsIndefiniteExecution = true
// First, we need to import the DuckDB modules plus any other modules we'll be
// using within this playground
import DuckDB
import PlaygroundSupport
import SwiftUI
/*:
## Step 1: Fetching Data
Before we can query data, we'll need some data to query.
In this example, and for the other playground pages in this series, we're using
the [Stack Overflow Annual Developer Survey](https://insights.stackoverflow.com/survey).
In the 2022 survey there were over 70,000 responses from 180 countries and
each and every response has been collected into a CSV for us to analyze.
Handy. But what's in it?
That seems like a great first task. Let's create a simple query that allows us
to see all the columns in the table plus they're types so we know what
we're working with. We'll use SwiftUI to visualize our results in a simple
table.
*/
// Now the real-stuff. we'll define an asynchronous function that performs
// the heavy-lifting in extracting our required data
func fetchData() async throws -> ResultSet {
// Here, we download the CSVs for the Stack Overflow survey. Subsequent calls
// to this method return a cached version you can view at
// `~/Documents/Shared Playground Data/org.duckdb/surveys`
let fileURL = try await SurveyLoader.downloadSurveyCSV(forYear: 2022)
// We'll use an in-memory store for our database, as we don't need to persist
// anything
let database = try Database(store: .inMemory)
// Next we create a connection. A connection is used to issue queries to the
// database. You can have multiple connections per database.
let connection = try database.connect()
// We're now ready to issue our first query! Notice how we enclose the file
// path for the CSV in single quotes. We'll limit this to zero as we're only
// interested in the column layout for now.
return try connection.query("SELECT * FROM '\(fileURL.path)' LIMIT 0")
}
/*:
## Step 2: Rendering Data
With our asynchronous data fetching function in hand, we can move on to
displaying the data.
We'll define a view with a single `rows` property initially set to `nil`. Then,
when the view loads we'll call the function we defined in Step 1 to load the
data asynchronously.
SwiftUI's `task(_:)` modifier is perfect for this. The `task(_:)` modifier runs
in the background when the view is loaded. We'll attach the `task(_:)`
modifier to our loading spinner so we can see that something is happening.
Finally, we'll process the result into rows for our view table and set the
`rows` property. This will act as SwiftUI's trigger to re-render the view.
*/
struct TableLayoutView: View {
// We'll define a new type to hold the view data for our table view
struct TableRow: Identifiable {
let id: String
let databaseType: String
}
// This will hold our view state and signal to the view whether or not it
// can show our gathered data
enum ViewState {
case loading
case result(ResultSet)
}
@State var state = ViewState.loading
var body: some View {
switch state {
// Whilst we're waiting for our data to load we'll display a spinner
case .loading:
ProgressView { Text("loading") }
.task {
do {
// Here, we kick-off the function we defined in step 1
self.state = .result(try await fetchData())
}
catch { print("error: \(error)") }
}
// Now our result set has been loaded we can display its data
case .result(let result):
Table(result) {
TableColumn("Column Name", value: \.name)
TableColumn("Column Type", value: \.typeName)
}
}
}
}
// Plus a small extension on Column to help us get the column type as a String
fileprivate extension Column {
var typeName: String {
String(describing: underlyingDatabaseType)
}
}
/*:
## Conclusion
And that's how easy it is to get started with DuckDB.
You've learnt how to create a database, connect to it, and issue queries.
Next-up, we'll see how we can use DuckDB to create incredible visulizations
using SwiftUI, TabularData and SwiftCharts. See you there.
[Next Playground](@next)
*/
// Kick-off the playground
PlaygroundPage.current.setLiveView(
TableLayoutView()
.frame(width: 640, height: 480, alignment: .center)
)

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>

View File

@@ -0,0 +1,190 @@
/*:
[Home](1.%20Welcome)
# Visualizing Data
## Introduction
In this playground, we build on the skills learnt in
[Getting Started](2.%20Getting%20Started) and begin to extract some
meaningful data from our data source.
> For this playground to work, it's important to access it via the
> `DuckDB.xcworkspace` file otherwise Xcode Playgrounds will be unable to find
> the DuckDB Swift module.
You'll see how to:
- Cast DuckDB columns to native Swift types
- Visualize extracted data using Swift Charts
*/
PlaygroundPage.current.needsIndefiniteExecution = true
// Import the DuckDB modules plus any other modules we'll be using within
// this playground
import DuckDB
import PlaygroundSupport
import SwiftUI
import Charts
/*:
## Step 1: Extracting Data
Thanks to our work in the [Getting Started](2.%20Getting%20Started) playground,
we can start now start to do some meaningful analysis on our data.
In this example we're going to take a look at which countries generated the
largest response in the
[Stack Overflow Annual Developer Survey](https://insights.stackoverflow.com/survey)
and plot the results into a chart.
Let's create a function that extracts the top 10 countries participating in
the survey and puts it into a format ready for visualization.
### Casting columns
To get the data from our columns out, we'll use the `cast(to:)` method on
Column. It's important that you only cast to a column that matches the
`underlyingDataType` of the Column you are casting from, or you'll get an
assert in debug builds and nil values in release mode.
Its possible to debug the column layout and therefore column types of any
ResultSet object by simply printing it to the console, or checking the
`underlyingDataType` member of the Column.
Once you know this, you can then select the appropriate Swift type to cast to.
For example, a Column with a `underlyingDatabaseType` of `DatabaseType.varchar`
casts to `String`, and a column with an underlying database type of
`DatabaseType.bigint` casts to `Int32` and for convenience `Int`. For DuckDB
composite types you can even cast to matching types which conform to
`Decodable`.
You can see which database types convert to which Swift types by checking out
the docs for `DatabaseType`.
Let's see this in practice.
*/
// We'll use this type as a source for our chart
struct ChartRow {
let nameOfCountry: String
let participantCount: Int
}
// It needs to conform to identifiable so that SwiftUI can group it correctly
extension ChartRow: Identifiable {
var id: String { nameOfCountry }
}
// Here, we'll define an asynchronous function for extracting our data.
func generateSurveyParticipantCountryChartData() async throws -> [ChartRow] {
// We'll connect to the database in the exact same way as the previous
// playground
let fileURL = try await SurveyLoader.downloadSurveyCSV(forYear: 2022)
let database = try Database(store: .inMemory)
let connection = try database.connect()
// For our query, we'll count and group by the Country column in our CSV
let result = try connection.query("""
SELECT Country, COUNT(Country) AS Count
FROM '\(fileURL.path)'
GROUP BY Country
ORDER BY Count DESC
LIMIT 10
""")
// We know our Country column has VARCHAR as its underlying data type and we
// know BIGINT is the underlying data type for the Count column. Let's cast
// them appropriately.
let countryColumn = result[0].cast(to: String.self)
let countColumn = result[1].cast(to: Int.self)
// Now that our columns have been cast, we can pull out the values. As
// Column conforms to Collection, this is fairly simple. However, we do need
// to guard against nil values and replace with a suitable default
let countries = countryColumn.map { $0 ?? "unknown" }
let counts = countColumn.map { $0 ?? 0 }
// Finally, we can zip up the results and generate our chart rows
var rows = [ChartRow]()
for (country, count) in zip(countries, counts) {
let row = ChartRow(nameOfCountry: country, participantCount: count)
rows.append(row)
}
return rows
}
/*:
## Step 2: Making Charts
Now that we have our data extraction routine ready to go, we can look into
visualizing it into something that's great to look at and easy to understand.
Thankfully, the combination of DuckDB, SwiftUI and Swift Charts make this
process really easy.
As in the previous playground we'll create a SwiftUI view to display our chart
that displays a spinner while the data is assembled using our routine from step 1.
Then, when our data is ready, we'll update our view and use Swift Charts to
visualize what we've gathered.
Let's take a look.
*/
struct PopularCountriesView: View {
enum ViewState {
case loading
case data([ChartRow])
}
@State private var state = ViewState.loading
var body: some View {
switch state {
// If we're still loading, we'll display a spinner and kick-off our
// routine from step 1
case .loading:
ProgressView { Text("Fetching Data") }
.task {
do {
let chartData = try await generateSurveyParticipantCountryChartData()
self.state = .data(chartData)
}
catch { print("unexpected error: \(error)") }
}
// Now we have our chart data we can display the chart
case .data(let chartRows):
Chart(chartRows) { row in
BarMark(
x: .value("Respondents", row.participantCount),
y: .value("Country", row.nameOfCountry)
)
}
}
}
}
/*:
## Conclusion
In just a few lines of code DuckDB has enabled you to:
- Open a CSV file withing DuckDB
- Analyze it using SQL
- Prepare it for display using Swift Charts.
You've also learnt how you to cast from DuckDB's underlying database types and
into native Swift types using Column's `cast(to:)` methods.
Now you're well on your way to becoming a DuckDB master!
*/
PlaygroundPage.current.setLiveView(
PopularCountriesView()
.frame(width: 640, height: 480, alignment: .center)
)

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>

View File

@@ -0,0 +1,65 @@
import Foundation
import PlaygroundSupport
import System
public enum SurveyLoader {
enum Failure {
case unableToDownloadSurveyContent
}
private static let localDataURL = playgroundSharedDataDirectory
.appending(component: "org.duckdb", directoryHint: .isDirectory)
.appending(component: "surveys")
public static func downloadSurveyCSV(forYear year: Int) async throws -> URL {
try await downloadSurveyIfNeeded(forYear: year)
return localSurveyDataURL(forYear: year)
.appending(component: "survey_results_public.csv")
}
public static func downloadSurveySchemaCSV(forYear year: Int) async throws -> URL {
try await downloadSurveyIfNeeded(forYear: year)
return localSurveyDataURL(forYear: year)
.appending(component: "survey_results_schema.csv")
}
public static func downloadSurveyIfNeeded(forYear year: Int) async throws {
let surveyDataURL = localSurveyDataURL(forYear: year)
if FileManager.default.fileExists(atPath: surveyDataURL.path) {
// survey already exists. skipping.
return
}
let surveyURL = remoteSurveyDataZipURL(forYear: year)
let (zipFileURL, _) = try await URLSession.shared.download(from: surveyURL)
try FileManager.default.createDirectory(
at: surveyDataURL, withIntermediateDirectories: true)
try Shell.execute("unzip '\(zipFileURL.path)' -d '\(surveyDataURL.path)'")
}
private static func localSurveyDataURL(forYear year: Int) -> URL {
localDataURL.appending(component: "\(year)")
}
private static func remoteSurveyDataZipURL(forYear year: Int) -> URL {
URL(string: "https://info.stackoverflowsolutions.com/rs/719-EMH-566/images/stack-overflow-developer-survey-\(year).zip")!
}
}
fileprivate enum Shell {
@discardableResult
static func execute(_ command: String) throws -> String {
let process = Process()
let pipe = Pipe()
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = ["-c", command]
process.standardOutput = pipe
process.standardError = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8)!
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='macos' display-mode='rendered' buildActiveScheme='true' importAppTypes='true'>
<pages>
<page name='1. Welcome'/>
<page name='2. Getting Started'/>
<page name='3. Visualizing Data'/>
</pages>
</playground>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>