-
-
Notifications
You must be signed in to change notification settings - Fork 744
/
Copy pathDatabaseCollation.swift
95 lines (88 loc) · 3.3 KB
/
DatabaseCollation.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// Import C SQLite functions
#if GRDBCIPHER
import SQLCipher
#elseif SWIFT_PACKAGE
import GRDBSQLite
#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
import SQLite3
#endif
import Foundation
/// `DatabaseCollation` is a custom string comparison function used by SQLite.
///
/// See also ``Database/CollationName``.
///
/// Related SQLite documentation: <https://www.sqlite.org/datatype3.html#collating_sequences>
///
/// ## Topics
///
/// ### Creating a Custom Collation
///
/// - ``init(_:function:)``
/// - ``name``
///
/// ### Built-in Collations
///
/// - ``caseInsensitiveCompare``
/// - ``localizedCaseInsensitiveCompare``
/// - ``localizedCompare``
/// - ``localizedStandardCompare``
/// - ``unicodeCompare``
public final class DatabaseCollation: Identifiable, Sendable {
/// The identifier of an SQLite collation.
///
/// SQLite identifies collations by their name (case insensitive).
public struct ID: Hashable {
var name: String
// Collation equality is based on the sqlite3_strnicmp SQLite function.
// (see https://www.sqlite.org/c3ref/create_collation.html). Computing
// a hash value that honors the Swift Hashable contract (value equality
// implies hash equality) is thus non trivial. But it's not that
// important, since this hashValue is only used when one adds
// or removes a collation from a database connection.
public func hash(into hasher: inout Hasher) {
hasher.combine(0)
}
/// Two collations are equal if they share the same name (case insensitive)
public static func == (lhs: Self, rhs: Self) -> Bool {
// See <https://www.sqlite.org/c3ref/create_collation.html>
return sqlite3_stricmp(lhs.name, rhs.name) == 0
}
}
/// The identifier of the collation.
public var id: ID { ID(name: name) }
/// The name of the collation.
public let name: String
let function: @Sendable (CInt, UnsafeRawPointer?, CInt, UnsafeRawPointer?) -> ComparisonResult
/// Creates a collation.
///
/// For example:
///
/// ```swift
/// let collation = DatabaseCollation("localized_standard") { (string1, string2) in
/// return (string1 as NSString).localizedStandardCompare(string2)
/// }
/// db.add(collation: collation)
/// try db.execute(sql: "CREATE TABLE file (name TEXT COLLATE localized_standard")
/// ```
///
/// - parameters:
/// - name: The collation name.
/// - function: A function that compares two strings.
public init(_ name: String, function: @escaping @Sendable (String, String) -> ComparisonResult) {
self.name = name
self.function = { (length1, buffer1, length2, buffer2) in
// Buffers are not C strings: they do not end with \0.
let string1 = String(
bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer1.unsafelyUnwrapped),
length: Int(length1),
encoding: .utf8,
freeWhenDone: false)!
let string2 = String(
bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer2.unsafelyUnwrapped),
length: Int(length2),
encoding: .utf8,
freeWhenDone: false)!
return function(string1, string2)
}
}
}