Skip to content

Commit 45d6c07

Browse files
authored
Merge pull request #102 from vapor/longtext-crash-repro
[Repro] Longtext columns cause crash on low memory systems
2 parents c8e0ce9 + 8ebf116 commit 45d6c07

File tree

3 files changed

+48
-9
lines changed

3 files changed

+48
-9
lines changed

Sources/MySQL/Bind/Bind.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ public final class Bind {
5656
MYSQL_TYPE_TIME:
5757
length = MemoryLayout<MYSQL_TIME>.size
5858
default:
59-
length = Int(field.cField.length)
59+
length = Int(field.cField.max_length)
6060
}
6161

6262
cBind.buffer_length = UInt(length)
63-
6463
cBind.buffer = UnsafeMutableRawPointer.allocate(bytes: length, alignedTo: MemoryLayout<Void>.alignment)
64+
65+
6566
cBind.length = UnsafeMutablePointer<UInt>.allocate(capacity: 1)
6667
cBind.is_null = UnsafeMutablePointer<my_bool>.allocate(capacity: 1)
6768
cBind.error = UnsafeMutablePointer<my_bool>.allocate(capacity: 1)

Sources/MySQL/Connection.swift

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ public final class Connection {
7676
guard let statement = mysql_stmt_init(cConnection) else {
7777
throw lastError
7878
}
79+
80+
// important! this must be set for field.max_length
81+
// to be properly filled
82+
var truth: my_bool = 1
83+
guard mysql_stmt_attr_set(statement, STMT_ATTR_UPDATE_MAX_LENGTH, &truth) == 0 else {
84+
throw lastError
85+
}
86+
7987
defer {
8088
mysql_stmt_close(statement)
8189
}
@@ -110,8 +118,20 @@ public final class Connection {
110118
mysql_free_result(metadata)
111119
}
112120

121+
// Execute the statement!
122+
// The data is ready to be fetched when this completes.
123+
guard mysql_stmt_execute(statement) == 0 else {
124+
throw lastError
125+
}
126+
127+
// buffers the data on the client
128+
// important! sets the max_length field
129+
mysql_stmt_store_result(statement)
130+
113131
// Parse the fields (columns) that will be returned
114132
// by this statement.
133+
// important! field information should not be parsed
134+
// until mysql_stmt_store_result has been called.
115135
let fields = try Fields(metadata, self)
116136

117137
// Use the fields data to create output bindings.
@@ -124,12 +144,6 @@ public final class Connection {
124144
throw lastError
125145
}
126146

127-
// Execute the statement!
128-
// The data is ready to be fetched when this completes.
129-
guard mysql_stmt_execute(statement) == 0 else {
130-
throw lastError
131-
}
132-
133147
var results: [StructuredData] = []
134148

135149
// This single dictionary is reused for all rows in the result set

Tests/MySQLTests/MySQLTests.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class MySQLTests: XCTestCase {
1515
("testTransaction", testTransaction),
1616
("testTransactionFailed", testTransactionFailed),
1717
("testBlob", testBlob),
18+
("testLongtext", testLongtext),
1819
]
1920

2021
var mysql: MySQL.Database!
@@ -270,7 +271,30 @@ class MySQLTests: XCTestCase {
270271
XCTFail("Timeout test failed.")
271272
}
272273
}
273-
274+
275+
func testLongtext() throws {
276+
let conn = try mysql.makeConnection()
277+
try conn.execute("DROP TABLE IF EXISTS items")
278+
try conn.execute("CREATE TABLE `items` ( " +
279+
"`id` int(10) unsigned NOT NULL AUTO_INCREMENT, " +
280+
"`title` varchar(255) NOT NULL DEFAULT '', " +
281+
"`imageUrl` varchar(255) NOT NULL DEFAULT '', " +
282+
"`html` longtext NOT NULL, " +
283+
"`isPrivate` tinyint(1) unsigned NOT NULL, " +
284+
"`isBusiness` tinyint(1) unsigned NOT NULL, " +
285+
"PRIMARY KEY (`id`)) " +
286+
"ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;")
287+
288+
try conn.execute("INSERT INTO `items` (`id`, `title`, `imageUrl`, `html`, `isPrivate`, `isBusiness`) " +
289+
"VALUES (1, 'test1', '12A34264-E5F6-48D4-AB27-422C4FD03277_10.jpeg', '<p>html</p>', 1, 1)")
290+
291+
let retrieved = try conn.execute("SELECT * from items")
292+
XCTAssertEqual(retrieved.array?.count ?? 0, 1)
293+
XCTAssertEqual(retrieved[0, "id"], 1)
294+
XCTAssertEqual(retrieved[0, "title"], "test1")
295+
XCTAssertEqual(retrieved[0, "html"]?.bytes?.makeString(), "<p>html</p>")
296+
}
297+
274298
func testPerformance() throws {
275299
let conn = try mysql.makeConnection()
276300
try conn.execute("DROP TABLE IF EXISTS things")

0 commit comments

Comments
 (0)