Skip to content

Commit c5a1e38

Browse files
committed
2 parents ee06b96 + f52bdc2 commit c5a1e38

File tree

8 files changed

+592
-8
lines changed

8 files changed

+592
-8
lines changed

Documentation/Index.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,22 @@ try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter)))
13991399
// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter)
14001400
```
14011401

1402+
We can set the full range of parameters by creating a `FTS4Config` object.
1403+
1404+
``` swift
1405+
let emails = VirtualTable("emails")
1406+
let subject = Expression<String>("subject")
1407+
let body = Expression<String>("body")
1408+
let config = FTS4Config()
1409+
.column(subject)
1410+
.column(body, [.unindexed])
1411+
.languageId("lid")
1412+
.order(.Desc)
1413+
1414+
try db.run(emails.create(.FTS4(config))
1415+
// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc")
1416+
```
1417+
14021418
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.
14031419

14041420
``` swift
@@ -1414,6 +1430,22 @@ let replies = emails.filter(subject.match("Re:*"))
14141430
// SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*'
14151431
```
14161432

1433+
### FTS5
1434+
1435+
When linking against a version of SQLite with [FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual table
1436+
in a similar fashion.
1437+
1438+
```swift
1439+
let emails = VirtualTable("emails")
1440+
let subject = Expression<String>("subject")
1441+
let body = Expression<String>("body")
1442+
let config = FTS5Config()
1443+
.column(subject)
1444+
.column(body, [.unindexed])
1445+
1446+
try db.run(emails.create(.FTS5(config))
1447+
// CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED)
1448+
```
14171449

14181450
## Executing Arbitrary SQL
14191451

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ These projects enhance or use SQLite.swift:
230230
Looking for something else? Try another Swift wrapper (or [FMDB][]):
231231
232232
- [Camembert](https://github.com/remirobert/Camembert)
233-
- [EonilSQLite3](https://github.com/Eonil/SQLite3)
233+
- [GRDB](https://github.com/groue/GRDB.swift)
234234
- [SQLiteDB](https://github.com/FahimF/SQLiteDB)
235235
- [Squeal](https://github.com/nerdyc/Squeal)
236236
- [SwiftData](https://github.com/ryanfowler/SwiftData)

SQLite.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@
4646
03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; };
4747
03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; };
4848
03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; };
49+
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
50+
19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
51+
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
52+
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
53+
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
54+
19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
4955
EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; };
5056
EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; };
5157
EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; };
@@ -154,6 +160,8 @@
154160
03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
155161
03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
156162
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; };
163+
19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = "<group>"; };
164+
19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = "<group>"; };
157165
39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
158166
39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
159167
39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
@@ -406,6 +414,7 @@
406414
EE247B331C3F142E00AE3E12 /* ValueTests.swift */,
407415
EE247B161C3F127200AE3E12 /* TestHelpers.swift */,
408416
EE247AE41C3F04ED00AE3E12 /* Info.plist */,
417+
19A1721B8984686B9963B45D /* FTS5Tests.swift */,
409418
);
410419
path = SQLiteTests;
411420
sourceTree = "<group>";
@@ -429,6 +438,7 @@
429438
children = (
430439
EE247AF51C3F06E900AE3E12 /* FTS4.swift */,
431440
EE247AF61C3F06E900AE3E12 /* R*Tree.swift */,
441+
19A1730E4390C775C25677D1 /* FTS5.swift */,
432442
);
433443
path = Extensions;
434444
sourceTree = "<group>";
@@ -786,6 +796,7 @@
786796
03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */,
787797
03A65E771C6BB2E60062603F /* Connection.swift in Sources */,
788798
03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */,
799+
19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */,
789800
);
790801
runOnlyForDeploymentPostprocessing = 0;
791802
};
@@ -808,6 +819,7 @@
808819
03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */,
809820
03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */,
810821
03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */,
822+
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */,
811823
);
812824
runOnlyForDeploymentPostprocessing = 0;
813825
};
@@ -840,6 +852,7 @@
840852
EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */,
841853
EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */,
842854
EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */,
855+
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */,
843856
);
844857
runOnlyForDeploymentPostprocessing = 0;
845858
};
@@ -862,6 +875,7 @@
862875
EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */,
863876
EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */,
864877
EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */,
878+
19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */,
865879
);
866880
runOnlyForDeploymentPostprocessing = 0;
867881
};
@@ -887,6 +901,7 @@
887901
EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */,
888902
EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */,
889903
EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */,
904+
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */,
890905
);
891906
runOnlyForDeploymentPostprocessing = 0;
892907
};
@@ -909,6 +924,7 @@
909924
EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */,
910925
EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */,
911926
EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */,
927+
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */,
912928
);
913929
runOnlyForDeploymentPostprocessing = 0;
914930
};

SQLite/Core/Connection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public final class Connection {
108108
/// The last rowid inserted into the database via this connection.
109109
public var lastInsertRowid: Int64? {
110110
let rowid = sqlite3_last_insert_rowid(handle)
111-
return rowid > 0 ? rowid : nil
111+
return rowid != 0 ? rowid : nil
112112
}
113113

114114
/// The last number of changes (inserts, updates, or deletes) made to the

SQLite/Extensions/FTS4.swift

Lines changed: 190 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4240
extension 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

Comments
 (0)