Google Cloud Datastore

Metadata

Google Cloud Datastore provides programmatic access to some of its metadata to support metaprogramming, implementing backend administrative functions, simplify consistent caching, and similar purposes; you can use it, for instance, to build a custom administration-console-style datastore viewer for your application. The metadata available includes information about the entity groups, namespaces, entity kinds, and properties your application uses, as well as the property representations for each property.

The Datastore Statistics tab of the Google Cloud Console also provides some metadata about your application but the data displayed there differs in some important respects from that returned by these functions.

  • Freshness. Reading metadata using the API gets current data, whereas data in the Datastore Statistics tab is updated only once daily.
  • Contents. Some metadata in the Datastore Statistics tab is not available via the APIs; the reverse is also true.
  • Speed. Metadata gets and queries are billed in the same way as datastore gets and queries. Metadata queries that fetch information on namespaces, kinds, and properties are generally slow to execute. As a rule of thumb, expect a metadata query that returns N entities to take about the same time as N ordinary queries each returning a single entity. Furthermore, property representation queries (non-keys-only property queries) are slower than keys-only property queries. Metadata gets of entity group metadata are somewhat faster than getting a regular entity.

Contents

  1. Entity group metadata
  2. Metadata queries

Entity group metadata

The High-Replication Datastore provides access to the "version" of an entity group, a strictly positive number that is guaranteed to increase on every change to the entity group. Entity group versions can be used, e.g., to easily write code to keep a consistent cache of a complex ancestor query on an entity group.

Entity group versions are obtained by querying on a special pseudo-entity that contains a positive integer __version__ property as follows:

Node.js (JSON)

function getEntityGroupVersion(key, callback) {
  datastore.lookup({
    keys: [{ path: [
      // The entity group ancestor path element.
      key.path[0],
      // The metadata pseudo entity path element.
      {'kind': '__entity_group__', 'id': 1}
    ]}]
  }).execute(function(err, result) {
    if (err) {
      callback(err, result);
    } else if (result.found) {
      // get metadata pseudo entity.
      var entity = result.found[0].entity;
      callback(null, entity.properties.__version__.integerValue);
    } else {
      callback('entity group metadata not found', result);
    }
  });
};

function testEntityGroupVersions(callback) {
  var entity1Key;
  async.series({
    'create new entity1': function(callback) {
      datastore.commit({
        mutation: {
          insertAutoId: [{
            key: { path: [{ kind: 'Simple' }] },
          }]
        },
        mode: 'NON_TRANSACTIONAL'
      }).execute(function(err, result) {
        if (!err) {
          entity1Key = result.mutationResult.insertAutoIdKeys[0];
        }
        callback(err, result);
      });
    },
    'get entity1 group version': function(callback) {
      getEntityGroupVersion(entity1Key, callback);
    },
    'create another entity group': function(callback) {
      datastore.commit({
        mutation: {
          insertAutoId: [{
            key: { path: [{ kind: 'Simple' }] },
          }]
        },
        mode: 'NON_TRANSACTIONAL'
      }).execute(callback);
    },
    'get entity1 group version (unchanged)': function(callback) {
      getEntityGroupVersion(entity1Key, callback);
    },
    'update entity1 group': function(callback) {
      datastore.commit({
        mutation: {
          insertAutoId: [{
            // insert a new child entity of entity1.
            key: { path: [entity1Key.path[0], { kind: 'Simple' }] },
          }]
        },
        mode: 'NON_TRANSACTIONAL'
      }).execute(callback);
    },
    'get entity1 group version (updated)': function(callback) {
      getEntityGroupVersion(entity1Key, callback);
    }}, function(err, results) {
      if (!err) {
        console.log(results['get entity1 group version'],
                    results['get entity1 group version (unchanged)'],
                    results['get entity1 group version (updated)']);
        assert.equal(results['get entity1 group version (unchanged)'],
                     results['get entity1 group version'])
        assert.notEqual(results['get entity1 group version (updated)'],
                        results['get entity1 group version'])
      }
      callback(err, results);
    });
}

Python (Protocol Buffers)

