Add CI/CD configuration and API documentation
This commit is contained in:
+183
@@ -0,0 +1,183 @@
|
||||
# ``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``
|
||||
Reference in New Issue
Block a user