Requêtes Datastore

Une requête Datastore récupère depuis Cloud Datastore des entités qui répondent à un ensemble déterminé de conditions.

Une requête type comprend les éléments suivants :

  • Un genre d'entité auquel s'applique la requête
  • Des filtres facultatifs basés sur les valeurs de propriété, les clés et les ancêtres des entités
  • Des ordres de tri facultatifs pour séquencer les résultats
Lorsque la requête est exécutée, elle récupère toutes les entités d'un genre donné qui satisfont à l'ensemble des filtres définis, en les triant dans l'ordre spécifié. Les requêtes s'exécutent en lecture seule.

Cette page décrit la structure et les genres de requêtes employés dans App Engine pour récupérer des données depuis Cloud Datastore.

Filtres

Les filtres d'une requête définissent des contraintes sur les propriétés, les clés et les ancêtres des entités à récupérer.

Filtres de propriété

Un filtre de propriété spécifie

  • Un nom de propriété
  • Un opérateur de comparaison
  • Une valeur de propriété
Par exemple :

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

La valeur de propriété doit être fournie par l'application. Elle ne peut pas faire référence à d'autres propriétés ni être calculée en fonction de celles-ci. Une entité satisfait au filtre si elle possède une propriété du nom donné, dont la valeur est comparable à celle spécifiée dans le filtre, de la manière décrite par l'opérateur de comparaison.

L'opérateur de comparaison peut être l'un des suivants (défini sous forme de constantes énumérées dans la classe imbriquée Query.FilterOperator) :

Opérateur Signification
EQUAL Égal à
LESS_THAN Inférieur à
LESS_THAN_OR_EQUAL Inférieur ou égal à
GREATER_THAN Supérieur à
GREATER_THAN_OR_EQUAL Supérieur ou égal à
NOT_EQUAL Différent de
IN Membre de (égal à l'une des valeurs d'une liste spécifiée)

L'opérateur NOT_EQUAL exécute deux requêtes: une dans laquelle tous les autres filtres restent inchangés et le filtre NOT_EQUAL est remplacé par un filtre LESS_THAN, puis une autre où il est remplacé par un filtre GREATER_THAN. Les résultats sont ensuite fusionnés dans l'ordre. Une requête ne doit pas comporter plus d'un filtre NOT_EQUAL, et une requête contenant un tel filtre ne peut coexister avec aucun autre filtre d'inégalité.

L'opérateur IN exécute également plusieurs requêtes : une pour chaque élément de la liste spécifiée, tous les autres filtres restant inchangés et le filtre IN étant remplacé par un filtre d'égalité EQUAL. Les résultats sont fusionnés dans l'ordre des éléments de la liste. Une requête comportant plus d'un filtre IN est exécutée sous la forme de plusieurs requêtes, une pour chaque combinaison possible de valeurs dans les listes IN.

Une requête unique contenant des opérateurs NOT_EQUAL ou IN est limitée à 30 sous-requêtes.

Pour en savoir plus sur la façon dont les requêtes NOT_EQUAL et IN se traduisent en plusieurs requêtes dans un framework JDO/JPA, consultez l'article Queries with != and IN filters (Requêtes avec les filtres != et IN).

Filtres de clé

Pour filtrer sur la valeur d'une clé d'entité, faites appel à la propriété spéciale Entity.KEY_RESERVED_PROPERTY :

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

Les tris par ordre croissant sur Entity.KEY_RESERVED_PROPERTY sont également acceptés.

En cas de comparaison d'inégalité, les clés sont triées selon les critères suivants, dans cet ordre :

  1. Chemin d'ancêtre
  2. Genre d'entité
  3. Identifiant (nom de clé ou ID numérique)

Les éléments du chemin d'ancêtre sont comparés de la même manière : par genre (chaîne), puis par nom de clé ou ID numérique. Les genres et les noms de clé sont des chaînes, et sont triés par valeur d'octet. Les ID numériques sont des entiers et sont triés par ordre numérique. Si des entités ayant le même parent et le même genre emploient une combinaison de chaînes de nom de clé et d'ID numériques, celles avec des ID numériques précèdent celles portant des noms de clé.

Les requêtes portant sur des clés utilisent des index, tout comme celles portant sur des propriétés. En outre, elles nécessitent des index personnalisés dans les mêmes cas, à quelques exceptions près : les filtres d'inégalité ou un ordre de tri croissant sur la clé ne nécessitent pas d'index personnalisé, contrairement à un ordre de tri décroissant sur la clé. Comme pour toutes les requêtes, le serveur Web de développement crée les entrées appropriées dans le fichier de configuration d'index lorsqu'une requête nécessitant un index personnalisé est testée.

Filtres d'ancêtre

Vous pouvez filtrer vos requêtes Datastore sur un ancêtre spécifié, de sorte que les résultats affichés n'incluent que les entités descendant de cet ancêtre :