def get_entity_group_version(self, entity_key):
  req = datastore.LookupRequest()
  key = req.key.add()

  # The entity group ancestor path element.
  key.path_element.add().CopyFrom(entity_key.path_element[0])
  # The metadata pseudo-entity path element.
  path_element = key.path_element.add()
  path_element.kind = '__entity_group__'
  path_element.id = 1

  result = self.datastore.lookup(req).found[0].entity
  for prop in result.property:
    if prop.name == '__version__':
      return prop.value.integer_value

def test_entity_group_versions(self):
  entity1 = datastore.Entity()
  entity1.key.path_element.add().kind = 'Simple'
  req = datastore.CommitRequest()
  req.mode = datastore.CommitRequest.NON_TRANSACTIONAL
  req.mutation.insert_auto_id.extend([entity1])
  resp = self.datastore.commit(req)
  key1 = resp.mutation_result.insert_auto_id_key[0]

  # Record entity1's entity group version.
  version = self.get_entity_group_version(key1)

  # Write to a different entity group.
  entity2 = datastore.Entity()
  entity2.key.path_element.add().kind = 'Simple'
  req = datastore.CommitRequest()
  req.mode = datastore.CommitRequest.NON_TRANSACTIONAL
  req.mutation.insert_auto_id.extend([entity2])
  self.datastore.commit(req)

  # Version is the same, as entity1's entity group has not changed.
  self.assertEquals(version, self.get_entity_group_version(key1))

  # Change entity1's entity group by adding a new child entity.
  entity3 = datastore.Entity()
  entity3.key.CopyFrom(key1)
  entity3.key.path_element.add().kind = 'Simple'
  req = datastore.CommitRequest()
  req.mode = datastore.CommitRequest.NON_TRANSACTIONAL
  req.mutation.insert_auto_id.extend([entity3])
  self.datastore.commit(req)

  # Version is now higher, as entity1's entity group has changed.
  self.assertTrue(self.get_entity_group_version(key1) > version)

Java (Protocol Buffers)

long getEntityGroupVersion(Key entityGroupAncestorKey) throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  LookupRequest.Builder request = LookupRequest.newBuilder();
  // Key is made of the entity group ancestor path and the metadata pseudo entity path.
  request.addKey(makeKey(entityGroupAncestorKey, "__entity_group__", 1));
  LookupResponse response = datastore.lookup(request.build());
  return response.getFound(0).getEntity().getProperty(0).getValue().getIntegerValue();
}

public void testGetEntityGroupVersion() throws DatastoreException {
  Key firstEntityKey = insertEntity(makeKey("Simple"));
  long version = getEntityGroupVersion(firstEntityKey);
  insertEntity(makeKey("Simple"));
  // Inserting another entity should not change the entity group version of the first entity.
  assertEquals(version, getEntityGroupVersion(firstEntityKey));
  insertEntity(makeKey(firstEntityKey, "Simple"));
  // Inserting a child of the first entity will change the entity group version.
  assertNotSame(version, getEntityGroupVersion(firstEntityKey));
}

/**
 * Helper method to insert an {@link Entity} into the datastore.
 */
Key insertEntity(Key.Builder entityKey) throws DatastoreException {
  Entity entity = Entity.newBuilder().setKey(entityKey).build();
  CommitRequest request = CommitRequest.newBuilder()
      .setMode(CommitRequest.Mode.NON_TRANSACTIONAL)
      .setMutation(Mutation.newBuilder().addInsertAutoId(entity))
      .build();
  CommitResponse response = datastore.commit(request);
  return response.getMutationResult().getInsertAutoIdKey(0);
}

The entity group version is guaranteed to increase on any change to the entity group; it may also (rarely) increase in the absence of user-visible changes.

Metadata queries

Three special entity kinds are reserved for metadata queries:

Entity Description
__namespace__ Used to find all the namespaces used in your application entities.
__kind__ Used to query a specific kind.
__property__ Used to query by a property of a kind.

These kinds will not conflict with others of the same names that may already exist in your application. By querying on these special kinds, you can retrieve entities containing the desired metadata.

The entities returned by metadata queries are generated dynamically, based on the current state of the Datastore. While you can create local entity objects of kinds __namespace__, __kind__, or __property__,any attempt to store them in the Datastore will fail.

Namespace queries

