66
77 See http://swift.org/LICENSE.txt for license information
88 See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9- */
9+ */
1010
1111import Foundation
1212import TSCBasic
@@ -15,6 +15,104 @@ import TSCBasic
1515
1616/// A minimal SQLite wrapper.
1717public struct SQLite {
18+ /// The location of the database.
19+ public let location : Location
20+
21+ /// The configuration for the database.
22+ public let configuration : Configuration
23+
24+ /// Pointer to the database.
25+ let db : OpaquePointer
26+
27+ /// Create or open the database at the given path.
28+ ///
29+ /// The database is opened in serialized mode.
30+ public init ( location: Location , configuration: Configuration = Configuration ( ) ) throws {
31+ self . location = location
32+ self . configuration = configuration
33+
34+ var handle : OpaquePointer ?
35+ try Self . checkError ( " Unable to open database at \( self . location) " ) {
36+ sqlite3_open_v2 (
37+ location. pathString,
38+ & handle,
39+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
40+ nil
41+ )
42+ }
43+
44+ guard let db = handle else {
45+ throw StringError ( " Unable to open database at \( self . location) " )
46+ }
47+ self . db = db
48+ try Self . checkError ( " Unable to configure database " ) { sqlite3_extended_result_codes ( db, 1 ) }
49+ try Self . checkError ( " Unable to configure database " ) { sqlite3_busy_timeout ( db, self . configuration. busyTimeoutSeconds) }
50+ }
51+
52+ @available ( * , deprecated, message: " use init(location:configuration) instead " )
53+ public init ( dbPath: AbsolutePath ) throws {
54+ try self . init ( location: . path( dbPath) )
55+ }
56+
57+ /// Prepare the given query.
58+ public func prepare( query: String ) throws -> PreparedStatement {
59+ try PreparedStatement ( db: self . db, query: query)
60+ }
61+
62+ /// Directly execute the given query.
63+ ///
64+ /// Note: Use withCString for string arguments.
65+ public func exec( query queryString: String , args: [ CVarArg ] = [ ] , _ callback: SQLiteExecCallback ? = nil ) throws {
66+ let query = withVaList ( args) { ptr in
67+ sqlite3_vmprintf ( queryString, ptr)
68+ }
69+
70+ let wcb = callback. map { CallbackWrapper ( $0) }
71+ let callbackCtx = wcb. map { Unmanaged . passUnretained ( $0) . toOpaque ( ) }
72+
73+ var err : UnsafeMutablePointer < Int8 > ?
74+ try Self . checkError { sqlite3_exec ( db, query, sqlite_callback, callbackCtx, & err) }
75+
76+ sqlite3_free ( query)
77+
78+ if let err = err {
79+ let errorString = String ( cString: err)
80+ sqlite3_free ( err)
81+ throw StringError ( errorString)
82+ }
83+ }
84+
85+ public func close( ) throws {
86+ try Self . checkError { sqlite3_close ( db) }
87+ }
88+
89+ public typealias SQLiteExecCallback = ( [ Column ] ) -> Void
90+
91+ public struct Configuration {
92+ public var busyTimeoutSeconds : Int32
93+
94+ public init ( ) {
95+ self . busyTimeoutSeconds = 5
96+ }
97+ }
98+
99+ public enum Location {
100+ case path( AbsolutePath )
101+ case memory
102+ case temporary
103+
104+ var pathString : String {
105+ switch self {
106+ case . path( let path) :
107+ return path. pathString
108+ case . memory:
109+ return " :memory: "
110+ case . temporary:
111+ return " "
112+ }
113+ }
114+ }
115+
18116 /// Represents an sqlite value.
19117 public enum SQLiteValue {
20118 case null
@@ -29,23 +127,28 @@ public struct SQLite {
29127 let stmt : OpaquePointer
30128
31129 /// Get integer at the given column index.
32- func int( at index: Int32 ) -> Int {
33- Int ( sqlite3_column_int64 ( stmt, index) )
130+ public func int( at index: Int32 ) -> Int {
131+ Int ( sqlite3_column_int64 ( self . stmt, index) )
34132 }
35133
36134 /// Get blob data at the given column index.
37- func blob( at index: Int32 ) -> Data {
135+ public func blob( at index: Int32 ) -> Data {
38136 let bytes = sqlite3_column_blob ( stmt, index) !
39137 let count = sqlite3_column_bytes ( stmt, index)
40138 return Data ( bytes: bytes, count: Int ( count) )
41139 }
42140
43141 /// Get string at the given column index.
44- func string( at index: Int32 ) -> String {
45- return String ( cString: sqlite3_column_text ( stmt, index) )
142+ public func string( at index: Int32 ) -> String {
143+ return String ( cString: sqlite3_column_text ( self . stmt, index) )
46144 }
47145 }
48146
147+ public struct Column {
148+ public var name : String
149+ public var value : String
150+ }
151+
49152 /// Represents a prepared statement.
50153 public struct PreparedStatement {
51154 typealias sqlite3_destructor_type = ( @convention ( c) ( UnsafeMutableRawPointer ? ) -> Void )
@@ -57,7 +160,7 @@ public struct SQLite {
57160
58161 public init ( db: OpaquePointer , query: String ) throws {
59162 var stmt : OpaquePointer ?
60- try sqlite { sqlite3_prepare_v2 ( db, query, - 1 , & stmt, nil ) }
163+ try checkError { sqlite3_prepare_v2 ( db, query, - 1 , & stmt, nil ) }
61164 self . stmt = stmt!
62165 }
63166
@@ -70,7 +173,7 @@ public struct SQLite {
70173 case SQLITE_DONE:
71174 return nil
72175 case SQLITE_ROW:
73- return Row ( stmt: stmt)
176+ return Row ( stmt: self . stmt)
74177 default :
75178 throw StringError ( String ( cString: sqlite3_errstr ( result) ) )
76179 }
@@ -82,13 +185,13 @@ public struct SQLite {
82185 let idx = Int32 ( idx) + 1
83186 switch argument {
84187 case . null:
85- try sqlite { sqlite3_bind_null ( stmt, idx) }
188+ try checkError { sqlite3_bind_null ( stmt, idx) }
86189 case . int( let int) :
87- try sqlite { sqlite3_bind_int64 ( stmt, idx, Int64 ( int) ) }
190+ try checkError { sqlite3_bind_int64 ( stmt, idx, Int64 ( int) ) }
88191 case . string( let str) :
89- try sqlite { sqlite3_bind_text ( stmt, idx, str, - 1 , Self . SQLITE_TRANSIENT) }
192+ try checkError { sqlite3_bind_text ( stmt, idx, str, - 1 , Self . SQLITE_TRANSIENT) }
90193 case . blob( let blob) :
91- try sqlite {
194+ try checkError {
92195 blob. withUnsafeBytes { ptr in
93196 sqlite3_bind_blob (
94197 stmt,
@@ -105,91 +208,37 @@ public struct SQLite {
105208
106209 /// Reset the prepared statement.
107210 public func reset( ) throws {
108- try sqlite { sqlite3_reset ( stmt) }
211+ try checkError { sqlite3_reset ( stmt) }
109212 }
110213
111214 /// Clear bindings from the prepared statment.
112215 public func clearBindings( ) throws {
113- try sqlite { sqlite3_clear_bindings ( stmt) }
216+ try checkError { sqlite3_clear_bindings ( stmt) }
114217 }
115218
116219 /// Finalize the statement and free up resources.
117220 public func finalize( ) throws {
118- try sqlite { sqlite3_finalize ( stmt) }
221+ try checkError { sqlite3_finalize ( stmt) }
119222 }
120223 }
121224
122- /// The path to the database file.
123- public let dbPath : AbsolutePath
124-
125- /// Pointer to the database.
126- let db : OpaquePointer
127-
128- /// Create or open the database at the given path.
129- ///
130- /// The database is opened in serialized mode.
131- public init ( dbPath: AbsolutePath ) throws {
132- self . dbPath = dbPath
133-
134- var db : OpaquePointer ? = nil
135- try sqlite ( " unable to open database at \( dbPath) " ) {
136- sqlite3_open_v2 (
137- dbPath. pathString,
138- & db,
139- SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
140- nil
141- )
225+ fileprivate class CallbackWrapper {
226+ var callback : SQLiteExecCallback
227+ init ( _ callback: @escaping SQLiteExecCallback ) {
228+ self . callback = callback
142229 }
143-
144- self . db = db!
145- try sqlite { sqlite3_extended_result_codes ( db, 1 ) }
146- try sqlite { sqlite3_busy_timeout ( db, 5 * 1000 /* 5s */) }
147- }
148-
149- /// Prepare the given query.
150- public func prepare( query: String ) throws -> PreparedStatement {
151- try PreparedStatement ( db: db, query: query)
152230 }
153231
154- /// Directly execute the given query.
155- ///
156- /// Note: Use withCString for string arguments.
157- public func exec( query: String , args: [ CVarArg ] = [ ] , _ callback: SQLiteExecCallback ? = nil ) throws {
158- let query = withVaList ( args) { ptr in
159- sqlite3_vmprintf ( query, ptr)
160- }
161-
162- let wcb = callback. map { CallbackWrapper ( $0) }
163- let callbackCtx = wcb. map { Unmanaged . passUnretained ( $0) . toOpaque ( ) }
164-
165- var err : UnsafeMutablePointer < Int8 > ? = nil
166- try sqlite { sqlite3_exec ( db, query, sqlite_callback, callbackCtx, & err) }
167-
168- if let err = err {
169- let errorString = String ( cString: err)
170- sqlite3_free ( err)
171- throw StringError ( errorString)
232+ private static func checkError( _ errorPrefix: String ? = nil , _ fn: ( ) -> Int32 ) throws {
233+ let result = fn ( )
234+ if result != SQLITE_OK {
235+ var error = " "
236+ if let errorPrefix = errorPrefix {
237+ error += errorPrefix + " : "
238+ }
239+ error += String ( cString: sqlite3_errstr ( result) )
240+ throw StringError ( error)
172241 }
173-
174- sqlite3_free ( query)
175- }
176-
177- public func close( ) throws {
178- try sqlite { sqlite3_close ( db) }
179- }
180-
181- public struct Column {
182- public var name : String
183- public var value : String
184- }
185-
186- public typealias SQLiteExecCallback = ( [ Column ] ) -> Void
187- }
188-
189- private class CallbackWrapper {
190- var callback : SQLite . SQLiteExecCallback
191- init ( _ callback: @escaping SQLite . SQLiteExecCallback ) {
192- self . callback = callback
193242 }
194243}
195244
@@ -204,7 +253,7 @@ private func sqlite_callback(
204253 let numColumns = Int ( numColumns)
205254 var result : [ SQLite . Column ] = [ ]
206255
207- for idx in 0 ..< numColumns {
256+ for idx in 0 ..< numColumns {
208257 var name = " "
209258 if let ptr = columnNames. advanced ( by: idx) . pointee {
210259 name = String ( cString: ptr)
@@ -216,20 +265,8 @@ private func sqlite_callback(
216265 result. append ( SQLite . Column ( name: name, value: value) )
217266 }
218267
219- let wcb = Unmanaged < CallbackWrapper > . fromOpaque ( ctx) . takeUnretainedValue ( )
268+ let wcb = Unmanaged < SQLite . CallbackWrapper > . fromOpaque ( ctx) . takeUnretainedValue ( )
220269 wcb. callback ( result)
221270
222271 return 0
223272}
224-
225- private func sqlite( _ errorPrefix: String ? = nil , _ fn: ( ) -> Int32 ) throws {
226- let result = fn ( )
227- if result != SQLITE_OK {
228- var error = " "
229- if let errorPrefix = errorPrefix {
230- error += errorPrefix + " : "
231- }
232- error += String ( cString: sqlite3_errstr ( result) )
233- throw StringError ( error)
234- }
235- }
0 commit comments