1
1
import SQLite3
2
2
import Foundation
3
3
4
- struct SqliteError : Error , Equatable , Hashable {
4
+ struct SqliteError : Error , Equatable , Hashable , CustomStringConvertible {
5
5
let code : Int32
6
+ let message : String
7
+ let file : String
8
+ let line : UInt
9
+
10
+ var description : String {
11
+ " Sqlite command at \( file) : \( line) failed with error \( code) and the following message: \( message) "
12
+ }
6
13
}
7
14
8
15
fileprivate extension Int32 {
9
16
10
17
/// Throws an error if a value is not a successful Sqlite return code.
11
- func assertSuccess( ) throws {
18
+ func assertSuccess( message : @autoclosure ( ) -> String , file : String , line : UInt ) throws {
12
19
switch self {
13
20
case SQLITE_OK, SQLITE_ROW, SQLITE_DONE:
14
21
return
15
22
default :
16
- throw SqliteError ( code: self )
23
+ throw SqliteError ( code: self , message : message ( ) , file : file , line : line )
17
24
}
18
25
}
26
+
27
+ func assertSuccess( message: @autoclosure ( ) -> String , in statement: SqliteConnection . Statement ) throws {
28
+ try assertSuccess ( message: " \( message ( ) ) in \( statement. query) " , file: statement. file, line: statement. line)
29
+ }
19
30
}
20
31
21
32
/// A lightweight wrapper around Sqlite that bridges common types.
@@ -30,9 +41,9 @@ final class SqliteConnection {
30
41
databaseUrl = url
31
42
}
32
43
33
- func connect( ) throws {
44
+ func connect( file : String = #fileID , line : UInt = #line ) throws {
34
45
guard ppDb == nil else { return }
35
- try sqlite3_open ( databaseUrl. path, & ppDb) . assertSuccess ( )
46
+ try sqlite3_open ( databaseUrl. path, & ppDb) . assertSuccess ( message : " Failed to open database " , file : file , line : line )
36
47
}
37
48
38
49
/// Creates
@@ -41,12 +52,12 @@ final class SqliteConnection {
41
52
/// - parameters: A list of indexed parameters to apply, of known convertable types.
42
53
/// - rowCallback: A callback to execute after each row is read (if any). This is used for extracting row data.
43
54
/// - Throws: A `SqliteError` if an error is encountered on any step of the process.
44
- func perform( query: String , parameters: [ Sqlite3Parameter ] = [ ] , rowCallback: ( _ row: Row ) throws -> Void = { _ in } ) throws {
55
+ func perform( query: String , parameters: [ Sqlite3Parameter ] = [ ] , file : String = #fileID , line : UInt = #line , rowCallback: ( _ row: Row ) throws -> Void = { _ in } ) throws {
45
56
guard let ppDb = ppDb else {
46
- throw SqliteError ( code: SQLITE_ERROR)
57
+ throw SqliteError ( code: SQLITE_ERROR, message : " Database pointer is nil " , file : file , line : line )
47
58
}
48
59
49
- let statement = try Statement ( query: query, db: ppDb)
60
+ let statement = try Statement ( query: query, db: ppDb, file : file , line : line )
50
61
try statement. bindIndexedParameters ( parameters)
51
62
defer { statement. finalize ( ) }
52
63
try statement. stepUntilDone ( rowCallback)
@@ -64,34 +75,40 @@ final class SqliteConnection {
64
75
65
76
/// A wrapper around a prepared query.
66
77
final class Statement {
67
- private var pointer : OpaquePointer ?
78
+ private( set) var pointer : OpaquePointer ?
79
+ let query : String
80
+ let file : String
81
+ let line : UInt
68
82
69
- fileprivate init ( pointer: OpaquePointer ? ) {
83
+ fileprivate init ( pointer: OpaquePointer ? , query : String , file : String = #fileID , line : UInt = #line ) {
70
84
self . pointer = pointer
85
+ self . query = query
86
+ self . file = file
87
+ self . line = line
71
88
}
72
89
73
- fileprivate convenience init ( query: String , db: OpaquePointer ) throws {
90
+ fileprivate convenience init ( query: String , db: OpaquePointer , file : String = #fileID , line : UInt = #line ) throws {
74
91
var pointer : OpaquePointer ?
75
- try sqlite3_prepare_v2 ( db, query, - 1 , & pointer, nil ) . assertSuccess ( )
76
- self . init ( pointer: pointer)
92
+ try sqlite3_prepare_v2 ( db, query, - 1 , & pointer, nil ) . assertSuccess ( message : " Failed to prepare query: \( query ) " , file : file , line : line )
93
+ self . init ( pointer: pointer, query : query )
77
94
}
78
95
79
96
/// Binds parameters to the query.
80
97
func bindIndexedParameters( _ parameters: [ Sqlite3Parameter ] ) throws {
81
98
for (parameter, index) in zip ( parameters, 1 ... ) {
82
- try parameter. bind ( at: index, statementPointer : pointer )
99
+ try parameter. bind ( at: index, statement : self )
83
100
}
84
101
}
85
102
86
103
/// Performs a step of the query.
87
104
/// - Returns: True if the execution returned a row.
88
105
private func step( ) throws -> Bool {
89
106
guard let pointer = pointer else {
90
- throw SqliteError ( code: SQLITE_ERROR)
107
+ throw SqliteError ( code: SQLITE_ERROR, message : " Statement pointer is nil for query: \( query ) " , file : file , line : line )
91
108
}
92
109
93
110
let result = sqlite3_step ( pointer)
94
- try result. assertSuccess ( )
111
+ try result. assertSuccess ( message : " Step failed for query: \( query ) " , file : file , line : line )
95
112
if result == SQLITE_DONE {
96
113
finalize ( )
97
114
}
@@ -168,48 +185,48 @@ private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.sel
168
185
169
186
/// A protocol for binding Swift data types to Sqlite parameters.
170
187
protocol Sqlite3Parameter {
171
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws
188
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws
172
189
}
173
190
174
191
extension Int : Sqlite3Parameter {
175
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws {
176
- try sqlite3_bind_int64 ( statementPointer , Int32 ( index) , Int64 ( self ) ) . assertSuccess ( )
192
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws {
193
+ try sqlite3_bind_int64 ( statement . pointer , Int32 ( index) , Int64 ( self ) ) . assertSuccess ( message : " Failed to bind integer \" \( self ) \" at index \( index ) " , in : statement )
177
194
}
178
195
}
179
196
180
197
extension Bool : Sqlite3Parameter {
181
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws {
182
- try ( self ? 1 : 0 ) . bind ( at: index, statementPointer : statementPointer )
198
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws {
199
+ try ( self ? 1 : 0 ) . bind ( at: index, statement : statement )
183
200
}
184
201
}
185
202
186
203
extension String : Sqlite3Parameter {
187
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws {
188
- try sqlite3_bind_text ( statementPointer , Int32 ( index) , self , - 1 , SQLITE_TRANSIENT) . assertSuccess ( )
204
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws {
205
+ try sqlite3_bind_text ( statement . pointer , Int32 ( index) , self , - 1 , SQLITE_TRANSIENT) . assertSuccess ( message : " Failed to bind text \" \( self ) \" at index \( index ) " , in : statement )
189
206
}
190
207
}
191
208
192
209
extension Data : Sqlite3Parameter {
193
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws {
210
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws {
194
211
try self . withUnsafeBytes { ( pointer: UnsafeRawBufferPointer ) in
195
- try sqlite3_bind_blob ( statementPointer , Int32 ( index) , pointer. baseAddress, Int32 ( self . count) , SQLITE_TRANSIENT) . assertSuccess ( )
212
+ try sqlite3_bind_blob ( statement . pointer , Int32 ( index) , pointer. baseAddress, Int32 ( self . count) , SQLITE_TRANSIENT) . assertSuccess ( message : " Failed to bind data of length \( count ) at index \( index ) " , in : statement )
196
213
}
197
214
}
198
215
}
199
216
200
217
/// This rounds to the nearest second, which is close enough for us.
201
218
extension Date : Sqlite3Parameter {
202
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws {
203
- try Int ( timeIntervalSinceReferenceDate) . bind ( at: index, statementPointer : statementPointer )
219
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws {
220
+ try Int ( timeIntervalSinceReferenceDate) . bind ( at: index, statement : statement )
204
221
}
205
222
}
206
223
207
224
extension Optional : Sqlite3Parameter where Wrapped: Sqlite3Parameter {
208
- func bind( at index: Int , statementPointer : OpaquePointer ? ) throws {
225
+ func bind( at index: Int , statement : SqliteConnection . Statement ) throws {
209
226
if let value = self {
210
- try value. bind ( at: index, statementPointer : statementPointer )
227
+ try value. bind ( at: index, statement : statement )
211
228
} else {
212
- try sqlite3_bind_null ( statementPointer , Int32 ( index) ) . assertSuccess ( )
229
+ try sqlite3_bind_null ( statement . pointer , Int32 ( index) ) . assertSuccess ( message : " Failed to bind null at index \( index ) " , in : statement )
213
230
}
214
231
}
215
232
}
0 commit comments