Files
SwiftDBAI/Sources/SwiftDBAI/Schema/DatabaseSchema.swift
Krishna Kumar fcd752466a SwiftDBAI: natural language queries for any SQLite database
Drop-in SwiftUI chat view, headless ChatEngine, LLM-agnostic via
AnyLanguageModel. Read-only by default with configurable allowlists.
Robust SQL parser with 63 tests. Includes demo app with GitHub stars dataset.
2026-04-05 17:11:12 -05:00

165 lines
4.9 KiB
Swift

// DatabaseSchema.swift
// SwiftDBAI
//
// Auto-introspected SQLite schema model types.
import Foundation
/// Complete schema representation of an SQLite database.
public struct DatabaseSchema: Sendable, Equatable {
/// All tables in the database, keyed by table name.
public let tables: [String: TableSchema]
/// Ordered table names (preserves discovery order).
public let tableNames: [String]
/// Returns a compact text description suitable for LLM system prompts.
public var schemaDescription: String {
var lines: [String] = []
for name in tableNames {
guard let table = tables[name] else { continue }
lines.append(table.descriptionForLLM)
}
return lines.joined(separator: "\n\n")
}
/// Returns a description suitable for LLM system prompts.
/// Alias for `schemaDescription` for API compatibility.
public func describeForLLM() -> String {
schemaDescription
}
public init(tables: [String: TableSchema], tableNames: [String]) {
self.tables = tables
self.tableNames = tableNames
}
}
/// Schema for a single SQLite table.
public struct TableSchema: Sendable, Equatable {
public let name: String
public let columns: [ColumnSchema]
public let primaryKey: [String]
public let foreignKeys: [ForeignKeySchema]
public let indexes: [IndexSchema]
/// Text description for embedding in LLM prompts.
public var descriptionForLLM: String {
var parts: [String] = []
let colDefs = columns.map { col in
var def = " \(col.name) \(col.type)"
if col.isPrimaryKey { def += " PRIMARY KEY" }
if col.isNotNull { def += " NOT NULL" }
if let defaultValue = col.defaultValue { def += " DEFAULT \(defaultValue)" }
return def
}
parts.append("TABLE \(name) (\n\(colDefs.joined(separator: ",\n"))\n)")
if !foreignKeys.isEmpty {
let fkDescs = foreignKeys.map {
" FOREIGN KEY (\($0.fromColumn)) REFERENCES \($0.toTable)(\($0.toColumn))"
}
parts.append("FOREIGN KEYS:\n\(fkDescs.joined(separator: "\n"))")
}
if !indexes.isEmpty {
let idxDescs = indexes.map {
" INDEX \($0.name) ON (\($0.columns.joined(separator: ", ")))\($0.isUnique ? " UNIQUE" : "")"
}
parts.append("INDEXES:\n\(idxDescs.joined(separator: "\n"))")
}
return parts.joined(separator: "\n")
}
public init(
name: String,
columns: [ColumnSchema],
primaryKey: [String],
foreignKeys: [ForeignKeySchema],
indexes: [IndexSchema]
) {
self.name = name
self.columns = columns
self.primaryKey = primaryKey
self.foreignKeys = foreignKeys
self.indexes = indexes
}
}
/// Schema for a single column.
public struct ColumnSchema: Sendable, Equatable {
/// Column position (0-based).
public let cid: Int
/// Column name.
public let name: String
/// Declared SQLite type (e.g. "TEXT", "INTEGER", "REAL", "BLOB").
public let type: String
/// Whether the column has a NOT NULL constraint.
public let isNotNull: Bool
/// Default value expression, if any.
public let defaultValue: String?
/// Whether this column is part of the primary key.
public let isPrimaryKey: Bool
public init(
cid: Int,
name: String,
type: String,
isNotNull: Bool,
defaultValue: String?,
isPrimaryKey: Bool
) {
self.cid = cid
self.name = name
self.type = type
self.isNotNull = isNotNull
self.defaultValue = defaultValue
self.isPrimaryKey = isPrimaryKey
}
}
/// Schema for a foreign key relationship.
public struct ForeignKeySchema: Sendable, Equatable {
/// Column in the source table.
public let fromColumn: String
/// Referenced table name.
public let toTable: String
/// Referenced column name.
public let toColumn: String
/// ON UPDATE action (e.g. "CASCADE", "NO ACTION").
public let onUpdate: String
/// ON DELETE action.
public let onDelete: String
public init(
fromColumn: String,
toTable: String,
toColumn: String,
onUpdate: String,
onDelete: String
) {
self.fromColumn = fromColumn
self.toTable = toTable
self.toColumn = toColumn
self.onUpdate = onUpdate
self.onDelete = onDelete
}
}
/// Schema for a database index.
public struct IndexSchema: Sendable, Equatable {
/// Index name.
public let name: String
/// Whether the index enforces uniqueness.
public let isUnique: Bool
/// Columns included in the index, in order.
public let columns: [String]
public init(name: String, isUnique: Bool, columns: [String]) {
self.name = name
self.isUnique = isUnique
self.columns = columns
}
}