Query q = new Query("Person").setAncestor(ancestorKey);

Types spéciaux de requêtes

Certains types spécifiques de requêtes méritent une attention particulière :

Requêtes sans genre

Une requête sans genre ni filtre d'ancêtre récupère toutes les entités d'une application à partir de Datastore. Cela inclut les entités créées et gérées par d'autres fonctionnalités d'App Engine, telles que les entités statistiques et les entités de métadonnées Blobstore (le cas échéant). Ces requêtes sans genre ne peuvent pas inclure de filtres ni d'ordres de tri sur les valeurs de propriété. Toutefois, elles peuvent filtrer par clés d'entité en spécifiant Entity.KEY_RESERVED_PROPERTY comme nom de propriété :

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

Requêtes ascendantes

Une requête dotée d'un filtre d'ancêtre limite ses résultats à l'entité spécifiée et à ses descendants :

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

Requêtes ascendantes sans genre

Une requête sans genre incluant un filtre d'ancêtre récupérera l'ancêtre spécifié et tous ses descendants, quel qu'en soit le genre. Ce type de requête ne nécessite pas d'index personnalisé. Comme toutes les requêtes sans genre, elle ne peut pas inclure de filtres ni d'ordres de tri sur les valeurs de propriété, mais elle peut filtrer sur la clé de l'entité :

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

L'exemple suivant montre comment récupérer toutes les entités descendant d'un ancêtre donné :

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

Requêtes ne contenant que des clés

Une requête ne contenant que des clés affiche uniquement les clés des entités de résultat, et non les entités elles-mêmes. Cela entraîne une latence et un coût inférieurs à ceux induits par la récupération d'entités entières :

Query q = new Query("Person").setKeysOnly();

Il est souvent plus économique de commencer par ce type de requête, puis d'extraire un sous-ensemble d'entités parmi les résultats, plutôt que d'exécuter une requête générale pouvant extraire plus d'entités que nécessaire.

Requêtes de projection

Parfois, les seuls éléments dont vous avez besoin dans les résultats d'une requête sont les valeurs de quelques propriétés spécifiques. Dans ce cas, vous pouvez employer une requête de projection pour ne récupérer que les propriétés qui vous intéressent réellement, à une latence et à un coût inférieurs à ceux induits par la récupération de l'entité entière. Pour en savoir plus, consultez la page Requêtes de projection.

Ordres de tri

L'ordre de tri d'une requête spécifie les éléments suivants :

  • Un nom de propriété
  • Un sens de tri (croissant ou décroissant)

Exemple :

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

Si une requête comprend plusieurs ordres de tri, ils sont appliqués selon la séquence spécifiée. L'exemple suivant effectue d'abord un tri par ordre croissant de nom, puis par ordre décroissant de taille :

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

Si aucun ordre de tri n'est spécifié, les résultats sont renvoyés dans l'ordre dans lequel ils sont récupérés depuis Datastore.

Remarque : En raison de la manière dont Datastore exécute les requêtes, si une requête spécifie des filtres d'inégalité sur une propriété et des ordres de tri sur d'autres propriétés, la propriété employée dans les filtres d'inégalité doit être triée avant les autres.

Index

Chaque requête Datastore calcule ses résultats au moyen d'un ou de plusieurs index, qui contiennent des clés d'entité dans un ordre spécifié par leurs propriétés et, éventuellement, par les ancêtres de l'entité. Les index sont mis à jour de manière incrémentielle pour refléter les modifications apportées par l'application à ses entités, afin que les résultats corrects de toutes les requêtes soient disponibles sans qu'aucun calcul supplémentaire ne soit nécessaire.

App Engine prédéfinit un index simple sur chaque propriété d'une entité. Une application App Engine peut définir d'autres index personnalisés dans un fichier de configuration d'index nommé datastore-indexes.xml, qui est généré dans le répertoire /war/WEB-INF/appengine-generated de votre application. Le serveur de développement ajoute automatiquement des suggestions à ce fichier lorsqu'il est confronté à des requêtes qui ne peuvent pas être exécutées avec les index existants. Vous pouvez définir des index manuellement en modifiant le fichier avant d'importer l'application.

Exemple d'interface de requête

L'API Datastore Java de bas niveau fournit une classe Query permettant de créer des requêtes, ainsi que l'interface PreparedQuery assurant la récupération d'entités à partir de Datastore :

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

Notez l'utilisation de FilterPredicate et de CompositeFilter pour créer des filtres. Si vous définissez un seul filtre sur une requête, vous pouvez simplement utiliser FilterPredicate :

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

Toutefois, si vous souhaitez définir plusieurs filtres sur une requête, vous devez utiliser CompositeFilter, qui requiert au moins deux filtres. L'exemple ci-dessus utilise l'assistant de raccourci CompositeFilterOperator.and. L'exemple suivant montre une manière de créer un filtre OR composite :

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

Étapes suivantes