You can use a namespace query to find all namespaces used in the application's entities. This allows you to perform activities such as administrative functions across multiple namespaces.

Namespace queries return entities of the special kind __namespace__ whose key name is the name of a namespace. (An exception is the default namespace designated by the empty string "": since the empty string is not a valid key name, this namespace is keyed with the numeric ID 1 instead.) Queries of this type support filtering only for ranges over the special pseudoproperty __key__, whose value is the entity's key. The results can be sorted by ascending (but not descending) __key__ value. Because __namespace__ entities have no properties, both keys-only and non-keys-only queries return the same information.

The following example returns a list of an application's namespaces in the range between two specified names, start and end:

Node.js (JSON)

function listNamespaces(start, end, callback) {
  var namespaceKey = function(namespace) {
    // namespace pseudo-kind.
    var pathElement = { kind: '__namespace__' };
    if (namespace) {
      pathElement.name = namespace;
    } else {
      // default namespace.
      pathElement.id = 1;
    }
    return { path: [pathElement] };
  };
  var namespaceRangeQuery = {
    kinds: [{ name: '__namespace__' }],
    // keys-only query.
    projection: [{ property: { name: '__key__' } }],
    filter: {
      // limit to specified namespace range.
      compositeFilter: {
        operator: 'AND',
        filters: [{
          propertyFilter: {
            property: { name: '__key__' },
            operator: 'GREATER_THAN_OR_EQUAL',
            value: { keyValue: namespaceKey(start) }
          }
        }, {
          propertyFilter: {
            property: { name: '__key__' },
            operator: 'LESS_THAN',
            value: { keyValue: namespaceKey(end) }
          }
        }]
      },
    }
  };
  datastore.runQuery({
    query: namespaceRangeQuery
  }).execute(function(err, result) {
    if (!err) {
      // build namespaces list from the query results.
      result = (result.batch.entityResults || []).map(
        function(entityResult) {
          var pathElement = entityResult.entity.key.path[0];
          return pathElement.id ? '<default>' : pathElement.name;
        });
    }
    callback(err, result);
  });
}

Python (Protocol Buffers)

req = datastore.RunQueryRequest()
query = req.query

# Namespace pseudo-kind.
query.kind.add().name = '__namespace__'
# Keys-only query.
query.projection.add().property.name = '__key__'

def namespace_key(namespace):
  key = datastore.Key()
  path_element = key.path_element.add()
  path_element.kind = '__namespace__'
  if namespace == '':
    # Default namespace.
    path_element.id = 1
  else:
    path_element.name = namespace
  return key

if namespace_start is not None or namespace_end is not None:
  composite_filter = query.filter.composite_filter
  composite_filter.operator = datastore.CompositeFilter.AND

  if namespace_start is not None:
    min_filter = composite_filter.filter.add().property_filter
    min_filter.property.name = '__key__'
    min_filter.operator = datastore.PropertyFilter.GREATER_THAN_OR_EQUAL
    min_filter.value.key_value.CopyFrom(namespace_key(namespace_start))

  if namespace_end is not None:
    max_filter = composite_filter.filter.add().property_filter
    max_filter.property.name = '__key__'
    max_filter.operator = datastore.PropertyFilter.LESS_THAN
    max_filter.value.key_value.CopyFrom(namespace_key(namespace_end))

resp = self.datastore.run_query(req)

namespaces = []
for entity_result in resp.batch.entity_result:
  path_element = entity_result.entity.key.path_element[0]
  if path_element.name:
    namespaces.append(path_element.name)
  else:
    # Default namespace.
    namespaces.append('<default>')

Java (Protocol Buffers)

