Skip to content

Commit 5812854

Browse files
vkuttypCopilot
andcommitted
perf: four targeted optimizations
1. Static DateFormatter/ISO8601DateFormatter singletons in pgDecode() and mysqlDecode() — eliminates per-cell formatter construction (was ~1ms each). Also static formatter in pgLiteral for date→SQL conversion. 2. Pre-compute sqlCols [SQLColumn] once per RowDescription in Postgres collectResults() and queryMulti() — was re-allocated on every data row. 3. MSSQL sendPacket: write chunks without flushing, flush only on last chunk (writeAndFlush) — reduces event-loop round-trips for multi-packet large queries. 4. SQLDataTable: O(n) columnIndex(for:) replaced with O(1) dictionary lookup built once at init. Applies to subscript[row, column: String] and column(named:). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 915205e commit 5812854

4 files changed

Lines changed: 57 additions & 18 deletions

File tree

Sources/CosmoMSSQL/MSSQLConnection.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,12 @@ public final class MSSQLConnection: SQLDatabase, @unchecked Sendable {
645645
header.encode(into: &pkt)
646646
pkt.writeBytes(payload.getBytes(at: payload.readerIndex + offset, length: chunkLen)!)
647647

648-
try await channel.writeAndFlush(pkt).get()
648+
if isLast {
649+
// Last chunk: writeAndFlush sends all buffered writes in one TCP segment
650+
try await channel.writeAndFlush(pkt).get()
651+
} else {
652+
channel.write(pkt, promise: nil)
653+
}
649654
packetID = packetID == 255 ? 1 : packetID + 1
650655
offset += chunkLen
651656
}

