Skip to content

pointfreeco/sharing-grdb

Repository files navigation

SharingGRDB

A fast, lightweight replacement for SwiftData, powered by SQL.

CI Slack

Learn more

This library was motivated and designed over the course of many episodes on Point-Free, a video series exploring advanced programming topics in the Swift language, hosted by Brandon Williams and Stephen Celis. To support the continued development of this library, subscribe today.

video poster image

Overview

SharingGRDB is a fast, lightweight replacement for SwiftData that deploys all the way back to the iOS 13 generation of targets. To populate data from the database you can use the @FetchAll property wrapper, which is similar to SwiftData's @Query macro:

SharingGRDB SwiftData
@FetchAll
var items: [Item]

@Table
struct Item {
  let id: Int
  var title = ""
  var isInStock = true
  var notes = ""
}
@Query
var items: [Item]

@Model
class Item {
  var title: String
  var isInStock: Bool
  var notes: String
  init(
    title: String = "",
    isInStock: Bool = true,
    notes: String = ""
  ) {
    self.title = title
    self.isInStock = isInStock
    self.notes = notes
  }
}

Both of the above examples fetch items from an external data store using Swift data types, and both are automatically observed by SwiftUI so that views are recomputed when the external data changes, but SharingGRDB is powered directly by SQLite using Sharing, StructuredQueries, and GRDB, and is usable from UIKit, @Observable models, and more.

For more information on SharingGRDB's querying capabilities, see Fetching model data.

Quick start

Before SharingGRDB's property wrappers can fetch data from SQLite, you need to provide–at runtime–the default database it should use. This is typically done as early as possible in your app's lifetime, like the app entry point in SwiftUI, and is analogous to configuring model storage in SwiftData:

SharingGRDB SwiftData
@main
struct MyApp: App {
  init() {
    prepareDependencies {
      let db = try! DatabaseQueue(
        // Create/migrate a database 
        // connection
      )
      $0.defaultDatabase = db
    }
  }
  // ...
}
@main
struct MyApp: App {
  let container = { 
    // Create/configure a container
    try! ModelContainer(/* ... */)
  }()
  
  var body: some Scene {
    WindowGroup {
      ContentView()
        .modelContainer(container)
    }
  }
}

Note

For more information on preparing a SQLite database, see Preparing a SQLite database.

This defaultDatabase connection is used implicitly by SharingGRDB's strategies, like @FetchAll and @FetchOne:

@FetchAll
var items: [Item]

@FetchOne(Item.where(\.isInStock).count())
var inStockItemsCount = 0

And you can access this database throughout your application in a way similar to how one accesses a model context, via a property wrapper:

SharingGRDB SwiftData
@Dependency(\.defaultDatabase) 
var database
    
let newItem = Item(/* ... */)
try database.write { db in
  try Item.insert(newItem)
    .execute(db))
}
@Environment(\.modelContext) 
var modelContext
    
let newItem = Item(/* ... */)
modelContext.insert(newItem)
try modelContext.save()

Note

For more information on how SharingGRDB compares to SwiftData, see Comparison with SwiftData.

This is all you need to know to get started with SharingGRDB, but there's much more to learn. Read the articles below to learn how to best utilize this library:

Performance

SharingGRDB leverages high-performance decoding from StructuredQueries to turn fetched data into your Swift domain types, and has a performance profile similar to invoking SQLite's C APIs directly.

See the following benchmarks against Lighter's performance test suite for a taste of how it compares:

Orders.fetchAll                          setup    rampup   duration
  SQLite (generated by Enlighter 1.4.10) 0        0.144    7.183
  Lighter (1.4.10)                       0        0.164    8.059
  SharingGRDB (0.2.0)                    0        0.172    8.511
  GRDB (7.4.1, manual decoding)          0        0.376    18.819
  SQLite.swift (0.15.3, manual decoding) 0        0.564    27.994
  SQLite.swift (0.15.3, Codable)         0        0.863    43.261
  GRDB (7.4.1, Codable)                  0.002    1.07     53.326

SQLite knowledge required

SQLite is one of the most established and widely distributed pieces of software in the history of software. Knowledge of SQLite is a great skill for any app developer to have, and this library does not want to conceal it from you. So, we feel that to best wield this library you should be familiar with the basics of SQLite, including schema design and normalization, SQL queries, including joins and aggregates, and performance, including indices.

With some basic knowledge you can apply this library to your database schema in order to query for data and keep your views up-to-date when data in the database changes, and you can use StructuredQueries to build queries, either using its type-safe, discoverable query building APIs, or using its #sql macro for writing safe SQL strings.

Demos

This repo comes with lots of examples to demonstrate how to solve common and complex problems with Sharing. Check out this directory to see them all, including:

  • Case Studies: A number of case studies demonstrating the built-in features of the library.

  • SyncUps: We also rebuilt Apple's Scrumdinger demo application using modern, best practices for SwiftUI development, including using this library to query and persist state using SQLite.

  • Reminders: A rebuild of Apple's Reminders app that uses a SQLite database to model the reminders, lists and tags. It features many advanced queries, such as searching, and stats aggregation.

Documentation

The documentation for releases and main are available here:

Installation

You can add SharingGRDB to an Xcode project by adding it to your project as a package…

https://github.com/pointfreeco/sharing-grdb

…and adding the SharingGRDB product to your target.

Tip

SharingGRDB's primary product is the SharingGRDB module, which includes all of the library's functionality, including the @Fetch family of property wrappers, the @Table macro, and tools for driving StructuredQueries using GRDB. This is the module that most library users should depend on.

If you are a library author that wishes to extend SharingGRDB with additional functionality, you may want to depend on a different module:

  • SharingGRDBCore: This product includes everything in SharingGRDB except the macros (@Table, #sql, etc.). This module can be imported to extend SharingGRDB with additional functionality without forcing the heavyweight dependency of SwiftSyntax on your users.
  • StructuredQueriesGRDB: This product includes everything in SharingGRDB except the @Fetch family of property wrappers. It can be imported if you want to extend StructuredQueries' GRDB driver but do not need access to observation tools provided by Sharing.
  • StructuredQueriesGRDBCore: This product includes everything in StructuredQueriesGRDB except the macros. This module can be imported to extend StructuredQueries' GRDB driver with additional functionality without forcing the heavyweight dependency of SwiftSyntax on your users.

If you want to use SharingGRDB in a SwiftPM project, it's as simple as adding it to your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/sharing-grdb", from: "0.2.0")
]

And then adding the following product to any target that needs access to the library:

.product(name: "SharingGRDB", package: "sharing-grdb"),

Community

If you want to discuss this library or have a question about how to use it to solve a particular problem, there are a number of places you can discuss with fellow Point-Free enthusiasts:

License

This library is released under the MIT license. See LICENSE for details.

About

A lightweight replacement for SwiftData and the Query macro.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages