EP 338 · Modern Search · Sep 8, 2025 ·Members

Video #338: Modern Search: Highlights & Snippets

smart_display

Loading stream…

Video #338: Modern Search: Highlights & Snippets

Episode: Video #338 Date: Sep 8, 2025 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep338-modern-search-highlights-snippets

Episode thumbnail

Description

SQLite’s full-text search capabilities come with many bells and whistles, including support for highlighting search term matches in your UI, as well as generating snippets for where matches appear in a larger corpus. We will take these APIs for a spin and enhance our Reminders search UI.

Video

Cloudflare Stream video ID: e0e6d427b177d228474e1b4409ba8b9d Local file: video_338_modern-search-highlights-snippets.mp4 *(download with --video 338)*

References

Transcript

0:05

This is all looking incredible. We are now tapping into the power of full-text search by allowing it to rank our documents based on search terms and a variety of other factors. We can even provide a custom set of weights so that a search term appearing in the title of a reminder scores a little more than if it appears in the notes or tags. And we are doing it all in a type-safe and schema-safe manner. Stephen

0:28

And it may be hard to believe, but there is even more power lurking in the shadows of the FTS5 module in SQLite. Let’s start with search term highlighting. The FTS5 module comes with a function called highlight that allows you to highlight the search terms inside the fields you are searching. This makes it possible to create very friendly search UI’s where not only are the search results being displayed to the user, but the actual bits of text that were matched from the query are highlighted in the view.

0:52

Sounds complex, but FTS5 makes this a breeze. Let’s check it out. Highlighting

0:59

Let’s start by just writing some SQL directly in a SQLite console so that we can see how to use the SQLite function. Let’s start with a simple

MATCH 1:15

That result set is about what we would expect. We get a few reminders that have the word “week” in their title, notes or tags.

MATCH 1:22

The highlight function from FTS5 allows us to select additional columns beyond these that will further apply a text transformation to the content. We can even jump to the docs of FTS5 to see how it is used.

MATCH 1:36

We will find that highlight takes exactly 4 arguments, the first of which is the virtual FTS table: SELECT highlight(reminderTexts, ) FROM reminderTexts WHERE reminderTexts MATCH 'week';

MATCH 1:49

The second argument is the index of the column we want to highlight. Let’s start with the title, which is index 1 when using 0-based indexing, which is what FTS5 uses: SELECT highlight(reminderTexts, 1, ) FROM reminderTexts WHERE reminderTexts MATCH 'week';

MATCH 1:59

And then you provide two strings that describe how to begin a highlighting portion and how you end it. So, for example, if we were dealing with markdown, we could use : sqlite> SELECT highlight(reminderTexts, 1, '', '') ..> FROM reminderTexts ..> WHERE reminderTexts MATCH 'week'; ┌─────────────────────────────────────────┐ │ highlight(reminderTexts, 1, '', '') │ ├─────────────────────────────────────────┤ │ Groceries │ │ Haircut next week │ │ Take a walk this week │ │ Send week**ly emails │ └─────────────────────────────────────────┘

MATCH 2:13

And now we see that FTS5 has done the work for us to find “week” inside the title and surround it with bolding tags. If we were to render this string with an AttributedString in SwiftUI, we would immediately get bolding for free.

MATCH 2:27

And you are of course to use other bold markers, such as if you were rendering HTML you could use the <strong> tag: sqlite> SELECT highlight(reminderTexts, 1, '<strong>', '</strong>') ..> FROM reminderTexts ..> WHERE reminderTexts MATCH 'week'; ┌──────────────────────────────────────────────────────┐ │ highlight(reminderTexts, 1, '<strong>', '</strong>') │ ├──────────────────────────────────────────────────────┤ │ Groceries │ │ Haircut next <strong>week</strong> │ │ Take a walk this <strong>week</strong> │ │ Send <strong>week</strong>ly emails │ └──────────────────────────────────────────────────────┘

MATCH 2:38

