From 3098bdf73e81c4194704183709fcff21e07b97ae Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:31:48 -0400 Subject: [PATCH 1/8] fix length issue --- Sources/MySQL/Bind.swift | 70 +++++++++------------------------------ Sources/MySQL/Error.swift | 9 +++++ 2 files changed, 25 insertions(+), 54 deletions(-) create mode 100644 Sources/MySQL/Error.swift diff --git a/Sources/MySQL/Bind.swift b/Sources/MySQL/Bind.swift index 1fee5721..56fff67c 100644 --- a/Sources/MySQL/Bind.swift +++ b/Sources/MySQL/Bind.swift @@ -53,8 +53,20 @@ public final class Bind { var cBind = CBind() cBind.buffer_type = field.cField.type - let length = Int(field.cField.length) - + let length: Int + + // FIXME: Find better way to get length + + switch field.cField.type { + case MYSQL_TYPE_DATE, + MYSQL_TYPE_DATETIME, + MYSQL_TYPE_TIMESTAMP, + MYSQL_TYPE_TIME: + length = MemoryLayout.size + default: + length = Int(field.cField.length) + } + cBind.buffer_length = UInt(length) cBind.buffer = UnsafeMutableRawPointer.allocate(bytes: length, alignedTo: MemoryLayout.alignment) @@ -156,67 +168,17 @@ public final class Bind { C binding. */ deinit { - guard cBind.buffer_type != MYSQL_TYPE_NULL else { - return - } - let bufferLength = Int(cBind.buffer_length) - - #if !NOJSON - if variant == MYSQL_TYPE_JSON { - cBind.buffer.assumingMemoryBound(to: UInt8.self).deinitialize() - } - #endif - - switch variant { - case MYSQL_TYPE_STRING, - MYSQL_TYPE_VAR_STRING, - MYSQL_TYPE_BLOB, - MYSQL_TYPE_DECIMAL, - MYSQL_TYPE_NEWDECIMAL, - MYSQL_TYPE_ENUM, - MYSQL_TYPE_SET: - cBind.buffer.assumingMemoryBound(to: UInt8.self).deinitialize() - case MYSQL_TYPE_LONG: - if cBind.is_unsigned == 1 { - cBind.buffer.assumingMemoryBound(to: UInt32.self).deinitialize() - } else { - cBind.buffer.assumingMemoryBound(to: Int32.self).deinitialize() - } - case MYSQL_TYPE_TINY: - if cBind.is_unsigned == 1 { - cBind.buffer.assumingMemoryBound(to: UInt8.self).deinitialize() - } else { - cBind.buffer.assumingMemoryBound(to: Int8.self).deinitialize() - } - case MYSQL_TYPE_LONGLONG: - if cBind.is_unsigned == 1 { - cBind.buffer.assumingMemoryBound(to: UInt64.self).deinitialize() - } else { - cBind.buffer.assumingMemoryBound(to: Int64.self).deinitialize() - } - case MYSQL_TYPE_DOUBLE: - cBind.buffer.assumingMemoryBound(to: Double.self).deinitialize() - case MYSQL_TYPE_DATE, - MYSQL_TYPE_DATETIME, - MYSQL_TYPE_TIMESTAMP, - MYSQL_TYPE_TIME: - cBind.buffer.assumingMemoryBound(to: MYSQL_TIME.self).deinitialize() - default: - print("[MySQL] Unsupported type: \(variant).") - break - } - cBind.buffer.deallocate(bytes: bufferLength, alignedTo: MemoryLayout.alignment) cBind.length.deinitialize() cBind.length.deallocate(capacity: 1) - + if let pointer = cBind.is_null { pointer.deinitialize() pointer.deallocate(capacity: 1) } - + if let pointer = cBind.error { pointer.deinitialize() pointer.deallocate(capacity: 1) diff --git a/Sources/MySQL/Error.swift b/Sources/MySQL/Error.swift new file mode 100644 index 00000000..0d085725 --- /dev/null +++ b/Sources/MySQL/Error.swift @@ -0,0 +1,9 @@ +// +// Error.swift +// MySQL +// +// Created by Tanner Nelson on 9/10/16. +// +// + +import Foundation From cf73bcfd41dd004961b14eee4ff2e6af16ec1363 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:31:54 -0400 Subject: [PATCH 2/8] deinit binds --- Sources/MySQL/Binds.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/MySQL/Binds.swift b/Sources/MySQL/Binds.swift index bdedc5d1..71e96699 100644 --- a/Sources/MySQL/Binds.swift +++ b/Sources/MySQL/Binds.swift @@ -66,6 +66,7 @@ public final class Binds { } deinit { + cBinds.deinitialize() cBinds.deallocate(capacity: binds.count) } From b797d107b3392d24e790ce0332dccd15e5941c57 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:32:05 -0400 Subject: [PATCH 3/8] move execute to connection --- Sources/MySQL/Connection.swift | 101 ++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/Sources/MySQL/Connection.swift b/Sources/MySQL/Connection.swift index fb11462e..4213aa38 100644 --- a/Sources/MySQL/Connection.swift +++ b/Sources/MySQL/Connection.swift @@ -34,12 +34,111 @@ public final class Connection { cConnection = mysql_init(nil) guard mysql_real_connect(cConnection, host, user, password, database, port, socket, flag) != nil else { - throw Database.Error.connection(error) + throw Error.connection(error) } mysql_set_character_set(cConnection, encoding) } + @discardableResult + public func execute(_ query: String, _ values: [NodeRepresentable] = []) throws -> [[String: Node]] { + + // Create a pointer to the statement + // This should only fail if memory is limited. + guard let statement = mysql_stmt_init(cConnection) else { + throw Error.statement(error) + } + defer { + mysql_stmt_close(statement) + } + + // Prepares the created statement + // This parses `?` in the query and + // prepares them to attach parameterized bindings. + guard mysql_stmt_prepare(statement, query, strlen(query)) == 0 else { + throw Error.prepare(error) + } + + // Transforms the `[Value]` array into bindings + // and applies those bindings to the statement. + let inputBinds = try Binds(values) + guard mysql_stmt_bind_param(statement, inputBinds.cBinds) == 0 else { + throw Error.inputBind(error) + } + + // Fetches metadata from the statement which has + // not yet run. + if let metadata = mysql_stmt_result_metadata(statement) { + defer { + mysql_free_result(metadata) + } + + // Parse the fields (columns) that will be returned + // by this statement. + let fields: Fields + do { + fields = try Fields(metadata) + } catch { + throw Error.fetchFields(self.error) + } + + // Use the fields data to create output bindings. + // These act as buffers for the data that will + // be returned when the statement is executed. + let outputBinds = Binds(fields) + + // Bind the output bindings to the statement. + guard mysql_stmt_bind_result(statement, outputBinds.cBinds) == 0 else { + throw Error.outputBind(error) + } + + // Execute the statement! + // The data is ready to be fetched when this completes. + guard mysql_stmt_execute(statement) == 0 else { + throw Error.execute(error) + } + + var results: [[String: Node]] = [] + + // Iterate over all of the rows that are returned. + // `mysql_stmt_fetch` will continue to return `0` + // as long as there are rows to be fetched. + while mysql_stmt_fetch(statement) == 0 { + var parsed: [String: Node] = [:] + + // For each row, loop over all of the fields expected. + for (i, field) in fields.fields.enumerated() { + + // For each field, grab the data from + // the output binding buffer and add + // it to the parsed results. + let output = outputBinds[i] + parsed[field.name] = output.value + + } + + results.append(parsed) + + // reset the bindings onto the statement to + // signal that they may be reused as buffers + // for the next row fetch. + guard mysql_stmt_bind_result(statement, outputBinds.cBinds) == 0 else { + throw Error.outputBind(error) + } + } + + return results + } else { + // no data is expected to return from + // this query, simply execute it. + guard mysql_stmt_execute(statement) == 0 else { + throw Error.execute(error) + } + return [] + } + } + + deinit { mysql_close(cConnection) mysql_thread_end() From 9d5697bec8b18b79c8b0cacb39df0b35c4da22f6 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:32:15 -0400 Subject: [PATCH 4/8] simplify database --- Sources/MySQL/Database.swift | 112 +---------------------------------- 1 file changed, 1 insertion(+), 111 deletions(-) diff --git a/Sources/MySQL/Database.swift b/Sources/MySQL/Database.swift index 90512a76..c7501184 100644 --- a/Sources/MySQL/Database.swift +++ b/Sources/MySQL/Database.swift @@ -14,24 +14,6 @@ import Core Holds a `Connection` to the MySQL database. */ public final class Database { - /** - A list of all Error messages that - can be thrown from calls to `Database`. - - All Error objects contain a String which - contains MySQL's last error message. - */ - public enum Error: Swift.Error { - case serverInit - case connection(String) - case inputBind(String) - case outputBind(String) - case fetchFields(String) - case prepare(String) - case statement(String) - case execute(String) - } - /** Attempts to establish a connection to a MySQL database engine running on host. @@ -118,99 +100,7 @@ public final class Database { connection = try makeConnection() } - // Create a pointer to the statement - // This should only fail if memory is limited. - guard let statement = mysql_stmt_init(connection.cConnection) else { - throw Error.statement(connection.error) - } - defer { - mysql_stmt_close(statement) - } - - // Prepares the created statement - // This parses `?` in the query and - // prepares them to attach parameterized bindings. - guard mysql_stmt_prepare(statement, query, strlen(query)) == 0 else { - throw Error.prepare(connection.error) - } - - // Transforms the `[Value]` array into bindings - // and applies those bindings to the statement. - let inputBinds = try Binds(values) - guard mysql_stmt_bind_param(statement, inputBinds.cBinds) == 0 else { - throw Error.inputBind(connection.error) - } - - // Fetches metadata from the statement which has - // not yet run. - if let metadata = mysql_stmt_result_metadata(statement) { - defer { - mysql_free_result(metadata) - } - - // Parse the fields (columns) that will be returned - // by this statement. - let fields: Fields - do { - fields = try Fields(metadata) - } catch { - throw Error.fetchFields(connection.error) - } - - // Use the fields data to create output bindings. - // These act as buffers for the data that will - // be returned when the statement is executed. - let outputBinds = Binds(fields) - - // Bind the output bindings to the statement. - guard mysql_stmt_bind_result(statement, outputBinds.cBinds) == 0 else { - throw Error.outputBind(connection.error) - } - - // Execute the statement! - // The data is ready to be fetched when this completes. - guard mysql_stmt_execute(statement) == 0 else { - throw Error.execute(connection.error) - } - - var results: [[String: Node]] = [] - - // Iterate over all of the rows that are returned. - // `mysql_stmt_fetch` will continue to return `0` - // as long as there are rows to be fetched. - while mysql_stmt_fetch(statement) == 0 { - var parsed: [String: Node] = [:] - - // For each row, loop over all of the fields expected. - for (i, field) in fields.fields.enumerated() { - - // For each field, grab the data from - // the output binding buffer and add - // it to the parsed results. - let output = outputBinds[i] - parsed[field.name] = output.value - - } - - results.append(parsed) - - // reset the bindings onto the statement to - // signal that they may be reused as buffers - // for the next row fetch. - guard mysql_stmt_bind_result(statement, outputBinds.cBinds) == 0 else { - throw Error.outputBind(connection.error) - } - } - - return results - } else { - // no data is expected to return from - // this query, simply execute it. - guard mysql_stmt_execute(statement) == 0 else { - throw Error.execute(connection.error) - } - return [] - } + return try connection.execute(query, values) } From 637f7f688fb418a25be54243ae04486d892b65d5 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:32:20 -0400 Subject: [PATCH 5/8] global error enum --- Sources/MySQL/Error.swift | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Sources/MySQL/Error.swift b/Sources/MySQL/Error.swift index 0d085725..94e7fe70 100644 --- a/Sources/MySQL/Error.swift +++ b/Sources/MySQL/Error.swift @@ -1,9 +1,17 @@ -// -// Error.swift -// MySQL -// -// Created by Tanner Nelson on 9/10/16. -// -// +/** + A list of all Error messages that + can be thrown from calls to `Database`. -import Foundation + All Error objects contain a String which + contains MySQL's last error message. +*/ +public enum Error: Swift.Error { + case serverInit + case connection(String) + case inputBind(String) + case outputBind(String) + case fetchFields(String) + case prepare(String) + case statement(String) + case execute(String) +} From fb071db4150e70d34809148b3bd51fa0d05587de Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:32:33 -0400 Subject: [PATCH 6/8] use length in field name --- Sources/MySQL/Field.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/MySQL/Field.swift b/Sources/MySQL/Field.swift index 18bdc49e..a51ce9be 100644 --- a/Sources/MySQL/Field.swift +++ b/Sources/MySQL/Field.swift @@ -7,6 +7,7 @@ #else import CMySQLMac #endif +import Core /** Wraps a MySQL C field struct. @@ -17,7 +18,15 @@ public final class Field { public let cField: CField public var name: String { - return String(cString: cField.name) + var name: String = "" + + let len = Int(cField.name_length) + cField.name.withMemoryRebound(to: Byte.self, capacity: len) { pointer in + let buff = UnsafeBufferPointer(start: pointer, count: Int(cField.name_length)) + name = Array(buff).string + } + + return name } public init(_ cField: CField) { From 311af7acda45b8ff07c6fc3a00dcb9b9b3bfeaf1 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:32:42 -0400 Subject: [PATCH 7/8] improve tests --- Tests/MySQLTests/MySQLTests.swift | 20 +++++++++++++++++++- Tests/MySQLTests/Utilities.swift | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Tests/MySQLTests/MySQLTests.swift b/Tests/MySQLTests/MySQLTests.swift index 675c6c95..426cdaee 100644 --- a/Tests/MySQLTests/MySQLTests.swift +++ b/Tests/MySQLTests/MySQLTests.swift @@ -170,11 +170,29 @@ class MySQLTests: XCTestCase { } } + func testSpam() { + do { + let c = try mysql.makeConnection() + + try c.execute("DROP TABLE IF EXISTS spam") + try c.execute("CREATE TABLE spam (s VARCHAR(64), time TIME)") + + for _ in 0..<10_000 { + try c.execute("INSERT INTO spam VALUES (?, ?)", ["hello", "13:42"]) + } + + let cn = try mysql.makeConnection() + try cn.execute("SELECT * FROM spam") + } catch { + XCTFail("Testing multiple failed: \(error)") + } + } + func testError() { do { try mysql.execute("error") XCTFail("Should have errored.") - } catch Database.Error.prepare(_) { + } catch MySQL.Error.prepare(_) { } catch { XCTFail("Wrong error: \(error)") diff --git a/Tests/MySQLTests/Utilities.swift b/Tests/MySQLTests/Utilities.swift index fbb5fd13..dca0c90b 100644 --- a/Tests/MySQLTests/Utilities.swift +++ b/Tests/MySQLTests/Utilities.swift @@ -10,7 +10,7 @@ extension MySQL.Database { password: "", database: "test" ) - try mysql.execute("SELECT @@version") + // try mysql.execute("SELECT @@version") return mysql } catch { print() From 38a4dc3c6fcbc8d8113e1a14263c4181b818fde7 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Sat, 10 Sep 2016 23:33:03 -0400 Subject: [PATCH 8/8] re-add version check --- Tests/MySQLTests/Utilities.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/MySQLTests/Utilities.swift b/Tests/MySQLTests/Utilities.swift index dca0c90b..fbb5fd13 100644 --- a/Tests/MySQLTests/Utilities.swift +++ b/Tests/MySQLTests/Utilities.swift @@ -10,7 +10,7 @@ extension MySQL.Database { password: "", database: "test" ) - // try mysql.execute("SELECT @@version") + try mysql.execute("SELECT @@version") return mysql } catch { print()