@@ -94,6 +94,172 @@ final class SQLiteDatabaseTests: XCTestCase {
9494 }
9595 }
9696
97+ func testAddAndRemoveCollation( ) throws {
98+ struct Entity : Hashable , SQLiteTransformable {
99+ let id : String
100+ let string : String ?
101+
102+ init ( _ id: Int , _ string: String ? = nil ) {
103+ self . id = String ( id)
104+ self . string = string
105+ }
106+
107+ init ( row: SQLiteRow ) throws {
108+ id = try row. value ( for: " id " )
109+ string = row. optionalValue ( for: " string " )
110+ }
111+
112+ var asArguments : SQLiteArguments {
113+ [
114+ " id " : . text( id) ,
115+ " string " : string. map { . text( $0) } ?? . null,
116+ ]
117+ }
118+ }
119+
120+ let apple = Entity ( 1 , " Apple " )
121+ let banana = Entity ( 2 , " banana " )
122+ let zebra = Entity ( 3 , " Zebra " )
123+ let null1 = Entity ( 4 )
124+ let null2 = Entity ( 5 )
125+
126+ try database. inTransaction { db in
127+ try db. write ( _createTableWithIDAsStringAndNullableString)
128+ try [ apple, banana, zebra, null1, null2]
129+ . forEach { entity in
130+ try db. write (
131+ _insertIDAndString,
132+ arguments: entity. asArguments
133+ )
134+ }
135+ }
136+
137+ let selectDefaultSorted : SQL = """
138+ SELECT * FROM test ORDER BY string;
139+ """
140+
141+ let selectCustomCaseSensitiveSorted : SQL = """
142+ SELECT * FROM test ORDER BY string COLLATE CUSTOM;
143+ """
144+
145+ let selectCustomCaseInsensitiveSorted : SQL = """
146+ SELECT * FROM test ORDER BY string COLLATE CUSTOM_NOCASE;
147+ """
148+
149+ let defaultSorted : [ Entity ] = try database. read ( selectDefaultSorted)
150+ XCTAssertEqual (
151+ defaultSorted,
152+ [ null1, null2, apple, zebra, banana]
153+ )
154+
155+ XCTAssertThrowsError (
156+ try database. read ( selectCustomCaseSensitiveSorted)
157+ ) { error in
158+ guard case SQLiteError . SQLITE_ERROR_MISSING_COLLSEQ = error else {
159+ XCTFail ( " Should have thrown SQLITE_ERROR " )
160+ return
161+ }
162+ }
163+
164+ try database. addCollation ( named: " CUSTOM " ) { $0. compare ( $1) }
165+ let customSorted : [ Entity ] = try database. read ( selectCustomCaseSensitiveSorted)
166+ XCTAssertEqual (
167+ customSorted,
168+ [ null1, null2, apple, zebra, banana]
169+ )
170+
171+ try database. addCollation (
172+ named: " CUSTOM_NOCASE "
173+ ) { $0. caseInsensitiveCompare ( $1) }
174+
175+ let customNoCaseSorted : [ Entity ] = try database
176+ . read ( selectCustomCaseInsensitiveSorted)
177+ XCTAssertEqual (
178+ customNoCaseSorted,
179+ [ null1, null2, apple, banana, zebra]
180+ )
181+
182+ try database. removeCollation ( named: " CUSTOM_NOCASE " )
183+ XCTAssertThrowsError (
184+ try database. read ( selectCustomCaseInsensitiveSorted)
185+ ) { error in
186+ guard case SQLiteError . SQLITE_ERROR_MISSING_COLLSEQ = error else {
187+ XCTFail ( " Should have thrown SQLITE_ERROR " )
188+ return
189+ }
190+ }
191+ let customSortedAfterRemovingNoCase : [ Entity ] = try database
192+ . read ( selectCustomCaseSensitiveSorted)
193+ XCTAssertEqual (
194+ customSortedAfterRemovingNoCase,
195+ [ null1, null2, apple, zebra, banana]
196+ )
197+ }
198+
199+ func testCustomLocalizedCollation( ) throws {
200+ try database. addCollation ( named: " LOCALIZED " ) { lhs, rhs in
201+ lhs. localizedStandardCompare ( rhs)
202+ }
203+
204+ // NOTE: ([toInsert], [binary sort], [localized sort])
205+ let cases : [ ( [ String ] , [ String ] , [ String ] ) ] = [
206+ // Basic Latin
207+ (
208+ [ " a " , " A " , " b " , " B " ] ,
209+ [ " A " , " B " , " a " , " b " ] ,
210+ [ " a " , " A " , " b " , " B " ]
211+ ) ,
212+
213+ // Accented Latin
214+ (
215+ [ " cafe " , " café " , " caffe " , " caffè " ] ,
216+ [ " cafe " , " caffe " , " caffè " , " café " ] ,
217+ [ " cafe " , " café " , " caffe " , " caffè " ]
218+ ) ,
219+
220+ // Chinese
221+ (
222+ [ " 长城 " , " 长江 " , " 上海 " , " 北京 " ] ,
223+ [ " 上海 " , " 北京 " , " 长城 " , " 长江 " ] ,
224+ [ " 上海 " , " 北京 " , " 长城 " , " 长江 " ]
225+ ) ,
226+
227+ // Mixed
228+ (
229+ [ " z " , " 中 " , " 9 " , " ñ " , " a " ] ,
230+ [ " 9 " , " a " , " z " , " ñ " , " 中 " ] ,
231+ [ " 9 " , " a " , " ñ " , " z " , " 中 " ]
232+ ) ,
233+ ]
234+
235+ for (toInsert, binarySort, localizedSort) in cases {
236+ try database. inTransaction { db in
237+ try db. execute ( raw: _createTableWithIDAsStringAndNullableString)
238+ try toInsert. enumerated ( ) . forEach { id, string in
239+ try db. write (
240+ _insertIDAndString,
241+ arguments: [
242+ " id " : . text( String ( id) ) ,
243+ " string " : . text( string) ,
244+ ]
245+ )
246+ }
247+ }
248+
249+ let binarySorted : [ String ] = try database
250+ . read ( " SELECT * FROM test ORDER BY string; " )
251+ . compactMap { $0 [ " string " ] ? . stringValue }
252+ XCTAssertEqual ( binarySorted, binarySort)
253+
254+ let localizedSorted : [ String ] = try database
255+ . read ( " SELECT * FROM test ORDER BY string COLLATE LOCALIZED; " )
256+ . compactMap { $0 [ " string " ] ? . stringValue }
257+ XCTAssertEqual ( localizedSorted, localizedSort)
258+
259+ try database. write ( " DROP TABLE test; " )
260+ }
261+ }
262+
97263 func testUserVersion( ) throws {
98264 XCTAssertEqual ( 0 , database. userVersion)
99265
0 commit comments