Giter Club home page Giter Club logo

grdbdiff's Introduction

๐Ÿšง EXPERIMENTAL - DON'T USE IN PRODUCTION ๐Ÿšง

GRDBDiff

Various diff algorithms for SQLite, based on GRDB.

Since it is possible to observe database changes, it is a natural desire to compute diffs between two consecutive observed values.

There are many diff algorithms, which perform various kinds of comparisons. GRDBDiff ships with a few of them. Make sure you pick one that suits your needs.

Demo Application

The repository comes with a demo application that shows you:

  • How to synchronize the annotations on a map view with the content of the database.
  • How to animate a table view according to changes in the database.
PlayersViewController Screenshot PlacesViewController Screenshot

Set Differences

What are the elements that were inserted, updated, deleted?

This is the question that Set Differences can answer.

Set Differences do not care about the ordering of elements. They are well suited, for example, for synchronizing the annotations in a map view with the content of the database. But they can not animate table views or collection views, which care a lot about the ordering of their cells.

You track Set Differences with those three ValueObservation methods:

extension ValueObservation {
    func setDifferencesFromRequest(...) -> ValueObservation
    func setDifferencesFromRequest(startingFrom:...) -> ValueObservation
    func setDifferences(...) -> ValueObservation
}

For example:

// Track favorite places
let request = Place.filter(Column("favorite")).orderedByPrimaryKey()
let observer = try ValueObservation
    .trackingAll(request)
    .setDifferencesFromRequest()
    .start(in: dbQueue) { diff: SetDiff<Place> in
        print(diff.inserted) // [Place]
        print(diff.updated)  // [Place]
        print(diff.deleted)  // [Place]
    }

You will choose one method or the other, depending on the type of the observed values. Record types can use setDifferencesFromRequest. The more general setDifferences variant requires a type that conform to both Identifiable and the standard Equatable protocols.

setDifferencesFromRequest()

Usage

// 1.
struct Place: FetchableRecord, TableRecord { ... }

// 2.
let request = Place.orderedByPrimaryKey()

// 3.
let placesObservation = ValueObservation.trackingAll(request)

// 4.
let diffObservation = placesObservation.setDifferencesFromRequest()

// 5.
let observer = diffObservation.start(in: dbQueue) { diff: SetDiff<Place> in
    print(diff.inserted) // [Place]
    print(diff.updated)  // [Place]
    print(diff.deleted)  // [Place]
}
  1. Define a Record type that conforms to both FetchableRecord and TableRecord protocols.

    FetchableRecord makes it possible to fetch places from the database. TableRecord provides the database primary key for places, which allows to identity places, and decide if they were inserted, updated, or deleted.

  2. Define a database request of the records you are interested in. Make sure the request is ordered by primary key. You'll get wrong results if the request is not properly ordered.

    Ordering records by primary key provides an efficient O(N) computation of diffs.

  3. Define a ValueObservation from the request, with the ValueObservation.trackingAll method.

  4. Derive a Set Differences observation with the setDifferencesFromRequest method.

  5. Start the observation and enjoy your diffs!

The Optional onUpdate Parameter

By default, the records notified in the diff.updated array are newly created values.

When you need to customize handling of updated records, provide a onUpdate closure. Its first parameter is an old record. The second one is a new database row. It returns the record that should be notified in diff.updated. It does not run on the main queue.

For example, this observation prints changes:

let diffObservation = placesObservation
    .setDifferencesFromRequest(onUpdate: { (place: Place, row: Row) in
        let newPlace = Place(row: row)
        print("changes: \(newPlace.databaseChanges(from: place))")
        return newPlace
    })

And this other one reuses record instances:

let diffObservation = placesObservation
    .setDifferencesFromRequest(onUpdate: { (place: Place, row: Row) in
        place.update(from: row)
        return place
    })

setDifferencesFromRequest(startingFrom:)

This method gives the same results as setDifferencesFromRequest(). The differences are:

  • The tracked record type must conform to a PersistableRecord protocol, on top of FetchableRecord and TableRecord.

  • The startingFrom parameter is passed an array of records used to compute the first diff. Make sure this array is ordered by primary key. You'll get wrong results otherwise.

setDifferences()

Usage

// 1.
struct Element: Identifiable, Equatable { ... }

// 2.
let elementsObservation = ValueObservation.tracking...

// 3.
let diffObservation = elementsObservation.setDifferences()

// 4.
let observer = diffObservation.start(in: dbQueue) { diff: SetDiff<Element> in
    print(diff.inserted) // [Element]
    print(diff.updated)  // [Element]
    print(diff.deleted)  // [Element]
}
  1. Define a type that conforms to both Identifiable and the standard Equatable protocols.

    Those two protocol allow to decide which elements they were inserted, updated, or deleted.

  2. Define a ValueObservation which notifies elements. Elements must be sorted by identity. They must not contain two elements with the same identity. You'll get wrong results otherwise.

    Ordering elements by primary key provides an efficient O(N) computation of diffs.

  3. Derive a Set Differences observation with the setDifferences method.

  4. Start the observation and enjoy your diffs!

The Identifiable Protocol

protocol Identifiable {
    associatedtype Identity: Equatable
    var identity: Identity { get }
}

Identifiable is the protocol for "identifiable" values, which have an identity.

When an identifiable type also adopts the Equatable protocol, two values that are equal must have the same identity. It is a programmer error to break this rule.

However, two values that share the same identity may not be equal. In GRDBDiff, a value has been "updated" if two versions share the same identity, but are not equal.

The Optional onUpdate Parameter

When you need to customize handling of updated elements, provide a onUpdate closure. Its first parameter is an old element. The second one is a new element. It returns the element that should be notified in diff.updated. It does not run on the main queue.

For example, this observation reuses element instances:

let diffObservation = elementsObservation
    .setDifferences(onUpdate: { (old: Element, new: Element) in
        old.update(from: new)
        return old
    })

The Optional startingFrom Parameter

The startingFrom parameter is passed an array of elements used to compute the first diff. Make sure this array is ordered by identity, and does not contain two elements with the same identity. You'll get wrong results otherwise.

UITableView and UICollectionView Animations

GRDBDiff does not ship with any diff algorithm able to perform such animation.

But you can leverage third-party libraries. See the demo application for an example of integration of Differ with GRDB.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.