-
Notifications
You must be signed in to change notification settings - Fork 43
Full-text search #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Full-text search #120
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
a23d2cd
Full-text search
stephencelis 48f5817
wip
stephencelis c843352
wip
stephencelis 0606e55
Update FTS5.swift
stephencelis 2a09df9
Merge remote-tracking branch 'origin/main' into fts
stephencelis 41ecef1
wip
stephencelis 5c49141
wip
stephencelis 79d2c1d
wip
stephencelis b4c01ea
wip
stephencelis 3783b88
wip
stephencelis 5e6fab6
Merge remote-tracking branch 'origin/main' into fts
stephencelis 930eab1
wip
stephencelis d130940
wip
stephencelis d71b8db
fix
stephencelis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import IssueReporting | ||
|
|
||
| /// A virtual table using the FTS5 extension. | ||
| /// | ||
| /// Apply this protocol to a `@Table` declaration to introduce FTS5 helpers. | ||
| public protocol FTS5: Table {} | ||
|
|
||
| extension TableDefinition where QueryValue: FTS5 { | ||
| /// A predicate expression from this table matched against another _via_ the `MATCH` operator. | ||
| /// | ||
| /// ```swift | ||
| /// ReminderText.where { $0.match("get") } | ||
| /// // SELECT … FROM "reminderTexts" WHERE ("reminderTexts" MATCH 'get') | ||
| /// ``` | ||
| /// | ||
| /// - Parameter pattern: A string expression describing the `MATCH` pattern. | ||
| /// - Returns: A predicate expression. | ||
| public func match(_ pattern: some StringProtocol) -> some QueryExpression<Bool> { | ||
| SQLQueryExpression( | ||
| """ | ||
| (\(QueryValue.self) MATCH \(bind: "\(pattern)")) | ||
| """ | ||
| ) | ||
| } | ||
|
|
||
| /// An expression representing the search result's rank. | ||
| public var rank: some QueryExpression<Double?> { | ||
| SQLQueryExpression( | ||
| """ | ||
| \(QueryValue.self)."rank" | ||
| """ | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| extension TableColumnExpression where Root: FTS5 { | ||
| /// A string expression highlighting matches in this column using the given delimiters. | ||
| /// | ||
| /// - Parameters: | ||
| /// - open: An opening delimiter denoting the beginning of a match, _e.g._ `"<b>"`. | ||
| /// - close: A closing delimiter denoting the end of a match, _e.g._, `"</b>"`. | ||
| /// - Returns: A string expression highlighting matches in this column. | ||
| public func highlight( | ||
| _ open: some StringProtocol, | ||
| _ close: some StringProtocol | ||
| ) -> some QueryExpression<String> { | ||
| SQLQueryExpression( | ||
| """ | ||
| highlight(\ | ||
| \(Root.self), \ | ||
| (\(cid)), | ||
| \(quote: "\(open)", delimiter: .text), \ | ||
| \(quote: "\(close)", delimiter: .text)\ | ||
| ) | ||
| """ | ||
| ) | ||
| } | ||
|
|
||
| /// A predicate expression from this column matched against another _via_ the `MATCH` operator. | ||
| /// | ||
| /// ```swift | ||
| /// ReminderText.where { $0.title.match("get") } | ||
| /// // SELECT … FROM "reminderTexts" WHERE ("reminderTexts"."title" MATCH 'get') | ||
| /// ``` | ||
| /// | ||
| /// - Parameter pattern: A string expression describing the `MATCH` pattern. | ||
| /// - Returns: A predicate expression. | ||
| public func match(_ pattern: some StringProtocol) -> some QueryExpression<Bool> { | ||
| Root.columns.match("\(name):\(pattern.quoted(.identifier))") | ||
| } | ||
|
|
||
| /// A string expression highlighting matches in text fragments of this column using the given | ||
| /// delimiters. | ||
| /// | ||
| /// - Parameters: | ||
| /// - open: An opening delimiter denoting the beginning of a match, _e.g._ `"<b>"`. | ||
| /// - close: A closing delimiter denoting the end of a match, _e.g._, `"</b>"`. | ||
| /// - ellipsis: Text indicating a truncation of text in the column. | ||
| /// - tokens: The maximum number of tokens in the returned text. | ||
| /// - Returns: A string expression highlighting matches in this column. | ||
| public func snippet( | ||
| _ open: some StringProtocol, | ||
| _ close: some StringProtocol, | ||
| _ ellipsis: some StringProtocol, | ||
| _ tokens: Int | ||
| ) -> some QueryExpression<String> { | ||
| SQLQueryExpression( | ||
| """ | ||
| snippet(\ | ||
| \(Root.self), \ | ||
| (\(cid)), | ||
| \(quote: "\(open)", delimiter: .text), \ | ||
| \(quote: "\(close)", delimiter: .text), \ | ||
| \(quote: "\(ellipsis)", delimiter: .text), \ | ||
| \(raw: tokens)\ | ||
| ) | ||
| """ | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| extension TableColumnExpression { | ||
| fileprivate var cid: some Statement<Int> { | ||
| SQLQueryExpression( | ||
| """ | ||
| SELECT "cid" FROM pragma_table_info(\(quote: Root.tableName, delimiter: .text)) \ | ||
| WHERE "name" = \(quote: name, delimiter: .text) | ||
| """ | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import Foundation | ||
| import InlineSnapshotTesting | ||
| import StructuredQueries | ||
| import StructuredQueriesTestSupport | ||
| import Testing | ||
|
|
||
| extension SnapshotTests { | ||
| @Suite struct FTSTests { | ||
| @Test func basics() { | ||
| assertQuery( | ||
| ReminderText | ||
| .where { $0.match("take OR apple") } | ||
| .order(by: \.rank) | ||
| .select { ($0.title.highlight("**", "**"), $0.notes.snippet("**", "**", "...", 10)) } | ||
| ) { | ||
| """ | ||
| SELECT highlight("reminderTexts", (SELECT "cid" FROM pragma_table_info('reminderTexts') WHERE "name" = 'title'), | ||
| '**', '**'), snippet("reminderTexts", (SELECT "cid" FROM pragma_table_info('reminderTexts') WHERE "name" = 'notes'), | ||
| '**', '**', '...', 10) | ||
| FROM "reminderTexts" | ||
| WHERE ("reminderTexts" MATCH 'take OR apple') | ||
| ORDER BY "reminderTexts"."rank" | ||
| """ | ||
| } results: { | ||
| """ | ||
| ┌──────────────────────┬───────────────────────┐ | ||
| │ "Groceries" │ "...Eggs, **Apple**s" │ | ||
| │ "**Take** out trash" │ "" │ | ||
| │ "**Take** a walk" │ "" │ | ||
| └──────────────────────┴───────────────────────┘ | ||
| """ | ||
| } | ||
| } | ||
|
|
||
| @Test func unranked() { | ||
| assertQuery( | ||
| ReminderText | ||
| .select { ($0.listTitle, $0.rank) } | ||
| .limit(1) | ||
| ) { | ||
| """ | ||
| SELECT "reminderTexts"."listTitle", "reminderTexts"."rank" | ||
| FROM "reminderTexts" | ||
| LIMIT 1 | ||
| """ | ||
| } results: { | ||
| """ | ||
| ┌────────────┬─────┐ | ||
| │ "Personal" │ nil │ | ||
| └────────────┴─────┘ | ||
| """ | ||
| } | ||
| } | ||
|
|
||
| @Test func columnMatch() { | ||
| assertQuery( | ||
| ReminderText | ||
| .where { $0.title.match("take") } | ||
| ) { | ||
| """ | ||
| SELECT "reminderTexts"."reminderID", "reminderTexts"."title", "reminderTexts"."notes", "reminderTexts"."listID", "reminderTexts"."listTitle", "reminderTexts"."tags" | ||
| FROM "reminderTexts" | ||
| WHERE ("reminderTexts" MATCH 'title:"take"') | ||
| """ | ||
| } results: { | ||
| """ | ||
| ┌────────────────────────────┐ | ||
| │ ReminderText( │ | ||
| │ reminderID: 4, │ | ||
| │ title: "Take a walk", │ | ||
| │ notes: "", │ | ||
| │ listID: 1, │ | ||
| │ listTitle: "Personal", │ | ||
| │ tags: "car kids" │ | ||
| │ ) │ | ||
| ├────────────────────────────┤ | ||
| │ ReminderText( │ | ||
| │ reminderID: 8, │ | ||
| │ title: "Take out trash", │ | ||
| │ notes: "", │ | ||
| │ listID: 2, │ | ||
| │ listTitle: "Family", │ | ||
| │ tags: "" │ | ||
| │ ) │ | ||
| └────────────────────────────┘ | ||
| """ | ||
| } | ||
| } | ||
|
|
||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.