List<String> getNamespacesInRange(String startNamespace, String endNamespace)
    throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  // Setup the key range. If the input is null, we are using 1, the default namespace.
  Key.Builder startKey = makeKey("__namespace__", startNamespace == null ? 1 : startNamespace);
  Key.Builder endKey = makeKey("__namespace__", endNamespace == null ? 1 : endNamespace);

  Query.Builder query = Query.newBuilder();
  // Entity kind pseudo-kind.
  query.addKindBuilder().setName("__namespace__");
  // keys-only query.
  query.addProjectionBuilder().setProperty(makePropertyReference("__key__"));
  query.setFilter(makeFilter(
      makeFilter("__key__", Operator.GREATER_THAN_OR_EQUAL, makeValue(startKey)).build(),
      makeFilter("__key__", Operator.LESS_THAN, makeValue(endKey)).build()));

  RunQueryRequest request = RunQueryRequest.newBuilder().setQuery(query).build();
  RunQueryResponse response = datastore.runQuery(request);
  List<String> namespaces = new ArrayList<String>();
  for (EntityResult entityResult : response.getBatch().getEntityResultList()) {
    PathElement pathElement = entityResult.getEntity().getKey().getPathElement(0);
    namespaces.add(pathElement.hasId() ? "<default>" : pathElement.getName());
  }
  return namespaces;
}

Kind queries

Kind queries return entities of kind __kind__ whose key name is the name of an entity kind. Queries of this type are implicitly restricted to the current namespace and support filtering only for ranges over the __key__ pseudoproperty. The results can be sorted by ascending (but not descending) __key__ value. Because __kind__ entities have no properties, both keys-only and non-keys-only queries return the same information.

The following example prints a list of the kinds used in an application:

Node.js (JSON)

function listAllKinds(callback) {
  datastore.runQuery({
    query: {
      // entity kind pseudo-kind.
      kinds: [{ name: '__kind__' }],
      // keys-only query.
      projection: [{ property: { name: '__key__' } }]
    },

  }).execute(function(err, result) {
    if (!err) {
      // build kinds list from the query results.
      result = (result.batch.entityResults || []).map(
        function(entityResult) {
          var pathElement = entityResult.entity.key.path[0];
          return pathElement.name;
        });
    }
    callback(err, result);
  });
}

Python (Protocol Buffers)

req = datastore.RunQueryRequest()
query = req.query

# Entity kind pseudo-kind.
query.kind.add().name = '__kind__'
# Keys-only query.
query.projection.add().property.name = '__key__'
resp = self.datastore.run_query(req)

kinds = [entity_result.entity.key.path_element[0].name
         for entity_result in resp.batch.entity_result]

Java (Protocol Buffers)

List<String> getAllKinds() throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  Query.Builder query = Query.newBuilder();
  // Entity kind pseudo-kind.
  query.addKindBuilder().setName("__kind__");
  // keys-only query.
  query.addProjectionBuilder().setProperty(makePropertyReference("__key__"));
  RunQueryRequest request = RunQueryRequest.newBuilder().setQuery(query).build();
  RunQueryResponse response = datastore.runQuery(request);
  List<String> kinds = new ArrayList<String>();
  for (EntityResult entityResult : response.getBatch().getEntityResultList()) {
    kinds.add(entityResult.getEntity().getKey().getPathElement(0).getName());
  }
  return kinds;
}

Property queries

Property queries return entities of kind __property__ denoting the properties associated with an entity kind. The entity representing property P of kind K is built as follows:

  • The entity's key has kind __property__ and key name P.
  • The parent entity's key has kind __kind__ and key name K.

The behavior of a property query depends on whether it is a keys-only or a non-keys-only (property representation) query, as detailed in the subsections below.

Property queries: keys-only

Keys-only property queries return a key for each indexed property of a specified entity kind. (Unindexed properties are not included.) The following example prints the names of all of an application's entity kinds and the properties associated with each:

Node.js (JSON)

function listAllProperties(callback) {
  datastore.runQuery({
    query: {
      // entity property pseudo-kind.
      kinds: [{ name: '__property__' }],
      // keys-only query.
      projection: [{ property: { name: '__key__' } }]
    }
  }).execute(function(err, result) {
    if (!err) {
      // build mapping from kind to list of properties.
      var mapping = {};
      (result.batch.entityResults || []).forEach(
        function(entityResult) {
          var kind = entityResult.entity.key.path[0].name;
          var property = entityResult.entity.key.path[1].name;
          if(!mapping[kind]) {
            mapping[kind] = [property];
          } else {
            mapping[kind].push(property);
          }
        });
      callback(err, mapping);
    } else {
      callback(err, result);
    }
  });
}

Python (Protocol Buffers)

