@@ -43,19 +43,30 @@ public class SchemaChanger: CustomStringConvertible {
4343
4444 public enum Operation {
4545 case addColumn( ColumnDefinition )
46+ case addIndex( IndexDefinition , ifNotExists: Bool )
4647 case dropColumn( String )
48+ case dropIndex( String , ifExists: Bool )
4749 case renameColumn( String , String )
4850 case renameTable( String )
51+ case createTable( columns: [ ColumnDefinition ] , ifNotExists: Bool )
4952
5053 /// Returns non-nil if the operation can be executed with a simple SQL statement
5154 func toSQL( _ table: String , version: SQLiteVersion ) -> String ? {
5255 switch self {
5356 case . addColumn( let definition) :
5457 return " ALTER TABLE \( table. quote ( ) ) ADD COLUMN \( definition. toSQL ( ) ) "
58+ case . addIndex( let definition, let ifNotExists) :
59+ return definition. toSQL ( ifNotExists: ifNotExists)
5560 case . renameColumn( let from, let to) where SQLiteFeature . renameColumn. isSupported ( by: version) :
5661 return " ALTER TABLE \( table. quote ( ) ) RENAME COLUMN \( from. quote ( ) ) TO \( to. quote ( ) ) "
5762 case . dropColumn( let column) where SQLiteFeature . dropColumn. isSupported ( by: version) :
5863 return " ALTER TABLE \( table. quote ( ) ) DROP COLUMN \( column. quote ( ) ) "
64+ case . dropIndex( let name, let ifExists) :
65+ return " DROP INDEX \( ifExists ? " IF EXISTS " : " " ) \( name. quote ( ) ) "
66+ case . createTable( let columns, let ifNotExists) :
67+ return " CREATE TABLE \( ifNotExists ? " IF NOT EXISTS " : " " ) \( table. quote ( ) ) ( " +
68+ columns. map { $0. toSQL ( ) } . joined ( separator: " , " ) +
69+ " ) "
5970 default : return nil
6071 }
6172 }
@@ -89,7 +100,7 @@ public class SchemaChanger: CustomStringConvertible {
89100 public class AlterTableDefinition {
90101 fileprivate var operations : [ Operation ] = [ ]
91102
92- let name : String
103+ public let name : String
93104
94105 init ( name: String ) {
95106 self . name = name
@@ -99,21 +110,73 @@ public class SchemaChanger: CustomStringConvertible {
99110 operations. append ( . addColumn( column) )
100111 }
101112
113+ public func add( index: IndexDefinition , ifNotExists: Bool = false ) {
114+ operations. append ( . addIndex( index, ifNotExists: ifNotExists) )
115+ }
116+
102117 public func drop( column: String ) {
103118 operations. append ( . dropColumn( column) )
104119 }
105120
121+ public func drop( index: String , ifExists: Bool = false ) {
122+ operations. append ( . dropIndex( index, ifExists: ifExists) )
123+ }
124+
106125 public func rename( column: String , to: String ) {
107126 operations. append ( . renameColumn( column, to) )
108127 }
109128 }
110129
130+ public class CreateTableDefinition {
131+ fileprivate var columnDefinitions : [ ColumnDefinition ] = [ ]
132+ fileprivate var indexDefinitions : [ IndexDefinition ] = [ ]
133+
134+ let name : String
135+ let ifNotExists : Bool
136+
137+ init ( name: String , ifNotExists: Bool ) {
138+ self . name = name
139+ self . ifNotExists = ifNotExists
140+ }
141+
142+ public func add( column: ColumnDefinition ) {
143+ columnDefinitions. append ( column)
144+ }
145+
146+ public func add< T> ( expression: Expression < T > ) where T: Value {
147+ add ( column: . init( name: columnName ( for: expression) , type: . init( expression: expression) , nullable: false ) )
148+ }
149+
150+ public func add< T> ( expression: Expression < T ? > ) where T: Value {
151+ add ( column: . init( name: columnName ( for: expression) , type: . init( expression: expression) , nullable: true ) )
152+ }
153+
154+ public func add( index: IndexDefinition ) {
155+ indexDefinitions. append ( index)
156+ }
157+
158+ var operations : [ Operation ] {
159+ precondition ( !columnDefinitions. isEmpty)
160+ return [
161+ . createTable( columns: columnDefinitions, ifNotExists: ifNotExists)
162+ ] + indexDefinitions. map { . addIndex( $0, ifNotExists: ifNotExists) }
163+ }
164+
165+ private func columnName< T> ( for expression: Expression < T > ) -> String {
166+ switch LiteralValue ( expression. template) {
167+ case . stringLiteral( let string) : return string
168+ default : fatalError ( " expression is not a literal string value " )
169+ }
170+ }
171+ }
172+
111173 private let connection : Connection
112174 private let schemaReader : SchemaReader
113175 private let version : SQLiteVersion
114176 static let tempPrefix = " tmp_ "
115177 typealias Block = ( ) throws -> Void
116178 public typealias AlterTableDefinitionBlock = ( AlterTableDefinition ) -> Void
179+ public typealias CreateTableDefinitionBlock = ( CreateTableDefinition ) -> Void
117180
118181 struct Options : OptionSet {
119182 let rawValue : Int
@@ -141,6 +204,15 @@ public class SchemaChanger: CustomStringConvertible {
141204 }
142205 }
143206
207+ public func create( table: String , ifNotExists: Bool = false , block: CreateTableDefinitionBlock ) throws {
208+ let createTableDefinition = CreateTableDefinition ( name: table, ifNotExists: ifNotExists)
209+ block ( createTableDefinition)
210+
211+ for operation in createTableDefinition. operations {
212+ try run ( table: table, operation: operation)
213+ }
214+ }
215+
144216 public func drop( table: String , ifExists: Bool = true ) throws {
145217 try dropTable ( table, ifExists: ifExists)
146218 }
@@ -151,6 +223,12 @@ public class SchemaChanger: CustomStringConvertible {
151223 try connection. run ( " ALTER TABLE \( table. quote ( ) ) RENAME TO \( to. quote ( ) ) " )
152224 }
153225
226+ // Runs arbitrary SQL. Should only be used if no predefined operations exist.
227+ @discardableResult
228+ public func run( _ sql: String , _ bindings: Binding ? ... ) throws -> Statement {
229+ return try connection. run ( sql, bindings)
230+ }
231+
154232 private func run( table: String , operation: Operation ) throws {
155233 try operation. validate ( )
156234
@@ -263,7 +341,9 @@ extension TableDefinition {
263341 func apply( _ operation: SchemaChanger . Operation ? ) -> TableDefinition {
264342 switch operation {
265343 case . none: return self
344+ case . createTable, . addIndex, . dropIndex: fatalError ( )
266345 case . addColumn: fatalError ( " Use 'ALTER TABLE ADD COLUMN (...)' " )
346+
267347 case . dropColumn( let column) :
268348 return TableDefinition ( name: name,
269349 columns: columns. filter { $0. name != column } ,
@@ -280,3 +360,13 @@ extension TableDefinition {
280360 }
281361 }
282362}
363+
364+ extension ColumnDefinition . Affinity {
365+ init < T> ( expression: Expression < T > ) where T: Value {
366+ self . init ( T . declaredDatatype)
367+ }
368+
369+ init < T> ( expression: Expression < T ? > ) where T: Value {
370+ self . init ( T . declaredDatatype)
371+ }
372+ }
0 commit comments