You can also choose to highlight as many fields as you want. For example, we can highlight the title, notes and tags all at once: sqlite> SELECT ...> highlight(reminderTexts, 1, '', '') as title, ...> highlight(reminderTexts, 2, '', '') as notes, ...> highlight(reminderTexts, 3, '', '') as tags ...> FROM reminderTexts ...> WHERE reminderTexts match 'week'; ┌───────────────────────────┬───────────────────────────────────────┬──────────────────────┐ │ title │ notes │ tags │ ├───────────────────────────┼───────────────────────────────────────┼──────────────────────┤ │ Groceries │ Check weekly specials │ weekend │ │ │ Eggs │ │ │ │ Apples │ │ │ │ Oatmeal │ │ │ │ Spinach │ │ ├───────────────────────────┼───────────────────────────────────────┼──────────────────────┤ │ Haircut next week │ Ask if I can reschedule next weekweekend,easy-win │ ├───────────────────────────┼───────────────────────────────────────┼──────────────────────┤ │ Take a walk this week │ │ weekend,fun │ ├───────────────────────────┼───────────────────────────────────────┼──────────────────────┤ │ Send weekly emails │ │ │ └───────────────────────────┴───────────────────────────────────────┴──────────────────────┘

MATCH 3:03

And if we search for multiple terms, each term will be individually bolded, such as if we search for “milk oatmeal”: sqlite> SELECT ...> highlight(reminderTexts, 1, '', '') as title, ...> highlight(reminderTexts, 2, '', '') as notes, ...> highlight(reminderTexts, 3, '', '') as tags ...> FROM reminderTexts ...> WHERE reminderTexts match 'milk oatmeal'; ┌───────────┬─────────────┬─────────┐ │ title │ notes │ tags │ ├───────────┼─────────────┼─────────┤ │ Groceries │ Milk │ weekend │ │ │ Eggs │ │ │ │ Apples │ │ │ │ Oatmeal │ │ │ │ Spinach │ │ └───────────┴─────────────┴─────────┘

MATCH 3:16

So it’s pretty amazing that FTS5 gives us all of this functionality with very little work on our part. This is where all of our hard work getting the virtual table into place and keeping it up-to-date starts to pay dividends.

MATCH 3:27

Now we just have to figure out how to make use of these tools in our app. Let’s start with highlighting the search terms found in the title. Currently we select all the data needed for each row in this select clause: .select { reminder, _, _, tag, remindersList in Row.Columns( color: remindersList.color, isPastDue: reminder.isPastDue, reminder: reminder, tags: tag.jsonTitles ) } 3:380 Just as we did in the query in the SQLite console, we would want to use the highlight function in this select. However, we can’t compute the highlighted title and then somehow cram it into the existing reminder data we are selecting: reminder: reminder,

MATCH 3:54

We instead need to select the highlighted title as additional data right alongside the other data we are selecting. And that means we need to add it to our Row selection data type: @Selection struct Row { … let formattedTitle: String … }

MATCH 4:06