req = datastore.RunQueryRequest()
query = req.query

# Entity property pseudo-kind.
query.kind.add().name = '__property__'
# Keys-only query.
query.projection.add().property.name = '__key__'

resp = self.datastore.run_query(req)

for entity_result in resp.batch.entity_result:
  kind = entity_result.entity.key.path_element[0].name
  property_name = entity_result.entity.key.path_element[1].name
  print 'kind:%s, property:%s' % (kind, property_name)

Java (Protocol Buffers)

Map<String, List<String>> getAllProperties() throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  Query.Builder query = Query.newBuilder();
  query.addKindBuilder().setName("__property__");
  // keys-only query.
  query.addProjectionBuilder().setProperty(makePropertyReference("__key__"));

  RunQueryRequest request = RunQueryRequest.newBuilder().setQuery(query).build();
  RunQueryResponse response = datastore.runQuery(request);
  Map<String, List<String>> properties = new HashMap<String, List<String>>();
  for (EntityResult entityResult : response.getBatch().getEntityResultList()) {
    String kind = entityResult.getEntity().getKey().getPathElement(0).getName();
    String property = entityResult.getEntity().getKey().getPathElement(1).getName();
    if (!properties.containsKey(kind)) {
      properties.put(kind, new ArrayList<String>());
    }
    properties.get(kind).add(property);
  }
  return properties;
}

Queries of this type are implicitly restricted to the current namespace and support filtering only for ranges over the pseudoproperty __key__, where the keys denote either __kind__ or __property__ entities. The results can be sorted by ascending (but not descending) __key__ value. Filtering is applied to kind-property pairs, ordered first by kind and second by property: for instance, suppose you have an entity with these properties:

  • kind Account with properties
    • balance
    • company
  • kind Employee with properties
    • name
    • ssn
  • kind Invoice with properties
    • date
    • amount
  • kind Manager with properties
    • name
    • title
  • kind Product with properties
    • description
    • price

The query to return the property data would look like this:

Node.js (JSON)

function listPropertyRange(callback) {
  var propertyKey = function(kind, property) {
    return { path: [{ kind: '__kind__', name: kind },
                    { kind: '__property__', name: property }] };
  };
  var propertyRangeQuery = {
    // entity property pseudo-kind.
    kinds: [{ name: '__property__' }],
    // keys-only query.
    projection: [{ property: { name: '__key__' } }],
    filter: {
      // limit to an arbitrary property range.
      compositeFilter: {
        operator: 'AND',
        filters: [{
          propertyFilter: {
            property: { name: '__key__' },
            operator: 'GREATER_THAN_OR_EQUAL',
            value: { keyValue: propertyKey('Employee', 'salary') }
          }
        }, {
          propertyFilter: {
            property: { name: '__key__' },
            operator: 'LESS_THAN',
            value: { keyValue: propertyKey('Manager', 'salary') }
          }
        }]
      },
    }
  };
  datastore.runQuery({
    query: propertyRangeQuery
  }).execute(function(err, result) {
    if (!err) {
      // build mapping from kind to list of properties.
      var mapping = {};
      (result.batch.entityResults || []).forEach(
        function(entityResult) {
          var kind = entityResult.entity.key.path[0].name;
          var property = entityResult.entity.key.path[1].name;
          if(!mapping[kind]) {
            mapping[kind] = [property];
          } else {
            mapping[kind].push(property);
          }
        });
      callback(err, mapping);
    } else {
      callback(err, result);
    }
  });
}

Python (Protocol Buffers)

req = datastore.RunQueryRequest()
query = req.query

# Entity property pseudo-kind.
query.kind.add().name = '__property__'
# Keys-only query.
query.projection.add().property.name = '__key__'

def property_key(kind, property_name):
  key = datastore.Key()
  kind_element = key.path_element.add()
  kind_element.kind = '__kind__'
  kind_element.name = kind
  property_element = key.path_element.add()
  property_element.kind = '__property__'
  property_element.name = property_name
  return key

composite_filter = query.filter.composite_filter
composite_filter.operator = datastore.CompositeFilter.AND

