Couchbase Lite: utilizzo delle viste

Home  >>  Sviluppo App  >>  Couchbase Lite: utilizzo delle viste

Couchbase Lite: utilizzo delle viste

On aprile 28, 2015, Posted by , In Sviluppo App, With No Comments

Introduzione

Le funzionalità di interrogazione e ordinamento offerte da Couchbase Lite utilizzano lo stesso modello che si trova in Couchbase Server. Tutto è basato sugli indici creati utilizzando una funzione di tipo Map/Reduce, eseguita su tutti i documenti memorizzati nel database. MapReduce è abbastanza semplice e, una volta creati gli indici, tramite essi saremo in grado di interrogare il database.

 Indici e Map/Reduce


 

La funzione Map

La funzione MapReduce viene invocata passando due argomenti: un documento e un emitter. L’emitter è un oggetto utilizzato per aggiungere voci ad un indice. Il documento è un documento JSON memorizzato nel database. Questa funzione Map verrà eseguita su tutti i documenti JSON disponibili. Significa che avremo la possibilità di utilizzare l’emitter per ogni documento. L’indice, nella terminologia Couchbase, è chiamato View.

Di seguito un semplice esempio con tre documenti JSON che rappresentano dei generici prodotti:

 



{
"type":"Product",
"created_at":"19850506",
"productName":"MyProduct"
}
{
"type":"Product",
"created_at":"20140805",
"productName":"AnotherProductName"
}
{
"type":"Product",
"created_at":"19860816",
"productName":"YetAnotherProductName"
}

Nella seguente funzione Map, il primo argomento è il nostro documento JSON rappresentato come un oggetto di tipo Map. Il secondo argomento è l’oggetto di tipo Emitter che permette di aggiungere una voce a un indice.

@Override

public void map(Map<String, Object> document, Emitter emitter) {

  if ("Product".equals(document.get("type"))) {

    emitter.emit(document.get("created_at"), document.get("productName"));

  }
}

La funzione Map aggiungerà una voce di indice per ogni documento memorizzato nel database per il quale il campo type vale “Product”. Questa voce dell’indice avrà una chiave e un valore. La chiave è il primo argomento del metodo

emitter.emit()

e il valore è il secondo argomento. Quindi, come si può vedere qui, la chiave è il campo “created_at” e il valore è il campo “productName”.

Ora abbiamo un indice che produce un output simile al seguente:

 

Key Value
19850506 MyProductName
docId3  
20140805 AnotherProductName
docId1  
19860816 YetAnotherProductName
docId2  

 Questo indice è ordinato per chiave utilizzando regole di confronto (collation) Unicode. Oltre al valore, ogni voce di indice ha sempre l’id del documento utilizzato per crearlo. E questo indice può naturalmente essere utilizzato per ricavare il documento dal database o si può applicare una funzione di riduzione.

La funzione Reduce

La funzione Reduce è totalmente facoltativa e non è comunemente utilizzata come la funzione Map. Il suo scopo è di elaborare l’indice risultante da una funzione Map che viene applicato a tutti i documenti del database. Può essere utilizzato ad esempio per calcolare il numero totale dei documenti di tipo “Product” nel database. Poiché abbiamo una funzione Map che ha già creato un indice con una voce per ogni prodotto nel database, possiamo quindi associare la seguente funzione reduce ad esso:


@Override

public Object reduce(List<object> keys, List<Object> values, boolean rereduce) {
  return new Integer(values.size());
}

Il primo argomento è la lista delle chiavi, il secondo è la lista dei valori e il terzo è un valore booleano che indica se un’altra funzione di Reduce può essere eseguita dpo questa. Per restituire il numero totale di prodotti, tutto quello che dobbiamo fare è restituire le dimensioni del primo o del secondo argomento. L’indice creato da una funzione MapReduce è chiamato View nella terminologia Couchbase. Quindi, se si desidera utilizzarla nel codice, è necessario richiamarla. Se non esiste, verrà creata:



// Creo una vista e registro le sue funzioni Map/Reduce:
View productView = database.getView("products");
productView.setMapReduce(new Mapper() {
    @Override
    public void map(Map<String, Object> document, Emitter emitter) {
      if ("Product".equals(document.get("type"))) {

        emitter.emit(document.get("created_at"), document.get("productName"));

      }
    }
    }, new Reducer() {
       @Override
       public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) {
         return new Integer(values.size());
       }
   }, "2");


L’oggetto ritornato é un Integer che rappresenta il numero totale di documenti che hanno come valore del campo “type” la stringa “Product”.

Query


Come interrogare il database tramite una vista

L’esempio seguente mostra una semplice interrogazione tramite vista:



Query query = database.getView("products").createQuery();
// non abbiamo bisogno di una reduce qui
query.setMapOnly(true);
QueryEnumerator result = query.run();
for (Iterator<QueryRow> it = result; it.hasNext(); ) {
   QueryRow row = it.next();
   String productName = (String) row.getValue();
   //Stiamo sviluppando in Adroid la Activity "MYAPP"
   Log.w("MYAPP", "Il nome del prodotto è %s", productName);
}

Qui sopra vediamo la forma più semplice di utilizzo di una View, prima otteniamo la view “products” e su questa creiamo la query. Il ciclo for restituirà tutti i documenti JSON disponibili nel database e indicizzati per type=”Product”. Nel logcat di Android Studio vedremo stampati i nomi dei prodotti.
Spesso non si desidera ricavare tutti i documenti JSON restituiti dalla vista, ma vogliamo filtrare i risultati in base a specifici attributi o in base ad un certo intervallo di valori di determinati attributi.

Quindi è possibile modificare alcuni parametri dell’oggetto query. Nell’esempio di qui sopra il risultato della query verrà loggato dal più vecchio al più recente. Se vogliamo ordinare i risultati in senso inverso possiamo utilizzare il parametro decrescente:

  • descending: se settato a true le chiavi verranno restituite in ordine decrescente. (Questo inverte anche il significato delle proprietà startKey e endKey, la query restituirà le chiavi dal valore più alto a quello più basso.)

A questo punto abbiamo tutti i prodotti dal più recente al più vecchio. Nel database potremmo avere molti documenti, e non possiamo visualizzarli tutti in un singolo frame della nostra applicazione mobile. Possiamo però limitare il numero di rows restituite dalla view con il parametro limit:

  • limit: se diverso da zero, rappresenta il massimo numero di rows restituite.

Assumiamo che esistano più di 3 documenti JSON che rappresentano dei prodotti nel database con valori ‘created_at’ dal 1968 al 1984. Il prossimo passo logico è di ottenere i 10 più recenti. Per fare questo si può usare il parametro skip. In questo modo implementiamo la paginazione:

  • skip: se diverso da zero, indica quante rows verranno skippate (iniziando dalla startKey se esiste.)

Il codice risultante per la visualizzazione del terzo screen di una paginazione di 10 rows sarà quindi:


Query query = database.getView("products").createQuery();
// non abbiamo bisogno di una reduce qui
query.setMapOnly(true);
query.setDescending(true);
query.setLimit(10);
query.setSkip(20);

Questo esempio di paginazione funziona bene se vogliamo mostrare tutti i prodotti senza applicare alcun particolare criterio. Ma se vogliamo restringere la lista dei prodotti compresi tra due date possiamo utilizzare i  parametri startKey e endKey:

  • startKey: il valore della chiave iniziale. Il valore di default, null, indica che la chiave iniziale è la prima.
  • endKey: L’ultima chiave da ritornare. Il valore di default, null, indica che la chiave finale è l’ultima disponibile.

Per ricavare i 10 prodotti più recenti tra tutti quelli creati dal 6 maggio 1985 al 14 aprile 2015 (mio compleanno!):


Query query = database.getView("products").createQuery();
// non abbiamo bisogno di una reduce qui
query.setMapOnly(true);
query.setDescending(true);
query.setLimit(10);
query.setSkip(0);
query.setStartKey(19850506);
query.setEndKey(20150414);

