Skip to content

Commit

Permalink
Merge pull request #46 from vapor/memory-bug
Browse files Browse the repository at this point in the history
Memory bug
  • Loading branch information
tanner0101 authored Sep 11, 2016
2 parents e126011 + 38a4dc3 commit e79e5c0
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 168 deletions.
70 changes: 16 additions & 54 deletions Sources/MySQL/Bind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<MYSQL_TIME>.size
default:
length = Int(field.cField.length)
}

cBind.buffer_length = UInt(length)

cBind.buffer = UnsafeMutableRawPointer.allocate(bytes: length, alignedTo: MemoryLayout<Void>.alignment)
Expand Down Expand Up @@ -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<Void>.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)
Expand Down
1 change: 1 addition & 0 deletions Sources/MySQL/Binds.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public final class Binds {
}

deinit {
cBinds.deinitialize()
cBinds.deallocate(capacity: binds.count)
}

Expand Down
101 changes: 100 additions & 1 deletion Sources/MySQL/Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
112 changes: 1 addition & 111 deletions Sources/MySQL/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}


Expand Down
17 changes: 17 additions & 0 deletions Sources/MySQL/Error.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
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)
}
11 changes: 10 additions & 1 deletion Sources/MySQL/Field.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#else
import CMySQLMac
#endif
import Core

/**
Wraps a MySQL C field struct.
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit e79e5c0

Please sign in to comment.