     }
 }
 
+private final class NFKCTokenizer : FTS5CustomTokenizer {
+    static let name = "nfkc"
+    
+    let porter: FTS5Tokenizer
+    
+    init(db: Database, arguments: [String]) throws {
+        // TODO: find a way to provide any wrapped tokenizer
+        porter = try db.makeTokenizer(.porter())
+    }
+    
+    deinit {
+        // TODO: test
+        print("NFKCTokenizer deinit")
+    }
+    
+    func tokenize(_ context: UnsafeMutableRawPointer?, _ flags: FTS5TokenizeFlags, _ pText: UnsafePointer<Int8>?, _ nText: Int32, _ xToken: FTS5TokenCallback?) -> Int32 {
+        
+        guard let pText = pText, let text = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: pText), length: Int(nText), encoding: .utf8, freeWhenDone: false) else {
+            return 0
+        }
+        
+        let nkfc = text.precomposedStringWithCompatibilityMapping
+        return nkfc.withCString { (cString: UnsafePointer<Int8>) in
+            porter.tokenize(context, flags, cString, Int32(nkfc.utf8.count), xToken)
+        }
+    }
+}
+
 class FTS5CustomTokenizerTests: GRDBTestCase {
     
-    func testDatabaseQueue() {
+    func testStopWordsDatabaseQueue() {
         assertNoError {
             let dbQueue = try makeDatabaseQueue()
             dbQueue.add(tokenizer: StopWordsTokenizer.self)

import XCTest
#if USING_SQLCIPHER
    import GRDBCipher
#elseif USING_CUSTOMSQLITE

        }
    }
    
    func testStopWordsDatabasePool() {
        assertNoError {
            let dbPool = try makeDatabaseQueue()
            dbPool.add(tokenizer: StopWordsTokenizer.self)