Which now means we need to provide this value when constructing Row.Columns in our select: Row.Columns( color: remindersList.color,, formattedTitle: ???, isPastDue: reminder.isPastDue, reminder: reminder, // tags: tag.jsonTitles, tags: #sql("'[]'") )

MATCH 4:14

And one of the great things about our StructuredQueries library, as we’ve said over and over, is that we can always resort to plain SQL strings if we ever find ourselves using advanced functionality of SQLite that is not supported by our query builder. And so we can literally write a SQL string for this title directly inline: formattedTitle: #sql("highlight(reminderTexts, 1, '', '')")

MATCH 4:43

And we can even make this a little safer by interpolating the type of the ReminderText table into the query: formattedTitle: #sql( "highlight(\(ReminderText.self), 1, '', '')" )

MATCH 4:50

That way we don’t accidentally introduce a typo.

MATCH 4:52

It is still a bummer we have to hardcode the 1 as the index of the column in the reminderTexts table. That is definitely something that is easy to get wrong.

MATCH 5:01

But, luckily for us, our StructuredQueries library does have support for this FTS5 function, and you can invoke it on the columns of virtual tables, like ReminderText . So, let’s give the reminderText table definition a name in our select closure: .select { reminder, reminderText, remindersList in … }

MATCH 5:12

And now we can invoke the highlight function on the title field of reminderText : formattedTitle: reminderText.title.highlight("", "")

MATCH 5:27

Notice that we don’t even have to specify the index of the title column inside the reminderTexts table. That can be figured out automatically.

MATCH 5:35

OK, we are now selecting the title from each found reminder with stylized highlighting. How do we display that in the UI? Well SwiftUI has support for rendering markdown, and so we just need to find a way to pass this formatted title down to the row view that is responsible for displaying the various parts of the reminder.

MATCH 5:50

Let’s add a new field to the ReminderRow for the formatted title, but because it does not need to always be used we will make it optional: struct ReminderRow: View { … var formattedTitle: String? … }

MATCH 6:02

Now when we render the title of the reminder we can first try rendering this formatted title as markdown in an AttributedString , and if the formatted title isn’t present we can fallback to the unformatted title.

MATCH 6:13

To aid in this we will define a little helper called highlight : func highlight(_ text: String) -> Text { } …that kind of acts as the SwiftUI analogy to FTS5’s highlight.

MATCH 6:26

To implement this we will try to construct an AttributedString from markdown: func highlight(_ text: String) -> Text { if let attributedText = try? AttributedString(markdown: text) { Text(attributedText) } else { Text(text) } }

MATCH 6:56

And now we can use this helper to highlight the formatted title, if it exists, or just fallback to the regular reminder title: highlight(formattedTitle ?? reminder.title) .foregroundStyle(reminder.isCompleted ? .gray : .primary)

MATCH 7:10

And we still have to pass this data to the view from the search feature: ReminderRow( color: Color(hex: row.color), formattedTitle: row.formattedTitle, isPastDue: row.isPastDue, reminder: row.reminder, tags: row.tags, onDetailsTapped: {} )

MATCH 7:27

And incredibly that is all it takes. We can run the app and search for “week” to see that it is highlighted in the titles of the search results. It’s absolutely amazing how easy SQLite makes this kind of advanced functionality possible. Snippets

MATCH 7:48

We have now added search result highlighting to our feature, and it only took a few lines of code. SQLite has made it so easy for us to add this functionality that I think it’s easy for us to take for granted. And this is just a small part of why we think it’s a safe bet to invest heavily into SQLite for the database of your applications. Brandon

MATCH 8:04

But there’s more. Highlighting search terms in the results is a great way to draw attention to why the results are being shown, but sometimes the document being search is so large that we can’t easily see those bolded terms. For example, we limit the notes of the reminder being displayed to just the first two lines, and so if the search term match happened on line 3 or later, you won’t even get to see that information.

MATCH 8:29

And this is where snippets come into play. The FTS5 module comes with another function that allows you to focus in on the search matches within a large document by truncating the content before and after the term.

MATCH 8:44

Let’s take a look at how this can be used to further improve the user experience of search in our app.

MATCH 8:51

Let’s start by seeing why we want this snippet functionality. If I search for “spinach” we will see that the “Groceries” reminder is found, and in the notes there is “Spinach”. Now we are lucky the the notes field is relatively short, and that we take extra care to remove all newlines from the notes when displaying it in the row, that way we can show as much info as possible.

MATCH 9:18

But let’s add more notes. I’ve got a few extra things I need from the grocery store: Chips Dips Yogurt Granola Tomatoes

MATCH 9:41

With that done let’s search for “Spinach” again, and now we will see that it doesn’t even show in the notes. It is too far from the beginning of the notes to be displayed in two lines.

MATCH 10:03

Well, luckily for us the FTS5 snippet function helps us with this. It is capable of generating a string from the notes such that the search terms are visible and so that some of the surrounding, less relevant content is truncated with ellipses.

MATCH 10:21

Let’s take a look at the docs to see what it offers.

MATCH 10:27

It takes 6 arguments total:

MATCH 10:30

The first argument is the FTS5 virtual table, which is reminderTexts in our case.

MATCH 10:37

The second argument is the index of the column that we want to generate the snippet for.

MATCH 10:43

The third and forth argument is the text that is inserted before and after the search match, very similar to how highlighting works.

MATCH 10:52

The fifth argument is the text to use when part of the document is truncated.

MATCH 11:04

And the last argument is the maximum number of tokens to return in the snippet, which must be between 1 and 64.

MATCH 11:15

So, let’s give this function a shot in the console. Let’s highlight the snippet of the notes column, which is at index 2, and use an ellipsis for the truncation, and use the max of 64 for the snippet: sqlite> select snippet(reminderTexts, 2, '', '', '...', 64) ..> from reminderTexts ..> where reminderTexts MATCH 'spinach'; ┌──────────────────────────────────────────────────┐ │ snippet(reminderTexts, 2, '', '', '...', 64) │ ├──────────────────────────────────────────────────┤ │ ...hips │ │ Dips │ │ Yogurt │ │ Granola │ │ Tomatoes │ │ Milk │ │ Eggs │ │ Apples │ │ Oatmeal │ │ Spinach │ └──────────────────────────────────────────────────┘

MATCH 12:03

With just that it has already truncated a bit from the beginning. We are no longer showing the “Check weekly specials” line, and the “C” in “Chips” has been truncated. That may even be just enough for us to see “Spinach” in the notes in our actual reminders app.

MATCH 12:21

But if we drop the number of tokens a bit, like say to 40, things will give a little shorter: sqlite> select snippet(reminderTexts, 2, '', '', '...', 40) ..> from reminderTexts ..> where reminderTexts MATCH 'spinach'; ┌──────────────────────────────────────────────────┐ │ snippet(reminderTexts, 2, '', '', '...', 40) │ ├──────────────────────────────────────────────────┤ │ ... │ │ Tomatoes │ │ Milk │ │ Eggs │ │ Apples │ │ Oatmeal │ │ Spinach │ └──────────────────────────────────────────────────┘

MATCH 12:32

Now you may be wondering how this 40 actually corresponds to what we are seeing here. There are certainly fewer than 40 words being shown here, and so why wouldn’t the entire notes be displayed?

MATCH 12:40

Well going from 64 to 40 certainly made things smaller, but to get a very concrete understanding, let’s drop things down to a single token: sqlite> select snippet(reminderTexts, 2, '', '', '...', 1) ..> from reminderTexts ..> where reminderTexts MATCH 'spinach'; ┌─────────────────────────────────────────────────┐ │ snippet(reminderTexts, 2, '', '', '...', 1) │ ├─────────────────────────────────────────────────┤ │ ...ina... │ └─────────────────────────────────────────────────┘

MATCH 12:59

OK, we just get ”ina”, which are the 3 characters in the middle of “Spinach”. Remember that we are using the trigram tokenizer for full-text search, which creates a 3-character token for every character in a column, and so this is a single token from the match.

MATCH 13:21

If we request 3 tokens, we’ll get 2 more character for each token: sqlite> select snippet(reminderTexts, 2, '', '', '...', 3) ..> from reminderTexts ..> where reminderTexts MATCH 'spinach'; ┌─────────────────────────────────────────────────┐ │ snippet(reminderTexts, 2, '', '', '...', 3) │ ├─────────────────────────────────────────────────┤ │ ...pinac... │ └─────────────────────────────────────────────────┘ So the trigram tokens of pin , ina , and nac .

MATCH 13:33

And so when we specified 40 tokens, the number of characters we get back is going to roughly be around 40, but with more characters for the highlighting and truncating delimiters, as well as a couple extra characters for the trigram tokenization.

MATCH 14:11

This behavior is very specific to trigram tokenization. If we were using another tokenizer, like the default FTS5 tokenizer that tokenizes each word, then snippet would return the number of word token specified, instead.

MATCH 14:41

Now let’s see what it takes to start using the snippet function in our reminders app. We will follow the pattern we have already set up when we added highlighting to the app. We will add a new property to our Row selection type to model the formatted notes: @Selection struct Row { … let formattedNotes: String … }

MATCH 15:02

And then when selecting the data we will make use of the snippet function that is defined on columns of FTS5 virtual tables: formattedNotes: reminderText.notes.snippet("", "", "...", 40),

MATCH 15:28

Then in the ReminderRow we will add a formattedNotes field that is optional so that it will be used only if it is present: struct ReminderRow: View { … var formattedNotes: String? … }

MATCH 15:40

And then the view we can use the highlight function on the formatted notes if it is present, otherwise we fallback to the reminder notes: let notes = formattedNotes ?? reminder.notes if !notes.isEmpty { highlight(notes.replacingOccurrences(of: "\n", with: " ")) … }

MATCH 16:00

And then finally we will update our construction of the ReminderRow to pass along the formatted notes: ReminderRow( … formattedNotes: row.formattedNotes, … )

MATCH 16:16

Unbelievable, that is all it takes. When we run the app and search for “Spinach” we now do see it in the results row because the a bit of the notes has been clipped from the beginning, and further the search term is highlighted in the notes. Bringing back tags

MATCH 17:16

It’s incredible to see how much functionality we get out of FTS5 with very little work. We are now offloading the difficult work of searching for our search terms in our database, highlight the fragments of the text that match the search terms, and further focus in on the matches within the text so that we can see exactly why the reminder is being shown to us. Stephen

MATCH 17:34

That’s right, this is incredible stuff, but now let’s show off a surprising benefit to using full-text search. It can help make some of our queries more efficient, even ones that are not doing searching. Because our FTS index is essentially a denormalized view into our data, we can sometimes just read the data directly from this index rather than computing it on the fly from our tables.

MATCH 17:53

And where this technically really shines is with tags. To compute the tags for a reminder one must join two tables since it is a many-to-many association. But often all we need is just a string representation of the tags associated with a reminder, not the full structured data of the details of the tags.

MATCH 18:11

Let’s see how we can use our FTS index to bring tag display back to our search results, and improve the efficiency of the query along the way.

MATCH 18:19

Recall that we had to comment out the joins for our tags, as well as the group clause: // .group { reminder, _ in reminder.id } // .leftJoin(ReminderTag.all) { $0.id.eq($2.reminderID) } // .leftJoin(Tag.all) { $2.tagID.eq($3.id) }

MATCH 18:26

…because these kinds of aggregations do not play nicely with some FTS5 functions, such as bm25 .

MATCH 18:32

And because these joins are commented out we had to comment out the aggregation of tags and instead hard coded an empty JSON array of tags: // tags: tag.jsonTitles, tags: #sql("'[]'")

MATCH 18:39

Let’s bring back this functionality to the app, but we’ll do so without needing to join the tags tables. Let’s start by hoping over to the SQLite console and selecting the tags field from each row in the reminderTexts FTS table: sqlite> select tags from reminderTexts; ┌──────────────────┐ │ tags │ ├──────────────────┤ │ weekend │ │ weekend easy-win │ │ │ │ weekend fun │ │ fun │ │ easy-win │ │ easy-win │ │ easy-win │ │ │ │ │ └──────────────────┘

MATCH 18:52

We made the choice at the beginning of this series to denormalize all of the tag data for a reminder into a single string by just separating the tags with a space. That certainly makes sense, and we could of course split on these strings to recover a Swift array of tags, but also we are free to store this data however we want in the index.

MATCH 19:07

What if we stored the data in a format that is still easily searchable by FTS, but that also is in a format that we tend to display often in the app? Then we could simply read the tags column from the index for display in the app instead of joining tags tables, shuffling data around with JSON, just to get an array of tags in Swift that we ultimately have to join together and format.

MATCH 19:27

Recall that the place we insert this tag data into the FTS index is in the AFTER INSERT and AFTER DELETE triggers we install when the database is provisioned. In particular, this select statement is what concatenates the tag titles together with a space separating them: .select { $0.title.groupConcat(" ") }

MATCH 19:51

Let’s change this so that denormalize the tags into a string that is already formatted how we want to display in the UI. That is, prefixed with “#” symbols and separated by spaces.

MATCH 19:59

To do that we can simply prepend a “#” symbol to the title before concatenating the tags together with a space between: .select { ("#" + $1.title).groupConcat(" ") }

MATCH 20:13

That’s all it takes, but that does not magically update our FTS index. If we were to relaunch the app it would still have the old format of tags in its index. To update the index with this new format we do need to do work to rebuild the index. There are various ways to do that, but we aren’t going to focus on that right now.

MATCH 20:29

Instead, we will just delete our existing app, reinstall, and re-seed the data. That will insert a bunch of data into our fresh database, which will give our triggers a chance to insert that data into the FTS index in the new format.

MATCH 20:42

Now if we open the database and select the tags column from the virtual table we will see the index holds the new format of our tags: sqlite> select tags from reminderTexts; ┌────────────────────┐ │ tags │ ├────────────────────┤ │ #weekend │ │ #weekend #easy-win │ │ │ │ #weekend #fun │ │ #fun │ │ #easy-win │ │ #easy-win │ │ #easy-win │ │ │ │ │ └────────────────────┘

MATCH 20:59

And if we perform a search for the word “week” and highlight the tags: sqlite> select highlight(reminderTexts, 3, '', '') ..> from reminderTexts ..> where reminderTexts MATCH 'week'; ┌─────────────────────────────────────────┐ │ highlight(reminderTexts, 3, '', '') │ ├─────────────────────────────────────────┤ │ #weekend │ │ #weekend #easy-win │ │ #weekend #fun │ │ │ └─────────────────────────────────────────┘

MATCH 21:17

…we will see we get something that we could easily feed into an attributed string and get free formatting for our tags.

MATCH 21:27

So, let’s start using this newly formatted data in our application. When constructing the Row.Columns in the search feature we can now use the formatted tags directly in the reminderText index rather than joining the tags tables: Row.Columns( … tags: reminderText.tags.highlight("", "") )

MATCH 21:45

But that means we need update the Row type so that its tags field is just a simple string: @Selection struct Row { … let tags: String }

MATCH 21:52

Which means we also need to update the ReminderRow to also take just a simple string description of tags: struct ReminderRow: View { let color: Color var formattedNotes: String? var formattedTitle: String? let isPastDue: Bool let reminder: Reminder let tags: String … }

MATCH 22:00

And now that the tags state is simpler we can also simplify how we compute the subtitle text displayed in the row: private var subtitleText: Text { Text( """ \(dueText) \(highlight(tags).foregroundStyle(.gray)) """ ) .font(.callout) }

MATCH 22:34

We just have a few more compiler errors to deal with. There’s a preview that needs to be updated so that we using a string for tags instead of an array: tags: "#weekend #fun"

MATCH 22:45

Next we need to update the Row struct inside the RemindersDetailModel to compute its tags as a plain string instead of an array of strings: @MainActor @Observable class RemindersDetailModel { … @Selection struct Row { let isPastDue: Bool let reminder: Reminder let tags: String } }

MATCH 22:59

And then that means we need to update our query to compute tags as a simple string. Now we could of course repeat the work we have done previously to prefix the tag titles with the “#” symbol and concatenate them together. But we have to take special care here because tags and left joined, and hence optionalized, and so we have to do a bit more coalescing to get this to compile: .select { Row.Columns( isPastDue: $0.isPastDue, reminder: $0, tags: ("#" + ($2.title ?? "")).groupConcat(" ") ?? "" ) }

MATCH 23:52

But there’s a better way. Our ReminderText FTS table already holds the tags for every reminder in a nicely-formatted string. So, what if we trade in the two joins to tag tables for a single join to the ReminderText table: .join(ReminderText.all) { $0.id.eq($1.reminderID) } .select { Row.Columns( isPastDue: $0.isPastDue, reminder: $0, tags: $1.tags ) }

MATCH 24:20

That’s one less join, and all the data is already formatted for us.

MATCH 24:24

It may seem weird for us to join this table even though we are not performing any searching, but there’s nothing wrong with doing that. For the most part it’s a table just like any other kind of table, and it happens to hold denormalized and nicely formatted data. And so it’s totally OK for us to join to it in order to get access to that data.

MATCH 25:12

And with that everything is compiling, and tags are back to working like they did before we commented out all of the joins. If we search for week we will see that the results that are displayed also have their tags highlighted where appropriate. Next time: The FTS query language

MATCH 25:28

The search functionality of our reminders app is really starting to shine. Not only are we using SQLite’s FTS5 module to provide advanced searching capabilities, including ranking, term highlighting and term snippets, but we are even able to leverage all of this work to make our queries more efficient across the board. Even for queries that have nothing to do with searching.

MATCH 25:45

We’re getting close to the end of this series, and we’ve already covered so many things that we didn’t plan on when we started. But that’s just because of how incredible and powerful the FTS5 module is. And there’s still one more thing we want to cover. Brandon

MATCH 25:57

It turns out that the text we feed to the

NEAR 26:23

Let’s explore these powers and see how we can leverage them to build an advanced search interface into our reminders app…next time! References SQLite FTS5 Extension FTS5 is an SQLite virtual table module that provides full-text search functionality to database applications. https://www.sqlite.org/fts5.html SQLiteData Brandon Williams & Stephen Celis A fast, lightweight replacement for SwiftData, powered by SQL. https://github.com/pointfreeco/sqlite-data StructuredQueries A library for building SQL in a safe, expressive, and composable manner. https://github.com/pointfreeco/swift-structured-queries Downloads Sample code 0338-fts-pt5 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .