@@ -251,4 +251,101 @@ final class SQLTokenizerTests: XCTestCase {
251251 XCTAssertNotNil(range)
252252 XCTAssertEqual(String(sql[range!]), #""from""#)
253253 }
254+
255+ // MARK: - SQLErrorPosition.parse
256+
257+ func testParseDataFusionError() {
258+ let msg = "Expected: an expression, found: FROM at Line: 1, Column 15"
259+ let pos = SQLErrorPosition.parse(from: msg)
260+ XCTAssertEqual(pos, SQLErrorPosition(line: 1, column: 15))
261+ }
262+
263+ func testParseDataFusionErrorWithColonAfterColumn() {
264+ let msg = "Error at Line: 2, Column: 10"
265+ let pos = SQLErrorPosition.parse(from: msg)
266+ XCTAssertEqual(pos, SQLErrorPosition(line: 2, column: 10))
267+ }
268+
269+ func testParseReturnsNilForNoPosition() {
270+ let msg = "Some generic error without position info"
271+ XCTAssertNil(SQLErrorPosition.parse(from: msg))
272+ }
273+
274+ func testParseReturnsNilForEmptyString() {
275+ XCTAssertNil(SQLErrorPosition.parse(from: ""))
276+ }
277+
278+ // MARK: - characterOffset
279+
280+ func testCharacterOffsetSingleLine() {
281+ let sql = "SELECT * FROM t"
282+ // Line 1, Column 8 → offset 7 (the '*')
283+ XCTAssertEqual(SQLTokenizer.characterOffset(line: 1, column: 8, in: sql), 7)
284+ }
285+
286+ func testCharacterOffsetMultiLine() {
287+ let sql = "SELECT *\nFROM t"
288+ // Line 2, Column 1 → offset 9 (the 'F' of FROM)
289+ XCTAssertEqual(SQLTokenizer.characterOffset(line: 2, column: 1, in: sql), 9)
290+ }
291+
292+ func testCharacterOffsetLineBeyondEnd() {
293+ let sql = "SELECT *"
294+ XCTAssertNil(SQLTokenizer.characterOffset(line: 3, column: 1, in: sql))
295+ }
296+
297+ func testCharacterOffsetColumnBeyondEnd() {
298+ let sql = "SELECT *"
299+ // Column 100 on a short string
300+ XCTAssertNil(SQLTokenizer.characterOffset(line: 1, column: 100, in: sql))
301+ }
302+
303+ // MARK: - tokenRange
304+
305+ func testTokenRangeAtKeyword() {
306+ let sql = "SELEC * FROM t"
307+ // Offset 0 is at the start of "SELEC" (which is an identifier, not a keyword)
308+ let range = SQLTokenizer.tokenRange(atOffset: 0, in: sql)
309+ XCTAssertNotNil(range)
310+ XCTAssertEqual((sql as NSString).substring(with: range!), "SELEC")
311+ }
312+
313+ func testTokenRangeAtWhitespaceSnapsToNextToken() {
314+ let sql = "SELECT * FROM t"
315+ // Offset 6 is inside the whitespace between SELECT and *
316+ let range = SQLTokenizer.tokenRange(atOffset: 7, in: sql)
317+ XCTAssertNotNil(range)
318+ XCTAssertEqual((sql as NSString).substring(with: range!), "*")
319+ }
320+
321+ func testTokenRangeAtIdentifier() {
322+ let sql = "SELECT * FROM mytable"
323+ // Offset 14 is at 'm' of "mytable"
324+ let range = SQLTokenizer.tokenRange(atOffset: 14, in: sql)
325+ XCTAssertNotNil(range)
326+ XCTAssertEqual((sql as NSString).substring(with: range!), "mytable")
327+ }
328+
329+ // MARK: - errorHighlightRange (end-to-end)
330+
331+ func testErrorHighlightRangeTypoInKeyword() {
332+ let sql = "SELEC * FROM t"
333+ // DataFusion would report Line: 1, Column 1 for SELEC
334+ let range = SQLTokenizer.errorHighlightRange(line: 1, column: 1, in: sql)
335+ XCTAssertNotNil(range)
336+ XCTAssertEqual((sql as NSString).substring(with: range!), "SELEC")
337+ }
338+
339+ func testErrorHighlightRangeMultiLine() {
340+ let sql = "SELECT *\nFRO t"
341+ // Line 2, Column 1 → "FRO"
342+ let range = SQLTokenizer.errorHighlightRange(line: 2, column: 1, in: sql)
343+ XCTAssertNotNil(range)
344+ XCTAssertEqual((sql as NSString).substring(with: range!), "FRO")
345+ }
346+
347+ func testErrorHighlightRangeInvalidPosition() {
348+ let sql = "SELECT * FROM t"
349+ XCTAssertNil(SQLTokenizer.errorHighlightRange(line: 99, column: 1, in: sql))
350+ }
254351}