Skip to content

Commit 046ff27

Browse files
Swift Snippets (#18)
* Swift Snippets * fix run Snippets in CI * fix build on Linux * Update docs and snippets
1 parent 2ce18b8 commit 046ff27

File tree

11 files changed

+217
-64
lines changed

11 files changed

+217
-64
lines changed

.github/workflows/docs.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ name: Docs
22
on:
33
push:
44
branches: ["main"]
5-
paths:
6-
- 'Sources/**'
7-
- '.github/workflows/docs.yml'
8-
- 'Package.swift'
9-
- 'Makefile'
105
workflow_dispatch:
116

127
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
@@ -30,14 +25,14 @@ jobs:
3025
with:
3126
fetch-depth: 0
3227
- name: Run build docs
33-
run: make Build/Docs
28+
run: make build/docs
3429
- name: Setup GitHub Pages
3530
id: pages
3631
uses: actions/configure-pages@v5
3732
- name: Upload artifact
3833
uses: actions/upload-pages-artifact@v3
3934
with:
40-
path: Build/Docs
35+
path: build/docs
4136
deploy:
4237
runs-on: ubuntu-latest
4338
needs: build

.github/workflows/test.yml

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,9 @@ on:
55
- main
66
tags-ignore:
77
- '**'
8-
paths:
9-
- 'Sources/**'
10-
- 'Tests/**'
11-
- '.github/workflows/test.yml'
12-
- 'Package.swift'
13-
- 'Makefile'
148
pull_request:
159
branches:
1610
- '**'
17-
paths:
18-
- 'Sources/**'
19-
- 'Tests/**'
20-
- '.github/workflows/**'
21-
- 'Package.swift'
22-
- 'Makefile'
2311

2412
jobs:
2513
Apple:
@@ -30,9 +18,9 @@ jobs:
3018
matrix:
3119
include:
3220
- name: macOS
33-
target: test-macos
21+
target: build/test-macos.xcresult
3422
- name: iOS
35-
target: test-ios
23+
target: build/test-ios.xcresult
3624
steps:
3725
- name: Checkout
3826
uses: actions/checkout@v4
@@ -48,6 +36,15 @@ jobs:
4836
- name: Build and test
4937
run: swift test
5038
shell: bash
39+
Snippets:
40+
name: Run snippets
41+
runs-on: macos-26
42+
steps:
43+
- name: Checkout
44+
uses: actions/checkout@v4
45+
- name: Run snippets
46+
run: make run-snippets
47+
shell: bash
5148
Linux:
5249
name: Test Linux
5350
runs-on: ubuntu-latest

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ xcuserdata/
88
/.build
99

1010
# Makefile output dir
11-
/Build
11+
/build

Makefile

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
TARGET_NAME = SQLyra
2-
OUTPUD_DIR = ./Build
3-
DERIVED_DATA_PATH = $(OUTPUD_DIR)/DerivedData
1+
TARGET = SQLyra
2+
SNIPPETS := $(notdir $(basename $(wildcard Snippets/*.swift)))
43

5-
.PHONY: clean lint format test test-macos test-ios test-linux
4+
.PHONY: clean lint format test-linux $(SNIPPETS) test-snippets learn preview-doc
65

76
clean:
87
swift package clean
9-
rm -rf $(OUTPUD_DIR)
8+
rm -rf ./build
9+
rm -rf ./Snippets/db.sqlite
1010

1111
README.md: Playgrounds/README.playground/Contents.swift
1212
cat $< | ./Scripts/markdown.swift > $@
@@ -19,60 +19,54 @@ lint:
1919
format:
2020
xcrun swift-format --recursive --in-place ./
2121

22-
# MARK: - Tests
22+
# MARK: - Apple Tests
2323

24-
test:
25-
swift test
24+
XCODEBUILD_TEST = xcodebuild test \
25+
-quiet \
26+
-scheme $(TARGET) \
27+
-resultBundlePath $@
2628

27-
test-macos: $(OUTPUD_DIR)/test-macos.xcresult
28-
test-ios: $(OUTPUD_DIR)/test-ios.xcresult
29+
XCCOV = xcrun xccov view --files-for-target $(TARGET) --report $@
2930

30-
XCODEBUILD_TEST = xcodebuild test -quiet -scheme $(TARGET_NAME) -resultBundlePath $@
31-
XCCOV = xcrun xccov view --files-for-target $(TARGET_NAME) --report $@
32-
33-
$(OUTPUD_DIR)/test-macos.xcresult:
31+
build/test-macos.xcresult:
3432
$(XCODEBUILD_TEST) -destination 'platform=macOS'
3533
$(XCCOV)
3634

37-
$(OUTPUD_DIR)/test-ios.xcresult:
35+
build/test-ios.xcresult:
3836
$(XCODEBUILD_TEST) -destination 'platform=iOS Simulator,name=iPhone 17'
3937
$(XCCOV)
4038

41-
# Apple Containerization or Docker
39+
# MARK: - Linux Tests
40+
41+
# Apple Container or Docker
4242
CONTAINER ?= container
4343

4444
test-linux:
4545
$(CONTAINER) run --rm -v "$(PWD):/src" -w /src swift:latest /bin/bash -c \
4646
"apt-get update && apt-get install -y libsqlite3-dev && swift test"
4747

48-
# MARK: - DocC
49-
50-
DOCC_ARCHIVE = $(DERIVED_DATA_PATH)/Build/Products/Debug/$(TARGET_NAME).doccarchive
48+
# MARK: - Snippets
5149

52-
$(DOCC_ARCHIVE):
53-
xcodebuild docbuild \
54-
-quiet \
55-
-scheme $(TARGET_NAME) \
56-
-destination "generic/platform=macOS" \
57-
-derivedDataPath $(DERIVED_DATA_PATH)
50+
$(SNIPPETS):
51+
swift run --quiet $@
5852

59-
$(OUTPUD_DIR)/Docs: $(DOCC_ARCHIVE)
60-
xcrun docc process-archive transform-for-static-hosting $^ \
61-
--hosting-base-path $(TARGET_NAME) \
62-
--output-path $@
63-
64-
# MARK: - DocC preview
53+
run-snippets: $(SNIPPETS)
6554

66-
DOC_CATALOG = Sources/$(TARGET_NAME)/$(TARGET_NAME).docc
67-
SYMBOL_GRAPHS = $(OUTPUD_DIR)/symbol-graphs
55+
learn:
56+
SWIFTPM_ENABLE_SNIPPETS=1 swift package learn
6857

69-
$(SYMBOL_GRAPHS):
70-
swift build --target $(TARGET_NAME) -Xswiftc -emit-symbol-graph -Xswiftc -emit-symbol-graph-dir -Xswiftc $@
58+
# MARK: - DocC
7159

72-
$(OUTPUD_DIR)/doc-preview: $(DOC_CATALOG) $(SYMBOL_GRAPHS)
73-
xcrun docc preview $(DOC_CATALOG) \
74-
--fallback-display-name $(TARGET_NAME) \
75-
--fallback-bundle-identifier org.swift.$(TARGET_NAME) \
76-
--fallback-bundle-version 1.0.0 \
77-
--additional-symbol-graph-dir $(SYMBOL_GRAPHS) \
60+
build/docs:
61+
env SQLYRA_DOCС_PLUGIN=1 \
62+
swift package --allow-writing-to-directory $@ \
63+
generate-documentation \
64+
--target $(TARGET) \
65+
--transform-for-static-hosting \
66+
--hosting-base-path $(TARGET) \
7867
--output-path $@
68+
69+
preview-doc:
70+
env SQLYRA_DOCС_PLUGIN=1 \
71+
swift package --disable-sandbox \
72+
preview-documentation --target $(TARGET)

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ let package = Package(
4343
),
4444
]
4545
)
46+
47+
if Context.environment["SQLYRA_DOCС_PLUGIN"] == "1" {
48+
package.dependencies.append(.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"))
49+
}

Snippets/GettingStarted.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// snippet.hide
2+
import Foundation
3+
// snippet.show
4+
import SQLyra
5+
6+
// snippet.hide
7+
struct WorkingDirectory: ~Copyable {
8+
private let fileManager = FileManager.default
9+
10+
deinit {
11+
try? removeDatabase()
12+
}
13+
14+
func prepare() throws {
15+
if !fileManager.currentDirectoryPath.hasSuffix("Snippets") {
16+
precondition(fileManager.changeCurrentDirectoryPath("Snippets/"), "couldn't change directory")
17+
}
18+
print("currentDirectoryPath:", fileManager.currentDirectoryPath)
19+
try removeDatabase()
20+
}
21+
22+
func removeDatabase() throws {
23+
try removeFile(path: "db.sqlite")
24+
}
25+
26+
func removeFile(path: String) throws {
27+
if fileManager.fileExists(atPath: path) {
28+
try fileManager.removeItem(atPath: path)
29+
}
30+
}
31+
}
32+
33+
let workingDirectory = WorkingDirectory()
34+
try workingDirectory.prepare()
35+
36+
// snippet.show
37+
let database = try Database.open(
38+
at: "db.sqlite",
39+
options: [.create, .readwrite]
40+
)
41+
42+
let schema = """
43+
CREATE TABLE IF NOT EXISTS contacts(
44+
id INT PRIMARY KEY NOT NULL,
45+
name TEXT
46+
);
47+
"""
48+
try database.execute(schema)
49+
50+
let insert = try database.prepare(
51+
"INSERT INTO contacts (id, name) VALUES (?, ?);"
52+
)
53+
try insert.bind(parameters: 1, "Paul")
54+
try insert.execute()
55+
try insert.bind(parameters: 2, "John")
56+
try insert.execute()
57+
58+
struct Contact: Codable {
59+
let id: Int
60+
let name: String?
61+
}
62+
63+
let contacts = try database.prepare("SELECT * FROM contacts;").array(Contact.self)
64+
print(contacts)
65+
// [GettingStarted.Contact(id: 1, name: Optional("Paul")), GettingStarted.Contact(id: 2, name: Optional("John"))]
66+
67+
// snippet.hide
68+
try workingDirectory.removeDatabase()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// snippet.hide
2+
#if !os(Linux)
3+
4+
import SQLyra
5+
6+
let db = try Database.open(at: ":memory:", options: [.memory, .readwrite])
7+
try db.execute("CREATE TABLE IF NOT EXISTS contacts (id INT, name TEXT);")
8+
9+
// snippet.show
10+
let statement = try db.prepare("INSERT INTO contacts (id, name) VALUES (?, ?);")
11+
try statement.bind(parameters: 1, "Paul")
12+
13+
assert(statement.sql == "INSERT INTO contacts (id, name) VALUES (?, ?);")
14+
assert(statement.expandedSQL == "INSERT INTO contacts (id, name) VALUES (1, 'Paul');")
15+
assert(statement.normalizedSQL == "INSERT INTO contacts(id,name)VALUES(?,?);")
16+
17+
// snippet.hide
18+
#endif

Snippets/SQLParameters.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// snippet.hide
2+
import SQLyra
3+
4+
let db = try Database.open(at: ":memory:", options: [.memory, .readwrite])
5+
try db.execute("CREATE TABLE users (id INT, email TEXT);")
6+
7+
// snippet.show
8+
let statement = try db.prepare("INSERT INTO users (id, email) VALUES (?, :login)")
9+
10+
assert(statement.parameterCount == 2)
11+
assert(statement.parameterName(at: 1) == nil)
12+
assert(statement.parameterName(at: 2) == ":login")
13+
assert(statement.parameterIndex(for: ":id") == 0) // invalid
14+
assert(statement.parameterIndex(for: ":login") == 2)

Sources/SQLyra/Database.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import SQLite3
22

33
/// SQLite database.
4+
///
5+
/// @Snippet(path: "SQLyra/Snippets/GettingStarted")
46
public final class Database {
57
/// Database open options.
68
public struct OpenOptions: OptionSet, Sendable {

Sources/SQLyra/PreparedStatement.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import FoundationEssentials
1010
///
1111
/// To execute an SQL statement, it must first be compiled into a byte-code program using one of these routines.
1212
/// Or, in other words, these routines are constructors for the prepared statement object.
13+
///
14+
/// [Prepared Statement Object](https://www.sqlite.org/c3ref/stmt.html)
1315
public final class PreparedStatement {
1416
let stmt: OpaquePointer
1517
let database: Database // release database after all statements
@@ -66,18 +68,27 @@ public final class PreparedStatement {
6668

6769
extension PreparedStatement {
6870
/// SQL text used to create prepared statement.
71+
///
72+
/// [Retrieving statement SQL](https://www.sqlite.org/c3ref/expanded_sql.html)
73+
/// @Snippet(path: "SQLyra/Snippets/RetrievingStatementSQL")
6974
public var sql: String { sqlite3_sql(stmt).string ?? "" }
7075

7176
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
7277
/// UTF-8 string containing the normalized SQL text of prepared statement.
7378
///
7479
/// The semantics used to normalize a SQL statement are unspecified and subject to change.
7580
/// At a minimum, literal values will be replaced with suitable placeholders.
81+
///
82+
/// [Retrieving statement SQL](https://www.sqlite.org/c3ref/expanded_sql.html)
83+
/// @Snippet(path: "SQLyra/Snippets/RetrievingStatementSQL")
7684
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
7785
public var normalizedSQL: String { sqlite3_normalized_sql(stmt).string ?? "" }
7886
#endif
7987

8088
/// SQL text of prepared statement with bound parameters expanded.
89+
///
90+
/// [Retrieving statement SQL](https://www.sqlite.org/c3ref/expanded_sql.html)
91+
/// @Snippet(path: "SQLyra/Snippets/RetrievingStatementSQL")
8192
public var expandedSQL: String {
8293
guard let pointer = sqlite3_expanded_sql(stmt) else { return "" }
8394
defer { sqlite3_free(pointer) }
@@ -89,14 +100,23 @@ extension PreparedStatement {
89100

90101
extension PreparedStatement {
91102
/// Number of SQL parameters.
103+
///
104+
/// [Number Of SQL Parameters](https://www.sqlite.org/c3ref/bind_parameter_count.html).
105+
/// @Snippet(path: "SQLyra/Snippets/SQLParameters")
92106
public var parameterCount: Int { Int(sqlite3_bind_parameter_count(stmt)) }
93107

94108
/// Name of a SQL parameter.
109+
///
110+
/// [Name of a SQL parameter](https://www.sqlite.org/c3ref/bind_parameter_name.html).
111+
/// @Snippet(path: "SQLyra/Snippets/SQLParameters")
95112
public func parameterName(at index: Int) -> String? {
96113
sqlite3_bind_parameter_name(stmt, Int32(index)).map { String(cString: $0) }
97114
}
98115

99-
/// Index of a parameter with a given name.
116+
/// Index of a parameter with a given name
117+
///
118+
/// [Index of a parameter with a given name](https://www.sqlite.org/c3ref/bind_parameter_index.html).
119+
/// @Snippet(path: "SQLyra/Snippets/SQLParameters")
100120
public func parameterIndex(for name: String) -> Int {
101121
Int(sqlite3_bind_parameter_index(stmt, name))
102122
}

0 commit comments

Comments
 (0)