Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ name: Docs
on:
push:
branches: ["main"]
paths:
- 'Sources/**'
- '.github/workflows/docs.yml'
- 'Package.swift'
- 'Makefile'
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
Expand All @@ -30,14 +25,14 @@ jobs:
with:
fetch-depth: 0
- name: Run build docs
run: make Build/Docs
run: make build/docs
- name: Setup GitHub Pages
id: pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: Build/Docs
path: build/docs
deploy:
runs-on: ubuntu-latest
needs: build
Expand Down
25 changes: 11 additions & 14 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,9 @@ on:
- main
tags-ignore:
- '**'
paths:
- 'Sources/**'
- 'Tests/**'
- '.github/workflows/test.yml'
- 'Package.swift'
- 'Makefile'
pull_request:
branches:
- '**'
paths:
- 'Sources/**'
- 'Tests/**'
- '.github/workflows/**'
- 'Package.swift'
- 'Makefile'

jobs:
Apple:
Expand All @@ -30,9 +18,9 @@ jobs:
matrix:
include:
- name: macOS
target: test-macos
target: build/test-macos.xcresult
- name: iOS
target: test-ios
target: build/test-ios.xcresult
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -48,6 +36,15 @@ jobs:
- name: Build and test
run: swift test
shell: bash
Snippets:
name: Run snippets
runs-on: macos-26
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run snippets
run: make run-snippets
shell: bash
Linux:
name: Test Linux
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ xcuserdata/
/.build

