@@ -29,14 +29,12 @@ extension Module {
2929 }
3030
3131 @warn_unused_result public static func FTS4( columns: [ Expressible ] = [ ] , tokenize tokenizer: Tokenizer ? = nil ) -> Module {
32- var columns = columns
33-
34- if let tokenizer = tokenizer {
35- columns. append ( " = " . join ( [ Expression < Void > ( literal: " tokenize " ) , Expression < Void > ( literal: tokenizer. description) ] ) )
36- }
37- return Module ( name: " fts4 " , arguments: columns)
32+ return FTS4 ( FTS4Config ( ) . columns ( columns) . tokenizer ( tokenizer) )
3833 }
3934
35+ @warn_unused_result public static func FTS4( config: FTS4Config ) -> Module {
36+ return Module ( name: " fts4 " , arguments: config. arguments ( ) )
37+ }
4038}
4139
4240extension VirtualTable {
@@ -156,3 +154,189 @@ extension Connection {
156154 }
157155
158156}
157+
158+ /// Configuration options shared between the [FTS4](https://www.sqlite.org/fts3.html) and
159+ /// [FTS5](https://www.sqlite.org/fts5.html) extensions.
160+ public class FTSConfig {
161+ public enum ColumnOption {
162+ /// [The notindexed= option](https://www.sqlite.org/fts3.html#section_6_5)
163+ case unindexed
164+ }
165+
166+ typealias ColumnDefinition = ( Expressible , options: [ ColumnOption ] )
167+ var columnDefinitions = [ ColumnDefinition] ( )
168+ var tokenizer : Tokenizer ?
169+ var prefixes = [ Int] ( )
170+ var externalContentSchema : SchemaType ?
171+ var isContentless : Bool = false
172+
173+ /// Adds a column definition
174+ public func column( column: Expressible , _ options: [ ColumnOption ] = [ ] ) -> Self {
175+ self . columnDefinitions. append ( ( column, options) )
176+ return self
177+ }
178+
179+ public func columns( columns: [ Expressible ] ) -> Self {
180+ for column in columns {
181+ self . column ( column)
182+ }
183+ return self
184+ }
185+
186+ /// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer)
187+ public func tokenizer( tokenizer: Tokenizer ? ) -> Self {
188+ self . tokenizer = tokenizer
189+ return self
190+ }
191+
192+ /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6)
193+ public func prefix( prefix: [ Int ] ) -> Self {
194+ self . prefixes += prefix
195+ return self
196+ }
197+
198+ /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2)
199+ public func externalContent( schema: SchemaType ) -> Self {
200+ self . externalContentSchema = schema
201+ return self
202+ }
203+
204+ /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1)
205+ public func contentless( ) -> Self {
206+ self . isContentless = true
207+ return self
208+ }
209+
210+ func formatColumnDefinitions( ) -> [ Expressible ] {
211+ return columnDefinitions. map { $0. 0 }
212+ }
213+
214+ func arguments( ) -> [ Expressible ] {
215+ return options ( ) . arguments
216+ }
217+
218+ func options( ) -> Options {
219+ var options = Options ( )
220+ options. append ( formatColumnDefinitions ( ) )
221+ if let tokenizer = tokenizer {
222+ options. append ( " tokenize " , value: Expression < Void > ( literal: tokenizer. description) )
223+ }
224+ options. appendCommaSeparated ( " prefix " , values: prefixes. sort ( ) . map { String ( $0) } )
225+ if isContentless {
226+ options. append ( " content " , value: " " )
227+ } else if let externalContentSchema = externalContentSchema {
228+ options. append ( " content " , value: externalContentSchema. tableName ( ) )
229+ }
230+ return options
231+ }
232+
233+ struct Options {
234+ var arguments = [ Expressible] ( )
235+
236+ mutating func append( columns: [ Expressible ] ) -> Options {
237+ arguments. appendContentsOf ( columns)
238+ return self
239+ }
240+
241+ mutating func appendCommaSeparated( key: String , values: [ String ] ) -> Options {
242+ if values. isEmpty {
243+ return self
244+ } else {
245+ return append ( key, value: values. joinWithSeparator ( " , " ) )
246+ }
247+ }
248+
249+ mutating func append( key: String , value: CustomStringConvertible ? ) -> Options {
250+ return append ( key, value: value? . description)
251+ }
252+
253+ mutating func append( key: String , value: String ? ) -> Options {
254+ return append ( key, value: value. map { Expression < String > ( $0) } )
255+ }
256+
257+ mutating func append( key: String , value: Expressible ? ) -> Options {
258+ if let value = value {
259+ arguments. append ( " = " . join ( [ Expression < Void > ( literal: key) , value] ) )
260+ }
261+ return self
262+ }
263+ }
264+ }
265+
266+ /// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension.
267+ public class FTS4Config : FTSConfig {
268+ /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4)
269+ public enum MatchInfo : CustomStringConvertible {
270+ case FTS3
271+ public var description : String {
272+ return " fts3 "
273+ }
274+ }
275+
276+ /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options)
277+ public enum Order : CustomStringConvertible {
278+ /// Data structures are optimized for returning results in ascending order by docid (default)
279+ case Asc
280+ /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid.
281+ case Desc
282+
283+ public var description : String {
284+ switch self {
285+ case Asc: return " asc "
286+ case Desc: return " desc "
287+ }
288+ }
289+ }
290+
291+ var compressFunction : String ?
292+ var uncompressFunction : String ?
293+ var languageId : String ?
294+ var matchInfo : MatchInfo ?
295+ var order : Order ?
296+
297+ override public init ( ) {
298+ }
299+
300+ /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1)
301+ public func compress( functionName: String ) -> Self {
302+ self . compressFunction = functionName
303+ return self
304+ }
305+
306+ /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1)
307+ public func uncompress( functionName: String ) -> Self {
308+ self . uncompressFunction = functionName
309+ return self
310+ }
311+
312+ /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3)
313+ public func languageId( columnName: String ) -> Self {
314+ self . languageId = columnName
315+ return self
316+ }
317+
318+ /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4)
319+ public func matchInfo( matchInfo: MatchInfo ) -> Self {
320+ self . matchInfo = matchInfo
321+ return self
322+ }
323+
324+ /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options)
325+ public func order( order: Order ) -> Self {
326+ self . order = order
327+ return self
328+ }
329+
330+ override func options( ) -> Options {
331+ var options = super. options ( )
332+ for (column, _) in ( columnDefinitions. filter { $0. options. contains ( . unindexed) } ) {
333+ options. append ( " notindexed " , value: column)
334+ }
335+ options. append ( " languageid " , value: languageId)
336+ options. append ( " compress " , value: compressFunction)
337+ options. append ( " uncompress " , value: uncompressFunction)
338+ options. append ( " matchinfo " , value: matchInfo)
339+ options. append ( " order " , value: order)
340+ return options
341+ }
342+ }
0 commit comments