See Document Model for more information on documents in Ditto.

Just like with conventional database querying, you execute query operations to fetch one or more documents that satisfy specific criteria and conditions.

To perform a single execution query on the Ditto store, call the execute API method on the store namespace as follows:

let result = await ditto.store.execute(query: "SELECT * FROM cars")

Using args to Query Dynamic Values

When dealing with data that may change dynamically during runtime, instead of defining the changing values directly in the query string, encapsulate them in a top-level args object you can use to reference the values in your queries.

To pass an argument to the execute function, use the :[argument] syntax with DQL where the [argument] maps to the field in the provided args object.

For example, here color is passed as an argument to the execute function, and, within the query string, :color placeholder references the color defined in a top-level args object.

let result = await ditto.store.execute(
  query: "SELECT * FROM cars WHERE color = :color",
  arguments: [ "color": "blue" ])

Once the previous example operation executes, the query becomes SELECT * FROM cars WHERE color = blue.

Managing Query Results

After executing a query, the result object that is returned includes both the overall content retrieved and individual items. Each item is encapsulated in independent QueryResultItem objects that you can use to access them either directly or as raw CBOR or JSON data.

Rather than retrieving the items as part of the query execution and making them available immediately after execution, each item is lazy loaded. Lazy loading involves postponing fetching and storing in memory until requested on-demand or accessed later.

Here is an example query execution to select all documents from the cars collection. The result is stored in the variable result. Then, each item is lazy loaded from the result object and stored in the items:


let result = await ditto.store.execute(query: "SELECT * FROM cars")

let items = result.items

Working with a QueryResultItem

The result items object is a collection ofQueryResultItem. Each item’s value can be independently managed to meet the needs of your scenario.

Value

To retrieve the value, call the value property on an item:

let result = await ditto.store.execute(query: "SELECT * FROM cars")

let item = result.items[0]
let itemValue = item.value
let itemValueColor = item.value["color"]

Materializing the Value

To help manage memory usage more efficiently, content associated with an item is lazily loaded, meaning it materializes — loads into memory — only upon the initial call to value.

To load the item’s value into memory, use any of the following methods as appropriate:


// Returns `true` if value is currently held materialized in memory, otherwise returns `false`
item.isMaterialized

// Loads the item's value into memory
item.materialize()

// Release item's value from memory
item.dematerialize()

Raw CBOR Value

To access the result items as CBOR data:

The result of this method call is not cached.


let result = await ditto.store.execute(query: "SELECT * FROM cars")

let cborSerializedItem = result.items[0].cborData()

Raw JSON Value

To access the result items as a JSON-string:

The result of this method call is not cached.


let result = await ditto.store.execute(query: "SELECT * FROM cars")

let jsonSerializedItem: String = result.items[0].jsonString()

let jsonAsData: Data = Data(jsonSerializedItem.utf8)

Diffing Results

There are some use cases where it may be required to calculate the difference between previous and current query results. For example, when you want to update a UI component or external HTTP system with the latest data, you may want to know which items have been added, removed, or changed since the last time you received the query result. The Ditto SDK provides a DittoDiffer class that allows you to opt-in to diffing only when necessary, thereby avoiding unnecessary performance and memory costs.

However, diffing is computationally expensive. It requires storing the previous query results in memory, leading to increased RAM usage, which can be particularly problematic for applications handling large datasets or frequent updates. Instead of computing diffs synchronously with every store observer update, it is recommended to debounce diffing, processing changes out-of-band at a cadence that best suits your use case.

A DittoDiffer is given query result items and compares them to the previous set of items it has received:

let differ = DittoDiffer() // []
let observer = ditto.store.registerObserver(
  query: "...") { queryResult in
    let diff = differ.diff(queryResult.items)
    // Handle the diff...
}

The diff() method returns a DittoDiff containing:

  • insertions: Indexes of new items in the new array
  • deletions: Indexes of items that were removed from the old array
  • updates: Indexes of items in the new array that were present in the old array and whose value has changed
  • moves: Pairs of indexes showing items that changed position

Example: Apple UIKit

Use this diff to update a UIKit class, such as UITableView, UICollectionView :

Swift
let collectionView = /* assuming a UICollectionView exists */
let differ = DittoDiffer()
ditto.store.registerObserver("...") { queryResult in

    /* ... Update data source content based on query result ... */

    let diff = differ.diff(queryResult.items)
    tableView.performBatchUpdates({
        let insertions = diff.insertions.map { IndexPath(item: $0, section: 0) }
        let deletions = diff.deletions.map { IndexPath(item: $0, section: 0) }
        let reloads = diff.updates.map { IndexPath(item: $0, section: 0) }
        
        collectionView.insertItems(at: insertions)         
        collectionView.deleteItems(at: deletions)         
        collectionView.reloadItems(at: reloads)
        
        collectionView.moves.map { move in
            let (from, to) = move
            collectionView.move(at: from, to: to)
        }
    }, completion: nil)
}

Key considerations

  • Keep references to arrays yourself: The differ doesn’t provide access to the old and new items themselves, so they need to be retained by the user if needed.

  • No comparison of document metadata: The differ performs a deep comparison of the value of each query result item but doesn’t take into account any metadata. Applying a series of changes to a document will not cause it to show up in updated unless those changes result in a different value from the initial state to the final state.

  • No async diffing: Computing a diff on a set of many query result items or very large query result items might block the device depending on its hardware.