Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for renaming columns within a table #689

Merged
merged 5 commits into from
Feb 14, 2020
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
84 changes: 69 additions & 15 deletions GRDB/QueryInterface/Schema/TableDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,13 @@ public final class TableDefinition {
/// See https://www.sqlite.org/lang_altertable.html
public final class TableAlteration {
private let name: String
private var addedColumns: [ColumnDefinition] = []

private enum TableAlterationKind {
case add(ColumnDefinition)
case rename(old: String, new: String)
}

private var alterations: [TableAlterationKind] = []

init(name: String) {
self.name = name
Expand All @@ -677,27 +683,75 @@ public final class TableAlteration {
@discardableResult
public func add(column name: String, _ type: Database.ColumnType? = nil) -> ColumnDefinition {
let column = ColumnDefinition(name: name, type: type)
addedColumns.append(column)
alterations.append(.add(column))
return column
}

#if GRDBCUSTOMSQLITE || GRDBCipher
/// Renames a column in a table.
///
/// try db.alter(table: "player") { t in
/// t.rename(column: "url", to: "home_url")
/// }
///
/// See https://www.sqlite.org/lang_altertable.html
///
/// - parameter name: the column name to rename.
/// - parameter newName: the new name of the column.
public func rename(column name: String, to newName: String) {
_rename(column: name, to: newName)
}
#else
/// Renames a column in a table.
///
/// try db.alter(table: "player") { t in
/// t.rename(column: "url", to: "home_url")
/// }
///
/// See https://www.sqlite.org/lang_altertable.html
///
/// - parameter name: the column name to rename.
/// - parameter newName: the new name of the column.
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func rename(column name: String, to newName: String) {
_rename(column: name, to: newName)
}
#endif

private func _rename(column name: String, to newName: String) {
alterations.append(.rename(old: name, new: newName))
}

fileprivate func sql(_ db: Database) throws -> String {
var statements: [String] = []

for column in addedColumns {
var chunks: [String] = []
chunks.append("ALTER TABLE")
chunks.append(name.quotedDatabaseIdentifier)
chunks.append("ADD COLUMN")
try chunks.append(column.sql(db, tableName: name, primaryKeyColumns: nil))
let statement = chunks.joined(separator: " ")
statements.append(statement)

if let indexDefinition = column.indexDefinition(in: name) {
statements.append(indexDefinition.sql())

for alteration in alterations {
switch alteration {
case let .add(column):
var chunks: [String] = []
chunks.append("ALTER TABLE")
chunks.append(name.quotedDatabaseIdentifier)
chunks.append("ADD COLUMN")
try chunks.append(column.sql(db, tableName: name, primaryKeyColumns: nil))
let statement = chunks.joined(separator: " ")
statements.append(statement)

if let indexDefinition = column.indexDefinition(in: name) {
statements.append(indexDefinition.sql())
}
case let .rename(oldName, newName):
var chunks: [String] = []
chunks.append("ALTER TABLE")
chunks.append(name.quotedDatabaseIdentifier)
chunks.append("RENAME COLUMN")
chunks.append(oldName.quotedDatabaseIdentifier)
chunks.append("TO")
chunks.append(newName.quotedDatabaseIdentifier)
let statement = chunks.joined(separator: " ")
statements.append(statement)
}
}

return statements.joined(separator: "; ")
}
}
Expand Down
35 changes: 35 additions & 0 deletions Tests/GRDBTests/TableDefinitionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import XCTest
#if GRDBCUSTOMSQLITE
import GRDBCustomSQLite
#else
#if GRDBCIPHER
import SQLCipher
#elseif SWIFT_PACKAGE
import CSQLite
#else
import SQLite3
#endif
import GRDB
#endif

Expand Down Expand Up @@ -495,6 +502,34 @@ class TableDefinitionTests: GRDBTestCase {
assertEqualSQL(sqlQueries[sqlQueries.count - 1], "ALTER TABLE \"test\" ADD COLUMN \"e\"")
}
}

func testAlterTableRenameColumn() throws {
guard sqlite3_libversion_number() >= 3025000 else {
return
}
#if !GRDBCUSTOMSQLITE && !GRDBCIPHER
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) else {
return
}
#endif
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
try db.create(table: "test") { t in
t.column("a", .text)
}

sqlQueries.removeAll()
try db.alter(table: "test") { t in
t.rename(column: "a", to: "b")
t.add(column: "c")
t.rename(column: "c", to: "d")
}

assertEqualSQL(sqlQueries[sqlQueries.count - 3], "ALTER TABLE \"test\" RENAME COLUMN \"a\" TO \"b\"")
assertEqualSQL(sqlQueries[sqlQueries.count - 2], "ALTER TABLE \"test\" ADD COLUMN \"c\"")
assertEqualSQL(sqlQueries[sqlQueries.count - 1], "ALTER TABLE \"test\" RENAME COLUMN \"c\" TO \"d\"")
}
}

func testDropTable() throws {
let dbQueue = try makeDatabaseQueue()
Expand Down