-
Notifications
You must be signed in to change notification settings - Fork 58
Expand file tree
/
Copy pathMySQL.swift
More file actions
358 lines (322 loc) · 10.4 KB
/
MySQL.swift
File metadata and controls
358 lines (322 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//
// MySQL.swift
// PerfectMySQL
//
// Created by Kyle Jessup on 2018-03-07.
//
#if os(Linux)
import SwiftGlibc
#else
import Darwin
#endif
import mysqlclient
/// Provide access to MySQL connector functions
public final class MySQL {
private static var initOnce: Bool = {
mysql_server_init(0, nil, nil)
return true
}()
var mysqlPtr: UnsafeMutablePointer<MYSQL>
/// Create mysql server connection and set ptr
public init() {
_ = MySQL.initOnce
mysqlPtr = mysql_init(nil)
}
deinit {
mysql_close(mysqlPtr)
}
/// Returns client info from mysql_get_client_info
public static func clientInfo() -> String {
return String(validatingUTF8: mysql_get_client_info()) ?? ""
}
public func ping() -> Bool {
return 0 == mysql_ping(mysqlPtr)
}
@available(*, deprecated)
public func close() {}
/// Return mysql error number
public func errorCode() -> UInt32 {
return mysql_errno(mysqlPtr)
}
/// Return mysql error message
public func errorMessage() -> String {
return String(validatingUTF8: mysql_error(mysqlPtr)) ?? ""
}
/// Return mysql server version
public func serverVersion() -> Int {
return Int(mysql_get_server_version(mysqlPtr))
}
/// Connects to a MySQL server
public func connect(host: String? = nil, user: String? = nil, password: String? = nil, db: String? = nil, port: UInt32 = 0, socket: String? = nil, flag: UInt = 0) -> Bool {
let check = mysql_real_connect(mysqlPtr,
host, user, password,
db, port,
socket, flag)
return check != nil && check == mysqlPtr
}
/// Selects a database
public func selectDatabase(named: String) -> Bool {
return 0 == mysql_select_db(mysqlPtr, named)
}
/// Returns table names matching an optional simple regular expression as an array of Strings
public func listTables(wildcard wild: String? = nil) -> [String] {
var result = [String]()
if let res = mysql_list_tables(mysqlPtr, wild) {
while let row = mysql_fetch_row(res) {
if let tabPtr = row[0] {
result.append(String(validatingUTF8: tabPtr) ?? "")
}
}
mysql_free_result(res)
}
return result
}
/// Returns database names matching an optional simple regular expression in an array of Strings
public func listDatabases(wildcard wild: String? = nil) -> [String] {
var result = [String]()
if let res = mysql_list_dbs(mysqlPtr, wild) {
while let row = mysql_fetch_row(res) {
if let tabPtr = row[0] {
result.append(String(validatingUTF8: tabPtr) ?? "")
}
}
mysql_free_result(res)
}
return result
}
/// Commits the transaction
public func commit() -> Bool {
var res = mysql_commit(mysqlPtr)
var FALSE = 0
return memcmp(&res, &FALSE, MemoryLayout.size(ofValue: res)) != 0
}
/// Rolls back the transaction
public func rollback() -> Bool {
var res = mysql_rollback(mysqlPtr)
var FALSE = 0
return memcmp(&res, &FALSE, MemoryLayout.size(ofValue: res)) != 0
}
/// Checks whether any more results exist
public func moreResults() -> Bool {
var res = mysql_more_results(mysqlPtr)
var FALSE = 0
return memcmp(&res, &FALSE, MemoryLayout.size(ofValue: res)) != 0
}
/// Returns/initiates the next result in multiple-result executions
public func nextResult() -> Int {
return Int(mysql_next_result(mysqlPtr))
}
/// Executes an SQL query using the specified string
public func query(statement stmt: String) -> Bool {
return 0 == mysql_real_query(mysqlPtr, stmt, UInt(stmt.utf8.count))
}
/// Retrieves a complete result set to the client
public func storeResults() -> MySQL.Results? {
guard let ret = mysql_store_result(mysqlPtr) else {
return nil
}
return MySQL.Results(ret)
}
public func lastInsertId() -> Int64 {
return Int64(mysql_insert_id(mysqlPtr))
}
public func numberAffectedRows() -> Int64 {
return Int64(mysql_affected_rows(mysqlPtr))
}
func exposedOptionToMySQLOption(_ o: MySQLOpt) -> mysql_option {
switch o {
case MySQLOpt.MYSQL_OPT_CONNECT_TIMEOUT:
return MYSQL_OPT_CONNECT_TIMEOUT
case MySQLOpt.MYSQL_OPT_COMPRESS:
return MYSQL_OPT_COMPRESS
case MySQLOpt.MYSQL_OPT_NAMED_PIPE:
return MYSQL_OPT_NAMED_PIPE
case MySQLOpt.MYSQL_INIT_COMMAND:
return MYSQL_INIT_COMMAND
case MySQLOpt.MYSQL_READ_DEFAULT_FILE:
return MYSQL_READ_DEFAULT_FILE
case MySQLOpt.MYSQL_READ_DEFAULT_GROUP:
return MYSQL_READ_DEFAULT_GROUP
case MySQLOpt.MYSQL_SET_CHARSET_DIR:
return MYSQL_SET_CHARSET_DIR
case MySQLOpt.MYSQL_SET_CHARSET_NAME:
return MYSQL_SET_CHARSET_NAME
case MySQLOpt.MYSQL_OPT_LOCAL_INFILE:
return MYSQL_OPT_LOCAL_INFILE
case MySQLOpt.MYSQL_OPT_PROTOCOL:
return MYSQL_OPT_PROTOCOL
case MySQLOpt.MYSQL_SHARED_MEMORY_BASE_NAME:
return MYSQL_SHARED_MEMORY_BASE_NAME
case MySQLOpt.MYSQL_OPT_READ_TIMEOUT:
return MYSQL_OPT_READ_TIMEOUT
case MySQLOpt.MYSQL_OPT_WRITE_TIMEOUT:
return MYSQL_OPT_WRITE_TIMEOUT
case MySQLOpt.MYSQL_OPT_USE_RESULT:
return MYSQL_OPT_USE_RESULT
/*
case MySQLOpt.MYSQL_OPT_USE_REMOTE_CONNECTION:
return MYSQL_OPT_USE_REMOTE_CONNECTION
case MySQLOpt.MYSQL_OPT_USE_EMBEDDED_CONNECTION:
return MYSQL_OPT_USE_EMBEDDED_CONNECTION
case MySQLOpt.MYSQL_OPT_GUESS_CONNECTION:
return MYSQL_OPT_GUESS_CONNECTION
case MySQLOpt.MYSQL_SET_CLIENT_IP:
return MYSQL_SET_CLIENT_IP
case MySQLOpt.MYSQL_SECURE_AUTH:
return MYSQL_SECURE_AUTH
*/
case MySQLOpt.MYSQL_REPORT_DATA_TRUNCATION:
return MYSQL_REPORT_DATA_TRUNCATION
case MySQLOpt.MYSQL_OPT_RECONNECT:
return MYSQL_OPT_RECONNECT
//case MySQLOpt.MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
//return MYSQL_OPT_SSL_VERIFY_SERVER_CERT
case MySQLOpt.MYSQL_PLUGIN_DIR:
return MYSQL_PLUGIN_DIR
case MySQLOpt.MYSQL_DEFAULT_AUTH:
return MYSQL_DEFAULT_AUTH
case MySQLOpt.MYSQL_OPT_BIND:
return MYSQL_OPT_BIND
case MySQLOpt.MYSQL_OPT_SSL_KEY:
return MYSQL_OPT_SSL_KEY
case MySQLOpt.MYSQL_OPT_SSL_CERT:
return MYSQL_OPT_SSL_CERT
case MySQLOpt.MYSQL_OPT_SSL_CA:
return MYSQL_OPT_SSL_CA
case MySQLOpt.MYSQL_OPT_SSL_CAPATH:
return MYSQL_OPT_SSL_CAPATH
case MySQLOpt.MYSQL_OPT_SSL_CIPHER:
return MYSQL_OPT_SSL_CIPHER
case MySQLOpt.MYSQL_OPT_SSL_CRL:
return MYSQL_OPT_SSL_CRL
case MySQLOpt.MYSQL_OPT_SSL_CRLPATH:
return MYSQL_OPT_SSL_CRLPATH
case .MYSQL_OPT_SSL_MODE:
return MYSQL_OPT_SSL_MODE
case MySQLOpt.MYSQL_OPT_CONNECT_ATTR_RESET:
return MYSQL_OPT_CONNECT_ATTR_RESET
case MySQLOpt.MYSQL_OPT_CONNECT_ATTR_ADD:
return MYSQL_OPT_CONNECT_ATTR_ADD
case MySQLOpt.MYSQL_OPT_CONNECT_ATTR_DELETE:
return MYSQL_OPT_CONNECT_ATTR_DELETE
case MySQLOpt.MYSQL_SERVER_PUBLIC_KEY:
return MYSQL_SERVER_PUBLIC_KEY
case MySQLOpt.MYSQL_ENABLE_CLEARTEXT_PLUGIN:
return MYSQL_ENABLE_CLEARTEXT_PLUGIN
case MySQLOpt.MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS:
return MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS
}
}
func exposedOptionToMySQLServerOption(_ o: MySQLServerOpt) -> enum_mysql_set_option {
switch o {
case MySQLServerOpt.MYSQL_OPTION_MULTI_STATEMENTS_ON:
return MYSQL_OPTION_MULTI_STATEMENTS_ON
case MySQLServerOpt.MYSQL_OPTION_MULTI_STATEMENTS_OFF:
return MYSQL_OPTION_MULTI_STATEMENTS_OFF
}
}
/// Sets connect options for connect()
@discardableResult
public func setOption(_ option: MySQLOpt) -> Bool {
return mysql_options(mysqlPtr, exposedOptionToMySQLOption(option), nil) == 0
}
/// Sets connect options for connect() with boolean option argument
@discardableResult
public func setOption(_ option: MySQLOpt, _ b: Bool) -> Bool {
var myB = my_bool(b ? 1 : 0)
return mysql_options(mysqlPtr, exposedOptionToMySQLOption(option), &myB) == 0
}
/// Sets connect options for connect() with integer option argument
@discardableResult
public func setOption(_ option: MySQLOpt, _ i: Int) -> Bool {
var myI = UInt32(i)
return mysql_options(mysqlPtr, exposedOptionToMySQLOption(option), &myI) == 0
}
/// Sets connect options for connect() with string option argument
@discardableResult
public func setOption(_ option: MySQLOpt, _ s: String) -> Bool {
var b = false
s.withCString { p in
b = mysql_options(mysqlPtr, exposedOptionToMySQLOption(option), p) == 0
}
return b
}
/// Sets server option (must be set after connect() is called)
@discardableResult
public func setServerOption(_ option: MySQLServerOpt) -> Bool {
return mysql_set_server_option(mysqlPtr, exposedOptionToMySQLServerOption(option)) == 0
}
/// Class used to manage and interact with result sets
public final class Results: IteratorProtocol {
var ptr: UnsafeMutablePointer<MYSQL_RES>
public typealias Element = [String?]
init(_ ptr: UnsafeMutablePointer<MYSQL_RES>) {
self.ptr = ptr
}
deinit {
mysql_free_result(ptr)
}
@available(*, deprecated)
public func close() {}
/// Seeks to an arbitrary row number in a query result set
public func dataSeek(_ offset: UInt) {
mysql_data_seek(ptr, my_ulonglong(offset))
}
/// Returns the number of rows in a result set
public func numRows() -> Int {
return Int(mysql_num_rows(ptr))
}
/// Returns the number of columns in a result set
/// Returns: Int
public func numFields() -> Int {
return Int(mysql_num_fields(ptr))
}
/// Fetches the next row from the result set
/// returning a String array of column names if row available
/// Returns: optional Element
public func next() -> Element? {
guard let row = mysql_fetch_row(ptr),
let lengths = mysql_fetch_lengths(ptr) else {
return nil
}
var ret: [String?] = []
for fieldIdx in 0..<numFields() {
let length = lengths[fieldIdx]
let rowVal = row[fieldIdx]
let len = Int(length)
if let raw = rowVal {
let s = raw.withMemoryRebound(to: UInt8.self, capacity: len) { UTF8Encoding.encode(generator: GenerateFromPointer(from: $0, count: len)) }
ret.append(s)
} else {
ret.append(nil)
}
}
return ret
}
/// passes a string array of the column names to the callback provided
public func forEachRow(callback: (Element) -> ()) {
while let element = next() {
callback(element)
}
}
}
}
#if swift(>=4.1)
#else
// Added for Swift 4.0/4.1 compat
extension UnsafeMutableRawBufferPointer {
static func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawBufferPointer {
return allocate(count: byteCount)
}
}
extension UnsafeMutablePointer {
func deallocate() {
deallocate(capacity: 0)
}
}
extension Collection {
func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try flatMap(transform)
}
}
#endif