Skip to content

Commit d7e7237

Browse files
committed
Added test for datatables
1 parent 1281379 commit d7e7237

1 file changed

Lines changed: 362 additions & 0 deletions

File tree

Tests/SQLClientSwiftTests/SQLClientSwiftTests.swift

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,366 @@ final class SQLClientSwiftTests: XCTestCase {
189189
XCTFail("Expected notConnected")
190190
} catch SQLClientError.notConnected {}
191191
}
192+
193+
// MARK: - SQLDataTable tests
194+
195+
func testDataTableRowAndColumnCount() async throws {
196+
let table = try await client.dataTable(
197+
"SELECT 1 AS A, 'hello' AS B UNION ALL SELECT 2, 'world'"
198+
)
199+
XCTAssertEqual(table.rowCount, 2)
200+
XCTAssertEqual(table.columnCount, 2)
201+
}
202+
203+
func testDataTableColumnNames() async throws {
204+
let table = try await client.dataTable("SELECT 42 AS Answer, 'hi' AS Greeting")
205+
XCTAssertEqual(table.columns[0].name, "Answer")
206+
XCTAssertEqual(table.columns[1].name, "Greeting")
207+
}
208+
209+
func testDataTableSubscriptByName() async throws {
210+
let table = try await client.dataTable("SELECT 99 AS Score")
211+
let cell = table[0, "Score"]
212+
if case .int32(let v) = cell {
213+
XCTAssertEqual(v, 99)
214+
} else {
215+
// Widen: some servers return tinyint/smallint for literals
216+
XCTAssertNotNil(cell.anyValue, "Expected a non-null numeric value")
217+
}
218+
}
219+
220+
func testDataTableSubscriptCaseInsensitive() async throws {
221+
let table = try await client.dataTable("SELECT 'test' AS MyColumn")
222+
// Access using different casing
223+
let byLower = table[0, "mycolumn"]
224+
let byUpper = table[0, "MYCOLUMN"]
225+
XCTAssertEqual(byLower.anyValue as? String, "test")
226+
XCTAssertEqual(byUpper.anyValue as? String, "test")
227+
}
228+
229+
func testDataTableSubscriptByIndex() async throws {
230+
let table = try await client.dataTable("SELECT 7 AS N, 'x' AS S")
231+
let second = table[0, 1]
232+
XCTAssertEqual(second.anyValue as? String, "x")
233+
}
234+
235+
func testDataTableSubscriptOutOfBoundsReturnsNull() async throws {
236+
let table = try await client.dataTable("SELECT 1 AS A")
237+
let oobRow = table[99, "A"]
238+
let oobCol = table[0, "DoesNotExist"]
239+
XCTAssertEqual(oobRow, .null)
240+
XCTAssertEqual(oobCol, .null)
241+
}
242+
243+
func testDataTableNullCell() async throws {
244+
let table = try await client.dataTable("SELECT NULL AS Val")
245+
XCTAssertEqual(table[0, "Val"], .null)
246+
XCTAssertNil(table[0, "Val"].anyValue)
247+
}
248+
249+
func testDataTableRowAsDictionary() async throws {
250+
let table = try await client.dataTable("SELECT 5 AS ID, 'Alice' AS Name")
251+
let dict = table.row(at: 0)
252+
XCTAssertEqual(dict.count, 2)
253+
XCTAssertNotNil(dict["ID"])
254+
XCTAssertEqual(dict["Name"]?.anyValue as? String, "Alice")
255+
}
256+
257+
func testDataTableColumnValues() async throws {
258+
let table = try await client.dataTable(
259+
"SELECT 10 AS X UNION ALL SELECT 20 UNION ALL SELECT 30"
260+
)
261+
let values = table.column(named: "X")
262+
XCTAssertEqual(values.count, 3)
263+
XCTAssertFalse(values.contains(.null))
264+
}
265+
266+
func testDataTableNameAssignment() async throws {
267+
let table = try await client.dataTable("SELECT 1 AS A", name: "MyTable")
268+
XCTAssertEqual(table.name, "MyTable")
269+
}
270+
271+
func testDataTableStringCellValue() async throws {
272+
let table = try await client.dataTable("SELECT 'SQLClient' AS Lib")
273+
if case .string(let s) = table[0, "Lib"] {
274+
XCTAssertEqual(s, "SQLClient")
275+
} else {
276+
XCTFail("Expected .string cell")
277+
}
278+
}
279+
280+
func testDataTableBoolCellValue() async throws {
281+
let table = try await client.dataTable("SELECT CAST(1 AS BIT) AS Flag")
282+
if case .bool(let b) = table[0, "Flag"] {
283+
XCTAssertTrue(b)
284+
} else {
285+
XCTFail("Expected .bool cell")
286+
}
287+
}
288+
289+
func testDataTableDateCellValue() async throws {
290+
let table = try await client.dataTable("SELECT GETDATE() AS Now")
291+
if case .date(let d) = table[0, "Now"] {
292+
XCTAssertTrue(d.timeIntervalSinceNow < 5)
293+
} else {
294+
XCTFail("Expected .date cell")
295+
}
296+
}
297+
298+
func testDataTableDecimalCellValue() async throws {
299+
let table = try await client.dataTable("SELECT CAST(3.14 AS DECIMAL(10,2)) AS Pi")
300+
if case .decimal(let d) = table[0, "Pi"] {
301+
XCTAssertEqual(d, Decimal(string: "3.14"))
302+
} else {
303+
XCTFail("Expected .decimal cell")
304+
}
305+
}
306+
307+
func testDataTableDisplayString() async throws {
308+
let table = try await client.dataTable("SELECT 'hello|world' AS Msg")
309+
// Pipes in strings should be escaped for Markdown
310+
let display = table[0, "Msg"].displayString
311+
XCTAssertTrue(display.contains("\\|"), "Pipe character should be escaped in displayString")
312+
}
313+
314+
// MARK: - toMarkdown
315+
316+
func testDataTableToMarkdownContainsColumnNames() async throws {
317+
let table = try await client.dataTable("SELECT 1 AS ID, 'Alice' AS Name")
318+
let md = table.toMarkdown()
319+
XCTAssertTrue(md.contains("ID"))
320+
XCTAssertTrue(md.contains("Name"))
321+
}
322+
323+
func testDataTableToMarkdownContainsValues() async throws {
324+
let table = try await client.dataTable("SELECT 42 AS Answer")
325+
let md = table.toMarkdown()
326+
XCTAssertTrue(md.contains("42"))
327+
}
328+
329+
func testDataTableToMarkdownHasHeaderSeparator() async throws {
330+
let table = try await client.dataTable("SELECT 1 AS A, 2 AS B")
331+
let md = table.toMarkdown()
332+
// Every GFM table has a separator row with dashes
333+
XCTAssertTrue(md.contains("---|"), "Markdown should include a header separator row")
334+
}
335+
336+
func testDataTableToMarkdownIncludesName() async throws {
337+
let table = try await client.dataTable("SELECT 1 AS A", name: "Results")
338+
let md = table.toMarkdown()
339+
XCTAssertTrue(md.hasPrefix("# Results"))
340+
}
341+
342+
func testDataTableToMarkdownLineCount() async throws {
343+
let table = try await client.dataTable(
344+
"SELECT 1 AS N UNION ALL SELECT 2 UNION ALL SELECT 3"
345+
)
346+
let lines = table.toMarkdown().components(separatedBy: "\n").filter { !$0.isEmpty }
347+
// header + separator + 3 data rows = 5 lines (no name prefix)
348+
XCTAssertEqual(lines.count, 5)
349+
}
350+
351+
// MARK: - decode<T>
352+
353+
func testDataTableDecodeDecodable() async throws {
354+
struct Row: Decodable {
355+
let id: Int
356+
let name: String
357+
}
358+
let table = try await client.dataTable(
359+
"SELECT 1 AS id, 'Alice' AS name UNION ALL SELECT 2, 'Bob'"
360+
)
361+
let rows: [Row] = try table.decode()
362+
XCTAssertEqual(rows.count, 2)
363+
XCTAssertEqual(rows[0].id, 1)
364+
XCTAssertEqual(rows[0].name, "Alice")
365+
XCTAssertEqual(rows[1].id, 2)
366+
XCTAssertEqual(rows[1].name, "Bob")
367+
}
368+
369+
func testDataTableDecodeOptionalField() async throws {
370+
struct Row: Decodable {
371+
let id: Int
372+
let note: String?
373+
}
374+
let table = try await client.dataTable("SELECT 7 AS id, NULL AS note")
375+
let rows: [Row] = try table.decode()
376+
XCTAssertEqual(rows[0].id, 7)
377+
XCTAssertNil(rows[0].note)
378+
}
379+
380+
// MARK: - toSQLRows
381+
382+
func testDataTableToSQLRowsCount() async throws {
383+
let table = try await client.dataTable(
384+
"SELECT 1 AS A UNION ALL SELECT 2 UNION ALL SELECT 3"
385+
)
386+
let sqlRows = table.toSQLRows()
387+
XCTAssertEqual(sqlRows.count, 3)
388+
}
389+
390+
func testDataTableToSQLRowsValues() async throws {
391+
let table = try await client.dataTable("SELECT 'hello' AS Msg")
392+
let sqlRows = table.toSQLRows()
393+
XCTAssertEqual(sqlRows[0].string("Msg"), "hello")
394+
}
395+
396+
// MARK: - JSON Codable
397+
398+
func testDataTableCodableRoundTrip() async throws {
399+
let table = try await client.dataTable(
400+
"SELECT 1 AS id, 'Alice' AS name, CAST(1 AS BIT) AS active"
401+
)
402+
let encoded = try JSONEncoder().encode(table)
403+
let decoded = try JSONDecoder().decode(SQLDataTable.self, from: encoded)
404+
405+
XCTAssertEqual(decoded.rowCount, table.rowCount)
406+
XCTAssertEqual(decoded.columnCount, table.columnCount)
407+
XCTAssertEqual(decoded.columns[0].name, "id")
408+
XCTAssertEqual(decoded.columns[1].name, "name")
409+
XCTAssertEqual(decoded[0, "name"].anyValue as? String, "Alice")
410+
}
411+
412+
func testDataTableCodablePreservesNullCell() async throws {
413+
let table = try await client.dataTable("SELECT NULL AS Val")
414+
let encoded = try JSONEncoder().encode(table)
415+
let decoded = try JSONDecoder().decode(SQLDataTable.self, from: encoded)
416+
XCTAssertEqual(decoded[0, "Val"], .null)
417+
}
418+
419+
// MARK: - asDataTable / asSQLDataSet on SQLClientResult
420+
421+
func testAsDataTableFromResult() async throws {
422+
let result = try await client.execute("SELECT 10 AS X, 20 AS Y")
423+
let table = result.asDataTable(name: "Test")
424+
XCTAssertEqual(table.name, "Test")
425+
XCTAssertEqual(table.rowCount, 1)
426+
XCTAssertEqual(table.columnCount, 2)
427+
}
428+
429+
func testAsSQLDataSetFromResult() async throws {
430+
let result = try await client.execute("SELECT 1 AS A; SELECT 2 AS B;")
431+
let ds = result.asSQLDataSet()
432+
XCTAssertEqual(ds.count, 2)
433+
XCTAssertNotNil(ds[0])
434+
XCTAssertNotNil(ds[1])
435+
}
436+
437+
// MARK: - SQLDataSet
438+
439+
func testDataSetCount() async throws {
440+
let ds = try await client.dataSet("SELECT 1 AS A; SELECT 2 AS B; SELECT 3 AS C;")
441+
XCTAssertEqual(ds.count, 3)
442+
}
443+
444+
func testDataSetSubscriptByIndex() async throws {
445+
let ds = try await client.dataSet("SELECT 'first' AS V; SELECT 'second' AS V;")
446+
XCTAssertEqual(ds[0]?[0, "V"].anyValue as? String, "first")
447+
XCTAssertEqual(ds[1]?[0, "V"].anyValue as? String, "second")
448+
}
449+
450+
func testDataSetSubscriptOutOfBoundsReturnsNil() async throws {
451+
let ds = try await client.dataSet("SELECT 1 AS A")
452+
XCTAssertNil(ds[99])
453+
}
454+
455+
func testDataSetSubscriptByName() async throws {
456+
// Use a temp table with a named result via a stored proc isn't feasible here,
457+
// so we verify that name-based lookup works via asSQLDataSet with a named table.
458+
let result = try await client.execute("SELECT 42 AS Val")
459+
var ds = result.asSQLDataSet()
460+
// The first table has no name by convention; rename via re-init for test purposes.
461+
// Instead, test that subscript by non-existent name returns nil gracefully.
462+
XCTAssertNil(ds["NonExistent"])
463+
}
464+
465+
func testDataSetTablesAreAccessible() async throws {
466+
let ds = try await client.dataSet(
467+
"SELECT 1 AS ID, 'Alice' AS Name UNION ALL SELECT 2, 'Bob';" +
468+
"SELECT 100 AS Score;"
469+
)
470+
let table0 = ds[0]
471+
let table1 = ds[1]
472+
XCTAssertEqual(table0?.rowCount, 2)
473+
XCTAssertEqual(table1?.rowCount, 1)
474+
XCTAssertEqual(table1?[0, "Score"].anyValue as? Int32, 100)
475+
}
476+
477+
// MARK: - SQLDataSet Codable
478+
479+
func testDataSetCodableRoundTrip() async throws {
480+
let ds = try await client.dataSet("SELECT 1 AS A; SELECT 'hello' AS B;")
481+
let encoded = try JSONEncoder().encode(ds)
482+
let decoded = try JSONDecoder().decode(SQLDataSet.self, from: encoded)
483+
XCTAssertEqual(decoded.count, ds.count)
484+
XCTAssertEqual(decoded[0]?.rowCount, ds[0]?.rowCount)
485+
}
486+
487+
// MARK: - SQLCellValue
488+
489+
func testSQLCellValueEquality() {
490+
XCTAssertEqual(SQLCellValue.null, SQLCellValue.null)
491+
XCTAssertEqual(SQLCellValue.string("hi"), SQLCellValue.string("hi"))
492+
XCTAssertNotEqual(SQLCellValue.string("hi"), SQLCellValue.string("bye"))
493+
XCTAssertNotEqual(SQLCellValue.null, SQLCellValue.string(""))
494+
}
495+
496+
func testSQLCellValueAnyValueTypes() {
497+
XCTAssertNil(SQLCellValue.null.anyValue)
498+
XCTAssertEqual(SQLCellValue.string("x").anyValue as? String, "x")
499+
XCTAssertEqual(SQLCellValue.int32(7).anyValue as? Int32, 7)
500+
XCTAssertEqual(SQLCellValue.bool(true).anyValue as? Bool, true)
501+
XCTAssertEqual(SQLCellValue.double(3.14).anyValue as? Double, 3.14)
502+
}
503+
504+
func testSQLCellValueDisplayStringNull() {
505+
XCTAssertEqual(SQLCellValue.null.displayString, "")
506+
}
507+
508+
func testSQLCellValueDisplayStringBool() {
509+
XCTAssertEqual(SQLCellValue.bool(true).displayString, "true")
510+
XCTAssertEqual(SQLCellValue.bool(false).displayString, "false")
511+
}
512+
513+
func testSQLCellValueCodableRoundTrip() throws {
514+
let values: [SQLCellValue] = [
515+
.null,
516+
.string("hello"),
517+
.int32(42),
518+
.int64(9_999_999_999),
519+
.double(3.14),
520+
.bool(true),
521+
.decimal(Decimal(string: "123.456")!),
522+
.uuid(UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!),
523+
]
524+
for original in values {
525+
let data = try JSONEncoder().encode(original)
526+
let restored = try JSONDecoder().decode(SQLCellValue.self, from: data)
527+
XCTAssertEqual(restored, original, "Round-trip failed for \(original)")
528+
}
529+
}
530+
531+
func testSQLCellValueCodableDate() throws {
532+
let now = Date(timeIntervalSince1970: 1_700_000_000)
533+
let original = SQLCellValue.date(now)
534+
let data = try JSONEncoder().encode(original)
535+
let restored = try JSONDecoder().decode(SQLCellValue.self, from: data)
536+
if case .date(let d) = restored {
537+
XCTAssertEqual(d.timeIntervalSince1970, now.timeIntervalSince1970, accuracy: 0.001)
538+
} else {
539+
XCTFail("Expected .date after round-trip")
540+
}
541+
}
542+
543+
func testSQLCellValueCodableBytes() throws {
544+
let bytes = Data([0xDE, 0xAD, 0xBE, 0xEF])
545+
let original = SQLCellValue.bytes(bytes)
546+
let data = try JSONEncoder().encode(original)
547+
let restored = try JSONDecoder().decode(SQLCellValue.self, from: data)
548+
if case .bytes(let b) = restored {
549+
XCTAssertEqual(b, bytes)
550+
} else {
551+
XCTFail("Expected .bytes after round-trip")
552+
}
553+
}
192554
}

0 commit comments

Comments
 (0)