min_filter = composite_filter.filter.add().property_filter
min_filter.property.name = '__key__'
min_filter.operator = datastore.PropertyFilter.GREATER_THAN_OR_EQUAL
min_filter.value.key_value.CopyFrom(property_key('Employee', 'salary'))

max_filter = composite_filter.filter.add().property_filter
max_filter.property.name = '__key__'
max_filter.operator = datastore.PropertyFilter.LESS_THAN
max_filter.value.key_value.CopyFrom(property_key('Manager', 'salary'))

resp = self.datastore.run_query(req)

for entity_result in resp.batch.entity_result:
  kind = entity_result.entity.key.path_element[0].name
  property_name = entity_result.entity.key.path_element[1].name
  print 'kind:%s, property:%s' % (kind, property_name)

Java (Protocol Buffers)

Map<String, List<String>> getPropertiesInRange(String startKind, String startProperty,
    String endKind, String endProperty) throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  Key.Builder startKey = makeKey("__kind__", startKind, "__property__", startProperty);
  Key.Builder endKey = makeKey("__kind__", endKind, "__property__", endProperty);

  Query.Builder query = Query.newBuilder();
  query.addKindBuilder().setName("__property__");
  // keys-only query.
  query.addProjectionBuilder().setProperty(makePropertyReference("__key__"));
  query.setFilter(makeFilter(
      makeFilter("__key__", Operator.GREATER_THAN_OR_EQUAL, makeValue(startKey)).build(),
      makeFilter("__key__", Operator.LESS_THAN, makeValue(endKey)).build()));

  RunQueryRequest request = RunQueryRequest.newBuilder().setQuery(query).build();
  RunQueryResponse response = datastore.runQuery(request);
  Map<String, List<String>> properties = new HashMap<String, List<String>>();
  for (EntityResult entityResult : response.getBatch().getEntityResultList()) {
    String kind = entityResult.getEntity().getKey().getPathElement(0).getName();
    String property = entityResult.getEntity().getKey().getPathElement(1).getName();
    if (!properties.containsKey(kind)) {
      properties.put(kind, new ArrayList<String>());
    }
    properties.get(kind).add(property);
  }
  return properties;
}

The above query would return the following:

Employee: ssn
Invoice: date
Invoice: amount
Manager: name

Notice that the results do not include the name property of kind Employee and the title property of kind Manager, nor any properties of kinds Account and Product, because they fall outside the range specified for the query.

Property queries also support ancestor filtering on a __kind__ or __property__ key, to limit the query results to a single kind or property. You can use this, for instance, to get the properties associated with a given entity kind, as in the following example:

Node.js (JSON)

function kindPropertyNames(kind, callback) {
  var kindKey = function(kind) {
    return { path: [{ kind: '__kind__', name: kind }] };
  };
  var propertyAncestorQuery = {
    // entity property pseudo-kind.
    kinds: [{ name: '__property__' }],
    // keys-only query.
    projection: [{ property: { name: '__key__' } }],
    // limit to specified kind.
    filter: {
      propertyFilter: {
        property: { name: '__key__' },
        operator: 'HAS_ANCESTOR',
        value: { keyValue: kindKey(kind) }
      }
    }
  };
  datastore.runQuery({
    query: propertyAncestorQuery
  }).execute(function(err, result) {
    if (!err) {
      // build list of property names from the query result.
      result = (result.batch.entityResults || []).map(
        function(entityResult) {
          var pathElement = entityResult.entity.key.path[0];
          return pathElement.name;
        });
    }
    callback(err, result);
  });
}

Python (Protocol Buffers)

req = datastore.RunQueryRequest()
query = req.query

kind_key = datastore.Key()
path_element = kind_key.path_element.add()
path_element.kind = '__kind__'
path_element.name = kind

# Entity property pseudo-kind.
query.kind.add().name = '__property__'
# Keys-only query.
query.projection.add().property.name = '__key__'
property_filter = query.filter.property_filter

property_filter.property.name = '__key__'
property_filter.operator = datastore.PropertyFilter.HAS_ANCESTOR
property_filter.value.key_value.CopyFrom(kind_key)

resp = self.datastore.run_query(req)

props = []
for entity_result in resp.batch.entity_result:
  props.append(entity_result.entity.key.path_element[1].name)

Java (Protocol Buffers)

