Skip to content

Commit 347cfa2

Browse files
committed
perf(editor): avoid O(n) Unicode comparison of query text during SwiftUI diffs
1 parent 03a7f5b commit 347cfa2

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

TablePro/Models/Query/QueryTabState.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,29 @@ struct TabQueryContent: Equatable {
288288
if queryNS.length != savedNS.length { return true }
289289
return queryNS != savedNS
290290
}
291+
292+
static func == (lhs: TabQueryContent, rhs: TabQueryContent) -> Bool {
293+
// `query` can be multiple megabytes and is bridged from the editor's NSTextStorage. The synthesized `==`
294+
// compared it with Swift's canonical Unicode equality, which walks the whole string through NFC normalization
295+
// on every SwiftUI diff and pins the CPU while editing a large query. NSString equality returns in O(1) when
296+
// the lengths differ (every keystroke changes the length) and uses literal comparison otherwise.
297+
guard (lhs.query as NSString).isEqual(to: rhs.query),
298+
lhs.queryParameters == rhs.queryParameters,
299+
lhs.isParameterPanelVisible == rhs.isParameterPanelVisible,
300+
lhs.sourceFileURL == rhs.sourceFileURL,
301+
lhs.loadMtime == rhs.loadMtime,
302+
lhs.externalModificationDetected == rhs.externalModificationDetected else {
303+
return false
304+
}
305+
switch (lhs.savedFileContent, rhs.savedFileContent) {
306+
case (nil, nil):
307+
return true
308+
case let (lhsSaved?, rhsSaved?):
309+
return (lhsSaved as NSString).isEqual(to: rhsSaved)
310+
default:
311+
return false
312+
}
313+
}
291314
}
292315

293316
struct TabDisplayState: Equatable {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import Foundation
2+
@testable import TablePro
3+
import Testing
4+
5+
@Suite("TabQueryContent.Equatable")
6+
struct TabQueryContentEqualityTests {
7+
@Test("Equal when all fields match")
8+
func equalWhenIdentical() {
9+
let a = TabQueryContent(query: "SELECT * FROM users;")
10+
let b = TabQueryContent(query: "SELECT * FROM users;")
11+
#expect(a == b)
12+
}
13+
14+
@Test("Not equal when the query changes length")
15+
func notEqualOnLengthChange() {
16+
var a = TabQueryContent(query: "SELECT 1")
17+
let b = TabQueryContent(query: "SELECT 12")
18+
#expect(a != b)
19+
a.query = "SELECT 12"
20+
#expect(a == b)
21+
}
22+
23+
@Test("Not equal when the query changes at the same length")
24+
func notEqualOnSameLengthChange() {
25+
let a = TabQueryContent(query: "abc")
26+
let b = TabQueryContent(query: "abd")
27+
#expect(a != b)
28+
}
29+
30+
@Test("Detects a single-character edit in a large query")
31+
func detectsEditInLargeQuery() {
32+
let base = String(repeating: "SELECT 1;\n", count: 100_000)
33+
let a = TabQueryContent(query: base)
34+
let b = TabQueryContent(query: base + "X")
35+
#expect(a != b)
36+
let c = TabQueryContent(query: base)
37+
#expect(a == c)
38+
}
39+
40+
@Test("Not equal when a non-text field differs")
41+
func notEqualOnOtherField() {
42+
var a = TabQueryContent(query: "Q")
43+
let b = TabQueryContent(query: "Q")
44+
a.isParameterPanelVisible = true
45+
#expect(a != b)
46+
a.isParameterPanelVisible = false
47+
#expect(a == b)
48+
}
49+
50+
@Test("savedFileContent participates in equality")
51+
func savedFileContentEquality() {
52+
var a = TabQueryContent(query: "Q")
53+
var b = TabQueryContent(query: "Q")
54+
#expect(a == b)
55+
a.savedFileContent = "disk"
56+
#expect(a != b)
57+
b.savedFileContent = "disk"
58+
#expect(a == b)
59+
b.savedFileContent = "other"
60+
#expect(a != b)
61+
}
62+
}

0 commit comments

Comments
 (0)