Initial implementation of SwiftDBAI

Chat with any SQLite database using natural language. Built on
AnyLanguageModel (HuggingFace) for LLM-agnostic provider support
and GRDB for SQLite access.

Core features:
- Auto schema introspection from sqlite_master (zero config)
- NL → SQL generation via any AnyLanguageModel provider
- Three rendering modes: text summary, data table, Swift Charts
- Drop-in DataChatView (SwiftUI) and headless ChatEngine
- Operation allowlist with read-only default
- Mutation policy with per-table control
- ToolExecutionDelegate for destructive operation confirmation
- Multi-turn conversation context
- 352 tests across 24 suites, all passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krishna Kumar
2026-04-04 09:30:56 -05:00
commit b1724fe7ca
55 changed files with 15506 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
// ScrollableDataTableViewTests.swift
// SwiftDBAITests
//
// Tests for the ScrollableDataTableView component.
import Foundation
import Testing
@testable import SwiftDBAI
@Suite("ScrollableDataTableView")
@MainActor
struct ScrollableDataTableViewTests {
// MARK: - Test Helpers
private func makeDataTable(
columnNames: [String] = ["id", "name", "score"],
inferredTypes: [DataTable.InferredType] = [.integer, .text, .real],
rowCount: Int = 5
) -> DataTable {
let columns = columnNames.enumerated().map { idx, name in
DataTable.Column(name: name, index: idx, inferredType: inferredTypes[idx])
}
let rows = (0..<rowCount).map { i in
DataTable.Row(
id: i,
values: [
.integer(Int64(i + 1)),
.text("Item \(i + 1)"),
.real(Double(i) * 10.5),
],
columnNames: columnNames
)
}
return DataTable(columns: columns, rows: rows, sql: "SELECT * FROM test", executionTime: 0.015)
}
private func makeEmptyDataTable() -> DataTable {
DataTable(columns: [], rows: [], sql: "", executionTime: 0)
}
// MARK: - Initialization Tests
@Test("Initializes with default parameters")
func initWithDefaults() {
let table = makeDataTable()
let view = ScrollableDataTableView(dataTable: table)
#expect(view.minimumColumnWidth == 80)
#expect(view.maximumColumnWidth == 250)
#expect(view.showAlternatingRows == true)
#expect(view.showFooter == true)
}
@Test("Initializes with custom parameters")
func initWithCustomParams() {
let table = makeDataTable()
let view = ScrollableDataTableView(
dataTable: table,
minimumColumnWidth: 100,
maximumColumnWidth: 300,
showAlternatingRows: false,
showFooter: false
)
#expect(view.minimumColumnWidth == 100)
#expect(view.maximumColumnWidth == 300)
#expect(view.showAlternatingRows == false)
#expect(view.showFooter == false)
}
@Test("Handles empty data table")
func handlesEmptyTable() {
let table = makeEmptyDataTable()
let view = ScrollableDataTableView(dataTable: table)
#expect(view.dataTable.isEmpty)
}
@Test("Handles single row table")
func handlesSingleRow() {
let table = makeDataTable(rowCount: 1)
let view = ScrollableDataTableView(dataTable: table)
#expect(view.dataTable.rowCount == 1)
#expect(view.dataTable.columnCount == 3)
}
@Test("Handles single column table")
func handlesSingleColumn() {
let columns = [DataTable.Column(name: "count", index: 0, inferredType: .integer)]
let rows = [
DataTable.Row(id: 0, values: [.integer(42)], columnNames: ["count"])
]
let table = DataTable(columns: columns, rows: rows, sql: "SELECT count(*) FROM t", executionTime: 0.001)
let view = ScrollableDataTableView(dataTable: table)
#expect(view.dataTable.columnCount == 1)
#expect(view.dataTable.rowCount == 1)
}
@Test("Handles large number of rows")
func handlesLargeRowCount() {
let table = makeDataTable(rowCount: 1000)
let view = ScrollableDataTableView(dataTable: table)
#expect(view.dataTable.rowCount == 1000)
}
@Test("Handles null values in cells")
func handlesNullValues() {
let columns = [
DataTable.Column(name: "name", index: 0, inferredType: .text),
DataTable.Column(name: "value", index: 1, inferredType: .null),
]
let rows = [
DataTable.Row(id: 0, values: [.text("test"), .null], columnNames: ["name", "value"])
]
let table = DataTable(columns: columns, rows: rows)
let view = ScrollableDataTableView(dataTable: table)
#expect(view.dataTable.rows[0][1] == .null)
}
@Test("Handles blob values in cells")
func handlesBlobValues() {
let columns = [
DataTable.Column(name: "data", index: 0, inferredType: .blob),
]
let blobData = Data([0x00, 0xFF, 0xAB])
let rows = [
DataTable.Row(id: 0, values: [.blob(blobData)], columnNames: ["data"])
]
let table = DataTable(columns: columns, rows: rows)
let view = ScrollableDataTableView(dataTable: table)
#expect(view.dataTable.rows[0][0] == QueryResult.Value.blob(blobData))
}
}