risultati della query

Ora, dopo aver completato i parametri di filtro degli indici, possiamo stampare sul logcat i risultati. Per prima cosa otteniamo l’oggetto Query dalla View; con i settaggi visti prima dell’oggetto Query, viene invocato il metodo run(), per ottenere i risultati. Essi sono ritornati in un oggetto di tipo QueryEnumerator, una Collection Enumarable di oggetti di tipo QueryRow.



Query query = database.getView("products").createQuery();
// non abbiamo qui bisogno di una reduce
query.setMapOnly(true);
query.setDescending(true);
query.setLimit(10);
query.setSkip(0);
query.setStartKey(19850506);
query.setEndKey(20150414);
//Ottieni i risultati
QueryEnumerator result = query.run();
for (Iterator<QueryRow> it = result; it.hasNext(); ) {
   QueryRow row = it.next();
   String productName = (String) row.getValue();
   Log.w("MYAPP", "Product named %s", productName);
}

Quindi i risultati ottenuti contengono i documenti che sono stati creati in un range specifico, definito dall’attributo ‘created_at’, che è stato specificato con i metodi setStartKey e setEndKey; basandosi sui parametri settati i risultati ottenuti dalla query alla view includono i primi 3 documenti JSON, poichè gli altri documenti non sono nel range ‘created_at’.
E’ importante anche sapere dove termina la paginazione: quante pagine vogliamo? Per definire questo aspetto possiamo usare una funzione di reduce. Tutto ciò di cui abbiamo bisogno è di settare MapOnly a false; il risultato è ancora un QueryEnumerator ma con solo una riga il cui valore è ciò che viene restituito dalla funzione di reduce.


Query query = database.getView("products").createQuery();
query.setMapOnly(false);
QueryEnumerator result = query.run();
Integer total = (Integer) result.getRow(0).getValue();

Se vogliamo filtrare i risultati in base a qualche criterio di ricerca dobbiamo usare i parametri startKey e endKey:


Query query = database.getView("products").createQuery();
query.setMapOnly(true);
query.setStartKey(19850506);
query.setEndKey(20150414);
QueryEnumerator result = query.run();
Integer total = (Integer) result.getRow(0).getValue();

Query by Key

Tutti i precedenti esempi di query presumono che non si conoscano gli ID (identificativi univoci dei documenti JSON nel database). Se abbiamo queste informazioni, cioè conosciamo gli ID dei documenti, possiamo effettuare query mirate per ricavare i relativi dati. Per questo si può usare il metodo setKeys() che vuole come parametro un array di chiavi:


Query query = database.createAllDocumentsQuery();
String[] keys = {"docId1","docId2"};
query.setKeys(keys);
QueryEnumerator result = query.run();

La query che restituisce tutti i documenti

Questo tipo di query può essere considerata come una built-in query. Non richiede una view specifica, può essere eseguita così com’è. Semplicemente ritorna tutte le chiavi dei documenti del database e può essere usata per effettuare query basate sul valore di una chiave.



@Override

public void map(Map<String, Object> document, Emitter emitter) {

    emitter.emit(document.getId(), null);

}

Questa query può utilizzare tutte le opzioni disponibili, come il range, il sorting, ecc. Per default questa view non restituisce tutto, ma solo i documenti non cancellati. Esiste anche la possibilità di ottenere documenti cancellati o eventuali conflitti esistenti. Per un approfondimento completo si rimanda alla documentazione ufficiale di Couchbase.

conclusioni

Nota Bene: una view non è una query, è un indice. Le Views sono persistenti, e devono essere aggiornate, in modo incrementale, ogni volta che i documenti vengono modificati. Quindi avere molte Views può essere costoso, in termini di prestazioni e quindi di consumo della batteria. E’ meglio perciò avere poche Views ed utilizzare le tecniche qui mostrate per effettuare le query in modo “intelligente”.

Lascia un Commento