add swiftUI code

This commit is contained in:
zeus
2025-01-22 14:09:10 +08:00
parent 68e7b7347c
commit 8a99853829
2531 changed files with 486215 additions and 0 deletions
@@ -0,0 +1,476 @@
struct SQLTableGenerator {
var name: String
var options: TableOptions
var columnGenerators: [SQLColumnGenerator]
/// Used for auto-referencing foreign keys: we need to know the columns
/// of the primary key before they exist in the database schema, hence
/// the name of "forward" primary key columns.
///
/// If nil, the primary key is the hidden rowID.
var forwardPrimaryKeyColumns: [SQLColumnDescriptor]?
var primaryKeyConstraint: KeyConstraint?
var uniqueKeyConstraints: [KeyConstraint]
var foreignKeyConstraints: [SQLForeignKeyConstraint]
var checkConstraints: [SQLExpression]
var literalConstraints: [SQL]
var indexGenerators: [SQLIndexGenerator]
struct KeyConstraint {
var columns: [String]
var conflictResolution: Database.ConflictResolution?
}
func sql(_ db: Database) throws -> String {
var statements: [String] = []
do {
var chunks: [String] = []
chunks.append("CREATE")
if options.contains(.temporary) {
chunks.append("TEMPORARY")
}
chunks.append("TABLE")
if options.contains(.ifNotExists) {
chunks.append("IF NOT EXISTS")
}
chunks.append(name.quotedDatabaseIdentifier)
do {
var items: [String] = []
try items.append(contentsOf: columnGenerators.map {
try $0.sql(db, tableName: name, primaryKeyColumns: { forwardPrimaryKeyColumns })
})
if let constraint = primaryKeyConstraint {
var chunks: [String] = []
chunks.append("PRIMARY KEY")
chunks.append("(\(constraint.columns.map(\.quotedDatabaseIdentifier).joined(separator: ", ")))")
if let conflictResolution = constraint.conflictResolution {
chunks.append("ON CONFLICT")
chunks.append(conflictResolution.rawValue)
}
items.append(chunks.joined(separator: " "))
}
for constraint in uniqueKeyConstraints {
var chunks: [String] = []
chunks.append("UNIQUE")
chunks.append("(\(constraint.columns.map(\.quotedDatabaseIdentifier).joined(separator: ", ")))")
if let conflictResolution = constraint.conflictResolution {
chunks.append("ON CONFLICT")
chunks.append(conflictResolution.rawValue)
}
items.append(chunks.joined(separator: " "))
}
for constraint in foreignKeyConstraints {
var chunks: [String] = []
chunks.append("FOREIGN KEY")
chunks.append("(\(constraint.columns.map(\.quotedDatabaseIdentifier).joined(separator: ", ")))")
chunks.append("REFERENCES")
if let destinationColumns = constraint.destinationColumns {
chunks.append("""
\(constraint.destinationTable.quotedDatabaseIdentifier)(\
\(destinationColumns.map(\.quotedDatabaseIdentifier).joined(separator: ", "))\
)
""")
} else if constraint.destinationTable.lowercased() == name.lowercased() {
// autoreference
let forwardPrimaryKeyColumns = forwardPrimaryKeyColumns ?? [.rowID]
chunks.append("""
\(constraint.destinationTable.quotedDatabaseIdentifier)(\
\(forwardPrimaryKeyColumns.map(\.name.quotedDatabaseIdentifier).joined(separator: ", "))\
)
""")
} else {
let primaryKey = try db.primaryKey(constraint.destinationTable)
chunks.append("""
\(constraint.destinationTable.quotedDatabaseIdentifier)(\
\(primaryKey.columns.map(\.quotedDatabaseIdentifier).joined(separator: ", "))\
)
""")
}
if let deleteAction = constraint.deleteAction {
chunks.append("ON DELETE")
chunks.append(deleteAction.rawValue)
}
if let updateAction = constraint.updateAction {
chunks.append("ON UPDATE")
chunks.append(updateAction.rawValue)
}
if constraint.isDeferred {
chunks.append("DEFERRABLE INITIALLY DEFERRED")
}
items.append(chunks.joined(separator: " "))
}
for checkExpression in checkConstraints {
var chunks: [String] = []
try chunks.append("CHECK (\(checkExpression.quotedSQL(db)))")
items.append(chunks.joined(separator: " "))
}
for literal in literalConstraints {
let context = SQLGenerationContext(db, argumentsSink: .literalValues)
try items.append(literal.sql(context))
}
chunks.append("(\(items.joined(separator: ", ")))")
}
var tableOptions: [String] = []
#if GRDBCUSTOMSQLITE || GRDBCIPHER
if options.contains(.strict) {
tableOptions.append("STRICT")
}
#else
if #available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) { // SQLite 3.37+
if options.contains(.strict) {
tableOptions.append("STRICT")
}
}
#endif
if options.contains(.withoutRowID) {
tableOptions.append("WITHOUT ROWID")
}
if !tableOptions.isEmpty {
chunks.append(tableOptions.joined(separator: ", "))
}
statements.append(chunks.joined(separator: " "))
}
let indexStatements = try indexGenerators.map { try $0.sql(db) }
statements.append(contentsOf: indexStatements)
return statements.joined(separator: "; ")
}
private struct ForeignKeyGenerator {
var columnNames: [String]
var columnGenerators: [SQLColumnGenerator]
var foreignKeyConstraint: SQLForeignKeyConstraint?
var indexGenerator: SQLIndexGenerator?
}
}
extension SQLTableGenerator {
init(_ db: Database, table: TableDefinition) throws {
var indexOptions: IndexOptions = []
if table.options.contains(.ifNotExists) { indexOptions.insert(.ifNotExists) }
func makeKeyConstraint(
_ db: Database,
constraint: TableDefinition.KeyConstraint,
forwardPrimaryKey: SQLPrimaryKeyDescriptor)
throws -> SQLTableGenerator.KeyConstraint
{
try SQLTableGenerator.KeyConstraint(
columns: constraint.components.flatMap { component -> [String] in
switch component {
case let .columnName(columnName):
return [columnName]
case let .columnDefinition(column):
return [column.name]
case let .foreignKeyDefinition(foreignKey):
return try Self.makeForeignKeyGenerator(
db, foreignKey: foreignKey,
originTable: table.name,
forwardPrimaryKey: forwardPrimaryKey,
indexOptions: indexOptions).columnNames
}
},
conflictResolution: constraint.conflictResolution)
}
var forwardPrimaryKeyColumns: [SQLColumnDescriptor]?
if let primaryKeyConstraint = table.primaryKeyConstraint {
forwardPrimaryKeyColumns = try Self.forwardPrimaryKeyColumns(
db, primaryKeyConstraint: primaryKeyConstraint,
originTable: table.name)
} else {
for component in table.columnComponents {
if case let .columnDefinition(column) = component, column.primaryKey != nil {
forwardPrimaryKeyColumns = [SQLColumnDescriptor(column)]
break
}
}
}
let forwardPrimaryKey = SQLPrimaryKeyDescriptor(
tableName: table.name,
primaryKeyColumns: forwardPrimaryKeyColumns)
var columnGenerators: [SQLColumnGenerator] = []
var foreignKeyConstraints: [SQLForeignKeyConstraint] = []
var indexGenerators: [SQLIndexGenerator] = []
for component in table.columnComponents {
switch component {
case let .columnDefinition(column):
columnGenerators.append(.columnDefinition(column))
case let .columnLiteral(sql):
columnGenerators.append(.columnLiteral(sql))
case let .foreignKeyDefinition(foreignKey):
let fkGenerator = try Self.makeForeignKeyGenerator(
db, foreignKey: foreignKey,
originTable: table.name,
forwardPrimaryKey: forwardPrimaryKey,
indexOptions: indexOptions)
columnGenerators.append(contentsOf: fkGenerator.columnGenerators)
if let indexGenerator = fkGenerator.indexGenerator {
indexGenerators.append(indexGenerator)
}
if let foreignKeyConstraint = fkGenerator.foreignKeyConstraint {
foreignKeyConstraints.append(foreignKeyConstraint)
}
case let .foreignKeyConstraint(constraint):
foreignKeyConstraints.append(constraint)
}
}
for columnGenerator in columnGenerators {
if case let .columnDefinition(column) = columnGenerator,
let index = column.indexDefinition(in: table.name, options: indexOptions)
{
indexGenerators.append(SQLIndexGenerator(index: index))
}
}
try self.init(
name: table.name,
options: table.options,
columnGenerators: columnGenerators,
forwardPrimaryKeyColumns: forwardPrimaryKeyColumns,
primaryKeyConstraint: table.primaryKeyConstraint.map {
try makeKeyConstraint(db, constraint: $0, forwardPrimaryKey: forwardPrimaryKey)
},
uniqueKeyConstraints: table.uniqueKeyConstraints.map {
try makeKeyConstraint(db, constraint: $0, forwardPrimaryKey: forwardPrimaryKey)
},
foreignKeyConstraints: foreignKeyConstraints,
checkConstraints: table.checkConstraints,
literalConstraints: table.literalConstraints,
indexGenerators: indexGenerators)
}
private static func forwardPrimaryKeyColumns(
_ db: Database,
primaryKeyConstraint: TableDefinition.KeyConstraint,
originTable: String)
throws -> [SQLColumnDescriptor]?
{
var forwardPrimaryKeyColumns: [SQLColumnDescriptor] = []
for component in primaryKeyConstraint.components {
switch component {
case let .columnDefinition(column):
forwardPrimaryKeyColumns.append(SQLColumnDescriptor(column))
case let .foreignKeyDefinition(foreignKey):
let fkGenerator = try makeForeignKeyGenerator(
db, foreignKey: foreignKey,
originTable: originTable,
forwardPrimaryKey: nil, // not known yet, since we're building it
indexOptions: [])
for columnGenerator in fkGenerator.columnGenerators {
switch columnGenerator {
case let .columnDefinition(column):
forwardPrimaryKeyColumns.append(SQLColumnDescriptor(column))
case .columnLiteral:
// Unknown column name
return nil
}
}
case let .columnName(name):
forwardPrimaryKeyColumns.append(SQLColumnDescriptor(name: name, type: nil))
}
}
return forwardPrimaryKeyColumns
}
private static func makeForeignKeyGenerator(
_ db: Database,
foreignKey: ForeignKeyDefinition,
originTable: String,
forwardPrimaryKey: SQLPrimaryKeyDescriptor?,
indexOptions: IndexOptions)
throws -> ForeignKeyGenerator
{
let destinationPrimaryKey: SQLPrimaryKeyDescriptor
if let table = foreignKey.table {
if let forwardPrimaryKey,
originTable.lowercased() == table.lowercased()
{
// autoreference
destinationPrimaryKey = forwardPrimaryKey
} else {
destinationPrimaryKey = try foreignKey.primaryKey(db)
}
} else {
if let forwardPrimaryKey,
originTable.singularized.lowercased() == foreignKey.name.singularized.lowercased()
{
// autoreference
destinationPrimaryKey = forwardPrimaryKey
} else {
destinationPrimaryKey = try foreignKey.primaryKey(db)
}
}
guard let primaryKeyColumns = destinationPrimaryKey.primaryKeyColumns else {
// Destination table has an hidden rowID primary key
let columnName = foreignKey.name + "Id"
let column = ColumnDefinition(name: columnName, type: .integer).references(
destinationPrimaryKey.tableName,
onDelete: foreignKey.deleteAction,
onUpdate: foreignKey.updateAction,
deferred: foreignKey.isDeferred)
if let notNullConflictResolution = foreignKey.notNullConflictResolution {
column.notNull(onConflict: notNullConflictResolution)
}
switch foreignKey.indexing {
case nil:
break
case .index:
column.indexed()
case .unique:
column.unique()
}
return ForeignKeyGenerator(
columnNames: [columnName],
columnGenerators: [SQLColumnGenerator.columnDefinition(column)],
foreignKeyConstraint: nil,
indexGenerator: nil)
}
assert(!primaryKeyColumns.isEmpty)
let columnNames = primaryKeyColumns.map {
foreignKey.name + $0.name.uppercasingFirstCharacter
}
if primaryKeyColumns.count == 1 {
// Destination table has a single column primary key
let pkColumn = primaryKeyColumns[0]
let columnName = columnNames[0]
let column = ColumnDefinition(name: columnName, type: pkColumn.type).references(
destinationPrimaryKey.tableName,
column: pkColumn.name,
onDelete: foreignKey.deleteAction,
onUpdate: foreignKey.updateAction,
deferred: foreignKey.isDeferred)
if let notNullConflictResolution = foreignKey.notNullConflictResolution {
column.notNull(onConflict: notNullConflictResolution)
}
switch foreignKey.indexing {
case nil:
break
case .index:
column.indexed()
case .unique:
column.unique()
}
return ForeignKeyGenerator(
columnNames: [columnName],
columnGenerators: [SQLColumnGenerator.columnDefinition(column)],
foreignKeyConstraint: nil,
indexGenerator: nil)
} else {
// Destination table has a composite primary key
let columnGenerators = zip(primaryKeyColumns, columnNames).map { pkColumn, columnName in
let column = ColumnDefinition(name: columnName, type: pkColumn.type)
if let notNullConflictResolution = foreignKey.notNullConflictResolution {
column.notNull(onConflict: notNullConflictResolution)
}
return SQLColumnGenerator.columnDefinition(column)
}
let foreignKeyConstraint = SQLForeignKeyConstraint(
columns: columnNames,
destinationTable: destinationPrimaryKey.tableName,
destinationColumns: nil,
deleteAction: foreignKey.deleteAction,
updateAction: foreignKey.updateAction,
isDeferred: foreignKey.isDeferred)
let indexGenerator: SQLIndexGenerator?
switch foreignKey.indexing {
case nil:
indexGenerator = nil
case .index:
indexGenerator = SQLIndexGenerator(
name: Database.defaultIndexName(on: originTable, columns: columnNames),
table: originTable,
expressions: columnNames.map { .column($0) },
options: indexOptions,
condition: nil)
case .unique:
indexGenerator = SQLIndexGenerator(
name: Database.defaultIndexName(on: originTable, columns: columnNames),
table: originTable,
expressions: columnNames.map { .column($0) },
options: indexOptions.union([.unique]),
condition: nil)
}
return ForeignKeyGenerator(
columnNames: columnNames,
columnGenerators: columnGenerators,
foreignKeyConstraint: foreignKeyConstraint,
indexGenerator: indexGenerator)
}
}
}
struct SQLColumnDescriptor {
static let rowID = SQLColumnDescriptor(name: Column.rowID.name, type: .integer)
var name: String
var type: Database.ColumnType?
}
extension SQLColumnDescriptor {
init(_ column: ColumnInfo) {
self.init(
name: column.name,
type: column.columnType)
}
init(_ column: ColumnDefinition) {
self.init(name: column.name, type: column.type)
}
}
struct SQLForeignKeyConstraint {
var columns: [String]
var destinationTable: String
var destinationColumns: [String]?
var deleteAction: Database.ForeignKeyAction?
var updateAction: Database.ForeignKeyAction?
var isDeferred: Bool
}
struct SQLPrimaryKeyDescriptor {
/// The name of the forward-declared table
var tableName: String
/// If nil, the primary key is the hidden rowID.
var primaryKeyColumns: [SQLColumnDescriptor]?
}
extension SQLPrimaryKeyDescriptor {
static func find(_ db: Database, table: String) throws -> Self {
let columnInfos = try db.primaryKey(table).columnInfos
return SQLPrimaryKeyDescriptor(
tableName: table,
primaryKeyColumns: columnInfos.map { columnInfos in
columnInfos.map { SQLColumnDescriptor($0) }
})
}
}