Unexpected SQLite with Swift

Written by gwendal.roue | Published 2017/05/02
Tech Story Tags: swift | sqlite | database

TLDRvia the TL;DR App

There is a Swift library that I enjoy almost daily because many apps I write use a database for local storage. GRDB.swift puts all bets on SQLite, a library that is both tremendously famous, and not very well-known.

SQLite is designed to be embedded, and to access local databases. This puts it quite apart from client-server databases, and brings a lot of unique features. Unfortunately very few developers use them, assuming they even know about their existence. This is because those features are usually unavailable unless you program in C.

There are also strong cultural assumptions about database libraries, from low-level drivers to full-fledged ORMs, with SQL query builders right in the middle. Breaking those assumptions requires a lot of communication and pedagogy, as exemplified by Realm, a database engine supported by a vivid community and a very active company.

The rest of this article lists a few unexpected features of SQLite and GRDB.

WAL Mode, aka Efficient Multithreading

WAL means Write Ahead Logging. It is a technique that allows SQLite to support concurrent database readers and writers, and greatly improves multi-threaded applications. For example, the UI thread is never blocked when it loads data from the database.

GRDB supports WAL mode through the DatabasePool class, and provides a concurrency guide that covers the best practices for efficient multithreaded applications.

Reactive SQLite

SQLite provides Data Change Notification Callbacks, and Compile-Time Authorization Callbacks. The first notify of modified rows, when the latter tell which tables and columns are involved in an SQL statement.

When you combine them, you are able to tell if an SQL statement has the opportunity to affect the results of another query. This is the recipe for database observation through RxGRDB (a reactive extension for GRDB over RxSwift), or FetchedRecordsController (a Swift version of Core Data’s NSFetchedResultsController).

A Protocol-Oriented ORM

An ORM (Object-Relational Mapping) maps the raw database rows to easy objects named records.

Features commonly found in ORMs are fetching and persistence methods, relationships, lazy loading, record auto-updating, and record uniquing. The last three of them are typical of ORMs that manage records and pretend they directly map database rows. This illusion generally introduces a base class for all record types, and fences between application threads in order to avoid mutation of shared data.

The focus on immutability and value types introduced by languages like Swift and Rust brings a new way to write an ORM: it is now possible to drop the common base class, share records across threads, and use the database as the single source of truth for the whole application. GRDB records and Rust’s Diesel build on those new foundations.

Useful Errors

Too many database libraries pretend database errors don’t happen, make it easy to overlook them, or emit inscrutable error messages. GRBD uses the full range of Swift errors, and provides as detailed errors as possible.

Hassle-Free SQL

GRDB welcomes developers who are not familiar with SQL and SQLite: its query interface makes it possible to access the database without writing a single line of SQL.

In the same time, no query builder can generate all possible queries, and the occasional complex query may be easier to read when written as plain SQL. That’s why GRDB makes sure that you never pay for your SQL skills.

For example, you can load records from SQL as well as from Swift requests:

// Two one liners that load the same array of Person:try Person.filter(emailColumn != nil).fetchAll(db)try Person.fetchAll(db, """SELECT * FROM personsWHERE email IS NOT NULL""")

Reactive extensions are just as versatile:

let request = SQLRequest("SELECT * FROM persons")request.rx.changes(in: dbPool).subscribe(onNext: { db: Database inprint("Persons table has changed.")})

try dbPool.write { db intry Person(name: "Arthur").insert(db)try Person(name: "Barbara").insert(db)} // Prints "Persons table has changed."

In the same spirit, GRDB supports all kinds of primary, unique, and foreign keys: you can freely design the integrity of your relational schema, and escape the dreary id column when it does not fit the bill.

SQLite and GRDB are two libraries that deliver both simple and advanced tools that help developing applications. Check out the documentation for more unexpected goodies such as advanced unicode support, record changes tracking, care for memory-constrained devices, tailored SQLite builds, customized full-text indexation…

http://groue.github.io/GRDB.swift/

This article is part of the GRDB Stories:

  1. Fetching Values
  2. Memory Consumption
  3. Querying the Database from the Debugger
  4. Primary Key Freedom
  5. FetchedRecordsController
  6. Loose Coupling between Objects and Database
  7. Four different ways to handle SQLite concurrency
  8. How to build an iOS application with GRDB.swift
  9. Unexpected SQLite with Swift

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!


Published by HackerNoon on 2017/05/02