Skip to content

Commit aed22fe

Browse files
committed
Support for temporary virtual tables.
1 parent eec220b commit aed22fe

File tree

11 files changed

+243
-47
lines changed

11 files changed

+243
-47
lines changed

Documentation/FullTextSearch.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Generally speaking, FTS5 is better than FTS4 which improves on FTS3. But this do
9191

9292
**FTS3 and FTS4 full-text tables store and index textual content.**
9393

94-
Create tables with the `create(virtualTable:using:)` method:
94+
Create tables with the `create(virtualTable:options:using:_:)` method:
9595

9696
```swift
9797
// CREATE VIRTUAL TABLE document USING fts3(content)
@@ -326,7 +326,7 @@ let documents = try Document.filter(Column("content").match(pattern)).fetchAll(d
326326

327327
To use FTS5, you'll need a [custom SQLite build] that activates the `SQLITE_ENABLE_FTS5` compilation option.
328328

329-
Create FTS5 tables with the `create(virtualTable:using:)` method:
329+
Create FTS5 tables with the `create(virtualTable:options:using:_:)` method:
330330

331331
```swift
332332
// CREATE VIRTUAL TABLE document USING fts5(content)

GRDB/Documentation.docc/DatabaseSchemaModifications.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ Unique constraints and unique indexes are somewhat different: don't miss the tip
252252

253253
- ``Database/alter(table:body:)``
254254
- ``Database/create(table:options:body:)``
255-
- ``Database/create(virtualTable:ifNotExists:using:)``
256-
- ``Database/create(virtualTable:ifNotExists:using:_:)``
255+
- ``Database/create(virtualTable:options:using:)``
256+
- ``Database/create(virtualTable:options:using:_:)``
257257
- ``Database/drop(table:)``
258258
- ``Database/dropFTS4SynchronizationTriggers(forTable:)``
259259
- ``Database/dropFTS5SynchronizationTriggers(forTable:)``
@@ -265,6 +265,7 @@ Unique constraints and unique indexes are somewhat different: don't miss the tip
265265
- ``TableDefinition``
266266
- ``TableOptions``
267267
- ``VirtualTableModule``
268+
- ``VirtualTableOptions``
268269

269270
### Database Views
270271

@@ -288,3 +289,5 @@ Those are legacy interfaces that are preserved for backwards compatibility. Thei
288289

289290
- ``Database/create(index:on:columns:unique:ifNotExists:condition:)``
290291
- ``Database/create(table:temporary:ifNotExists:withoutRowID:body:)``
292+
- ``Database/create(virtualTable:ifNotExists:using:)``
293+
- ``Database/create(virtualTable:ifNotExists:using:_:)``

GRDB/FTS/FTS3.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// The virtual table module for the FTS3 full-text engine.
22
///
33
/// To create FTS3 tables, use the ``Database`` method
4-
/// ``Database/create(virtualTable:ifNotExists:using:_:)``:
4+
/// ``Database/create(virtualTable:options:using:_:)``:
55
///
66
/// ```swift
77
/// // CREATE VIRTUAL TABLE document USING fts3(content)
@@ -63,7 +63,7 @@ public struct FTS3 {
6363
/// }
6464
/// ```
6565
///
66-
/// See ``Database/create(virtualTable:ifNotExists:using:_:)``
66+
/// See ``Database/create(virtualTable:options:using:_:)``
6767
public init() { }
6868

6969
/// Returns an array of tokens found in the string argument.
@@ -143,7 +143,7 @@ extension FTS3: VirtualTableModule {
143143
/// virtual table.
144144
///
145145
/// You don't create instances of this class. Instead, you use the `Database`
146-
/// ``Database/create(virtualTable:ifNotExists:using:_:)`` method:
146+
/// ``Database/create(virtualTable:options:using:_:)`` method:
147147
///
148148
/// ```swift
149149
/// try db.create(virtualTable: "document", using: FTS3()) { t in // t is FTS3TableDefinition

GRDB/FTS/FTS3TokenizerDescriptor.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,13 @@ public struct FTS3TokenizerDescriptor: Sendable {
106106
if !separators.isEmpty {
107107
// TODO: test "=" and "\"", "(" and ")" as separators, with
108108
// both FTS3Pattern(matchingAnyTokenIn:tokenizer:)
109-
// and Database.create(virtualTable:using:)
109+
// and Database.create(virtualTable:options:using:_:)
110110
arguments.append("separators=" + separators.sorted().map { String($0) }.joined())
111111
}
112112
if !tokenCharacters.isEmpty {
113113
// TODO: test "=" and "\"", "(" and ")" as tokenCharacters, with
114114
// both FTS3Pattern(matchingAnyTokenIn:tokenizer:)
115-
// and Database.create(virtualTable:using:)
115+
// and Database.create(virtualTable:options:using:_:)
116116
arguments.append("tokenchars=" + tokenCharacters.sorted().map { String($0) }.joined())
117117
}
118118
return FTS3TokenizerDescriptor("unicode61", arguments: arguments)

GRDB/FTS/FTS4.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// The virtual table module for the FTS4 full-text engine.
22
///
33
/// To create FTS4 tables, use the ``Database`` method
4-
/// ``Database/create(virtualTable:ifNotExists:using:_:)``:
4+
/// ``Database/create(virtualTable:options:using:_:)``:
55
///
66
/// ```swift
77
/// // CREATE VIRTUAL TABLE document USING fts4(content)
@@ -31,7 +31,7 @@ public struct FTS4 {
3131
/// }
3232
/// ```
3333
///
34-
/// See ``Database/create(virtualTable:ifNotExists:using:_:)``
34+
/// See ``Database/create(virtualTable:options:using:_:)``
3535
public init() { }
3636
}
3737

@@ -102,6 +102,16 @@ extension FTS4: VirtualTableModule {
102102
case .synchronized(let contentTable):
103103
// https://www.sqlite.org/fts3.html#_external_content_fts4_tables_
104104

105+
if definition.configuration.temporary {
106+
// SQLite can't rebuild the index of temporary tables:
107+
//
108+
// sqlite> CREATE TABLE t(id INTEGER PRIMARY KEY, a, b, c);
109+
// sqlite> CREATE VIRTUAL TABLE temp.ft USING fts4(content="t", b, c);
110+
// sqlite> INSERT INTO ft(ft) VALUES('rebuild');
111+
// Runtime error: SQL logic error
112+
fatalError("Temporary external content FTS4 tables are not supported.")
113+
}
114+
105115
let rowIDColumn = try db.primaryKey(contentTable).rowIDColumn ?? Column.rowID.name
106116
let ftsTable = tableName.quotedDatabaseIdentifier
107117
let content = contentTable.quotedDatabaseIdentifier
@@ -149,7 +159,7 @@ extension FTS4: VirtualTableModule {
149159
/// virtual table.
150160
///
151161
/// You don't create instances of this class. Instead, you use the `Database`
152-
/// ``Database/create(virtualTable:ifNotExists:using:_:)`` method:
162+
/// ``Database/create(virtualTable:options:using:_:)`` method:
153163
///
154164
/// ```swift
155165
/// try db.create(virtualTable: "document", using: FTS4()) { t in // t is FTS4TableDefinition

GRDB/FTS/FTS5.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Foundation
1313
/// The virtual table module for the FTS5 full-text engine.
1414
///
1515
/// To create FTS5 tables, use the ``Database`` method
16-
/// ``Database/create(virtualTable:ifNotExists:using:_:)``:
16+
/// ``Database/create(virtualTable:options:using:_:)``:
1717
///
1818
/// ```swift
1919
/// // CREATE VIRTUAL TABLE document USING fts5(content)
@@ -85,7 +85,7 @@ public struct FTS5 {
8585
/// }
8686
/// ```
8787
///
88-
/// See ``Database/create(virtualTable:ifNotExists:using:_:)``
88+
/// See ``Database/create(virtualTable:options:using:_:)``
8989
public init() { }
9090

9191
// Support for FTS5Pattern initializers. Don't make public. Users tokenize
@@ -143,7 +143,7 @@ extension FTS5: VirtualTableModule {
143143

144144
/// Reserved; part of the VirtualTableModule protocol.
145145
///
146-
/// See Database.create(virtualTable:using:)
146+
/// See Database.create(virtualTable:options:using:_:)
147147
public func makeTableDefinition(configuration: VirtualTableConfiguration) -> FTS5TableDefinition {
148148
FTS5TableDefinition(configuration: configuration)
149149
}
@@ -219,14 +219,24 @@ extension FTS5: VirtualTableModule {
219219

220220
/// Reserved; part of the VirtualTableModule protocol.
221221
///
222-
/// See Database.create(virtualTable:using:)
222+
/// See Database.create(virtualTable:options:using:_:)
223223
public func database(_ db: Database, didCreate tableName: String, using definition: FTS5TableDefinition) throws {
224224
switch definition.contentMode {
225225
case .raw:
226226
break
227227
case .synchronized(let contentTable):
228228
// https://sqlite.org/fts5.html#external_content_tables
229229

230+
if definition.configuration.temporary {
231+
// SQLite can't rebuild the index of temporary tables:
232+
//
233+
// sqlite> CREATE TABLE t(id INTEGER PRIMARY KEY, a, b, c);
234+
// sqlite> CREATE VIRTUAL TABLE temp.ft USING fts5(content="t",content_rowid="a",b,c);
235+
// sqlite> INSERT INTO ft(ft) VALUES('rebuild');
236+
// Runtime error: SQL logic error
237+
fatalError("Temporary external content FTS5 tables are not supported.")
238+
}
239+
230240
let rowIDColumn = try db.primaryKey(contentTable).rowIDColumn ?? Column.rowID.name
231241
let ftsTable = tableName.quotedDatabaseIdentifier
232242
let content = contentTable.quotedDatabaseIdentifier
@@ -274,7 +284,7 @@ extension FTS5: VirtualTableModule {
274284
/// virtual table.
275285
///
276286
/// You don't create instances of this class. Instead, you use the `Database`
277-
/// ``Database/create(virtualTable:ifNotExists:using:_:)`` method:
287+
/// ``Database/create(virtualTable:options:using:_:)`` method:
278288
///
279289
/// ```swift
280290
/// try db.create(virtualTable: "document", using: FTS5()) { t in // t is FTS5TableDefinition

GRDB/FTS/FTS5TokenizerDescriptor.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,14 @@ public struct FTS5TokenizerDescriptor: Sendable {
112112
if !separators.isEmpty {
113113
// TODO: test "=" and "\"", "(" and ")" as separators, with
114114
// both FTS3Pattern(matchingAnyTokenIn:tokenizer:)
115-
// and Database.create(virtualTable:using:)
115+
// and Database.create(virtualTable:options:using:_:)
116116
components.append("separators")
117117
components.append(separators.sorted().map { String($0) }.joined())
118118
}
119119
if !tokenCharacters.isEmpty {
120120
// TODO: test "=" and "\"", "(" and ")" as tokenCharacters, with
121121
// both FTS3Pattern(matchingAnyTokenIn:tokenizer:)
122-
// and Database.create(virtualTable:using:)
122+
// and Database.create(virtualTable:options:using:_:)
123123
components.append("tokenchars")
124124
components.append(tokenCharacters.sorted().map { String($0) }.joined())
125125
}
@@ -197,14 +197,14 @@ public struct FTS5TokenizerDescriptor: Sendable {
197197
if !separators.isEmpty {
198198
// TODO: test "=" and "\"", "(" and ")" as separators, with
199199
// both FTS3Pattern(matchingAnyTokenIn:tokenizer:)
200-
// and Database.create(virtualTable:using:)
200+
// and Database.create(virtualTable:options:using:_:)
201201
components.append("separators")
202202
components.append(separators.sorted().map { String($0) }.joined())
203203
}
204204
if !tokenCharacters.isEmpty {
205205
// TODO: test "=" and "\"", "(" and ")" as tokenCharacters, with
206206
// both FTS3Pattern(matchingAnyTokenIn:tokenizer:)
207-
// and Database.create(virtualTable:using:)
207+
// and Database.create(virtualTable:options:using:_:)
208208
components.append("tokenchars")
209209
components.append(tokenCharacters.sorted().map { String($0) }.joined())
210210
}

0 commit comments

Comments
 (0)