@@ -27,36 +27,63 @@ import Foundation
2727 12. If foreign keys constraints were originally enabled, reenable them now.
2828*/
2929public class SchemaChanger : CustomStringConvertible {
30- enum SchemaChangeError : LocalizedError {
30+ public enum Error : LocalizedError {
31+ case invalidColumnDefinition( String )
3132 case foreignKeyError( [ ForeignKeyError ] )
3233
33- var errorDescription : String ? {
34+ public var errorDescription : String ? {
3435 switch self {
3536 case . foreignKeyError( let errors) :
3637 return " Foreign key errors: \( errors) "
38+ case . invalidColumnDefinition( let message) :
39+ return " Invalid column definition: \( message) "
3740 }
3841 }
3942 }
4043
4144 public enum Operation {
42- case none
43- case add( ColumnDefinition )
44- case remove( String )
45+ case addColumn( ColumnDefinition )
46+ case dropColumn( String )
4547 case renameColumn( String , String )
4648 case renameTable( String )
4749
4850 /// Returns non-nil if the operation can be executed with a simple SQL statement
4951 func toSQL( _ table: String , version: SQLiteVersion ) -> String ? {
5052 switch self {
51- case . add ( let definition) :
53+ case . addColumn ( let definition) :
5254 return " ALTER TABLE \( table. quote ( ) ) ADD COLUMN \( definition. toSQL ( ) ) "
5355 case . renameColumn( let from, let to) where version >= ( 3 , 25 , 0 ) :
5456 return " ALTER TABLE \( table. quote ( ) ) RENAME COLUMN \( from. quote ( ) ) TO \( to. quote ( ) ) "
55- case . remove ( let column) where version >= ( 3 , 35 , 0 ) :
57+ case . dropColumn ( let column) where version >= ( 3 , 35 , 0 ) :
5658 return " ALTER TABLE \( table. quote ( ) ) DROP COLUMN \( column. quote ( ) ) "
5759 default : return nil
5860 }
5961 }
62+
63+ func validate( ) throws {
64+ switch self {
65+ case . addColumn( let definition) :
66+ // The new column may take any of the forms permissible in a CREATE TABLE statement, with the following restrictions:
67+ // - The column may not have a PRIMARY KEY or UNIQUE constraint.
68+ // - The column may not have a default value of CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP, or an expression in parentheses
69+ // - If a NOT NULL constraint is specified, then the column must have a default value other than NULL.
70+ guard definition. primaryKey == nil else {
71+ throw Error . invalidColumnDefinition ( " can not add primary key column " )
72+ }
73+ let invalidValues : [ LiteralValue ] = [ . CURRENT_TIME, . CURRENT_DATE, . CURRENT_TIMESTAMP]
74+ if invalidValues. contains ( definition. defaultValue) {
75+ throw Error . invalidColumnDefinition ( " Invalid default value " )
76+ }
77+ if !definition. nullable && definition. defaultValue == . NULL {
78+ throw Error . invalidColumnDefinition ( " NOT NULL columns must have a default value other than NULL " )
79+ }
80+ case . dropColumn:
81+ // The DROP COLUMN command only works if the column is not referenced by any other parts of the schema
82+ // and is not a PRIMARY KEY and does not have a UNIQUE constraint
83+ break
84+ default : break
85+ }
86+ }
6087 }
6188
6289 public class AlterTableDefinition {
@@ -69,11 +96,11 @@ public class SchemaChanger: CustomStringConvertible {
6996 }
7097
7198 public func add( _ column: ColumnDefinition ) {
72- operations. append ( . add ( column) )
99+ operations. append ( . addColumn ( column) )
73100 }
74101
75102 public func remove( _ column: String ) {
76- operations. append ( . remove ( column) )
103+ operations. append ( . dropColumn ( column) )
77104 }
78105
79106 public func rename( _ column: String , to: String ) {
@@ -116,7 +143,15 @@ public class SchemaChanger: CustomStringConvertible {
116143 try dropTable ( table)
117144 }
118145
146+ // Beginning with release 3.25.0 (2018-09-15), references to the table within trigger bodies and
147+ // view definitions are also renamed.
148+ public func rename( table: String , to: String ) throws {
149+ try connection. run ( " ALTER TABLE \( table. quote ( ) ) RENAME TO \( to. quote ( ) ) " )
150+ }
151+
119152 private func run( table: String , operation: Operation ) throws {
153+ try operation. validate ( )
154+
120155 if let sql = operation. toSQL ( table, version: version) {
121156 try connection. run ( sql)
122157 } else {
@@ -129,10 +164,10 @@ public class SchemaChanger: CustomStringConvertible {
129164 try disableRefIntegrity {
130165 let tempTable = " \( SchemaChanger . tempPrefix) \( table) "
131166 try moveTable ( from: table, to: tempTable, options: [ . temp] , operation: operation)
132- try moveTable ( from : tempTable, to: table)
167+ try rename ( table : tempTable, to: table)
133168 let foreignKeyErrors = try connection. foreignKeyCheck ( )
134169 if foreignKeyErrors. count > 0 {
135- throw SchemaChangeError . foreignKeyError ( foreignKeyErrors)
170+ throw Error . foreignKeyError ( foreignKeyErrors)
136171 }
137172 }
138173 }
@@ -153,22 +188,24 @@ public class SchemaChanger: CustomStringConvertible {
153188 try block ( )
154189 }
155190
156- private func moveTable( from: String , to: String , options: Options = . default, operation: Operation = . none ) throws {
191+ private func moveTable( from: String , to: String , options: Options = . default, operation: Operation ? = nil ) throws {
157192 try copyTable ( from: from, to: to, options: options, operation: operation)
158193 try dropTable ( from)
159194 }
160195
161- private func copyTable( from: String , to: String , options: Options = . default, operation: Operation ) throws {
196+ private func copyTable( from: String , to: String , options: Options = . default, operation: Operation ? ) throws {
162197 let fromDefinition = TableDefinition (
163198 name: from,
164199 columns: try connection. columnInfo ( table: from) ,
165200 indexes: try connection. indexInfo ( table: from)
166201 )
167- let toDefinition = fromDefinition. apply ( . renameTable( to) ) . apply ( operation)
202+ let toDefinition = fromDefinition
203+ . apply ( . renameTable( to) )
204+ . apply ( operation)
168205
169206 try createTable ( definition: toDefinition, options: options)
170207 try createTableIndexes ( definition: toDefinition)
171- if case . remove = operation {
208+ if case . dropColumn = operation {
172209 try copyTableContents ( from: fromDefinition. apply ( operation) , to: toDefinition)
173210 } else {
174211 try copyTableContents ( from: fromDefinition, to: toDefinition)
@@ -221,11 +258,11 @@ extension IndexDefinition {
221258}
222259
223260extension TableDefinition {
224- func apply( _ operation: SchemaChanger . Operation ) -> TableDefinition {
261+ func apply( _ operation: SchemaChanger . Operation ? ) -> TableDefinition {
225262 switch operation {
226263 case . none: return self
227- case . add : fatalError ( " Use 'ALTER TABLE ADD COLUMN (...)' " )
228- case . remove ( let column) :
264+ case . addColumn : fatalError ( " Use 'ALTER TABLE ADD COLUMN (...)' " )
265+ case . dropColumn ( let column) :
229266 return TableDefinition ( name: name,
230267 columns: columns. filter { $0. name != column } ,
231268 indexes: indexes. filter { !$0. columns. contains ( column) }
0 commit comments