Skip to content

Commit 53db258

Browse files
committed
Provide a type implementing SQLDatabaseReportedVersion, vend it from SQLiteDatabase, and use it to enable version-dependent RETURNING and UPSERT support for SQLite.
1 parent da984b2 commit 53db258

File tree

2 files changed

+84
-2
lines changed

2 files changed

+84
-2
lines changed

Sources/SQLiteKit/SQLiteConnection+SQLKit.swift

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,93 @@ extension SQLiteDatabase {
44
}
55
}
66

7+
internal struct _SQLiteDatabaseVersion: SQLDatabaseReportedVersion {
8+
/// The numeric value of the version. The format of the value is the one described in
9+
/// https://sqlite.org/c3ref/c_source_id.html for the `SQLITE_VERSION_NUMBER` constant.
10+
let intValue: Int
11+
12+
/// The string representation of the version. The string is formatted according to the description in
13+
/// https://sqlite.org/c3ref/c_source_id.html for the `SQLITE_VERSION` constant.
14+
///
15+
/// This value is not used for equality or ordering comparisons; it is really only useful as a display value. We
16+
/// maintain a stored property for it here rather than always generating it as-needed from the numeric value so
17+
/// that we don't accidentally drop any additional information a particular library version might contain.
18+
///
19+
/// - Note: The string value should always represent the same version as the numeric value. This requirement is
20+
/// asserted in debug builds, but not otherwise enforced.
21+
let stringValue: String
22+
23+
/// Separates a numeric value into individual components and returns them.
24+
static func components(of intValue: Int) -> (major: Int, minor: Int, patch: Int) {
25+
let major = intValue / 1_000_000,
26+
minor = (intValue - major * 1_000_000) / 1_000,
27+
patch = intValue - major * 1_000_000 - minor * 1_000
28+
return (major: major, minor: minor, patch: patch)
29+
}
30+
31+
/// Get the version value representing the runtime version of the SQLite3 library in use.
32+
static var runtimeVersion: _SQLiteDatabaseVersion {
33+
self.init(intValue: Int(SQLiteConnection.libraryVersion()), stringValue: SQLiteConnection.libraryVersionString())
34+
}
35+
36+
/// Build a version value from individual components and synthesize the approiate string value.
37+
init(major: Int, minor: Int, patch: Int) {
38+
self.init(intValue: major * 1_000_000 + minor * 1_000 + patch)
39+
}
40+
41+
/// Designated initializer. Build a version value from the combined numeric value and a corresponding string value.
42+
/// If the string value is omitted, it is synthesized
43+
init(intValue: Int, stringValue: String? = nil) {
44+
let components = Self.components(of: intValue)
45+
46+
self.intValue = intValue
47+
if let stringValue = stringValue {
48+
assert(stringValue.hasPrefix("\(components.major).\(components.minor).\(components.patch)"), "SQLite version string '\(stringValue)' must match numeric version '\(intValue)'")
49+
self.stringValue = stringValue
50+
} else {
51+
self.stringValue = "\(components.major).\(components.major).\(components.patch)"
52+
}
53+
}
54+
55+
/// The major version number. This is likely to be 3 for a long time to come yet.
56+
var majorVersion: Int { Self.components(of: self.intValue).major }
57+
58+
/// The minor version number.
59+
var minorVersion: Int { Self.components(of: self.intValue).minor }
60+
61+
/// The patch version number.
62+
var patchVersion: Int { Self.components(of: self.intValue).patch }
63+
64+
/// See ``SQLDatabaseReportedVersion/isEqual(to:)``.
65+
func isEqual(to otherVersion: SQLDatabaseReportedVersion) -> Bool {
66+
(otherVersion as? _SQLiteDatabaseVersion).map { $0.intValue == self.intValue } ?? false
67+
}
68+
69+
/// See ``SQLDatabaseReportedVersion/isOlder(than:)``.
70+
func isOlder(than otherVersion: SQLDatabaseReportedVersion) -> Bool {
71+
(otherVersion as? _SQLiteDatabaseVersion).map {
72+
(self.majorVersion < $0.majorVersion ? true :
73+
(self.majorVersion > $0.majorVersion ? false :
74+
(self.minorVersion < $0.minorVersion ? true :
75+
(self.minorVersion > $0.minorVersion ? false :
76+
(self.patchVersion < $0.patchVersion ? true : false)))))
77+
} ?? false
78+
}
79+
}
80+
781
private struct _SQLiteSQLDatabase: SQLDatabase {
882
let database: SQLiteDatabase
983

1084
var eventLoop: EventLoop {
11-
return self.database.eventLoop
85+
self.database.eventLoop
86+
}
87+
88+
var version: SQLDatabaseReportedVersion? {
89+
_SQLiteDatabaseVersion.runtimeVersion
1290
}
1391

1492
var logger: Logger {
15-
return self.database.logger
93+
self.database.logger
1694
}
1795

1896
var dialect: SQLDialect {

Sources/SQLiteKit/SQLiteDialect.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public struct SQLiteDialect: SQLDialect {
2121
public var enumSyntax: SQLEnumSyntax { .unsupported }
2222
public var triggerSyntax: SQLTriggerSyntax { .init(create: [.supportsBody, .supportsCondition]) }
2323
public var alterTableSyntax: SQLAlterTableSyntax { .init(allowsBatch: false) }
24+
public var upsertSyntax: SQLUpsertSyntax { self.isAtLeastVersion(3, 24, 0) ? .standard : .unsupported } // `UPSERT` was added to SQLite in 3.24.0.
25+
public var supportsReturning: Bool { self.isAtLeastVersion(3, 35, 0) } // `RETURNING` was added to SQLite in 3.35.0.
2426
public var unionFeatures: SQLUnionFeatures { [.union, .unionAll, .intersect, .except] }
2527

2628
public func customDataType(for dataType: SQLDataType) -> SQLExpression? {
@@ -34,5 +36,7 @@ public struct SQLiteDialect: SQLDialect {
3436

3537
public init() { }
3638

39+
private func isAtLeastVersion(_ major: Int, _ minor: Int, _ patch: Int) -> Bool {
40+
_SQLiteDatabaseVersion.runtimeVersion.isNotOlder(than: _SQLiteDatabaseVersion(major: major, minor: minor, patch: patch))
3741
}
3842
}

0 commit comments

Comments
 (0)