Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ extension EnlighterASTGenerator {
info: "A `URL` pointing to the database to be used."),
.init(name: "readOnly",
info:
"For protocol conformance, only allowed value: `true`.")
"For protocol conformance, only allowed value: `true`."),
.init(name: "pragmas",
info: "SQLite pragmas to apply to each connection."),
]
),
inlinable: options.inlinable
Expand All @@ -318,11 +320,13 @@ extension EnlighterASTGenerator {
.init(
declaration: .makeInit(public: options.public,
.init(keywordArg: "url", .name("URL")),
.init(keywordArg: "readOnly", .bool, .false)
.init(keywordArg: "readOnly", .bool, .false),
.init(keywordArg: "pragmas",
.name("[SQLConnectionHandler.Pragma] = []")),
),
statements: [
.raw(
"self.\(api.connectionHandler) = .simplePool(url: url, readOnly: readOnly)"
"self.\(api.connectionHandler) = .simplePool(url: url, readOnly: readOnly, pragmas: pragmas)"
)
],
comment: .init(
Expand All @@ -348,7 +352,9 @@ extension EnlighterASTGenerator {
info: "A `URL` pointing to the database to be used."),
.init(name: "readOnly",
info: "Whether the database should be opened "
+ "readonly (default: `false`).")
+ "readonly (default: `false`)."),
.init(name: "pragmas",
info: "SQLite pragmas to apply to each connection."),
]
),
inlinable: options.inlinable
Expand Down
43 changes: 33 additions & 10 deletions Sources/Lighter/ConnectionHandlers/SQLConnectionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,27 @@ open class SQLConnectionHandler: @unchecked Sendable {
* a lot of async calls.
*/
public static func reopen(url: URL, readOnly: Bool = false,
writeTimeout: TimeInterval = 10.0)
-> SQLConnectionHandler
writeTimeout: TimeInterval = 10.0,
pragmas: [Pragma] = []) -> SQLConnectionHandler
{
SQLConnectionHandler(url: url, readOnly: readOnly,
writeTimeout: writeTimeout)
SQLConnectionHandler(url: url,
readOnly: readOnly,
writeTimeout: writeTimeout,
pragmas: pragmas)
}

public static func simplePool(url: URL, readOnly: Bool,
maxAge: TimeInterval = 3.0,
maximumPoolSizePerConfiguration: Int = 8,
writeTimeout: TimeInterval = 10.0)
-> SimplePool
writeTimeout: TimeInterval = 10.0,
pragmas: [Pragma] = []) -> SimplePool
{
SimplePool(url: url, readOnly: readOnly, maxAge: maxAge,
SimplePool(url: url,
readOnly: readOnly,
maxAge: maxAge,
maximumPoolSizePerConfiguration: maximumPoolSizePerConfiguration,
writeTimeout: writeTimeout)
writeTimeout: writeTimeout,
pragmas: pragmas)
}

/**
Expand All @@ -56,6 +61,16 @@ open class SQLConnectionHandler: @unchecked Sendable {

// MARK: - Configuration

public struct Pragma: Hashable, Sendable
{
public let statement: String

@inlinable
public init(_ statement: String) {
self.statement = statement
}
}

public struct Configuration: Hashable {
// Note: Would be nicer to just have the URL attached to a config and have
// the handlers independent of specific URLs, but that makes other
Expand All @@ -72,14 +87,18 @@ open class SQLConnectionHandler: @unchecked Sendable {
public let url : URL
public let readOnly : Bool
public let writeTimeout : TimeInterval
public let pragmas : [Pragma]

@inlinable
public init(url: URL, readOnly: Bool = false,
writeTimeout: TimeInterval = 10.0)
public init(url : URL,
readOnly : Bool = false,
writeTimeout : TimeInterval = 10.0,
pragmas : [Pragma] = [])
{
self.url = url
self.readOnly = readOnly
self.writeTimeout = writeTimeout
self.pragmas = pragmas
}


Expand Down Expand Up @@ -140,6 +159,10 @@ open class SQLConnectionHandler: @unchecked Sendable {

sqlite3_busy_timeout(db, Int32(writeTimeout * 1000 /* ms */))

for pragma in pragmas {
sqlite3_exec(db, pragma.statement, nil, nil, nil)
}

return db
}

Expand Down
8 changes: 6 additions & 2 deletions Sources/Lighter/ConnectionHandlers/SimplePool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,16 @@ extension SQLConnectionHandler {
public init(url: URL, readOnly: Bool,
maxAge: TimeInterval = 3.0,
maximumPoolSizePerConfiguration: Int = 8,
writeTimeout: TimeInterval)
writeTimeout: TimeInterval,
pragmas: [Pragma] = [])
{
self.maxAge = maxAge
self.maxPerConfiguration = maximumPoolSizePerConfiguration

super.init(url: url, readOnly: readOnly, writeTimeout: writeTimeout)
super.init(url: url,
readOnly: readOnly,
writeTimeout: writeTimeout,
pragmas: pragmas)

#if os(iOS)
lifecycle = AppLifecycleHandler(owner: self)
Expand Down
9 changes: 7 additions & 2 deletions Sources/Lighter/Database/SQLDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ public protocol SQLDatabase: SQLDatabaseOperations {
* - Parameters:
* - url: The filesystem `URL` to a SQLite3 database file.
* - readOnly: Whether the database should be opened read-only.
* - pragmas: SQLite pragmas to apply to each connection.
*/
init(url: URL, readOnly: Bool)
init(url: URL, readOnly: Bool, pragmas: [SQLConnectionHandler.Pragma])

/**
* Create or open the SQL database at the given URL.
Expand All @@ -80,7 +81,11 @@ public protocol SQLDatabase: SQLDatabaseOperations {
* - overwrite: Whether the database should be deleted if it
* exists already (useful during development).
* - databaseFileURL: The "source" database to be copied.
* - pragmas: SQLite pragmas to apply to each connection.
*/
static func bootstrap(at url: URL, readOnly: Bool, overwrite: Bool,
static func bootstrap(at url: URL,
readOnly: Bool,
pragmas: [SQLConnectionHandler.Pragma],
overwrite: Bool,
copying databaseFileURL: URL) throws -> Self
}
62 changes: 46 additions & 16 deletions Sources/Lighter/Database/SQLDatabaseCreation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public extension SQLDatabase {
* - databaseFileURL: The URL of the database to be copied.
* - Returns: The initialized database if successful.
*/
static func bootstrap(at url: URL, readOnly: Bool = false,
static func bootstrap(at url: URL,
readOnly: Bool = false,
pragmas: [SQLConnectionHandler.Pragma],
overwrite: Bool = false,
copying databaseFileURL: URL) throws -> Self
{
Expand All @@ -56,7 +58,7 @@ public extension SQLDatabase {
try fm.removeItem(at: url)
}
else {
return Self.init(url: url, readOnly: readOnly)
return Self.init(url: url, readOnly: readOnly, pragmas: pragmas)
}
}

Expand All @@ -66,7 +68,7 @@ public extension SQLDatabase {
}

try fm.copyItem(at: databaseFileURL, to: url)
return Self.init(url: url, readOnly: readOnly)
return Self.init(url: url, readOnly: readOnly, pragmas: pragmas)
}

/**
Expand Down Expand Up @@ -97,6 +99,7 @@ public extension SQLDatabase {
domains : FileManager.SearchPathDomainMask
= .userDomainMask,
readOnly : Bool = false,
pragmas : [SQLConnectionHandler.Pragma],
overwrite : Bool = false,
copying databaseFileURL: URL) throws -> Self
{
Expand All @@ -106,7 +109,10 @@ public extension SQLDatabase {
}

let url = dir.appendingPathComponent(databaseFileURL.lastPathComponent)
return try bootstrap(at: url, readOnly: readOnly, overwrite: overwrite,
return try bootstrap(at: url,
readOnly: readOnly,
pragmas: pragmas,
overwrite: overwrite,
copying: databaseFileURL)
}
}
Expand Down Expand Up @@ -139,7 +145,9 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder {
* exists already (useful during development).
* - Returns: The initialized database if successful.
*/
static func bootstrap(at url: URL, readOnly: Bool = false,
static func bootstrap(at url: URL,
readOnly: Bool = false,
pragmas: [SQLConnectionHandler.Pragma],
overwrite: Bool = false) throws -> Self
{
let fm = FileManager.default
Expand All @@ -148,7 +156,7 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder {
try fm.removeItem(at: url)
}
else {
return Self.init(url: url, readOnly: readOnly)
return Self.init(url: url, readOnly: readOnly, pragmas: pragmas)
}
}

Expand All @@ -170,7 +178,7 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder {
throw SQLError(db)
}

return Self(url: url, readOnly: readOnly)
return Self(url: url, readOnly: readOnly, pragmas: pragmas)
}

/**
Expand Down Expand Up @@ -206,6 +214,7 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder {
= .userDomainMask,
filename : String? = nil,
readOnly : Bool = false,
pragmas : [SQLConnectionHandler.Pragma],
overwrite : Bool = false) throws -> Self
{
guard let dir = FileManager.default.urls(for: directory, in: domains).first
Expand All @@ -216,7 +225,12 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder {
let filename = filename ?? String(describing: self) + ".sqlite3"
let url = dir.appendingPathComponent(filename)

return try bootstrap(at: url, readOnly: readOnly, overwrite: overwrite)
return try bootstrap(
at: url,
readOnly: readOnly,
pragmas: pragmas,
overwrite: overwrite
)
}
}
#endif // canImport(Foundation)
Expand Down Expand Up @@ -254,14 +268,18 @@ public extension SQLDatabase where Self: SQLDatabaseAsyncOperations {
* exists already (useful during development).
* - databaseFileURL: The "source" database to be copied.
*/
static func bootstrap(at url: URL, readOnly: Bool = false,
static func bootstrap(at url: URL,
readOnly: Bool = false,
pragmas: [SQLConnectionHandler.Pragma],
overwrite: Bool = false,
copying databaseFileURL: URL) async throws -> Self
{
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
do {
let db = try self.bootstrap(at: url, readOnly: readOnly,
let db = try self.bootstrap(at: url,
readOnly: readOnly,
pragmas: pragmas,
overwrite: overwrite,
copying: databaseFileURL)
continuation.resume(returning: db)
Expand Down Expand Up @@ -299,14 +317,18 @@ public extension SQLDatabase where Self: SQLDatabaseAsyncOperations {
domains : FileManager.SearchPathDomainMask
= .userDomainMask,
readOnly : Bool = false,
pragmas : [SQLConnectionHandler.Pragma],
overwrite : Bool = false,
copying databaseFileURL: URL) async throws -> Self
{
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
do {
let db = try self.bootstrap(into: directory, domains: domains,
readOnly: readOnly, overwrite: overwrite,
let db = try self.bootstrap(into: directory,
domains: domains,
readOnly: readOnly,
pragmas: pragmas,
overwrite: overwrite,
copying: databaseFileURL)
continuation.resume(returning: db)
}
Expand Down Expand Up @@ -345,14 +367,18 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder,
* exists already (useful during development).
* - Returns: The initialized database if successful.
*/
static func bootstrap(at url: URL, readOnly: Bool = false,
static func bootstrap(at url: URL,
readOnly: Bool = false,
pragmas: [SQLConnectionHandler.Pragma],
overwrite: Bool = false)
async throws -> Self
{
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
do {
let db = try self.bootstrap(at: url, readOnly: readOnly,
let db = try self.bootstrap(at: url,
readOnly: readOnly,
pragmas: pragmas,
overwrite: overwrite)
continuation.resume(returning: db)
}
Expand Down Expand Up @@ -394,13 +420,17 @@ public extension SQLDatabase where Self: SQLCreationStatementsHolder,
= .userDomainMask,
filename : String? = nil,
readOnly : Bool = false,
pragmas : [SQLConnectionHandler.Pragma],
overwrite : Bool = false) async throws -> Self
{
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
do {
let db = try self.bootstrap(into: directory, domains: domains,
filename: filename, readOnly: readOnly,
let db = try self.bootstrap(into: directory,
domains: domains,
filename: filename,
readOnly: readOnly,
pragmas: pragmas,
overwrite: overwrite)
continuation.resume(returning: db)
}
Expand Down
Loading