diff --git a/Documentation/Index.md b/Documentation/Index.md index 700deb9f..0579e6f3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1399,6 +1399,22 @@ try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) ``` +We can set the full range of parameters by creating a `FTS4Config` object. + +``` swift +let emails = VirtualTable("emails") +let subject = Expression("subject") +let body = Expression("body") +let config = FTS4Config() + .column(subject) + .column(body, [.unindexed]) + .languageId("lid") + .order(.Desc) + +try db.run(emails.create(.FTS4(config)) +// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc") +``` + Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. ``` swift @@ -1414,6 +1430,22 @@ let replies = emails.filter(subject.match("Re:*")) // SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*' ``` +### FTS5 + +When linking against a version of SQLite with [FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual table +in a similar fashion. + +```swift +let emails = VirtualTable("emails") +let subject = Expression("subject") +let body = Expression("body") +let config = FTS5Config() + .column(subject) + .column(body, [.unindexed]) + +try db.run(emails.create(.FTS5(config)) +// CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED) +``` ## Executing Arbitrary SQL diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 786c2c77..88d74dc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,12 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -154,6 +160,8 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -406,6 +414,7 @@ EE247B331C3F142E00AE3E12 /* ValueTests.swift */, EE247B161C3F127200AE3E12 /* TestHelpers.swift */, EE247AE41C3F04ED00AE3E12 /* Info.plist */, + 19A1721B8984686B9963B45D /* FTS5Tests.swift */, ); path = SQLiteTests; sourceTree = ""; @@ -429,6 +438,7 @@ children = ( EE247AF51C3F06E900AE3E12 /* FTS4.swift */, EE247AF61C3F06E900AE3E12 /* R*Tree.swift */, + 19A1730E4390C775C25677D1 /* FTS5.swift */, ); path = Extensions; sourceTree = ""; @@ -786,6 +796,7 @@ 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, + 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -808,6 +819,7 @@ 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, + 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -840,6 +852,7 @@ EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, + 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -862,6 +875,7 @@ EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, + 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -887,6 +901,7 @@ EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, + 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -909,6 +924,7 @@ EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, + 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index e5a1d815..466c42c7 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -29,14 +29,12 @@ extension Module { } @warn_unused_result public static func FTS4(columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { - var columns = columns - - if let tokenizer = tokenizer { - columns.append("=".join([Expression(literal: "tokenize"), Expression(literal: tokenizer.description)])) - } - return Module(name: "fts4", arguments: columns) + return FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) } + @warn_unused_result public static func FTS4(config: FTS4Config) -> Module { + return Module(name: "fts4", arguments: config.arguments()) + } } extension VirtualTable { @@ -156,3 +154,189 @@ extension Connection { } } + +/// Configuration options shared between the [FTS4](https://www.sqlite.org/fts3.html) and +/// [FTS5](https://www.sqlite.org/fts5.html) extensions. +public class FTSConfig { + public enum ColumnOption { + /// [The notindexed= option](https://www.sqlite.org/fts3.html#section_6_5) + case unindexed + } + + typealias ColumnDefinition = (Expressible, options: [ColumnOption]) + var columnDefinitions = [ColumnDefinition]() + var tokenizer: Tokenizer? + var prefixes = [Int]() + var externalContentSchema: SchemaType? + var isContentless: Bool = false + + /// Adds a column definition + public func column(column: Expressible, _ options: [ColumnOption] = []) -> Self { + self.columnDefinitions.append((column, options)) + return self + } + + public func columns(columns: [Expressible]) -> Self { + for column in columns { + self.column(column) + } + return self + } + + /// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer) + public func tokenizer(tokenizer: Tokenizer?) -> Self { + self.tokenizer = tokenizer + return self + } + + /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) + public func prefix(prefix: [Int]) -> Self { + self.prefixes += prefix + return self + } + + /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) + public func externalContent(schema: SchemaType) -> Self { + self.externalContentSchema = schema + return self + } + + /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) + public func contentless() -> Self { + self.isContentless = true + return self + } + + func formatColumnDefinitions() -> [Expressible] { + return columnDefinitions.map { $0.0 } + } + + func arguments() -> [Expressible] { + return options().arguments + } + + func options() -> Options { + var options = Options() + options.append(formatColumnDefinitions()) + if let tokenizer = tokenizer { + options.append("tokenize", value: Expression(literal: tokenizer.description)) + } + options.appendCommaSeparated("prefix", values:prefixes.sort().map { String($0) }) + if isContentless { + options.append("content", value: "") + } else if let externalContentSchema = externalContentSchema { + options.append("content", value: externalContentSchema.tableName()) + } + return options + } + + struct Options { + var arguments = [Expressible]() + + mutating func append(columns: [Expressible]) -> Options { + arguments.appendContentsOf(columns) + return self + } + + mutating func appendCommaSeparated(key: String, values: [String]) -> Options { + if values.isEmpty { + return self + } else { + return append(key, value: values.joinWithSeparator(",")) + } + } + + mutating func append(key: String, value: CustomStringConvertible?) -> Options { + return append(key, value: value?.description) + } + + mutating func append(key: String, value: String?) -> Options { + return append(key, value: value.map { Expression($0) }) + } + + mutating func append(key: String, value: Expressible?) -> Options { + if let value = value { + arguments.append("=".join([Expression(literal: key), value])) + } + return self + } + } +} + +/// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. +public class FTS4Config : FTSConfig { + /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) + public enum MatchInfo : CustomStringConvertible { + case FTS3 + public var description: String { + return "fts3" + } + } + + /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) + public enum Order : CustomStringConvertible { + /// Data structures are optimized for returning results in ascending order by docid (default) + case Asc + /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid. + case Desc + + public var description: String { + switch self { + case Asc: return "asc" + case Desc: return "desc" + } + } + } + + var compressFunction: String? + var uncompressFunction: String? + var languageId: String? + var matchInfo: MatchInfo? + var order: Order? + + override public init() { + } + + /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) + public func compress(functionName: String) -> Self { + self.compressFunction = functionName + return self + } + + /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) + public func uncompress(functionName: String) -> Self { + self.uncompressFunction = functionName + return self + } + + /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) + public func languageId(columnName: String) -> Self { + self.languageId = columnName + return self + } + + /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) + public func matchInfo(matchInfo: MatchInfo) -> Self { + self.matchInfo = matchInfo + return self + } + + /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) + public func order(order: Order) -> Self { + self.order = order + return self + } + + override func options() -> Options { + var options = super.options() + for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { + options.append("notindexed", value: column) + } + options.append("languageid", value: languageId) + options.append("compress", value: compressFunction) + options.append("uncompress", value: uncompressFunction) + options.append("matchinfo", value: matchInfo) + options.append("order", value: order) + return options + } +} diff --git a/SQLite/Extensions/FTS5.swift b/SQLite/Extensions/FTS5.swift new file mode 100644 index 00000000..97056819 --- /dev/null +++ b/SQLite/Extensions/FTS5.swift @@ -0,0 +1,97 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension Module { + @warn_unused_result public static func FTS5(config: FTS5Config) -> Module { + return Module(name: "fts5", arguments: config.arguments()) + } +} + +/// Configuration for the [FTS5](https://www.sqlite.org/fts5.html) extension. +/// +/// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version +/// of SQLite. +public class FTS5Config : FTSConfig { + public enum Detail : CustomStringConvertible { + /// store rowid, column number, term offset + case Full + /// store rowid, column number + case Column + /// store rowid + case None + + public var description: String { + switch self { + case Full: return "full" + case Column: return "column" + case None: return "none" + } + } + } + + var detail: Detail? + var contentRowId: Expressible? + var columnSize: Int? + + override public init() { + } + + /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) + public func contentRowId(column: Expressible) -> Self { + self.contentRowId = column + return self + } + + /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) + public func columnSize(size: Int) -> Self { + self.columnSize = size + return self + } + + /// [The Detail Option](https://www.sqlite.org/fts5.html#section_4_6) + public func detail(detail: Detail) -> Self { + self.detail = detail + return self + } + + override func options() -> Options { + var options = super.options() + options.append("content_rowid", value: contentRowId) + if let columnSize = columnSize { + options.append("columnsize", value: Expression(value: columnSize)) + } + options.append("detail", value: detail) + return options + } + + override func formatColumnDefinitions() -> [Expressible] { + return columnDefinitions.map { definition in + if definition.options.contains(.unindexed) { + return " ".join([definition.0, Expression(literal: "UNINDEXED")]) + } else { + return definition.0 + } + } + } +} diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index 7f889955..c1071972 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -44,6 +44,137 @@ class FTS4Tests : XCTestCase { } +class FTS4ConfigTests : XCTestCase { + var config: FTS4Config! + + override func setUp() { + super.setUp() + config = FTS4Config() + } + + func test_empty_config() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4()", + sql(config)) + } + + func test_config_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\")", + sql(config.column(string))) + } + + func test_config_columns() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", \"int\")", + sql(config.columns([string, int]))) + } + + func test_config_unindexed_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", notindexed=\"string\")", + sql(config.column(string, [.unindexed]))) + } + + func test_external_content_view() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"view\")", + sql(config.externalContent(_view ))) + } + + func test_external_content_virtual_table() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"virtual_table\")", + sql(config.externalContent(virtualTable))) + } + + func test_tokenizer_simple() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=simple)", + sql(config.tokenizer(.Simple))) + } + + func test_tokenizer_porter() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=porter)", + sql(config.tokenizer(.Porter))) + } + + func test_tokenizer_unicode61() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61)", + sql(config.tokenizer(.Unicode61()))) + } + + func test_tokenizer_unicode61_with_options() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) + } + + func test_content_less() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"\")", + sql(config.contentless())) + } + + func test_config_matchinfo() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(matchinfo=\"fts3\")", + sql(config.matchInfo(.FTS3))) + } + + func test_config_order_asc() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"asc\")", + sql(config.order(.Asc))) + } + + func test_config_order_desc() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"desc\")", + sql(config.order(.Desc))) + } + + func test_config_compress() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(compress=\"compress_foo\")", + sql(config.compress("compress_foo"))) + } + + func test_config_uncompress() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(uncompress=\"uncompress_foo\")", + sql(config.uncompress("uncompress_foo"))) + } + + func test_config_languageId() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(languageid=\"lid\")", + sql(config.languageId("lid"))) + } + + func test_config_all() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\")", + sql(config + .tokenizer(.Porter) + .column(int) + .column(string, [.unindexed]) + .column(date, [.unindexed]) + .externalContent(table) + .matchInfo(.FTS3) + .languageId("lid") + .order(.Desc) + .prefix([2, 4])) + ) + } + + func sql(config: FTS4Config) -> String { + return virtualTable.create(.FTS4(config)) + } +} + class FTS4IntegrationTests : SQLiteTestCase { #if !SQLITE_SWIFT_STANDALONE func test_registerTokenizer_registersTokenizer() { diff --git a/SQLiteTests/FTS5Tests.swift b/SQLiteTests/FTS5Tests.swift new file mode 100644 index 00000000..aa3965bd --- /dev/null +++ b/SQLiteTests/FTS5Tests.swift @@ -0,0 +1,124 @@ +import XCTest +import SQLite + +class FTS5Tests: XCTestCase { + var config: FTS5Config! + + override func setUp() { + super.setUp() + config = FTS5Config() + } + + func test_empty_config() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5()", + sql(config)) + } + + func test_config_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\")", + sql(config.column(string))) + } + + func test_config_columns() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\", \"int\")", + sql(config.columns([string, int]))) + } + + func test_config_unindexed_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\" UNINDEXED)", + sql(config.column(string, [.unindexed]))) + } + + func test_external_content_table() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"table\")", + sql(config.externalContent(table))) + } + + func test_external_content_view() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"view\")", + sql(config.externalContent(_view))) + } + + func test_external_content_virtual_table() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"virtual_table\")", + sql(config.externalContent(virtualTable))) + } + + func test_content_less() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"\")", + sql(config.contentless())) + } + + func test_content_rowid() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content_rowid=\"string\")", + sql(config.contentRowId(string))) + } + + func test_tokenizer_porter() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=porter)", + sql(config.tokenizer(.Porter))) + } + + func test_tokenizer_unicode61() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61)", + sql(config.tokenizer(.Unicode61()))) + } + + func test_tokenizer_unicode61_with_options() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) + } + + func test_column_size() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(columnsize=1)", + sql(config.columnSize(1))) + } + + func test_detail_full() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"full\")", + sql(config.detail(.Full))) + } + + func test_detail_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"column\")", + sql(config.detail(.Column))) + } + + func test_detail_none() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"none\")", + sql(config.detail(.None))) + } + + func test_fts5_config_all() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, tokenize=porter, prefix=\"2,4\", content=\"table\")", + sql(config + .tokenizer(.Porter) + .column(int) + .column(string, [.unindexed]) + .column(date, [.unindexed]) + .externalContent(table) + .prefix([2, 4])) + ) + } + + func sql(config: FTS5Config) -> String { + return virtualTable.create(.FTS5(config)) + } +}