Advanced PostgreSQL With Vapor
Learn to use advanced PostgreSQL functionalities, joining tables, views, indexes and full-text search in your Vapor server app. By Mahdi Bahrami.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Advanced PostgreSQL With Vapor
25 mins
- Getting Started
- Joining Tables
- Using Joins
- Different Types of Joins
- Using Views for Frequently Accessed Queries
- Making an Employee Model
- Creating Views Using SQLKit
- Using Materialized Views for Frequently Accessed Data
- Full-Text Search
- Trying Full-Text Search
- Using Full-Text Search Operators
- Indexing Columns
- Running Queries in a Database Client
- Explaining SQL Queries
- Creating Indexes
- Using Indexes in SQL Queries
- Where to Go From Here?
Creating Indexes
Go back to Recruiter. Make a file named CreateIndexes in Migrations. Add this migration to it:
import Fluent
import SQLKit
struct CreatePG_TRGMExtension: AsyncMigration {
func prepare(on database: Database) async throws {
let sqlDatabase = (database as! SQLDatabase)
try await sqlDatabase.raw("CREATE EXTENSION pg_trgm").run()
}
func revert(on database: Database) async throws {
let sqlDatabase = (database as! SQLDatabase)
try await sqlDatabase.raw("DROP EXTENSION pg_trgm").run()
}
}
This enables PostgreSQL’s pg_trgm
module, which is disabled by default. You’ll need this module to instruct PostgreSQL on how to make the a Gin index over the columns you want.
Add another migration to this file, this time to create a Gin index over the first_name
and last_name
columns:
struct CreatePersonFirstNameAndLastNameIndex: AsyncMigration {
private typealias FieldKeys = Person.FieldKeys
func prepare(on database: Database) async throws {
let sqlDatabase = (database as! SQLDatabase)
try await sqlDatabase.raw("""
CREATE INDEX person_first_name_last_name_idx
ON person
USING GIN
(\(raw: FieldKeys.firstName.description) gin_trgm_ops,
\(raw: FieldKeys.lastName.description) gin_trgm_ops)
""").run()
}
func revert(on database: Database) async throws {
let sqlDatabase = (database as! SQLDatabase)
try await sqlDatabase
.raw("DROP INDEX person_first_name_last_name_idx")
.run()
}
}
In prepare(on:)
, you ask PostgreSQL to create a new index:
- Named
person_first_name_last_name_idx
. - On the
person
table. - Using the Gin index type.
- Over the columns
first_name
andlast_name
and using thegin_trgm_ops
operator class. Gin indexes support specifying more than one column, so here you create a multi-column index on two columns instead of creating two indexes.
gin_trgm_ops
is part of the pg_trgm
module, which is why you needed to enable pg_trgm
in the last migration.
gin_trgm_ops
are out of scope for this tutorial. You can learn more about them in PostgreSQL’s official documentation.
Don’t forget to add your new migrations to your configure.swift:
app.migrations.add([
CreateCompany(),
SeedCompany(),
CreatePerson(),
SeedPerson(),
CreateEmployee(),
CreatePG_TRGMExtension(),
CreatePersonFirstNameAndLastNameIndex(),
])
Build and run Recruiter to create the index. Gin indexes are helpful and flexible, but they’re also quite heavy, so creating and keeping them updated is costly. Luckily, you have only 100,000 people, so the index creation shouldn’t take more than a second!
Using Indexes in SQL Queries
Now, open Postico and rerun the Explain statement you did last time:
EXPLAIN ANALYZE SELECT * FROM person WHERE first_name ILIKE '%jul%';
PostgreSQL automatically uses the index you made earlier. That’s because PostgreSQL’s query planner can identify that the first_name
column has an index available and that it’ll be helpful for your specific query.
You can see the execution time has decreased from 55ms, when you had no index over the first_name
column, to 2.3ms when there’s a useful index available. That’s more than 20 times faster than before, and this execution time difference will only increase with more people in the person
table.
Note that the execution times might differ on your device, but they will remain close to what you see here.
Where to Go From Here?
You can download the sample project by clicking Download Materials at the top or bottom of this tutorial.
To learn more about SQLKit, you can read SQLKit’s readme.
If you want to learn more about PostgreSQL’s great features, see the official PostgreSQL documentation.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below.