Tutorial: Querying data subsets

Serverless DatabaseData Structure Querying data subsets

Reading and subscription requests may be refined to select only a subset of the watched data nodes, that is, a subset of their child nodes. Such selections (or constraints) are called queries. For example, it is possible to select only last or first n child nodes of the watched data node, or a range of child nodes with respect to their keys (or names).

The ordering of child nodes with respect to their keys, which is used to find out the last or first n ones or a range of them, is formally defined in the Key Ordering section.

Reading the last or first n elements of a list

In Javascript, the previously presented get() and subscribe() methods take an additional optional parameter of type Constraint. The First() and Last() constraints make it possible to select respectively the first n or last n children of the watched data node:

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;

// Reference to our list of contacts
const contactsNode = database.rootNode.relativeNode("contacts");

// Get first 5 elements of the list
contactsNode.get(Webcom.Constraint.First(5)) // a constraint can be passed as parameter
    .then(snapshot => {
        const first5Contacts = Object.values(snapshot.val());
        console.log("The first five contacts are:" , first5Contacts.map(contact => contact.firstName).join(", "));
    });

// Subscribe to last 5 elements of the list
contactsNode.subscribe(
    Webcom.Event.ValueChange,
    Webcom.Callback(snapshot => {
        // this callback will be called immediately and also in future, each time one of the 5 last nodes is changed
        const last5Contacts = Object.values(snapshot.val());
        console.log("The last five contacts are now:" , last5Contacts.map(contact => contact.firstName).join(", "));
    }),
    Webcom.Constraint.Last(5) // a constraint can be passed as 3rd parameter
);

In Kotlin, the previously presented get() and subscribe(...) methods take an additional optional constraint parameter of type Constraint. The First and Last subclasses make it possible to respectively select the first n or last n children of the watched data node:

import com.orange.webcom.sdk.datasync.subscription.Constraint.*
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*

// Reference to our list of contacts
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"

// Get first 5 elements of the list
contactsNode.get(First(5)) { // a constraint can be passed as parameter
    if (it is WebcomResult.Success) { // it: WebcomResult<DatasyncValue>.Success
        val first5Contacts = it.result.asListOf(Contact::class)
        println("The first five contacts are: ${first5Contacts.map { it.firstName }?.joinToString(", ")}")
    }
}

// Subscribe to last 5 elements of the list
val subscription = contactsNode.subscribe(Value.Changed::class, Last(5)) { // a constraint can be passed as 2nd parameter
    if (it is Notification.DataNotification) { // it: Notification<Value.Changed>.DataNotification
        val last5Contacts = it.data.value.asListOf(Contact::class)
        println("The last five contacts are now: ${last5Contacts.map { it.firstName }?.joinToString(", ")}")
    }
}

In Swift, the subscribe() method accepts a DatasyncChildrenConstraint value in the chidrenConstraint parameter, which makes it possible to refine the queried data. The first(limit:) and last(limit:) constraint values make it possible to respectively select the first n or last n children of the watched data node:

// Reference to our list of contacts
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"

// Subscribe to last 5 elements of the list
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .last(limit: 5)) { event in
    guard let last5Contacts: [Contact] = event.value.decoded() else {
        return
    }
    print("The last five contacts are now:", last5Contacts.map(\.firstName).joined(separator: ", "))
}

// or:

// Subscribe to first 5 elements of the list
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .first(limit: 5)) { event in
    guard let first5Contacts: [Contact] = event.value.decoded() else {
        return
    }
    print("The first five contacts are now:", first5Contacts.map(\.firstName).joined(separator: ", "))
}

Reading a range of keys

Before querying a range of child nodes, please read the Key Ordering section to understand how the keys of child nodes (ie their names) are sorted under a data node.

In JavaScript, the Between() constraint selects all children between 2 keys (inclusive).

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;

// Reference to our list of contacts
const contactsNode = database.rootNode.relativeNode("contacts");

// Subscribe to the elements between "a" and "l" (inclusive)
contactsNode.subscribe(
    Webcom.Event.ValueChange,
    Webcom.Callback(snapshot => {
        const rangeContacts = Object.values(snapshot.val());
        console.log("Contacts between 'a' and 'l' are now:", rangeContacts.map(contact => contact.firstName).join(", "))
    }),
    Webcom.Constraint.Between("a", "l")
);

In Kotlin, the Constraint class has the Between sub-class that queries a range of child nodes between a lower and an upper keys (inclusive):

import com.orange.webcom.sdk.datasync.subscription.Constraint.*
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*

// Reference to our list of contacts
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"

// Subscribe to the elements between "a" and "l" (inclusive)
val subscription = contactsNode.subscribe(Value.Changed::class, Between("a", "l")) { // it: Notification<Value.Changed>
    if (it is Notification.DataNotification) { // it.data: Value.Changed
        val rangeContacts = it.data.value.asListOf(Contact::class)
        println("Contacts between 'a' and 'l' are now: ${rangeContacts.map { it.firstName }?.joinToString(", ")}")
    }
}

In Swift, the between(startKey:endKey:) constraint value represents a query of child nodes between a lower and an upper keys (inclusive):

// Reference to our list of contacts
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"

// Subscribe to the elements between "a" and "l" (inclusive)
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .between(startKey: "a", endKey: "l")) { event in
    guard let rangeContacts: [Contact] = event.value.decoded() else {
        return
    }
    print("Contacts between 'a' and 'l' are now:", rangeContacts.map(\.firstName).joined(separator: ", "))
    // => jimix
}

Reading from a key with a limit

It is possible to query the n child nodes after or before a given key (child name). Such a query is still computed along the key order definition.

In JavaScript, the StartAt() and EndAt() constraints select either all (no given limit parameter) or n (passed as the limit parameter) children respectively starting from or ending at a given key (inclusive).

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
const database = app.serverlessDb;

// Reference to our list of contacts
const contactsNode = database.rootNode.relativeNode("contacts");

// Subscribe to the 2 elements from "k"
contactsNode.subscribe(
    Webcom.Event.ValueChange,
    Webcom.Callback(snapshot => {
        const twoFromKContacts = Object.values(snapshot.val());
        console.log("The 2 contacts from 'k' are now:", twoFromKContacts.map(contact => contact.firstName).join(", "))
    }),
    Webcom.Constraint.StartAt("k", 2)
);

In Kotlin, the Constraint class has the StartAt and EndAt sub-classes that query either all (no given limit property) or n (given as the limit property) children respectively starting from or ending at a given key (inclusive).

import com.orange.webcom.sdk.datasync.subscription.Constraint.*
import com.orange.webcom.sdk.datasync.subscription.SubscribableEvent.*

// Reference to our list of contacts
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"

// Subscribe to the elements between "a" and "l" (inclusive)
val subscription = contactsNode.subscribe(Value.Changed::class, Between("a", "l")) { // it: Notification<Value.Changed>
    if (it is Notification.DataNotification) { // it.data: Value.Changed
        val twoFromKContacts = it.data.value.asListOf(Contact::class)
        println("The 2 contacts from 'k' are now: ${twoFromKContacts.map { it.firstName }?.joinToString(", ")}")
    }
}

In Swift, simply use the startAt(key:limit:) or endAt(key:limit:) constraint value.

// Reference to our list of contacts
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"

// Subscribe to the 2 elements from "k"
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .startAt(key: "k", limit: 2)) { event in
    guard let twoFromKContacts: [Contact] = event.value.decoded() else {
        return
    }
    print("The 2 contacts from 'k' are now:", twoFromKContacts.map(\.firstName).joined(separator: ", "))
    // => lennon, macca
}