add swiftUI code
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
#if canImport(CoreGraphics)
|
||||
import CoreGraphics
|
||||
|
||||
/// CGFloat adopts DatabaseValueConvertible
|
||||
extension CGFloat: DatabaseValueConvertible {
|
||||
/// Returns a REAL database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Double(self).databaseValue
|
||||
}
|
||||
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> CGFloat? {
|
||||
guard let double = Double.fromDatabaseValue(dbValue) else {
|
||||
return nil
|
||||
}
|
||||
return CGFloat(double)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
import Foundation
|
||||
|
||||
/// Data is convertible to and from DatabaseValue.
|
||||
extension Data: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
public init(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
if let bytes = sqlite3_column_blob(sqliteStatement, index) {
|
||||
let count = Int(sqlite3_column_bytes(sqliteStatement, index))
|
||||
self.init(bytes: bytes, count: count) // copy bytes
|
||||
} else {
|
||||
self.init()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a BLOB database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
DatabaseValue(storage: .blob(self))
|
||||
}
|
||||
|
||||
/// Returns a `Data` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a data blob, returns it.
|
||||
///
|
||||
/// If the database value contains a string, returns this string converted
|
||||
/// to UTF8 data.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Data? {
|
||||
switch dbValue.storage {
|
||||
case .blob(let data):
|
||||
return data
|
||||
case .string(let string):
|
||||
// Implicit conversion from string to blob, just as SQLite does
|
||||
// See <https://www.sqlite.org/c3ref/column_blob.html>
|
||||
return string.data(using: .utf8)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
withUnsafeBytes {
|
||||
sqlite3_bind_blob(sqliteStatement, index, $0.baseAddress, CInt($0.count), SQLITE_TRANSIENT)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the given closure after binding a statement argument.
|
||||
///
|
||||
/// The binding is valid only during the execution of this method.
|
||||
///
|
||||
/// - parameter sqliteStatement: An SQLite statement.
|
||||
/// - parameter index: 1-based index to statement arguments.
|
||||
/// - parameter body: The closure to execute when argument is bound.
|
||||
func withBinding<T>(to sqliteStatement: SQLiteStatement, at index: CInt, do body: () throws -> T) throws -> T {
|
||||
try withUnsafeBytes {
|
||||
let code = sqlite3_bind_blob(
|
||||
sqliteStatement, index,
|
||||
$0.baseAddress, CInt($0.count), nil /* SQLITE_STATIC */)
|
||||
try checkBindingSuccess(code: code, sqliteStatement: sqliteStatement)
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Conversions
|
||||
|
||||
extension Data {
|
||||
static func fastDecodeNoCopy(
|
||||
fromStatement sqliteStatement: SQLiteStatement,
|
||||
atUncheckedIndex index: CInt,
|
||||
context: @autoclosure () -> RowDecodingContext)
|
||||
throws -> Data
|
||||
{
|
||||
guard sqlite3_column_type(sqliteStatement, index) != SQLITE_NULL else {
|
||||
throw RowDecodingError.valueMismatch(
|
||||
Data.self,
|
||||
sqliteStatement: sqliteStatement,
|
||||
index: index,
|
||||
context: context())
|
||||
}
|
||||
guard let bytes = sqlite3_column_blob(sqliteStatement, index) else {
|
||||
return Data()
|
||||
}
|
||||
let count = Int(sqlite3_column_bytes(sqliteStatement, index))
|
||||
return Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), count: count, deallocator: .none)
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
import Foundation
|
||||
|
||||
/// A database value that holds date components.
|
||||
public struct DatabaseDateComponents: Sendable {
|
||||
|
||||
/// The SQLite formats for date components.
|
||||
public enum Format: String, Sendable {
|
||||
|
||||
/// The format "yyyy-MM-dd".
|
||||
case YMD = "yyyy-MM-dd"
|
||||
|
||||
/// The format "yyyy-MM-dd HH:mm".
|
||||
///
|
||||
/// This format is lexically comparable with SQLite's CURRENT_TIMESTAMP.
|
||||
case YMD_HM = "yyyy-MM-dd HH:mm"
|
||||
|
||||
/// The format "yyyy-MM-dd HH:mm:ss".
|
||||
///
|
||||
/// This format is lexically comparable with SQLite's CURRENT_TIMESTAMP.
|
||||
case YMD_HMS = "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
/// The format "yyyy-MM-dd HH:mm:ss.SSS".
|
||||
///
|
||||
/// This format is lexically comparable with SQLite's CURRENT_TIMESTAMP.
|
||||
case YMD_HMSS = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
|
||||
/// The format "HH:mm".
|
||||
case HM = "HH:mm"
|
||||
|
||||
/// The format "HH:mm:ss".
|
||||
case HMS = "HH:mm:ss"
|
||||
|
||||
/// The format "HH:mm:ss.SSS".
|
||||
case HMSS = "HH:mm:ss.SSS"
|
||||
|
||||
var hasYMDComponents: Bool {
|
||||
switch self {
|
||||
case .YMD, .YMD_HM, .YMD_HMS, .YMD_HMSS:
|
||||
return true
|
||||
case .HM, .HMS, .HMSS:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The date components
|
||||
public let dateComponents: DateComponents
|
||||
|
||||
/// The database format
|
||||
public let format: Format
|
||||
|
||||
/// Creates a DatabaseDateComponents from a DateComponents and a format.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - dateComponents: An optional DateComponents.
|
||||
/// - format: The format used for storing the date components in
|
||||
/// the database.
|
||||
public init(_ dateComponents: DateComponents, format: Format) {
|
||||
self.format = format
|
||||
self.dateComponents = dateComponents
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseDateComponents: StatementColumnConvertible {
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
guard let cString = sqlite3_column_text(sqliteStatement, index) else {
|
||||
return nil
|
||||
}
|
||||
let length = Int(sqlite3_column_bytes(sqliteStatement, index)) // avoid an strlen
|
||||
let components = cString.withMemoryRebound(
|
||||
to: CChar.self,
|
||||
capacity: length + 1 /* trailing \0 */) { cString in
|
||||
SQLiteDateParser().components(cString: cString, length: length)
|
||||
}
|
||||
guard let components else {
|
||||
return nil
|
||||
}
|
||||
self.init(components.dateComponents, format: components.format)
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseDateComponents: DatabaseValueConvertible {
|
||||
/// Returns a TEXT database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
let dateString: String?
|
||||
switch format {
|
||||
case .YMD_HM, .YMD_HMS, .YMD_HMSS, .YMD:
|
||||
let year = dateComponents.year ?? 0
|
||||
let month = dateComponents.month ?? 1
|
||||
let day = dateComponents.day ?? 1
|
||||
dateString = String(format: "%04d-%02d-%02d", year, month, day)
|
||||
default:
|
||||
dateString = nil
|
||||
}
|
||||
|
||||
let timeString: String?
|
||||
switch format {
|
||||
case .YMD_HM, .HM:
|
||||
let hour = dateComponents.hour ?? 0
|
||||
let minute = dateComponents.minute ?? 0
|
||||
timeString = String(format: "%02d:%02d", hour, minute)
|
||||
case .YMD_HMS, .HMS:
|
||||
let hour = dateComponents.hour ?? 0
|
||||
let minute = dateComponents.minute ?? 0
|
||||
let second = dateComponents.second ?? 0
|
||||
timeString = String(format: "%02d:%02d:%02d", hour, minute, second)
|
||||
case .YMD_HMSS, .HMSS:
|
||||
let hour = dateComponents.hour ?? 0
|
||||
let minute = dateComponents.minute ?? 0
|
||||
let second = dateComponents.second ?? 0
|
||||
let nanosecond = dateComponents.nanosecond ?? 0
|
||||
timeString = String(
|
||||
format: "%02d:%02d:%02d.%03d",
|
||||
hour, minute, second, Int(round(Double(nanosecond) / 1_000_000.0)))
|
||||
default:
|
||||
timeString = nil
|
||||
}
|
||||
|
||||
return [dateString, timeString].compactMap { $0 }.joined(separator: " ").databaseValue
|
||||
}
|
||||
|
||||
/// Creates a `DatabaseDateComponents` from the specified database value.
|
||||
///
|
||||
/// The supported formats are:
|
||||
///
|
||||
/// - `YYYY-MM-DD`
|
||||
/// - `YYYY-MM-DD HH:MM`
|
||||
/// - `YYYY-MM-DD HH:MM:SS`
|
||||
/// - `YYYY-MM-DD HH:MM:SS.SSS`
|
||||
/// - `YYYY-MM-DDTHH:MM`
|
||||
/// - `YYYY-MM-DDTHH:MM:SS`
|
||||
/// - `YYYY-MM-DDTHH:MM:SS.SSS`
|
||||
/// - `HH:MM`
|
||||
/// - `HH:MM:SS`
|
||||
/// - `HH:MM:SS.SSS`
|
||||
///
|
||||
/// Related SQLite documentation: <https://www.sqlite.org/lang_datefunc.html>
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> DatabaseDateComponents? {
|
||||
guard let string = String.fromDatabaseValue(dbValue) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return SQLiteDateParser().components(from: string)
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseDateComponents: Decodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let stringValue = try container.decode(String.self)
|
||||
guard let decodedValue = DatabaseDateComponents.fromDatabaseValue(stringValue.databaseValue) else {
|
||||
throw DecodingError.dataCorruptedError(in: container,
|
||||
debugDescription: "Unable to initialise databaseDateComponent")
|
||||
}
|
||||
self = decodedValue
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseDateComponents: Encodable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(String.fromDatabaseValue(databaseValue)!)
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import Foundation
|
||||
|
||||
/// DatabaseValueConvertible is free for ReferenceConvertible types whose
|
||||
/// ReferenceType is itself DatabaseValueConvertible.
|
||||
///
|
||||
/// class FooReference { ... }
|
||||
/// struct Foo : ReferenceConvertible {
|
||||
/// typealias ReferenceType = FooReference
|
||||
/// }
|
||||
///
|
||||
/// // If the ReferenceType adopts DatabaseValueConvertible...
|
||||
/// extension FooReference : DatabaseValueConvertible { ... }
|
||||
///
|
||||
/// // ... then the ReferenceConvertible type can freely adopt DatabaseValueConvertible:
|
||||
/// extension Foo : DatabaseValueConvertible { /* empty */ }
|
||||
extension DatabaseValueConvertible where Self: ReferenceConvertible, Self.ReferenceType: DatabaseValueConvertible {
|
||||
|
||||
public var databaseValue: DatabaseValue {
|
||||
(self as! ReferenceType).databaseValue
|
||||
}
|
||||
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
ReferenceType.fromDatabaseValue(dbValue).flatMap { cast($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseValueConvertible
|
||||
where
|
||||
Self: Decodable & ReferenceConvertible,
|
||||
Self.ReferenceType: DatabaseValueConvertible
|
||||
{
|
||||
public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? {
|
||||
// Preserve custom database decoding
|
||||
return ReferenceType.fromDatabaseValue(databaseValue).flatMap { cast($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseValueConvertible
|
||||
where
|
||||
Self: Encodable & ReferenceConvertible,
|
||||
Self.ReferenceType: DatabaseValueConvertible
|
||||
{
|
||||
public var databaseValue: DatabaseValue {
|
||||
// Preserve custom database encoding
|
||||
return (self as! ReferenceType).databaseValue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import Foundation
|
||||
|
||||
#if !os(Linux)
|
||||
/// NSDate is stored in the database using the format
|
||||
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
|
||||
extension NSDate: DatabaseValueConvertible {
|
||||
/// Returns a TEXT database value that contains the date encoded as
|
||||
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
|
||||
public var databaseValue: DatabaseValue {
|
||||
(self as Date).databaseValue
|
||||
}
|
||||
|
||||
/// Creates an `NSDate` with the specified database value.
|
||||
///
|
||||
/// If the database value contains a number, that number is interpreted as a
|
||||
/// timeinterval since 00:00:00 UTC on 1 January 1970.
|
||||
///
|
||||
/// If the database value contains a string, that string is interpreted as a
|
||||
/// [SQLite date](https://sqlite.org/lang_datefunc.html) in the UTC time
|
||||
/// zone. Nil is returned if the date string does not contain at least the
|
||||
/// year, month and day components. Other components (minutes, etc.)
|
||||
/// are set to zero if missing.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
guard let date = Date.fromDatabaseValue(dbValue) else {
|
||||
return nil
|
||||
}
|
||||
return cast(date)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Date is stored in the database using the format
|
||||
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
|
||||
extension Date: DatabaseValueConvertible {
|
||||
/// Returns a TEXT database value that contains the date encoded as
|
||||
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
|
||||
public var databaseValue: DatabaseValue {
|
||||
storageDateFormatter.string(from: self).databaseValue
|
||||
}
|
||||
|
||||
/// Creates an `Date` with the specified database value.
|
||||
///
|
||||
/// If the database value contains a number, that number is interpreted as a
|
||||
/// timeinterval since 00:00:00 UTC on 1 January 1970.
|
||||
///
|
||||
/// If the database value contains a string, that string is interpreted as a
|
||||
/// [SQLite date](https://sqlite.org/lang_datefunc.html) in the UTC time
|
||||
/// zone. Nil is returned if the date string does not contain at least the
|
||||
/// year, month and day components. Other components (minutes, etc.)
|
||||
/// are set to zero if missing.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Date? {
|
||||
if let databaseDateComponents = DatabaseDateComponents.fromDatabaseValue(dbValue) {
|
||||
return Date(databaseDateComponents: databaseDateComponents)
|
||||
}
|
||||
if let timestamp = Double.fromDatabaseValue(dbValue) {
|
||||
return Date(timeIntervalSince1970: timestamp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
init?(databaseDateComponents: DatabaseDateComponents) {
|
||||
guard databaseDateComponents.format.hasYMDComponents else {
|
||||
// Refuse to turn hours without any date information into Date:
|
||||
return nil
|
||||
}
|
||||
guard let date = UTCCalendar.date(from: databaseDateComponents.dateComponents) else {
|
||||
return nil
|
||||
}
|
||||
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
|
||||
}
|
||||
|
||||
/// Creates a date from a [Julian Day](https://en.wikipedia.org/wiki/Julian_day).
|
||||
public init?(julianDay: Double) {
|
||||
// Conversion uses the same algorithm as SQLite: https://www.sqlite.org/src/artifact/8ec787fed4929d8c
|
||||
// TODO: check for overflows one day, and return nil when computation can't complete.
|
||||
let JD = Int64(julianDay * 86400000)
|
||||
let Z = Int(((JD + 43200000)/86400000))
|
||||
var A = Int(((Double(Z) - 1867216.25)/36524.25))
|
||||
A = Z + 1 + A - (A/4)
|
||||
let B = A + 1524
|
||||
let C = Int(((Double(B) - 122.1)/365.25))
|
||||
let D = (36525*(C&32767))/100
|
||||
let E = Int((Double(B-D)/30.6001))
|
||||
let X1 = Int((30.6001*Double(E)))
|
||||
let day = B - D - X1
|
||||
let month = E<14 ? E-1 : E-13
|
||||
let year = month>2 ? C - 4716 : C - 4715
|
||||
var s = Int(((JD + 43200000) % 86400000))
|
||||
var second = Double(s)/1000.0
|
||||
s = Int(second)
|
||||
second -= Double(s)
|
||||
let hour = s/3600
|
||||
s -= hour*3600
|
||||
let minute = s/60
|
||||
second += Double(s - minute*60)
|
||||
|
||||
var dateComponents = DateComponents()
|
||||
dateComponents.year = year
|
||||
dateComponents.month = month
|
||||
dateComponents.day = day
|
||||
dateComponents.hour = hour
|
||||
dateComponents.minute = minute
|
||||
dateComponents.second = Int(second)
|
||||
dateComponents.nanosecond = Int((second - Double(Int(second))) * 1.0e9)
|
||||
|
||||
guard let date = UTCCalendar.date(from: dateComponents) else {
|
||||
return nil
|
||||
}
|
||||
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Date: StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_INTEGER, SQLITE_FLOAT:
|
||||
self.init(timeIntervalSince1970: sqlite3_column_double(sqliteStatement, index))
|
||||
case SQLITE_TEXT:
|
||||
guard let components = DatabaseDateComponents(sqliteStatement: sqliteStatement, index: index),
|
||||
let date = Date(databaseDateComponents: components)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The DatabaseDate date formatter for stored dates.
|
||||
private let storageDateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
return formatter
|
||||
}()
|
||||
|
||||
// The NSCalendar for stored dates.
|
||||
private let UTCCalendar: Calendar = {
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
calendar.locale = Locale(identifier: "en_US_POSIX")
|
||||
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
|
||||
return calendar
|
||||
}()
|
||||
@@ -0,0 +1,59 @@
|
||||
#if !os(Linux)
|
||||
import Foundation
|
||||
|
||||
/// Decimal adopts DatabaseValueConvertible
|
||||
extension Decimal: DatabaseValueConvertible {
|
||||
/// Returns a TEXT decimal value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
NSDecimalNumber(decimal: self)
|
||||
.description(withLocale: Locale(identifier: "en_US_POSIX"))
|
||||
.databaseValue
|
||||
}
|
||||
|
||||
/// Creates an `Decimal` with the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer or a double, returns a
|
||||
/// `Decimal` initialized from this number.
|
||||
///
|
||||
/// If the database value contains a string, parses the string with the
|
||||
/// `en_US_POSIX` locale.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
switch dbValue.storage {
|
||||
case .int64(let int64):
|
||||
return self.init(int64)
|
||||
case .double(let double):
|
||||
return self.init(double)
|
||||
case let .string(string):
|
||||
// Must match NSNumber.fromDatabaseValue(_:)
|
||||
return self.init(string: string, locale: _posixLocale)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decimal adopts StatementColumnConvertible
|
||||
extension Decimal: StatementColumnConvertible {
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_INTEGER:
|
||||
self.init(sqlite3_column_int64(sqliteStatement, index))
|
||||
case SQLITE_FLOAT:
|
||||
self.init(sqlite3_column_double(sqliteStatement, index))
|
||||
case SQLITE_TEXT:
|
||||
self.init(
|
||||
string: String(cString: sqlite3_column_text(sqliteStatement, index)!),
|
||||
locale: _posixLocale)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
let _posixLocale = Locale(identifier: "en_US_POSIX")
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#if !os(Linux)
|
||||
import Foundation
|
||||
|
||||
/// NSData is convertible to and from DatabaseValue.
|
||||
extension NSData: DatabaseValueConvertible {
|
||||
|
||||
/// Returns a BLOB database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
(self as Data).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `NSData` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a data blob, returns it.
|
||||
///
|
||||
/// If the database value contains a string, returns this string converted
|
||||
/// to UTF8 data.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
guard let data = Data.fromDatabaseValue(dbValue) else {
|
||||
return nil
|
||||
}
|
||||
return cast(data)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
/// NSNull adopts DatabaseValueConvertible
|
||||
extension NSNull: DatabaseValueConvertible {
|
||||
|
||||
/// Returns the NULL database value.
|
||||
public var databaseValue: DatabaseValue { .null }
|
||||
|
||||
/// Returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? { nil }
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#if !os(Linux)
|
||||
import Foundation
|
||||
|
||||
private let integerRoundingBehavior = NSDecimalNumberHandler(
|
||||
roundingMode: .plain,
|
||||
scale: 0,
|
||||
raiseOnExactness: false,
|
||||
raiseOnOverflow: false,
|
||||
raiseOnUnderflow: false,
|
||||
raiseOnDivideByZero: false)
|
||||
|
||||
/// NSNumber adopts DatabaseValueConvertible
|
||||
extension NSNumber: DatabaseValueConvertible {
|
||||
|
||||
/// A database value.
|
||||
///
|
||||
/// If the number is an integer `NSDecimalNumber`, returns an INTEGER
|
||||
/// database value.
|
||||
///
|
||||
/// Otherwise, returns an INTEGER or REAL database value, according to the
|
||||
/// value stored in the `NSNumber`.
|
||||
public var databaseValue: DatabaseValue {
|
||||
// Don't lose precision: store integers that fits in Int64 as Int64
|
||||
if let decimal = self as? NSDecimalNumber,
|
||||
decimal == decimal.rounding(accordingToBehavior: integerRoundingBehavior), // integer
|
||||
decimal.compare(NSDecimalNumber(value: Int64.max)) != .orderedDescending, // decimal <= Int64.max
|
||||
decimal.compare(NSDecimalNumber(value: Int64.min)) != .orderedAscending // decimal >= Int64.min
|
||||
{
|
||||
return int64Value.databaseValue
|
||||
}
|
||||
|
||||
switch String(cString: objCType) {
|
||||
case "c":
|
||||
return Int64(int8Value).databaseValue
|
||||
case "C":
|
||||
return Int64(uint8Value).databaseValue
|
||||
case "s":
|
||||
return Int64(int16Value).databaseValue
|
||||
case "S":
|
||||
return Int64(uint16Value).databaseValue
|
||||
case "i":
|
||||
return Int64(int32Value).databaseValue
|
||||
case "I":
|
||||
return Int64(uint32Value).databaseValue
|
||||
case "l":
|
||||
return Int64(intValue).databaseValue
|
||||
case "L":
|
||||
let uint = uintValue
|
||||
GRDBPrecondition(
|
||||
UInt64(uint) <= UInt64(Int64.max),
|
||||
"could not convert \(uint) to an Int64 that can be stored in the database")
|
||||
return Int64(uint).databaseValue
|
||||
case "q":
|
||||
return Int64(int64Value).databaseValue
|
||||
case "Q":
|
||||
let uint64 = uint64Value
|
||||
GRDBPrecondition(
|
||||
uint64 <= UInt64(Int64.max),
|
||||
"could not convert \(uint64) to an Int64 that can be stored in the database")
|
||||
return Int64(uint64).databaseValue
|
||||
case "f":
|
||||
return Double(floatValue).databaseValue
|
||||
case "d":
|
||||
return doubleValue.databaseValue
|
||||
case "B":
|
||||
return boolValue.databaseValue
|
||||
case let objCType:
|
||||
// Assume a GRDB bug: there is no point throwing any error.
|
||||
fatalError("DatabaseValueConvertible: Unsupported NSNumber type: \(objCType)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `NSNumber` from the specified database value.
|
||||
///
|
||||
/// If the database value is an integer or a double, returns an `NSNumber`
|
||||
/// initialized from this number.
|
||||
///
|
||||
/// If the database value is a string, returns an `NSDecimalNumber` parsed
|
||||
/// with the `en_US_POSIX` locale.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
switch dbValue.storage {
|
||||
case .int64(let int64):
|
||||
return self.init(value: int64)
|
||||
case .double(let double):
|
||||
return self.init(value: double)
|
||||
case let .string(string):
|
||||
// Must match Decimal.fromDatabaseValue(_:)
|
||||
guard let decimal = Decimal(string: string, locale: posixLocale) else { return nil }
|
||||
return NSDecimalNumber(decimal: decimal) as? Self
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let posixLocale = Locale(identifier: "en_US_POSIX")
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#if !os(Linux)
|
||||
import Foundation
|
||||
|
||||
/// NSString adopts DatabaseValueConvertible
|
||||
extension NSString: DatabaseValueConvertible {
|
||||
|
||||
/// Returns a TEXT database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
(self as String).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `NSString` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a string, returns it.
|
||||
///
|
||||
/// If the database value contains a data blob, parses this data as an
|
||||
/// UTF8 string.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
guard let string = String.fromDatabaseValue(dbValue) else {
|
||||
return nil
|
||||
}
|
||||
return self.init(string: string)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
import Foundation
|
||||
|
||||
// inspired by: http://jordansmith.io/performant-date-parsing/
|
||||
|
||||
@usableFromInline
|
||||
struct SQLiteDateParser {
|
||||
@usableFromInline
|
||||
init() { }
|
||||
|
||||
func components(from dateString: String) -> DatabaseDateComponents? {
|
||||
dateString.withCString { cString in
|
||||
components(cString: cString, length: strlen(cString))
|
||||
}
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
func components(cString: UnsafePointer<CChar>, length: Int) -> DatabaseDateComponents? {
|
||||
assert(strlen(cString) == length)
|
||||
|
||||
// "HH:MM" is the shortest valid string
|
||||
guard length >= 5 else { return nil }
|
||||
|
||||
// "YYYY-..." -> datetime
|
||||
if cString[4] == UInt8(ascii: "-") {
|
||||
var components = DateComponents()
|
||||
var parser = Parser(cString: cString, length: length)
|
||||
guard let format = parseDatetimeFormat(parser: &parser, into: &components),
|
||||
parser.length == 0
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return DatabaseDateComponents(components, format: format)
|
||||
}
|
||||
|
||||
// "HH-:..." -> time
|
||||
if cString[2] == UInt8(ascii: ":") {
|
||||
var components = DateComponents()
|
||||
var parser = Parser(cString: cString, length: length)
|
||||
guard let format = parseTimeFormat(parser: &parser, into: &components),
|
||||
parser.length == 0
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return DatabaseDateComponents(components, format: format)
|
||||
}
|
||||
|
||||
// Invalid
|
||||
return nil
|
||||
}
|
||||
|
||||
// - YYYY-MM-DD
|
||||
// - YYYY-MM-DD HH:MM
|
||||
// - YYYY-MM-DD HH:MM:SS
|
||||
// - YYYY-MM-DD HH:MM:SS.SSS
|
||||
// - YYYY-MM-DDTHH:MM
|
||||
// - YYYY-MM-DDTHH:MM:SS
|
||||
// - YYYY-MM-DDTHH:MM:SS.SSS
|
||||
private func parseDatetimeFormat(
|
||||
parser: inout Parser,
|
||||
into components: inout DateComponents)
|
||||
-> DatabaseDateComponents.Format?
|
||||
{
|
||||
guard let year = parser.parseNNNN(),
|
||||
parser.parse("-"),
|
||||
let month = parser.parseNN(),
|
||||
parser.parse("-"),
|
||||
let day = parser.parseNN()
|
||||
else { return nil }
|
||||
|
||||
components.year = year
|
||||
components.month = month
|
||||
components.day = day
|
||||
if parser.length == 0 { return .YMD }
|
||||
|
||||
guard parser.parse(" ") || parser.parse("T")
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch parseTimeFormat(parser: &parser, into: &components) {
|
||||
case .HM: return .YMD_HM
|
||||
case .HMS: return .YMD_HMS
|
||||
case .HMSS: return .YMD_HMSS
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
// - HH:MM
|
||||
// - HH:MM:SS
|
||||
// - HH:MM:SS.SSS
|
||||
private func parseTimeFormat(
|
||||
parser: inout Parser,
|
||||
into components: inout DateComponents)
|
||||
-> DatabaseDateComponents.Format?
|
||||
{
|
||||
guard let hour = parser.parseNN(),
|
||||
parser.parse(":"),
|
||||
let minute = parser.parseNN()
|
||||
else { return nil }
|
||||
|
||||
components.hour = hour
|
||||
components.minute = minute
|
||||
if parser.length == 0 || parseTimeZone(parser: &parser, into: &components) { return .HM }
|
||||
|
||||
guard parser.parse(":"),
|
||||
let second = parser.parseNN()
|
||||
else { return nil }
|
||||
|
||||
components.second = second
|
||||
if parser.length == 0 || parseTimeZone(parser: &parser, into: &components) { return .HMS }
|
||||
|
||||
guard parser.parse(".") else { return nil }
|
||||
|
||||
// Parse one to three digits
|
||||
// Rationale: https://github.com/groue/GRDB.swift/pull/362
|
||||
var nanosecond = 0
|
||||
guard parser.parseDigit(into: &nanosecond) else { return nil }
|
||||
if parser.length == 0 || parseTimeZone(parser: &parser, into: &components) {
|
||||
components.nanosecond = nanosecond * 100_000_000
|
||||
return .HMSS
|
||||
}
|
||||
guard parser.parseDigit(into: &nanosecond) else { return nil }
|
||||
if parser.length == 0 || parseTimeZone(parser: &parser, into: &components) {
|
||||
components.nanosecond = nanosecond * 10_000_000
|
||||
return .HMSS
|
||||
}
|
||||
guard parser.parseDigit(into: &nanosecond) else { return nil }
|
||||
components.nanosecond = nanosecond * 1_000_000
|
||||
while parser.parseDigit() != nil { }
|
||||
_ = parseTimeZone(parser: &parser, into: &components)
|
||||
return .HMSS
|
||||
}
|
||||
|
||||
private func parseTimeZone(
|
||||
parser: inout Parser,
|
||||
into components: inout DateComponents)
|
||||
-> Bool
|
||||
{
|
||||
if parser.parse("Z") {
|
||||
components.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
return true
|
||||
}
|
||||
|
||||
if parser.parse("+"),
|
||||
let hour = parser.parseNN(),
|
||||
parser.parse(":"),
|
||||
let minute = parser.parseNN()
|
||||
{
|
||||
components.timeZone = TimeZone(secondsFromGMT: hour * 3600 + minute * 60)
|
||||
return true
|
||||
}
|
||||
|
||||
if parser.parse("-"),
|
||||
let hour = parser.parseNN(),
|
||||
parser.parse(":"),
|
||||
let minute = parser.parseNN()
|
||||
{
|
||||
components.timeZone = TimeZone(secondsFromGMT: -(hour * 3600 + minute * 60))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private struct Parser {
|
||||
var cString: UnsafePointer<CChar>
|
||||
var length: Int
|
||||
|
||||
private mutating func shift() {
|
||||
cString += 1
|
||||
length -= 1
|
||||
}
|
||||
|
||||
mutating func parse(_ scalar: Unicode.Scalar) -> Bool {
|
||||
guard length > 0, cString[0] == UInt8(ascii: scalar) else {
|
||||
return false
|
||||
}
|
||||
shift()
|
||||
return true
|
||||
}
|
||||
|
||||
mutating func parseDigit() -> Int? {
|
||||
guard length > 0 else {
|
||||
return nil
|
||||
}
|
||||
let char = cString[0]
|
||||
let digit = char - CChar(bitPattern: UInt8(ascii: "0"))
|
||||
guard digit >= 0 && digit <= 9 else {
|
||||
return nil
|
||||
}
|
||||
shift()
|
||||
return Int(digit)
|
||||
}
|
||||
|
||||
mutating func parseDigit(into number: inout Int) -> Bool {
|
||||
guard let digit = parseDigit() else {
|
||||
return false
|
||||
}
|
||||
number = number * 10 + digit
|
||||
return true
|
||||
}
|
||||
|
||||
mutating func parseNNNN() -> Int? {
|
||||
var number = 0
|
||||
guard parseDigit(into: &number)
|
||||
&& parseDigit(into: &number)
|
||||
&& parseDigit(into: &number)
|
||||
&& parseDigit(into: &number)
|
||||
else {
|
||||
// Don't restore self to initial state because we don't need it
|
||||
return nil
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
mutating func parseNN() -> Int? {
|
||||
var number = 0
|
||||
guard parseDigit(into: &number)
|
||||
&& parseDigit(into: &number)
|
||||
else {
|
||||
// Don't restore self to initial state because we don't need it
|
||||
return nil
|
||||
}
|
||||
return number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
#if !os(Linux)
|
||||
/// NSURL stores its absoluteString in the database.
|
||||
extension NSURL: DatabaseValueConvertible {
|
||||
|
||||
/// Returns a TEXT database value containing the absolute URL.
|
||||
public var databaseValue: DatabaseValue {
|
||||
absoluteString?.databaseValue ?? .null
|
||||
}
|
||||
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
guard let string = String.fromDatabaseValue(dbValue) else {
|
||||
return nil
|
||||
}
|
||||
return cast(URL(string: string))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// URL stores its absoluteString in the database.
|
||||
extension URL: DatabaseValueConvertible { }
|
||||
@@ -0,0 +1,91 @@
|
||||
import Foundation
|
||||
|
||||
#if !os(Linux)
|
||||
/// NSUUID adopts DatabaseValueConvertible
|
||||
extension NSUUID: DatabaseValueConvertible {
|
||||
/// Returns a BLOB database value containing the uuid bytes.
|
||||
public var databaseValue: DatabaseValue {
|
||||
var uuidBytes = ContiguousArray(repeating: UInt8(0), count: 16)
|
||||
return uuidBytes.withUnsafeMutableBufferPointer { buffer in
|
||||
getBytes(buffer.baseAddress!)
|
||||
return NSData(bytes: buffer.baseAddress, length: 16).databaseValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `NSUUID` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a string, parses this string as an uuid.
|
||||
///
|
||||
/// If the database value contains a data blob that contains 16 bytes,
|
||||
/// returns a uuid from those bytes.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
switch dbValue.storage {
|
||||
case .blob(let data) where data.count == 16:
|
||||
return data.withUnsafeBytes {
|
||||
self.init(uuidBytes: $0.bindMemory(to: UInt8.self).baseAddress)
|
||||
}
|
||||
case .string(let string):
|
||||
return self.init(uuidString: string)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// UUID adopts DatabaseValueConvertible
|
||||
extension UUID: DatabaseValueConvertible {
|
||||
/// Returns a BLOB database value containing the uuid bytes.
|
||||
public var databaseValue: DatabaseValue {
|
||||
withUnsafeBytes(of: uuid) {
|
||||
Data(bytes: $0.baseAddress!, count: $0.count).databaseValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `UUID` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a string, parses this string as an uuid.
|
||||
///
|
||||
/// If the database value contains a data blob that contains 16 bytes,
|
||||
/// returns a uuid from those bytes.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UUID? {
|
||||
switch dbValue.storage {
|
||||
case .blob(let data) where data.count == 16:
|
||||
return data.withUnsafeBytes {
|
||||
UUID(uuid: $0.bindMemory(to: uuid_t.self).first!)
|
||||
}
|
||||
case .string(let string):
|
||||
return UUID(uuidString: string)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UUID: StatementColumnConvertible {
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_TEXT:
|
||||
let string = String(cString: sqlite3_column_text(sqliteStatement, index)!)
|
||||
guard let uuid = UUID(uuidString: string) else {
|
||||
return nil
|
||||
}
|
||||
self.init(uuid: uuid.uuid)
|
||||
case SQLITE_BLOB:
|
||||
guard sqlite3_column_bytes(sqliteStatement, index) == 16,
|
||||
let blob = sqlite3_column_blob(sqliteStatement, index) else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
self.init(uuid: blob.assumingMemoryBound(to: uuid_t.self).pointee)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
import Foundation
|
||||
|
||||
private struct DatabaseValueDecodingContainer: SingleValueDecodingContainer {
|
||||
let dbValue: DatabaseValue
|
||||
let codingPath: [any CodingKey]
|
||||
|
||||
/// Decodes a null value.
|
||||
///
|
||||
/// - returns: Whether the encountered value was null.
|
||||
func decodeNil() -> Bool { dbValue.isNull }
|
||||
|
||||
/// Decodes a single value of the given type.
|
||||
///
|
||||
/// - parameter type: The type to decode as.
|
||||
/// - returns: A value of the requested type.
|
||||
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value
|
||||
/// cannot be converted to the requested type.
|
||||
/// - throws: `DecodingError.valueNotFound` if the encountered encoded value is null.
|
||||
func decode(_ type: Bool.Type) throws -> Bool {
|
||||
if let result = Bool.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Int.Type) throws -> Int {
|
||||
if let result = Int.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Int8.Type) throws -> Int8 {
|
||||
if let result = Int8.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Int16.Type) throws -> Int16 {
|
||||
if let result = Int16.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Int32.Type) throws -> Int32 {
|
||||
if let result = Int32.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Int64.Type) throws -> Int64 {
|
||||
if let result = Int64.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: UInt.Type) throws -> UInt {
|
||||
if let result = UInt.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: UInt8.Type) throws -> UInt8 {
|
||||
if let result = UInt8.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: UInt16.Type) throws -> UInt16 {
|
||||
if let result = UInt16.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: UInt32.Type) throws -> UInt32 {
|
||||
if let result = UInt32.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: UInt64.Type) throws -> UInt64 {
|
||||
if let result = UInt64.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Float.Type) throws -> Float {
|
||||
if let result = Float.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: Double.Type) throws -> Double {
|
||||
if let result = Double.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func decode(_ type: String.Type) throws -> String {
|
||||
if let result = String.fromDatabaseValue(dbValue) {
|
||||
return result
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a single value of the given type.
|
||||
///
|
||||
/// - parameter type: The type to decode as.
|
||||
/// - returns: A value of the requested type.
|
||||
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value
|
||||
/// cannot be converted to the requested type.
|
||||
/// - throws: `DecodingError.valueNotFound` if the encountered encoded value is null.
|
||||
func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
|
||||
if let type = T.self as? any DatabaseValueConvertible.Type {
|
||||
// Prefer DatabaseValueConvertible decoding over Decodable.
|
||||
// This allows custom database decoding, such as decoding Date from
|
||||
// String, for example.
|
||||
if let result = type.fromDatabaseValue(dbValue) {
|
||||
return result as! T
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(in: self, debugDescription: "value mismatch")
|
||||
}
|
||||
} else {
|
||||
return try T(from: DatabaseValueDecoder(dbValue: dbValue, codingPath: codingPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct DatabaseValueDecoder: Decoder {
|
||||
let dbValue: DatabaseValue
|
||||
let codingPath: [any CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any] { [:] }
|
||||
|
||||
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
|
||||
// We need to switch to JSON decoding
|
||||
throw JSONRequiredError()
|
||||
}
|
||||
|
||||
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
|
||||
// We need to switch to JSON decoding
|
||||
throw JSONRequiredError()
|
||||
}
|
||||
|
||||
func singleValueContainer() throws -> SingleValueDecodingContainer {
|
||||
DatabaseValueDecodingContainer(dbValue: dbValue, codingPath: codingPath)
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseValueConvertible where Self: Decodable {
|
||||
public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? {
|
||||
do {
|
||||
return try self.init(from: DatabaseValueDecoder(dbValue: databaseValue, codingPath: []))
|
||||
} catch is JSONRequiredError {
|
||||
guard let data = Data.fromDatabaseValue(databaseValue) else {
|
||||
return nil
|
||||
}
|
||||
return try? databaseJSONDecoder().decode(Self.self, from: data)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseValueConvertible where Self: Decodable & RawRepresentable, Self.RawValue: DatabaseValueConvertible {
|
||||
public static func fromDatabaseValue(_ databaseValue: DatabaseValue) -> Self? {
|
||||
// Preserve custom database decoding
|
||||
return RawValue.fromDatabaseValue(databaseValue).flatMap { self.init(rawValue: $0) }
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import Foundation
|
||||
|
||||
private struct DatabaseValueEncodingContainer: SingleValueEncodingContainer {
|
||||
let encode: (DatabaseValue) -> Void
|
||||
let jsonEncoder: JSONEncoder
|
||||
|
||||
var codingPath: [any CodingKey] { [] }
|
||||
|
||||
/// Encodes a null value.
|
||||
///
|
||||
/// - throws: `EncodingError.invalidValue` if a null value is invalid in the current context for this format.
|
||||
/// - precondition: May not be called after a previous `self.encode(_:)` call.
|
||||
mutating func encodeNil() throws { encode(.null) }
|
||||
|
||||
/// Encodes a single value of the given type.
|
||||
///
|
||||
/// - parameter value: The value to encode.
|
||||
/// - throws: `EncodingError.invalidValue` if the given value is invalid in the current context for this format.
|
||||
/// - precondition: May not be called after a previous `self.encode(_:)` call.
|
||||
mutating func encode(_ value: Bool) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Int) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Int8) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Int16) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Int32) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Int64) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: UInt) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: UInt8) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: UInt16) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: UInt32) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: UInt64) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Float) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: Double) throws { encode(value.databaseValue) }
|
||||
mutating func encode(_ value: String) throws { encode(value.databaseValue) }
|
||||
|
||||
/// Encodes a single value of the given type.
|
||||
///
|
||||
/// - parameter value: The value to encode.
|
||||
/// - throws: `EncodingError.invalidValue` if the given value is invalid in the current context for this format.
|
||||
/// - precondition: May not be called after a previous `self.encode(_:)` call.
|
||||
mutating func encode<T>(_ value: T) throws where T: Encodable {
|
||||
if let dbValueConvertible = value as? any DatabaseValueConvertible {
|
||||
// Prefer DatabaseValueConvertible encoding over Decodable.
|
||||
// This allows us to encode Date as String, for example.
|
||||
encode(dbValueConvertible.databaseValue)
|
||||
} else {
|
||||
try DatabaseValueEncoder(jsonEncoder: jsonEncoder, encode: encode).encode(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DatabaseValueEncoder: Encoder {
|
||||
let encode: (DatabaseValue) -> Void
|
||||
let jsonEncoder: JSONEncoder
|
||||
var requiresJSON = false
|
||||
|
||||
init(
|
||||
jsonEncoder: JSONEncoder,
|
||||
encode: @escaping (DatabaseValue) -> Void
|
||||
) {
|
||||
self.jsonEncoder = jsonEncoder
|
||||
self.encode = encode
|
||||
}
|
||||
|
||||
/// The path of coding keys taken to get to this point in encoding.
|
||||
/// A `nil` value indicates an unkeyed container.
|
||||
var codingPath: [any CodingKey] { [] }
|
||||
|
||||
/// Any contextual information set by the user for encoding.
|
||||
var userInfo: [CodingUserInfoKey: Any] = [:]
|
||||
|
||||
/// Returns an encoding container appropriate for holding multiple values keyed by the given key type.
|
||||
///
|
||||
/// - parameter type: The key type to use for the container.
|
||||
/// - returns: A new keyed encoding container.
|
||||
/// - precondition: May not be called after a prior `self.unkeyedContainer()` call.
|
||||
/// - precondition: May not be called after a value has been encoded through
|
||||
/// a previous `self.singleValueContainer()` call.
|
||||
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
|
||||
// We need to perform JSON encoding. Unfortunately we can't access the
|
||||
// inner container of Foundation's JSONEncoder. At this point we must
|
||||
// throw an error so that the caller can retry encoding from scratch.
|
||||
// Unfortunately (bis), we can't throw right from here, so let's
|
||||
// return a JSONRequiredEncoder that will throw as soon as possible.
|
||||
requiresJSON = true
|
||||
let container = JSONRequiredEncoder.KeyedContainer<Key>(codingPath: codingPath)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
/// Returns an encoding container appropriate for holding multiple unkeyed values.
|
||||
///
|
||||
/// - returns: A new empty unkeyed container.
|
||||
/// - precondition: May not be called after a prior `self.container(keyedBy:)` call.
|
||||
/// - precondition: May not be called after a value has been encoded through
|
||||
/// a previous `self.singleValueContainer()` call.
|
||||
func unkeyedContainer() -> UnkeyedEncodingContainer {
|
||||
// We need to perform JSON encoding. Unfortunately we can't access the
|
||||
// inner container of Foundation's JSONEncoder. At this point we must
|
||||
// throw an error so that the caller can retry encoding from scratch.
|
||||
// Unfortunately (bis), we can't throw right from here, so let's
|
||||
// return a JSONRequiredEncoder that will throw as soon as possible.
|
||||
requiresJSON = true
|
||||
return JSONRequiredEncoder(codingPath: codingPath)
|
||||
}
|
||||
|
||||
/// Returns an encoding container appropriate for holding a single primitive value.
|
||||
///
|
||||
/// - returns: A new empty single value container.
|
||||
/// - precondition: May not be called after a prior `self.container(keyedBy:)` call.
|
||||
/// - precondition: May not be called after a prior `self.unkeyedContainer()` call.
|
||||
/// - precondition: May not be called after a value has been encoded through
|
||||
/// a previous `self.singleValueContainer()` call.
|
||||
func singleValueContainer() -> SingleValueEncodingContainer {
|
||||
DatabaseValueEncodingContainer(encode: encode, jsonEncoder: jsonEncoder)
|
||||
}
|
||||
|
||||
func encode<T: Encodable>(_ value: T) throws {
|
||||
do {
|
||||
try value.encode(to: self)
|
||||
if requiresJSON {
|
||||
// Here we handle empty arrays and dictionaries.
|
||||
throw JSONRequiredError()
|
||||
}
|
||||
} catch is JSONRequiredError {
|
||||
let jsonData = try jsonEncoder.encode(value)
|
||||
|
||||
// Store JSON String in the database for easier debugging and
|
||||
// database inspection. Thanks to SQLite weak typing, we won't
|
||||
// have any trouble decoding this string into data when we
|
||||
// eventually perform JSON decoding.
|
||||
// TODO: possible optimization: avoid this conversion to string,
|
||||
// and store raw data bytes as an SQLite string
|
||||
let jsonString = String(data: jsonData, encoding: .utf8)!
|
||||
try jsonString.encode(to: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseValueConvertible where Self: Encodable {
|
||||
public var databaseValue: DatabaseValue {
|
||||
var dbValue: DatabaseValue! = nil
|
||||
try! DatabaseValueEncoder(
|
||||
jsonEncoder: Self.databaseJSONEncoder(),
|
||||
encode: { dbValue = $0 }
|
||||
)
|
||||
.encode(self)
|
||||
return dbValue
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseValueConvertible where Self: Encodable & RawRepresentable, Self.RawValue: DatabaseValueConvertible {
|
||||
public var databaseValue: DatabaseValue {
|
||||
// Preserve custom database encoding
|
||||
return rawValue.databaseValue
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
extension SQLSelectable where Self: RawRepresentable, Self.RawValue: SQLSelectable {
|
||||
public var sqlSelection: SQLSelection {
|
||||
rawValue.sqlSelection
|
||||
}
|
||||
}
|
||||
|
||||
extension SQLOrderingTerm where Self: RawRepresentable, Self.RawValue: SQLOrderingTerm {
|
||||
public var sqlOrdering: SQLOrdering {
|
||||
rawValue.sqlOrdering
|
||||
}
|
||||
}
|
||||
|
||||
extension SQLSpecificExpressible where Self: RawRepresentable, Self.RawValue: SQLSpecificExpressible { }
|
||||
|
||||
extension SQLExpressible where Self: RawRepresentable, Self.RawValue: SQLExpressible {
|
||||
/// Returns the raw value as an SQL expression.
|
||||
public var sqlExpression: SQLExpression {
|
||||
rawValue.sqlExpression
|
||||
}
|
||||
}
|
||||
|
||||
extension StatementBinding where Self: RawRepresentable, Self.RawValue: StatementBinding {
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
rawValue.bind(to: sqliteStatement, at: index)
|
||||
}
|
||||
}
|
||||
|
||||
/// `StatementColumnConvertible` is free for `RawRepresentable` types whose raw
|
||||
/// value is itself `StatementColumnConvertible`.
|
||||
///
|
||||
/// // If the RawValue adopts StatementColumnConvertible...
|
||||
/// enum Color : Int {
|
||||
/// case red
|
||||
/// case white
|
||||
/// case rose
|
||||
/// }
|
||||
///
|
||||
/// // ... then the RawRepresentable type can freely
|
||||
/// // adopt StatementColumnConvertible:
|
||||
/// extension Color: StatementColumnConvertible { }
|
||||
extension StatementColumnConvertible where Self: RawRepresentable, Self.RawValue: StatementColumnConvertible {
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
guard let rawValue = RawValue(sqliteStatement: sqliteStatement, index: index) else {
|
||||
return nil
|
||||
}
|
||||
self.init(rawValue: rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// `DatabaseValueConvertible` is free for `RawRepresentable` types whose raw
|
||||
/// value is itself `DatabaseValueConvertible`.
|
||||
///
|
||||
/// // If the RawValue adopts DatabaseValueConvertible...
|
||||
/// enum Color : Int {
|
||||
/// case red
|
||||
/// case white
|
||||
/// case rose
|
||||
/// }
|
||||
///
|
||||
/// // ... then the RawRepresentable type can freely
|
||||
/// // adopt DatabaseValueConvertible:
|
||||
/// extension Color: DatabaseValueConvertible { }
|
||||
extension DatabaseValueConvertible where Self: RawRepresentable, Self.RawValue: DatabaseValueConvertible {
|
||||
public var databaseValue: DatabaseValue {
|
||||
rawValue.databaseValue
|
||||
}
|
||||
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
RawValue.fromDatabaseValue(dbValue).flatMap { self.init(rawValue: $0) }
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
struct JSONRequiredError: Error { }
|
||||
|
||||
/// The encoder that always ends up with a JSONRequiredError
|
||||
struct JSONRequiredEncoder: Encoder {
|
||||
var codingPath: [any CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any] { Record.databaseEncodingUserInfo }
|
||||
|
||||
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey {
|
||||
let container = KeyedContainer<Key>(codingPath: codingPath)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
func unkeyedContainer() -> UnkeyedEncodingContainer { self }
|
||||
|
||||
func singleValueContainer() -> SingleValueEncodingContainer { self }
|
||||
|
||||
struct KeyedContainer<KeyType: CodingKey>: KeyedEncodingContainerProtocol {
|
||||
var codingPath: [any CodingKey]
|
||||
var userInfo: [CodingUserInfoKey: Any] { Record.databaseEncodingUserInfo }
|
||||
|
||||
// swiftlint:disable comma
|
||||
func encodeNil(forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Bool, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int8, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int16, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int32, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int64, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt8, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt16, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt32, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt64, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Float, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Double, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: String, forKey key: KeyType) throws { throw JSONRequiredError() }
|
||||
func encode<T>(_ value: T, forKey key: KeyType) throws where T: Encodable { throw JSONRequiredError() }
|
||||
// swiftlint:enable comma
|
||||
|
||||
func nestedContainer<NestedKey>(
|
||||
keyedBy keyType: NestedKey.Type,
|
||||
forKey key: KeyType)
|
||||
-> KeyedEncodingContainer<NestedKey>
|
||||
where NestedKey: CodingKey
|
||||
{
|
||||
let container = KeyedContainer<NestedKey>(codingPath: codingPath + [key])
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
func nestedUnkeyedContainer(forKey key: KeyType) -> UnkeyedEncodingContainer {
|
||||
JSONRequiredEncoder(codingPath: codingPath)
|
||||
}
|
||||
|
||||
func superEncoder() -> Encoder {
|
||||
JSONRequiredEncoder(codingPath: codingPath)
|
||||
}
|
||||
|
||||
func superEncoder(forKey key: KeyType) -> Encoder {
|
||||
JSONRequiredEncoder(codingPath: codingPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONRequiredEncoder: SingleValueEncodingContainer {
|
||||
func encodeNil() throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Bool ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int8 ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int16 ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int32 ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Int64 ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt8 ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt16) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt32) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: UInt64) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Float ) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: Double) throws { throw JSONRequiredError() }
|
||||
func encode(_ value: String) throws { throw JSONRequiredError() }
|
||||
func encode<T>(_ value: T) throws where T: Encodable { throw JSONRequiredError() }
|
||||
}
|
||||
|
||||
extension JSONRequiredEncoder: UnkeyedEncodingContainer {
|
||||
var count: Int { 0 }
|
||||
|
||||
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type)
|
||||
-> KeyedEncodingContainer<NestedKey>
|
||||
where NestedKey: CodingKey
|
||||
{
|
||||
let container = KeyedContainer<NestedKey>(codingPath: codingPath)
|
||||
return KeyedEncodingContainer(container)
|
||||
}
|
||||
|
||||
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { self }
|
||||
|
||||
mutating func superEncoder() -> Encoder { self }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
extension Optional: StatementBinding where Wrapped: StatementBinding {
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
switch self {
|
||||
case .none:
|
||||
return sqlite3_bind_null(sqliteStatement, index)
|
||||
case let .some(value):
|
||||
return value.bind(to: sqliteStatement, at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: SQLExpressible where Wrapped: SQLExpressible {
|
||||
public var sqlExpression: SQLExpression {
|
||||
switch self {
|
||||
case .none:
|
||||
return .null
|
||||
case let .some(value):
|
||||
return value.sqlExpression
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: SQLOrderingTerm where Wrapped: SQLOrderingTerm {
|
||||
public var sqlOrdering: SQLOrdering {
|
||||
switch self {
|
||||
case .none:
|
||||
return .expression(.null)
|
||||
case let .some(value):
|
||||
return value.sqlOrdering
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: SQLSelectable where Wrapped: SQLSelectable {
|
||||
public var sqlSelection: SQLSelection {
|
||||
switch self {
|
||||
case .none:
|
||||
return .expression(.null)
|
||||
case let .some(value):
|
||||
return value.sqlSelection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: SQLSpecificExpressible where Wrapped: SQLSpecificExpressible { }
|
||||
|
||||
extension Optional: DatabaseValueConvertible where Wrapped: DatabaseValueConvertible {
|
||||
public var databaseValue: DatabaseValue {
|
||||
switch self {
|
||||
case .none:
|
||||
return .null
|
||||
case let .some(value):
|
||||
return value.databaseValue
|
||||
}
|
||||
}
|
||||
|
||||
public static func fromMissingColumn() -> Self? {
|
||||
.some(.none) // success
|
||||
}
|
||||
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
|
||||
if let value = Wrapped.fromDatabaseValue(dbValue) {
|
||||
// Valid value
|
||||
return value
|
||||
} else if dbValue.isNull {
|
||||
// NULL
|
||||
return .some(.none)
|
||||
} else {
|
||||
// Invalid value
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: StatementColumnConvertible where Wrapped: StatementColumnConvertible {
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func fromStatement(_ sqliteStatement: SQLiteStatement, atUncheckedIndex index: CInt) -> Self? {
|
||||
if let value = Wrapped.fromStatement(sqliteStatement, atUncheckedIndex: index) {
|
||||
// Valid value
|
||||
return value
|
||||
} else if sqlite3_column_type(sqliteStatement, index) == SQLITE_NULL {
|
||||
// NULL
|
||||
return .some(.none)
|
||||
} else {
|
||||
// Invalid value
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
guard let value = Wrapped(sqliteStatement: sqliteStatement, index: index) else {
|
||||
return nil
|
||||
}
|
||||
self = .some(value)
|
||||
}
|
||||
}
|
||||
+927
@@ -0,0 +1,927 @@
|
||||
// MARK: - Value Types
|
||||
|
||||
/// Bool adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Bool: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
public init(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
self = sqlite3_column_int64(sqliteStatement, index) != 0
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
(self ? 1 : 0).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `Bool` from the specified database value.
|
||||
///
|
||||
/// If the database value contains an integer or a double, returns whether
|
||||
/// this number is zero.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Bool? {
|
||||
// IMPLEMENTATION NOTE
|
||||
//
|
||||
// https://www.sqlite.org/lang_expr.html#booleanexpr
|
||||
//
|
||||
// > # Boolean Expressions
|
||||
// >
|
||||
// > The SQL language features several contexts where an expression is
|
||||
// > evaluated and the result converted to a boolean (true or false)
|
||||
// > value. These contexts are:
|
||||
// >
|
||||
// > - the WHERE clause of a SELECT, UPDATE or DELETE statement,
|
||||
// > - the ON or USING clause of a join in a SELECT statement,
|
||||
// > - the HAVING clause of a SELECT statement,
|
||||
// > - the WHEN clause of an SQL trigger, and
|
||||
// > - the WHEN clause or clauses of some CASE expressions.
|
||||
// >
|
||||
// > To convert the results of an SQL expression to a boolean value,
|
||||
// > SQLite first casts the result to a NUMERIC value in the same way as
|
||||
// > a CAST expression. A numeric zero value (integer value 0 or real
|
||||
// > value 0.0) is considered to be false. A NULL value is still NULL.
|
||||
// > All other values are considered true.
|
||||
// >
|
||||
// > For example, the values NULL, 0.0, 0, 'english' and '0' are all
|
||||
// > considered to be false. Values 1, 1.0, 0.1, -0.1 and '1english' are
|
||||
// > considered to be true.
|
||||
//
|
||||
// OK so we have to support boolean for all storage classes?
|
||||
// Actually we won't, because of the SQLite boolean interpretation of
|
||||
// strings:
|
||||
//
|
||||
// The doc says that "english" should be false, and "1english" should
|
||||
// be true. I guess "-1english" and "0.1english" should be true also.
|
||||
// And... what about "0.0e10english"?
|
||||
//
|
||||
// Ideally, we'd ask SQLite to perform the conversion itself, and return
|
||||
// its own boolean interpretation of the string. Unfortunately, it looks
|
||||
// like it is not so easy...
|
||||
//
|
||||
// So we could take a short route, and assume all strings are false,
|
||||
// since most strings are falsey for SQLite.
|
||||
//
|
||||
// Considering all strings falsey is unfortunately very
|
||||
// counter-intuitive. This is not the correct way to tackle the boolean
|
||||
// problem.
|
||||
//
|
||||
// Instead, let's use the fact that the BOOLEAN typename has Numeric
|
||||
// affinity (https://www.sqlite.org/datatype3.html), and that the doc
|
||||
// says:
|
||||
//
|
||||
// > SQLite does not have a separate Boolean storage class. Instead,
|
||||
// > Boolean values are stored as integers 0 (false) and 1 (true).
|
||||
//
|
||||
// So we extract bools from Integer and Real only. Integer because it is
|
||||
// the natural boolean storage class, and Real because Numeric affinity
|
||||
// store big numbers as Real.
|
||||
|
||||
switch dbValue.storage {
|
||||
case .int64(let int64):
|
||||
return (int64 != 0)
|
||||
case .double(let double):
|
||||
return (double != 0.0)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, self ? 1 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Int adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Int: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = Int(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `Int` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { Int(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Int8 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Int8: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = Int8(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `Int8` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int8? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { Int8(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Int16 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Int16: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = Int16(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `Int16` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int16? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { Int16(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Int32 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Int32: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = Int32(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `Int32` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int32? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { Int32(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Int64 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Int64: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
public init(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
self = sqlite3_column_int64(sqliteStatement, index)
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
DatabaseValue(storage: .int64(self))
|
||||
}
|
||||
|
||||
/// Returns a `Int64` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer, returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Int64? {
|
||||
switch dbValue.storage {
|
||||
case .int64(let int64):
|
||||
return int64
|
||||
case .double(let double):
|
||||
guard double >= Double(Int64.min) else { return nil }
|
||||
guard double < Double(Int64.max) else { return nil }
|
||||
return Int64(double)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// UInt adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension UInt: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = UInt(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `UInt` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { UInt(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// UInt8 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension UInt8: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = UInt8(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `UInt8` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt8? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { UInt8(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// UInt16 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension UInt16: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = UInt16(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `UInt16` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt16? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { UInt16(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// UInt32 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension UInt32: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = UInt32(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `UInt32` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt32? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { UInt32(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// UInt64 adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension UInt64: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
let int64 = sqlite3_column_int64(sqliteStatement, index)
|
||||
guard let v = UInt64(exactly: int64) else { return nil }
|
||||
self = v
|
||||
}
|
||||
|
||||
/// Returns an INTEGER database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Int64(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `UInt64` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer representable in this type,
|
||||
/// returns this integer.
|
||||
///
|
||||
/// If the database value contains a double representable in this type after
|
||||
/// rounding toward zero, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> UInt64? {
|
||||
Int64.fromDatabaseValue(dbValue).flatMap { UInt64(exactly: $0) }
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_int64(sqliteStatement, index, Int64(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Double adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Double: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
public init(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
self = sqlite3_column_double(sqliteStatement, index)
|
||||
}
|
||||
|
||||
/// Returns a REAL database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
DatabaseValue(storage: .double(self))
|
||||
}
|
||||
|
||||
/// Returns a `Double` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer, returns the conversion.
|
||||
///
|
||||
/// If the database value contains a double, returns this double.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Double? {
|
||||
switch dbValue.storage {
|
||||
case .int64(let int64):
|
||||
return Double(int64)
|
||||
case .double(let double):
|
||||
return double
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_double(sqliteStatement, index, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Float adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension Float: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
public init(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
self = Float(sqlite3_column_double(sqliteStatement, index))
|
||||
}
|
||||
|
||||
/// Returns a REAL database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
Double(self).databaseValue
|
||||
}
|
||||
|
||||
/// Returns a `Float` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a integer, returns the conversion.
|
||||
///
|
||||
/// If the database value contains a double, returns the conversion.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Float? {
|
||||
switch dbValue.storage {
|
||||
case .int64(let int64):
|
||||
return Float(int64)
|
||||
case .double(let double):
|
||||
return Float(double)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_double(sqliteStatement, index, Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// String adopts DatabaseValueConvertible and StatementColumnConvertible.
|
||||
extension String: DatabaseValueConvertible, StatementColumnConvertible {
|
||||
|
||||
/// Returns a value initialized from a raw SQLite statement pointer.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - sqliteStatement: A pointer to an SQLite statement.
|
||||
/// - index: The column index.
|
||||
public init(sqliteStatement: SQLiteStatement, index: CInt) {
|
||||
self = String(cString: sqlite3_column_text(sqliteStatement, index)!)
|
||||
}
|
||||
|
||||
/// Returns a TEXT database value.
|
||||
public var databaseValue: DatabaseValue {
|
||||
DatabaseValue(storage: .string(self))
|
||||
}
|
||||
|
||||
/// Returns a `String` from the specified database value.
|
||||
///
|
||||
/// If the database value contains a string, returns it.
|
||||
///
|
||||
/// If the database value contains a data blob, parses this data as an
|
||||
/// UTF8 string.
|
||||
///
|
||||
/// Otherwise, returns nil.
|
||||
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> String? {
|
||||
switch dbValue.storage {
|
||||
case .blob(let data):
|
||||
// Implicit conversion from blob to string, just as SQLite does
|
||||
// See <https://www.sqlite.org/c3ref/column_blob.html>
|
||||
return String(data: data, encoding: .utf8)
|
||||
case .string(let string):
|
||||
return string
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt {
|
||||
sqlite3_bind_text(sqliteStatement, index, self, -1, SQLITE_TRANSIENT)
|
||||
}
|
||||
|
||||
/// Calls the given closure after binding a statement argument.
|
||||
///
|
||||
/// The binding is valid only during the execution of this method.
|
||||
///
|
||||
/// - parameter sqliteStatement: An SQLite statement.
|
||||
/// - parameter index: 1-based index to statement arguments.
|
||||
/// - parameter body: The closure to execute when argument is bound.
|
||||
func withBinding<T>(to sqliteStatement: SQLiteStatement, at index: CInt, do body: () throws -> T) throws -> T {
|
||||
try withCString {
|
||||
let code = sqlite3_bind_text(sqliteStatement, index, $0, -1, nil /* SQLITE_STATIC */)
|
||||
try checkBindingSuccess(code: code, sqliteStatement: sqliteStatement)
|
||||
return try body()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SQL Functions
|
||||
|
||||
extension DatabaseFunction {
|
||||
/// An SQL function that calls the Foundation
|
||||
/// `String.capitalized` property.
|
||||
///
|
||||
/// `NULL` is returned for non-strings values.
|
||||
///
|
||||
/// This function is automatically added by GRDB to your database
|
||||
/// connections. It is the function used by the query interface's
|
||||
/// ``SQLSpecificExpressible/capitalized``:
|
||||
///
|
||||
/// ```swift
|
||||
/// let nameColumn = Column("name")
|
||||
/// let request = Player.select(nameColumn.capitalized)
|
||||
/// let names = try String.fetchAll(dbQueue, request) // [String]
|
||||
/// ```
|
||||
public static let capitalize =
|
||||
DatabaseFunction("swiftCapitalizedString", argumentCount: 1, pure: true) { dbValues in
|
||||
guard let string = String.fromDatabaseValue(dbValues[0]) else {
|
||||
return nil
|
||||
}
|
||||
return string.capitalized
|
||||
}
|
||||
|
||||
/// An SQL function that calls the Swift
|
||||
/// `String.lowercased()` method.
|
||||
///
|
||||
/// `NULL` is returned for non-strings values.
|
||||
///
|
||||
/// This function is automatically added by GRDB to your database
|
||||
/// connections. It is the function used by the query interface's
|
||||
/// ``SQLSpecificExpressible/lowercased``:
|
||||
///
|
||||
/// ```swift
|
||||
/// let nameColumn = Column("name")
|
||||
/// let request = Player.select(nameColumn.lowercased)
|
||||
/// let names = try String.fetchAll(dbQueue, request) // [String]
|
||||
/// ```
|
||||
public static let lowercase =
|
||||
DatabaseFunction("swiftLowercaseString", argumentCount: 1, pure: true) { dbValues in
|
||||
guard let string = String.fromDatabaseValue(dbValues[0]) else {
|
||||
return nil
|
||||
}
|
||||
return string.lowercased()
|
||||
}
|
||||
|
||||
/// An SQL function that calls the Swift
|
||||
/// `String.uppercased()` method.
|
||||
///
|
||||
/// `NULL` is returned for non-strings values.
|
||||
///
|
||||
/// This function is automatically added by GRDB to your database
|
||||
/// connections. It is the function used by the query interface's
|
||||
/// ``SQLSpecificExpressible/uppercased``:
|
||||
///
|
||||
/// ```swift
|
||||
/// let nameColumn = Column("name")
|
||||
/// let request = Player.select(nameColumn.uppercased)
|
||||
/// let names = try String.fetchAll(dbQueue, request) // [String]
|
||||
/// ```
|
||||
public static let uppercase =
|
||||
DatabaseFunction("swiftUppercaseString", argumentCount: 1, pure: true) { dbValues in
|
||||
guard let string = String.fromDatabaseValue(dbValues[0]) else {
|
||||
return nil
|
||||
}
|
||||
return string.uppercased()
|
||||
}
|
||||
|
||||
/// An SQL function that calls the Foundation
|
||||
/// `String.localizedCapitalized` property.
|
||||
///
|
||||
/// `NULL` is returned for non-strings values.
|
||||
///
|
||||
/// This function is automatically added by GRDB to your database
|
||||
/// connections. It is the function used by the query interface's
|
||||
/// ``SQLSpecificExpressible/localizedCapitalized``:
|
||||
///
|
||||
/// ```swift
|
||||
/// let nameColumn = Column("name")
|
||||
/// let request = Player.select(nameColumn.localizedCapitalized)
|
||||
/// let names = try String.fetchAll(dbQueue, request) // [String]
|
||||
/// ```
|
||||
public static let localizedCapitalize =
|
||||
DatabaseFunction("swiftLocalizedCapitalizedString", argumentCount: 1, pure: true) { dbValues in
|
||||
guard let string = String.fromDatabaseValue(dbValues[0]) else {
|
||||
return nil
|
||||
}
|
||||
return string.localizedCapitalized
|
||||
}
|
||||
|
||||
|
||||
/// An SQL function that calls the Foundation
|
||||
/// `String.localizedLowercase` property.
|
||||
///
|
||||
/// `NULL` is returned for non-strings values.
|
||||
///
|
||||
/// This function is automatically added by GRDB to your database
|
||||
/// connections. It is the function used by the query interface's
|
||||
/// ``SQLSpecificExpressible/localizedLowercased``:
|
||||
///
|
||||
/// ```swift
|
||||
/// let nameColumn = Column("name")
|
||||
/// let request = Player.select(nameColumn.localizedLowercase)
|
||||
/// let names = try String.fetchAll(dbQueue, request) // [String]
|
||||
/// ```
|
||||
public static let localizedLowercase =
|
||||
DatabaseFunction("swiftLocalizedLowercaseString", argumentCount: 1, pure: true) { dbValues in
|
||||
guard let string = String.fromDatabaseValue(dbValues[0]) else {
|
||||
return nil
|
||||
}
|
||||
return string.localizedLowercase
|
||||
}
|
||||
|
||||
/// An SQL function that calls the Foundation
|
||||
/// `String.localizedUppercase` property.
|
||||
///
|
||||
/// `NULL` is returned for non-strings values.
|
||||
///
|
||||
/// This function is automatically added by GRDB to your database
|
||||
/// connections. It is the function used by the query interface's
|
||||
/// ``SQLSpecificExpressible/localizedUppercased``:
|
||||
///
|
||||
/// ```swift
|
||||
/// let nameColumn = Column("name")
|
||||
/// let request = Player.select(nameColumn.localizedUppercase)
|
||||
/// let names = try String.fetchAll(dbQueue, request) // [String]
|
||||
/// ```
|
||||
public static let localizedUppercase =
|
||||
DatabaseFunction("swiftLocalizedUppercaseString", argumentCount: 1, pure: true) { dbValues in
|
||||
guard let string = String.fromDatabaseValue(dbValues[0]) else {
|
||||
return nil
|
||||
}
|
||||
return string.localizedUppercase
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SQLite Collations
|
||||
|
||||
extension DatabaseCollation {
|
||||
// Here we define a set of predefined collations.
|
||||
//
|
||||
// We should avoid renaming those collations, because database created with
|
||||
// earlier versions of the library may have used those collations in the
|
||||
// definition of tables. A renaming would prevent SQLite to find the
|
||||
// collation.
|
||||
//
|
||||
// Yet we're not absolutely stuck: we could register support for obsolete
|
||||
// collation names with sqlite3_collation_needed().
|
||||
// See https://www.sqlite.org/capi3ref.html#sqlite3_collation_needed
|
||||
|
||||
/// A collation that compares strings according to the built-in `==` and
|
||||
/// `<=` operators of the Swift String.
|
||||
///
|
||||
/// This collation is automatically added by GRDB to your database
|
||||
/// connections.
|
||||
///
|
||||
/// You can use the collation when creating database tables:
|
||||
///
|
||||
/// ```swift
|
||||
/// try db.create(table: "player") { t in
|
||||
/// t.column("name", .text).collate(.unicodeCompare)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Embed the collation name in your raw SQL queries:
|
||||
///
|
||||
/// ```swift
|
||||
/// let collationName = DatabaseCollation.unicodeCompare.name
|
||||
/// dbQueue.execute(sql: """
|
||||
/// CREATE TABLE player (
|
||||
/// name TEXT COLLATE \(collationName)
|
||||
/// )
|
||||
/// """)
|
||||
/// ```
|
||||
public static let unicodeCompare =
|
||||
DatabaseCollation("swiftCompare") { (lhs, rhs) in
|
||||
(lhs < rhs) ? .orderedAscending : ((lhs == rhs) ? .orderedSame : .orderedDescending)
|
||||
}
|
||||
|
||||
/// A collation that compares strings according to the Foundation
|
||||
/// `String.caseInsensitiveCompare(_:)` method.
|
||||
///
|
||||
/// This collation is automatically added by GRDB to your database
|
||||
/// connections.
|
||||
///
|
||||
/// You can use the collation when creating database tables:
|
||||
///
|
||||
/// ```swift
|
||||
/// try db.create(table: "player") { t in
|
||||
/// t.column("name", .text).collate(.caseInsensitiveCompare)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Embed the collation name in your raw SQL queries:
|
||||
///
|
||||
/// ```swift
|
||||
/// let collationName = DatabaseCollation.caseInsensitiveCompare.name
|
||||
/// dbQueue.execute(sql: """
|
||||
/// CREATE TABLE player (
|
||||
/// name TEXT COLLATE \(collationName)
|
||||
/// )
|
||||
/// """)
|
||||
/// ```
|
||||
public static let caseInsensitiveCompare =
|
||||
DatabaseCollation("swiftCaseInsensitiveCompare") { (lhs, rhs) in
|
||||
lhs.caseInsensitiveCompare(rhs)
|
||||
}
|
||||
|
||||
/// A collation that compares strings according to the Foundation
|
||||
/// `String.localizedCaseInsensitiveCompare(_:)` method.
|
||||
///
|
||||
/// This collation is automatically added by GRDB to your database
|
||||
/// connections.
|
||||
///
|
||||
/// You can use the collation when creating database tables:
|
||||
///
|
||||
/// ```swift
|
||||
/// try db.create(table: "player") { t in
|
||||
/// t.column("name", .text).collate(.localizedCaseInsensitiveCompare)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Embed the collation name in your raw SQL queries:
|
||||
///
|
||||
/// ```swift
|
||||
/// let collationName = DatabaseCollation.localizedCaseInsensitiveCompare.name
|
||||
/// dbQueue.execute(sql: """
|
||||
/// CREATE TABLE player (
|
||||
/// name TEXT COLLATE \(collationName)
|
||||
/// )
|
||||
/// """)
|
||||
/// ```
|
||||
public static let localizedCaseInsensitiveCompare =
|
||||
DatabaseCollation("swiftLocalizedCaseInsensitiveCompare") { (lhs, rhs) in
|
||||
lhs.localizedCaseInsensitiveCompare(rhs)
|
||||
}
|
||||
|
||||
/// A collation that compares strings according to the Foundation
|
||||
/// `String.localizedCompare(_:)` method.
|
||||
///
|
||||
/// This collation is automatically added by GRDB to your database
|
||||
/// connections.
|
||||
///
|
||||
/// You can use the collation when creating database tables:
|
||||
///
|
||||
/// ```swift
|
||||
/// try db.create(table: "player") { t in
|
||||
/// t.column("name", .text).collate(.localizedCompare)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Embed the collation name in your raw SQL queries:
|
||||
///
|
||||
/// ```swift
|
||||
/// let collationName = DatabaseCollation.localizedCompare.name
|
||||
/// dbQueue.execute(sql: """
|
||||
/// CREATE TABLE player (
|
||||
/// name TEXT COLLATE \(collationName)
|
||||
/// )
|
||||
/// """)
|
||||
/// ```
|
||||
public static let localizedCompare =
|
||||
DatabaseCollation("swiftLocalizedCompare") { (lhs, rhs) in
|
||||
lhs.localizedCompare(rhs)
|
||||
}
|
||||
|
||||
/// A collation that compares strings according to the Foundation
|
||||
/// `String.localizedStandardCompare(_:)` method.
|
||||
///
|
||||
/// This collation is automatically added by GRDB to your database
|
||||
/// connections.
|
||||
///
|
||||
/// You can use the collation when creating database tables:
|
||||
///
|
||||
/// ```swift
|
||||
/// try db.create(table: "player") { t in
|
||||
/// t.column("name", .text).collate(.localizedStandardCompare)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Embed the collation name in your raw SQL queries:
|
||||
///
|
||||
/// ```swift
|
||||
/// let collationName = DatabaseCollation.localizedStandardCompare.name
|
||||
/// dbQueue.execute(sql: """
|
||||
/// CREATE TABLE player (
|
||||
/// name TEXT COLLATE \(collationName)
|
||||
/// )
|
||||
/// """)
|
||||
/// ```
|
||||
public static let localizedStandardCompare =
|
||||
DatabaseCollation("swiftLocalizedStandardCompare") { (lhs, rhs) in
|
||||
lhs.localizedStandardCompare(rhs)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user