add swiftUI code

This commit is contained in:
zeus
2025-01-22 14:09:10 +08:00
parent 68e7b7347c
commit 8a99853829
2531 changed files with 486215 additions and 0 deletions
@@ -0,0 +1,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)
}
}
@@ -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)!)
}
}
@@ -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
@@ -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
}
}
}
@@ -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) }
}
}
@@ -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
}
}
@@ -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) }
}
}
@@ -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)
}
}
@@ -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)
}
}