Skip to content

Commit affcec8

Browse files
committed
Use DROP/RENAME COLUMN when available
1 parent 5c66460 commit affcec8

File tree

5 files changed

+64
-4
lines changed

5 files changed

+64
-4
lines changed

Sources/SQLite/Schema/Connection+Schema.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import Foundation
22

33
extension Connection {
4+
var sqliteVersion: String? {
5+
(try? scalar("SELECT sqlite_version()")) as? String
6+
}
7+
8+
var sqliteVersionTriple: (Int, Int, Int) {
9+
guard let version = sqliteVersion,
10+
let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3,
11+
let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else {
12+
return (0, 0, 0)
13+
}
14+
return (major, minor, point)
15+
}
16+
417
// Changing the foreign_keys setting affects the execution of all statements prepared using the database
518
// connection, including those prepared before the setting was changed.
619
//

Sources/SQLite/Schema/SchemaChanger.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import Foundation
2727
12. If foreign keys constraints were originally enabled, reenable them now.
2828
*/
2929
public class SchemaChanger: CustomStringConvertible {
30+
typealias SQLiteVersion = (Int, Int, Int)
31+
3032
enum SchemaChangeError: LocalizedError {
3133
case foreignKeyError([ForeignKeyError])
3234

@@ -46,9 +48,14 @@ public class SchemaChanger: CustomStringConvertible {
4648
case renameTable(String)
4749

4850
/// Returns non-nil if the operation can be executed with a simple SQL statement
49-
func toSQL(_ table: String) -> String? {
51+
func toSQL(_ table: String, version: SQLiteVersion) -> String? {
5052
switch self {
51-
case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())"
53+
case .add(let definition):
54+
return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())"
55+
case .renameColumn(let from, let to) where version.0 >= 3 && version.1 >= 25:
56+
return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())"
57+
case .remove(let column) where version.0 >= 3 && version.1 >= 35:
58+
return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())"
5259
default: return nil
5360
}
5461
}
@@ -77,6 +84,7 @@ public class SchemaChanger: CustomStringConvertible {
7784
}
7885

7986
private let connection: Connection
87+
private let version: SQLiteVersion
8088
static let tempPrefix = "tmp_"
8189
typealias Block = () throws -> Void
8290
public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void
@@ -87,8 +95,14 @@ public class SchemaChanger: CustomStringConvertible {
8795
static let temp = Options(rawValue: 1)
8896
}
8997

90-
public init(connection: Connection) {
98+
public convenience init(connection: Connection) {
99+
self.init(connection: connection,
100+
version: connection.sqliteVersionTriple)
101+
}
102+
103+
init(connection: Connection, version: SQLiteVersion) {
91104
self.connection = connection
105+
self.version = version
92106
}
93107

94108
public func alter(table: String, block: AlterTableDefinitionBlock) throws {
@@ -105,7 +119,7 @@ public class SchemaChanger: CustomStringConvertible {
105119
}
106120

107121
private func run(table: String, operation: Operation) throws {
108-
if let sql = operation.toSQL(table) {
122+
if let sql = operation.toSQL(table, version: version) {
109123
try connection.run(sql)
110124
} else {
111125
try doTheTableDance(table: table, operation: operation)

Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,11 @@ class ConnectionSchemaTests: SQLiteTestCase {
136136
ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil)
137137
])
138138
}
139+
140+
func test_sqlite_version_triple() {
141+
let version = db.sqliteVersionTriple
142+
XCTAssertEqual(version.0, 3)
143+
XCTAssertGreaterThan(version.1, 0)
144+
XCTAssertGreaterThanOrEqual(version.2, 0)
145+
}
139146
}

Tests/SQLiteTests/Schema/SchemaChangerTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ class SchemaChangerTests: SQLiteTestCase {
5757
XCTAssertFalse(columns.contains("age"))
5858
}
5959

60+
func test_remove_column_legacy() throws {
61+
schemaChanger = .init(connection: db, version: (3, 24, 0)) // DROP COLUMN introduced in 3.35.0
62+
63+
try schemaChanger.alter(table: "users") { table in
64+
table.remove("age")
65+
}
66+
let columns = try db.columnInfo(table: "users").map(\.name)
67+
XCTAssertFalse(columns.contains("age"))
68+
}
69+
6070
func test_rename_column() throws {
6171
try schemaChanger.alter(table: "users") { table in
6272
table.rename("age", to: "age2")
@@ -67,6 +77,18 @@ class SchemaChangerTests: SQLiteTestCase {
6777
XCTAssertTrue(columns.contains("age2"))
6878
}
6979

80+
func test_rename_column_legacy() throws {
81+
schemaChanger = .init(connection: db, version: (3, 24, 0)) // RENAME COLUMN introduced in 3.25.0
82+
83+
try schemaChanger.alter(table: "users") { table in
84+
table.rename("age", to: "age2")
85+
}
86+
87+
let columns = try db.columnInfo(table: "users").map(\.name)
88+
XCTAssertFalse(columns.contains("age"))
89+
XCTAssertTrue(columns.contains("age2"))
90+
}
91+
7092
func test_add_column() throws {
7193
let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil)
7294

Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ColumnDefinitionTests: XCTestCase {
2929
"\"real_column\" REAL DEFAULT 123.123")
3030
]
3131

32+
#if !os(Linux)
3233
override class var defaultTestSuite: XCTestSuite {
3334
let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self)
3435

@@ -44,6 +45,7 @@ class ColumnDefinitionTests: XCTestCase {
4445
@objc func verify() {
4546
XCTAssertEqual(definition.toSQL(), expected)
4647
}
48+
#endif
4749
}
4850

4951
class AffinityTests: XCTestCase {
@@ -105,6 +107,7 @@ class IndexDefinitionTests: XCTestCase {
105107
"CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")")
106108
]
107109

110+
#if !os(Linux)
108111
override class var defaultTestSuite: XCTestSuite {
109112
let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self)
110113

@@ -121,6 +124,7 @@ class IndexDefinitionTests: XCTestCase {
121124
@objc func verify() {
122125
XCTAssertEqual(definition.toSQL(ifNotExists: ifNotExists), expected)
123126
}
127+
#endif
124128

125129
func test_validate() {
126130

0 commit comments

Comments
 (0)