Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Core/RowDecodingError.swift
T
2025-01-22 14:09:10 +08:00

206 lines
6.5 KiB
Swift

/// A key that is used to decode a value in a row
@usableFromInline
enum RowKey: Hashable, Sendable {
/// A column name
case columnName(String)
/// A column index
case columnIndex(Int)
/// A scope
case scope(String)
/// A key of prefetched rows
case prefetchKey(String)
}
/// A decoding error
@usableFromInline
enum RowDecodingError: Error {
@usableFromInline
struct Context: CustomDebugStringConvertible, Sendable {
/// A description of what went wrong, for debugging purposes.
@usableFromInline
let debugDescription: String
let rowImpl: ArrayRowImpl // Sendable
/// The row that could not be decoded
var row: Row { Row(impl: rowImpl) }
/// Nil for RowDecodingError.keyNotFound, in order to avoid redundancy
let key: RowKey?
/// The SQL query
let sql: String?
/// The SQL query arguments
let statementArguments: StatementArguments?
init(decodingContext: RowDecodingContext, debugDescription: String) {
self.debugDescription = debugDescription
self.rowImpl = ArrayRowImpl(columns: decodingContext.row)
self.key = decodingContext.key
self.sql = decodingContext.sql
self.statementArguments = decodingContext.statementArguments
}
}
case keyNotFound(RowKey, Context)
case valueMismatch(Any.Type, Context)
var context: Context {
switch self {
case .keyNotFound(_, let context),
.valueMismatch(_, let context):
return context
}
}
/// Convenience method that builds the
/// `could not decode <Type> from database value <value>` error message.
static func valueMismatch(
_ type: Any.Type,
context: RowDecodingContext,
databaseValue: DatabaseValue)
-> Self
{
valueMismatch(
type,
RowDecodingError.Context(decodingContext: context, debugDescription: """
could not decode \(type) from database value \(databaseValue)
"""))
}
/// Convenience method that builds the
/// `could not decode <Type> from database value <value>` error message.
@usableFromInline
static func valueMismatch(
_ type: Any.Type,
sqliteStatement: SQLiteStatement,
index: CInt,
context: RowDecodingContext)
-> Self
{
valueMismatch(
type,
context: context,
databaseValue: DatabaseValue(sqliteStatement: sqliteStatement, index: index))
}
/// Convenience method that builds the
/// `could not decode <Type> from database value <value>` error message.
static func valueMismatch(
_ type: Any.Type,
statement: Statement,
index: Int)
-> Self
{
valueMismatch(
type,
context: RowDecodingContext(statement: statement, index: index),
databaseValue: DatabaseValue(sqliteStatement: statement.sqliteStatement, index: CInt(index)))
}
/// Convenience method that builds the `column not found: <column>`
/// error message.
@usableFromInline
static func columnNotFound(_ columnName: String, context: RowDecodingContext) -> Self {
keyNotFound(
.columnName(columnName),
RowDecodingError.Context(decodingContext: context, debugDescription: """
column not found: \(String(reflecting: columnName))
"""))
}
}
@usableFromInline
struct RowDecodingContext {
/// The row that is decoded
let row: Row
let key: RowKey?
/// The SQL query
let sql: String?
/// The SQL query arguments
let statementArguments: StatementArguments?
@usableFromInline
init(row: Row, key: RowKey? = nil) {
if let statement = row.statement {
self.key = key
self.row = row.copy()
self.sql = statement.sql
self.statementArguments = statement.arguments
} else if let sqliteStatement = row.sqliteStatement {
self.key = key
self.row = row.copy()
self.sql = String(cString: sqlite3_sql(sqliteStatement)).trimmedSQLStatement
self.statementArguments = nil // Can't rebuild them
} else {
self.key = key
self.row = row.copy()
self.sql = nil
self.statementArguments = nil
}
}
/// Convenience initializer
@usableFromInline
init(statement: Statement, index: Int) {
self.key = .columnIndex(index)
self.row = Row(copiedFromSQLiteStatement: statement.sqliteStatement, statement: statement)
self.sql = statement.sql
self.statementArguments = statement.arguments
}
}
extension RowDecodingError: CustomStringConvertible {
@usableFromInline
var description: String {
let context = self.context
let row = context.row
var chunks: [String] = []
if let key = context.key {
switch key {
case let .columnIndex(columnIndex):
let rowIndex = row.index(row.startIndex, offsetBy: columnIndex)
let columnName = row.columnNames[rowIndex]
chunks.append("column: \(String(reflecting: columnName))")
chunks.append("column index: \(columnIndex)")
case let .columnName(columnName):
if let columnIndex = row.index(forColumn: columnName) {
chunks.append("column: \(String(reflecting: columnName))")
chunks.append("column index: \(columnIndex)")
} else {
// column name is already mentioned in context.debugDescription
}
case .prefetchKey:
// key is already mentioned in context.debugDescription
break
case .scope:
// scope is already mentioned in context.debugDescription
break
}
}
chunks.append("row: \(row.description)")
if let sql = context.sql {
chunks.append("sql: `\(sql)`")
}
if let statementArguments = context.statementArguments {
chunks.append("arguments: \(statementArguments)")
}
return "\(context.debugDescription) - \(chunks.joined(separator: ", "))"
}
}