# Makefile output dir
/Build
/build
76 changes: 35 additions & 41 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
TARGET_NAME = SQLyra
OUTPUD_DIR = ./Build
DERIVED_DATA_PATH = $(OUTPUD_DIR)/DerivedData
TARGET = SQLyra
SNIPPETS := $(notdir $(basename $(wildcard Snippets/*.swift)))

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

clean:
swift package clean
rm -rf $(OUTPUD_DIR)
rm -rf ./build
rm -rf ./Snippets/db.sqlite

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

# MARK: - Tests
# MARK: - Apple Tests

test:
swift test
XCODEBUILD_TEST = xcodebuild test \
-quiet \
-scheme $(TARGET) \
-resultBundlePath $@

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

XCODEBUILD_TEST = xcodebuild test -quiet -scheme $(TARGET_NAME) -resultBundlePath $@
XCCOV = xcrun xccov view --files-for-target $(TARGET_NAME) --report $@

$(OUTPUD_DIR)/test-macos.xcresult:
build/test-macos.xcresult:
$(XCODEBUILD_TEST) -destination 'platform=macOS'
$(XCCOV)

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

# Apple Containerization or Docker
# MARK: - Linux Tests

# Apple Container or Docker
CONTAINER ?= container

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

# MARK: - DocC

DOCC_ARCHIVE = $(DERIVED_DATA_PATH)/Build/Products/Debug/$(TARGET_NAME).doccarchive
# MARK: - Snippets

$(DOCC_ARCHIVE):
xcodebuild docbuild \
-quiet \
-scheme $(TARGET_NAME) \
-destination "generic/platform=macOS" \
-derivedDataPath $(DERIVED_DATA_PATH)
$(SNIPPETS):
swift run --quiet $@

$(OUTPUD_DIR)/Docs: $(DOCC_ARCHIVE)
xcrun docc process-archive transform-for-static-hosting $^ \
--hosting-base-path $(TARGET_NAME) \
--output-path $@

# MARK: - DocC preview
run-snippets: $(SNIPPETS)

DOC_CATALOG = Sources/$(TARGET_NAME)/$(TARGET_NAME).docc
SYMBOL_GRAPHS = $(OUTPUD_DIR)/symbol-graphs
learn:
SWIFTPM_ENABLE_SNIPPETS=1 swift package learn

$(SYMBOL_GRAPHS):
swift build --target $(TARGET_NAME) -Xswiftc -emit-symbol-graph -Xswiftc -emit-symbol-graph-dir -Xswiftc $@
# MARK: - DocC

$(OUTPUD_DIR)/doc-preview: $(DOC_CATALOG) $(SYMBOL_GRAPHS)
xcrun docc preview $(DOC_CATALOG) \
--fallback-display-name $(TARGET_NAME) \
--fallback-bundle-identifier org.swift.$(TARGET_NAME) \
--fallback-bundle-version 1.0.0 \
--additional-symbol-graph-dir $(SYMBOL_GRAPHS) \
build/docs:
env SQLYRA_DOCС_PLUGIN=1 \
swift package --allow-writing-to-directory $@ \
generate-documentation \
--target $(TARGET) \
--transform-for-static-hosting \
--hosting-base-path $(TARGET) \
--output-path $@

preview-doc:
env SQLYRA_DOCС_PLUGIN=1 \
swift package --disable-sandbox \
preview-documentation --target $(TARGET)
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@ let package = Package(
),
]
)

if Context.environment["SQLYRA_DOCС_PLUGIN"] == "1" {
package.dependencies.append(.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"))
}
68 changes: 68 additions & 0 deletions Snippets/GettingStarted.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// snippet.hide
import Foundation
// snippet.show
import SQLyra

// snippet.hide
struct WorkingDirectory: ~Copyable {
private let fileManager = FileManager.default

deinit {
try? removeDatabase()
}

func prepare() throws {
if !fileManager.currentDirectoryPath.hasSuffix("Snippets") {
precondition(fileManager.changeCurrentDirectoryPath("Snippets/"), "couldn't change directory")
}
print("currentDirectoryPath:", fileManager.currentDirectoryPath)
try removeDatabase()
}

func removeDatabase() throws {
try removeFile(path: "db.sqlite")
}

func removeFile(path: String) throws {
if fileManager.fileExists(atPath: path) {
try fileManager.removeItem(atPath: path)
}
}
}

let workingDirectory = WorkingDirectory()
try workingDirectory.prepare()

// snippet.show
let database = try Database.open(
at: "db.sqlite",
options: [.create, .readwrite]
)

let schema = """
CREATE TABLE IF NOT EXISTS contacts(
id INT PRIMARY KEY NOT NULL,
name TEXT
);
"""
try database.execute(schema)

let insert = try database.prepare(
"INSERT INTO contacts (id, name) VALUES (?, ?);"
)
try insert.bind(parameters: 1, "Paul")
try insert.execute()
try insert.bind(parameters: 2, "John")
try insert.execute()

struct Contact: Codable {
let id: Int
let name: String?
}

let contacts = try database.prepare("SELECT * FROM contacts;").array(Contact.self)
print(contacts)
// [GettingStarted.Contact(id: 1, name: Optional("Paul")), GettingStarted.Contact(id: 2, name: Optional("John"))]

// snippet.hide
try workingDirectory.removeDatabase()
18 changes: 18 additions & 0 deletions Snippets/RetrievingStatementSQL.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// snippet.hide
#if !os(Linux)

import SQLyra

let db = try Database.open(at: ":memory:", options: [.memory, .readwrite])
try db.execute("CREATE TABLE IF NOT EXISTS contacts (id INT, name TEXT);")

// snippet.show
let statement = try db.prepare("INSERT INTO contacts (id, name) VALUES (?, ?);")
try statement.bind(parameters: 1, "Paul")

assert(statement.sql == "INSERT INTO contacts (id, name) VALUES (?, ?);")
assert(statement.expandedSQL == "INSERT INTO contacts (id, name) VALUES (1, 'Paul');")
assert(statement.normalizedSQL == "INSERT INTO contacts(id,name)VALUES(?,?);")

// snippet.hide
#endif
14 changes: 14 additions & 0 deletions Snippets/SQLParameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// snippet.hide
import SQLyra

let db = try Database.open(at: ":memory:", options: [.memory, .readwrite])
try db.execute("CREATE TABLE users (id INT, email TEXT);")

// snippet.show
let statement = try db.prepare("INSERT INTO users (id, email) VALUES (?, :login)")

assert(statement.parameterCount == 2)
assert(statement.parameterName(at: 1) == nil)
assert(statement.parameterName(at: 2) == ":login")
assert(statement.parameterIndex(for: ":id") == 0) // invalid
assert(statement.parameterIndex(for: ":login") == 2)
2 changes: 2 additions & 0 deletions Sources/SQLyra/Database.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import SQLite3

/// SQLite database.
///
/// @Snippet(path: "SQLyra/Snippets/GettingStarted")
public final class Database {
/// Database open options.
public struct OpenOptions: OptionSet, Sendable {
Expand Down
22 changes: 21 additions & 1 deletion Sources/SQLyra/PreparedStatement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import FoundationEssentials
///
/// To execute an SQL statement, it must first be compiled into a byte-code program using one of these routines.
/// Or, in other words, these routines are constructors for the prepared statement object.
///
/// [Prepared Statement Object](https://www.sqlite.org/c3ref/stmt.html)
public final class PreparedStatement {
let stmt: OpaquePointer
let database: Database // release database after all statements
Expand Down Expand Up @@ -66,18 +68,27 @@ public final class PreparedStatement {

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

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

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

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

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

/// Index of a parameter with a given name.
/// Index of a parameter with a given name
///
/// [Index of a parameter with a given name](https://www.sqlite.org/c3ref/bind_parameter_index.html).
/// @Snippet(path: "SQLyra/Snippets/SQLParameters")
public func parameterIndex(for name: String) -> Int {
Int(sqlite3_bind_parameter_index(stmt, name))
}
Expand Down
Loading