List<String> getPropertiesForKind(String kind) throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  Query.Builder query = Query.newBuilder();
  query.addKindBuilder().setName("__property__");
  // keys-only query.
  query.addProjectionBuilder().setProperty(makePropertyReference("__key__"));
  // Metadata properties have their kind as an ancestor.
  query.setFilter(
      makeFilter("__key__", Operator.HAS_ANCESTOR, makeValue(makeKey("__kind__", kind))));

  RunQueryRequest request = RunQueryRequest.newBuilder().setQuery(query).build();
  RunQueryResponse response = datastore.runQuery(request);
  List<String> properties = new ArrayList<String>();
  for (EntityResult entityResult : response.getBatch().getEntityResultList()) {
    Key entityKey = entityResult.getEntity().getKey();
    properties.add(entityKey.getPathElement(entityKey.getPathElementCount() - 1).getName());
  }
  return properties;
}

Property queries: non-keys-only (property representation)

Non-keys-only property queries, known as property representation queries, return additional information on the representations used by each kind-property pair. The entity returned for property P of kind K has the same key as for a corresponding keys-only query, along with an additional property_representation property returning the property's representations.

The following table maps value types to their representations:

Value type JSON field name and type(s) Protocol buffer field name and type Representation
Integer integerValue: number or string integer_value: int64 INT64
Floating-point number doubleValue: number double_value double: DOUBLE
Boolean booleanValue: true or false integer_value: bool BOOLEAN
Text string stringValue: string string_value: string STRING
Byte string blobValue: string blob_value: bytes STRING
Datastore key keyValue: a JSON Datastore key key_value:a Key message REFERENCE

The following example finds all representations of a specified property for a given entity kind:

Node.js (JSON)

function propertyRepresentation(kind, property, callback) {
  var propertyKey = function(kind, property) {
    return { path: [{ kind: '__kind__', name: kind },
                    { kind: '__property__', name: property }] };
  };
  var propertyEqualityQuery = {
    // entity property pseudo-kind.
    kinds: [{ name: '__property__' }],
    filter: {
      // limit to the specified property.
      propertyFilter: {
        property: { name: '__key__' },
        operator: 'EQUAL',
        value: { keyValue: propertyKey(kind, property) }
      }
    }
  };
  datastore.runQuery({
    query: propertyEqualityQuery
  }).execute(function(err, result) {
    if (!err && result.batch.entityResults) {
      // extract the list of property representations.
      var entity = result.batch.entityResults[0].entity;
      callback(null, entity.properties.property_representation.listValue);
    } else {
      callback(err, result);
    }
  });
}

Python (Protocol Buffers)

req = datastore.RunQueryRequest()
query = req.query

property_key = datastore.Key()
path_element = property_key.path_element.add()
path_element.kind = '__kind__'
path_element.name = kind
path_element = property_key.path_element.add()
path_element.kind = '__property__'
path_element.name = property_name

query.kind.add().name = '__property__'
property_filter = query.filter.property_filter

property_filter.property.name = '__key__'
property_filter.operator = datastore.PropertyFilter.EQUAL
property_filter.value.key_value.CopyFrom(property_key)

resp = self.datastore.run_query(req)

representations = []
entity = resp.batch.entity_result[0].entity
for prop in entity.property:
  if prop.name == 'property_representation':
    for value in prop.value.list_value:
      representations.append(value.string_value)
    break

Java (Protocol Buffers)

String getPropertyRepresentation(String kind, String property) throws DatastoreException {
  // import static com.google.apphosting.client.datastoreservice.client.DatastoreHelper.*;

  Query.Builder query = Query.newBuilder();
  query.addKindBuilder().setName("__property__");
  query.setFilter(makeFilter("__key__", Operator.EQUAL,
      makeValue(makeKey("__kind__", kind, "__property__", property))));
  RunQueryRequest request = RunQueryRequest.newBuilder().setQuery(query).build();
  RunQueryResponse response = datastore.runQuery(request);
  Entity entity = response.getBatch().getEntityResult(0).getEntity();
  // A property could have more than one type of value. Then, the valueList will be larger.
  return entity.getProperty(0).getValue().getListValue(0).getStringValue();
}

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.