Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/FTS/FTS3.swift
T
2025-01-22 14:09:10 +08:00

191 lines
6.1 KiB
Swift

/// The virtual table module for the FTS3 full-text engine.
///
/// To create FTS3 tables, use the ``Database`` method
/// ``Database/create(virtualTable:ifNotExists:using:_:)``:
///
/// ```swift
/// // CREATE VIRTUAL TABLE document USING fts3(content)
/// try db.create(virtualTable: "document", using: FTS3()) { t in
/// t.column("content")
/// }
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/fts3.html>
///
/// ## Topics
///
/// ### The FTS3 Module
///
/// - ``init()``
/// - ``FTS3TableDefinition``
/// - ``FTS3TokenizerDescriptor``
///
/// ### Full-Text Search Pattern
///
/// - ``FTS3Pattern``
///
/// ### Tokenizing Strings
///
/// - ``tokenize(_:withTokenizer:)``
public struct FTS3 {
/// Options for Latin script characters.
public enum Diacritics: Sendable {
/// Do not remove diacritics from Latin script characters. This option
/// matches the `remove_diacritics=0` tokenizer argument.
///
/// Related SQLite documentation: <https://www.sqlite.org/fts3.html#tokenizer>
case keep
/// Remove diacritics from Latin script characters. This option matches
/// the `remove_diacritics=1` tokenizer argument.
case removeLegacy
#if GRDBCUSTOMSQLITE
/// Remove diacritics from Latin script characters. This option matches
/// the `remove_diacritics=2` tokenizer argument.
case remove
#elseif !GRDBCIPHER
/// Remove diacritics from Latin script characters. This option matches
/// the `remove_diacritics=2` tokenizer argument.
@available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) // SQLite 3.27+
case remove
#endif
}
/// Creates an FTS3 module.
///
/// For example:
///
/// ```swift
/// // CREATE VIRTUAL TABLE document USING fts3(content)
/// try db.create(virtualTable: "document", using: FTS3()) { t in
/// t.column("content")
/// }
/// ```
///
/// See ``Database/create(virtualTable:ifNotExists:using:_:)``
public init() { }
/// Returns an array of tokens found in the string argument.
///
/// For example:
///
/// ```swift
/// // ["sqlite", "database"]
/// try FTS3.tokenize("SQLite database")
///
/// // ["gustave", "doré"])
/// try FTS3.tokenize("Gustave Doré")
/// ```
///
/// Results can be altered with the `tokenizer` argument:
///
/// ```swift
/// // ["sqlite", "databas"]
/// try FTS3.tokenize("SQLite database", withTokenizer: .porter)
///
/// // ["gustave", "dore"])
/// try FTS3.tokenize("Gustave Doré", withTokenizer: .unicode61())
/// ```
///
/// Related SQLite documentation:
///
/// - <https://www.sqlite.org/fts3.html#tokenizer>
/// - <https://www.sqlite.org/fts3.html#querying_tokenizers>
///
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
public static func tokenize(
_ string: String,
withTokenizer tokenizer: FTS3TokenizerDescriptor = .simple)
throws -> [String]
{
try DatabaseQueue().inDatabase { db in
var tokenizerChunks: [String] = []
tokenizerChunks.append(tokenizer.name)
for option in tokenizer.arguments {
tokenizerChunks.append("\"\(option)\"")
}
let tokenizerSQL = tokenizerChunks.joined(separator: ", ")
try db.execute(sql: "CREATE VIRTUAL TABLE tokens USING fts3tokenize(\(tokenizerSQL))")
return try String.fetchAll(db, sql: """
SELECT token FROM tokens WHERE input = ? ORDER BY position
""", arguments: [string])
}
}
}
extension FTS3: VirtualTableModule {
public var moduleName: String { "fts3" }
public func makeTableDefinition(configuration: VirtualTableConfiguration) -> FTS3TableDefinition {
FTS3TableDefinition()
}
public func moduleArguments(for definition: FTS3TableDefinition, in db: Database) -> [String] {
var arguments = definition.columns
if let tokenizer = definition.tokenizer {
if tokenizer.arguments.isEmpty {
arguments.append("tokenize=\(tokenizer.name)")
} else {
arguments.append(
"tokenize=\(tokenizer.name) " + tokenizer.arguments
.map { "\"\($0)\"" as String }
.joined(separator: " "))
}
}
return arguments
}
public func database(_ db: Database, didCreate tableName: String, using definition: FTS3TableDefinition) { }
}
/// A `FTS3TableDefinition` lets you define the components of an FTS3
/// virtual table.
///
/// You don't create instances of this class. Instead, you use the `Database`
/// ``Database/create(virtualTable:ifNotExists:using:_:)`` method:
///
/// ```swift
/// try db.create(virtualTable: "document", using: FTS3()) { t in // t is FTS3TableDefinition
/// t.column("content")
/// }
/// ```
public final class FTS3TableDefinition {
fileprivate var columns: [String] = []
/// The virtual table tokenizer.
///
/// For example:
///
/// ```swift
/// // CREATE VIRTUAL TABLE documents USING fts3(tokenize=porter)
/// try db.create(virtualTable: "document", using: FTS3()) { t in
/// t.tokenizer = .porter
/// }
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/fts3.html#creating_and_destroying_fts_tables>
public var tokenizer: FTS3TokenizerDescriptor?
/// Appends a table column.
///
/// For example:
///
/// ```swift
/// // CREATE VIRTUAL TABLE document USING fts3(content)
/// try db.create(virtualTable: "document", using: FTS3()) { t in
/// t.column("content")
/// }
/// ```
///
/// - parameter name: the column name.
public func column(_ name: String) {
columns.append(name)
}
}
// Explicit non-conformance to Sendable: `FTS3TableDefinition` is a mutable
// class and there is no known reason for making it thread-safe.
@available(*, unavailable)
extension FTS3TableDefinition: Sendable { }