Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Documentation.docc/Extension/DatabaseValueConvertible.md
T
2025-01-28 12:28:03 +08:00

184 lines
6.3 KiB
Markdown

# ``GRDB/DatabaseValueConvertible``
A type that can convert itself into and out of a database value.
## Overview
A `DatabaseValueConvertible` type supports conversion to and from database values (null, integers, doubles, strings, and blobs). `DatabaseValueConvertible` is adopted by `Bool`, `Int`, `String`, `Date`, etc.
> Note: Types that converts to and from multiple columns in a database row must not conform to the `DatabaseValueConvertible` protocol. Those types are called **record types**, and should conform to record protocols instead. See <doc:QueryInterface>.
> Note: Standard collections `Array`, `Set`, and `Dictionary` do not conform to `DatabaseValueConvertible`. To store arrays, sets, or dictionaries in individual database values, wrap them as properties of `Codable` record types. They will automatically be stored as JSON objects and arrays. See <doc:QueryInterface>.
## Conforming to the DatabaseValueConvertible Protocol
To conform to `DatabaseValueConvertible`, implement the two requirements ``fromDatabaseValue(_:)-21zzv`` and ``databaseValue-1ob9k``. Do not customize the ``fromMissingColumn()-7iamp`` requirement. If your type `MyValue` conforms, then the conformance of the optional type `MyValue?` is automatic.
The implementation of `fromDatabaseValue` must return nil if the type can not be decoded from the raw database value. This nil value will have GRDB throw a decoding error accordingly.
For example:
```swift
struct EvenInteger {
let value: Int // Guaranteed even
init?(_ value: Int) {
guard value.isMultiple(of: 2) else {
return nil // Not an even number
}
self.value = value
}
}
extension EvenInteger: DatabaseValueConvertible {
var databaseValue: DatabaseValue {
value.databaseValue
}
static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
guard let value = Int.fromDatabaseValue(dbValue) else {
return nil // Not an integer
}
return EvenInteger(value) // Nil if not even
}
}
```
### Built-in RawRepresentable support
`DatabaseValueConvertible` implementation is ready-made for `RawRepresentable` types whose raw value is itself `DatabaseValueConvertible`, such as enums:
```swift
enum Grape: String {
case chardonnay, merlot, riesling
}
// Encodes and decodes `Grape` as a string in the database:
extension Grape: DatabaseValueConvertible { }
```
### Built-in Codable support
`DatabaseValueConvertible` is also ready-made for `Codable` types, which are automatically coded and decoded from JSON arrays and objects:
```swift
struct Color: Codable {
var red: Double
var green: Double
var blue: Double
}
// Encodes and decodes `Color` as a JSON object in the database:
extension Color: DatabaseValueConvertible { }
```
By default, such codable value types are encoded and decoded with the standard [JSONEncoder](https://developer.apple.com/documentation/foundation/jsonencoder) and [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder). `Data` values are handled with the `.base64` strategy, `Date` with the `.millisecondsSince1970` strategy, and non conforming floats with the `.throw` strategy.
To customize the JSON format, provide an explicit implementation for the `DatabaseValueConvertible` requirements, or implement these two methods:
```swift
protocol DatabaseValueConvertible {
static func databaseJSONDecoder() -> JSONDecoder
static func databaseJSONEncoder() -> JSONEncoder
}
```
### Adding support for the Tagged library
[Tagged](https://github.com/pointfreeco/swift-tagged) is a popular library that makes it possible to enhance the type-safety of our programs with dedicated wrappers around basic types. For example:
```swift
import Tagged
struct Player: Identifiable {
// Thanks to Tagged, Player.ID can not be mismatched with Team.ID or
// Award.ID, even though they all wrap strings.
typealias ID = Tagged<Player, String>
var id: ID
var name: String
var score: Int
}
```
Applications that use both Tagged and GRDB will want to add those lines somewhere:
```swift
import GRDB
import Tagged
// Add database support to Tagged values
extension Tagged: SQLExpressible where RawValue: SQLExpressible { }
extension Tagged: StatementBinding where RawValue: StatementBinding { }
extension Tagged: StatementColumnConvertible where RawValue: StatementColumnConvertible { }
extension Tagged: DatabaseValueConvertible where RawValue: DatabaseValueConvertible { }
```
This makes it possible to use `Tagged` values in all the expected places:
```swift
let id: Player.ID = ...
let player = try Player.find(db, id: id)
```
## Optimized Values
For extra performance, custom value types can conform to both `DatabaseValueConvertible` and ``StatementColumnConvertible``. This extra protocol grants raw access to the [low-level C SQLite interface](https://www.sqlite.org/c3ref/column_blob.html) when decoding values.
For example:
```swift
extension EvenInteger: StatementColumnConvertible {
init?(sqliteStatement: SQLiteStatement, index: CInt) {
let int64 = sqlite3_column_int64(sqliteStatement, index)
guard let value = Int(exactly: int64) else {
return nil // Does not fit Int (probably a 32-bit architecture)
}
self.init(value) // Nil if not even
}
}
```
This extra conformance is not required: only aim at the low-level C interface if you have identified a performance issue after profiling your application!
## Topics
### Creating a Value
- ``fromDatabaseValue(_:)-21zzv``
- ``fromMissingColumn()-7iamp``
### Accessing the DatabaseValue
- ``databaseValue-1ob9k``
### Configuring the JSON format for the standard Decodable protocol
- ``databaseJSONDecoder()-7zou9``
- ``databaseJSONEncoder()-37sff``
### Fetching Values from Raw SQL
- ``fetchCursor(_:sql:arguments:adapter:)-6elcz``
- ``fetchAll(_:sql:arguments:adapter:)-1cqyb``
- ``fetchSet(_:sql:arguments:adapter:)-5jene``
- ``fetchOne(_:sql:arguments:adapter:)-qvqp``
### Fetching Values from a Prepared Statement
- ``fetchCursor(_:arguments:adapter:)-4l6af``
- ``fetchAll(_:arguments:adapter:)-3abuc``
- ``fetchSet(_:arguments:adapter:)-6y54n``
- ``fetchOne(_:arguments:adapter:)-3d7ax``
### Fetching Values from a Request
- ``fetchCursor(_:_:)-8q4r6``
- ``fetchAll(_:_:)-9hkqs``
- ``fetchSet(_:_:)-1foke``
- ``fetchOne(_:_:)-o6yj``
### Supporting Types
- ``DatabaseValueCursor``
- ``StatementBinding``