Sources/CosmoMySQL/Protocol/MySQLDecoder.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,17 @@ func mysqlCachingSHA2Password(password: String, nonce: [UInt8]) -> [UInt8] {
183183

184184
// MARK: - MySQL type → SQLValue
185185

186+
// Static formatters — DateFormatter construction is expensive; allocating one per cell
187+
// (old behaviour) added significant overhead for date/datetime-heavy result sets.
188+
private nonisolated(unsafe) let _mysqlDateFmt: DateFormatter = {
189+
let f = DateFormatter(); f.locale = Locale(identifier: "en_US_POSIX")
190+
f.dateFormat = "yyyy-MM-dd"; return f
191+
}()
192+
private nonisolated(unsafe) let _mysqlDateTimeFmt: DateFormatter = {
193+
let f = DateFormatter(); f.locale = Locale(identifier: "en_US_POSIX")
194+
f.dateFormat = "yyyy-MM-dd HH:mm:ss"; return f
195+
}()
196+
186197
func mysqlDecode(columnType: UInt8, isUnsigned: Bool, text: String?) -> SQLValue {
187198
guard let text = text else { return .null }
188199
switch columnType {
@@ -206,11 +217,9 @@ func mysqlDecode(columnType: UInt8, isUnsigned: Bool, text: String?) -> SQLValue
206217
case 0xFE where text.count == 36: // CHAR(36) — likely UUID
207218
return UUID(uuidString: text).map { .uuid($0) } ?? .string(text)
208219
case 0x0A: // DATE
209-
let fmt = DateFormatter(); fmt.dateFormat = "yyyy-MM-dd"
210-
return fmt.date(from: text).map { .date($0) } ?? .string(text)
220+
return _mysqlDateFmt.date(from: text).map { .date($0) } ?? .string(text)
211221
case 0x0B, 0x0C, 0x07: // TIME, DATETIME, TIMESTAMP
212-
let fmt = DateFormatter(); fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
213-
return fmt.date(from: text).map { .date($0) } ?? .string(text)
222+
return _mysqlDateTimeFmt.date(from: text).map { .date($0) } ?? .string(text)
214223
default:
215224
return .string(text)
216225
}

Sources/CosmoPostgres/PostgresConnection.swift

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,18 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
307307
var allSets: [[SQLRow]] = []
308308
var current: [SQLRow] = []
309309
var columns: [PGColumnDesc] = []
310+
var sqlCols: [SQLColumn] = [] // computed once per RowDescription
310311
var pendingError: (any Error)?
311312

312313
loop: while true {
313314
let m = try await receiveMessage()
314315
switch m {
315316
case .rowDescription(let cols):
316317
columns = cols
318+
sqlCols = cols.map { SQLColumn(name: $0.name, dataTypeID: $0.typeOID) }
317319
current = []
318320
case .dataRow(let rawValues):
319321
if pendingError == nil {
320-
let sqlCols = columns.map { SQLColumn(name: $0.name, dataTypeID: $0.typeOID) }
321322
let values = zip(columns, rawValues).map { col, raw -> SQLValue in
322323
guard var buf = raw else { return .null }
323324
return pgDecode(typeOID: col.typeOID, buffer: &buf)
@@ -329,6 +330,7 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
329330
allSets.append(current)
330331
current = []
331332
columns = []
333+
sqlCols = []
332334
}
333335
case .readyForQuery:
334336
break loop
@@ -399,6 +401,7 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
399401

400402
private func collectResults() async throws -> [SQLRow] {
401403
var columns: [PGColumnDesc] = []
404+
var sqlCols: [SQLColumn] = [] // computed once per RowDescription, shared across rows
402405
var rows: [SQLRow] = []
403406
var pendingError: (any Error)?
404407

@@ -407,11 +410,9 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
407410
switch msg {
408411
case .rowDescription(let cols):
409412
columns = cols
413+
sqlCols = cols.map { SQLColumn(name: $0.name, dataTypeID: $0.typeOID) }
410414
case .dataRow(let rawValues):
411415
if pendingError == nil {
412-
let sqlCols = columns.map {
413-
SQLColumn(name: $0.name, dataTypeID: $0.typeOID)
414-
}
415416
let values = zip(columns, rawValues).map { col, raw -> SQLValue in
416417
guard var buf = raw else { return .null }
417418
return pgDecode(typeOID: col.typeOID, buffer: &buf)
@@ -463,6 +464,24 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
463464

464465
// MARK: - PostgreSQL text decoder
465466

467+
// Static formatters — DateFormatter/ISO8601DateFormatter are expensive to construct;
468+
// allocating one per cell (old behaviour) added measurable overhead on date-heavy result sets.
469+
private nonisolated(unsafe) let _pgDateFmt: DateFormatter = {
470+
let f = DateFormatter(); f.locale = Locale(identifier: "en_US_POSIX")
471+
f.dateFormat = "yyyy-MM-dd"; return f
472+
}()
473+
private nonisolated(unsafe) let _pgTsFmt: ISO8601DateFormatter = {
474+
let f = ISO8601DateFormatter()
475+
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
476+
return f
477+
}()
478+
private nonisolated(unsafe) let _pgTsFmt2: ISO8601DateFormatter = {
479+
// Postgres may omit fractional seconds
480+
let f = ISO8601DateFormatter()
481+
f.formatOptions = [.withInternetDateTime]
482+
return f
483+
}()
484+
466485
func pgDecode(typeOID: UInt32, buffer: inout ByteBuffer) -> SQLValue {
467486
let text = buffer.readString(length: buffer.readableBytes) ?? ""
468487
switch typeOID {
@@ -481,18 +500,22 @@ func pgDecode(typeOID: UInt32, buffer: inout ByteBuffer) -> SQLValue {
481500
case 2950: // uuid
482501
return UUID(uuidString: text).map { .uuid($0) } ?? .string(text)
483502
case 1082: // date
484-
let fmt = DateFormatter(); fmt.dateFormat = "yyyy-MM-dd"
485-
return fmt.date(from: text).map { .date($0) } ?? .string(text)
503+
return _pgDateFmt.date(from: text).map { .date($0) } ?? .string(text)
486504
case 1114, 1184: // timestamp, timestamptz
487-
let fmt = ISO8601DateFormatter()
488-
return fmt.date(from: text).map { .date($0) } ?? .string(text)
505+
return (_pgTsFmt.date(from: text) ?? _pgTsFmt2.date(from: text)).map { .date($0) } ?? .string(text)
489506
default:
490507
return .string(text)
491508
}
492509
}
493510

494511
// MARK: - SQLValue → PostgreSQL literal
495512

513+
private nonisolated(unsafe) let _pgLiteralFmt: ISO8601DateFormatter = {
514+
let f = ISO8601DateFormatter()
515+
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
516+
return f
517+
}()
518+
496519
private extension SQLValue {
497520
var pgLiteral: String {
498521
switch self {
@@ -509,9 +532,7 @@ private extension SQLValue {
509532
case .string(let v): return "'\(v.replacingOccurrences(of: "'", with: "''"))'"
510533
case .bytes(let v): return "E'\\\\x" + v.map { String(format: "%02x", $0) }.joined() + "'"
511534
case .uuid(let v): return "'\(v.uuidString)'"
512-
case .date(let v):
513-
let fmt = ISO8601DateFormatter()
514-
return "'\(fmt.string(from: v))'"
535+
case .date(let v): return "'\(_pgLiteralFmt.string(from: v))'"
515536
}
516537
}
517538
}

Sources/CosmoSQLCore/SQLDataTable.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ public struct SQLDataTable: Sendable {
165165
public let columns: [SQLDataColumn]
166166
public let rows: [[SQLCellValue]]
167167

168+
// O(1) column name → index lookup (case-insensitive, built once at init)
169+
private let _colIndex: [String: Int]
170+
168171
// MARK: Dimensions
169172

170173
public var rowCount: Int { rows.count }
@@ -178,6 +181,7 @@ public struct SQLDataTable: Sendable {
178181
SQLDataColumn(name: $0.name, table: $0.table)
179182
}
180183
self.rows = sqlRows.map { row in row.values.map { SQLCellValue($0) } }
184+
self._colIndex = Dictionary(uniqueKeysWithValues: self.columns.enumerated().map { ($1.name.lowercased(), $0) })
181185
}
182186

183187
// MARK: Subscript access
@@ -290,8 +294,7 @@ public struct SQLDataTable: Sendable {
290294
// MARK: Private helpers
291295

292296
private func columnIndex(for name: String) -> Int? {
293-
let lower = name.lowercased()
294-
return columns.firstIndex { $0.name.lowercased() == lower }
297+
_colIndex[name.lowercased()]
295298
}
296299
}
297300

@@ -312,6 +315,7 @@ extension SQLDataTable: Codable {
312315
self.name = try c.decodeIfPresent(String.self, forKey: .name)
313316
self.columns = try c.decode([SQLDataColumn].self, forKey: .columns)
314317
self.rows = try c.decode([[SQLCellValue]].self, forKey: .rows)
318+
self._colIndex = Dictionary(uniqueKeysWithValues: self.columns.enumerated().map { ($1.name.lowercased(), $0) })
315319
}
316320
}
317321

0 commit comments

Comments
 (0)