thirdparty/google_appengine/google/appengine/api/datastore_file_stub.py
changeset 686 df109be0567c
parent 297 35211afcd563
child 828 f5fd65cc3bf3
equal deleted inserted replaced
685:a440ced9a75f 686:df109be0567c
    36 
    36 
    37 
    37 
    38 import datetime
    38 import datetime
    39 import logging
    39 import logging
    40 import os
    40 import os
    41 import pickle
       
    42 import struct
    41 import struct
    43 import sys
    42 import sys
    44 import tempfile
    43 import tempfile
    45 import threading
    44 import threading
    46 import warnings
    45 import warnings
    47 
    46 
       
    47 import cPickle as pickle
       
    48 
    48 from google.appengine.api import api_base_pb
    49 from google.appengine.api import api_base_pb
       
    50 from google.appengine.api import apiproxy_stub
    49 from google.appengine.api import datastore
    51 from google.appengine.api import datastore
    50 from google.appengine.api import datastore_admin
    52 from google.appengine.api import datastore_admin
    51 from google.appengine.api import datastore_errors
    53 from google.appengine.api import datastore_errors
    52 from google.appengine.api import datastore_types
    54 from google.appengine.api import datastore_types
    53 from google.appengine.api import users
    55 from google.appengine.api import users
    62 
    64 
    63 entity_pb.Reference.__hash__ = lambda self: hash(self.Encode())
    65 entity_pb.Reference.__hash__ = lambda self: hash(self.Encode())
    64 datastore_pb.Query.__hash__ = lambda self: hash(self.Encode())
    66 datastore_pb.Query.__hash__ = lambda self: hash(self.Encode())
    65 
    67 
    66 
    68 
       
    69 _MAXIMUM_RESULTS = 1000
       
    70 
       
    71 
       
    72 _MAX_QUERY_OFFSET = 4000
       
    73 
    67 class _StoredEntity(object):
    74 class _StoredEntity(object):
    68   """Simple wrapper around an entity stored by the stub.
    75   """Simple wrapper around an entity stored by the stub.
    69 
    76 
    70   Public properties:
    77   Public properties:
    71     native: Native protobuf Python object, entity_pb.EntityProto.
    78     protobuf: Native protobuf Python object, entity_pb.EntityProto.
    72     encoded: Encoded binary representation of above protobuf.
    79     encoded_protobuf: Encoded binary representation of above protobuf.
       
    80     native: datastore.Entity instance.
    73   """
    81   """
    74 
    82 
    75   def __init__(self, entity):
    83   def __init__(self, entity):
    76     """Create a _StoredEntity object and store an entity.
    84     """Create a _StoredEntity object and store an entity.
    77 
    85 
    78     Args:
    86     Args:
    79       entity: entity_pb.EntityProto to store.
    87       entity: entity_pb.EntityProto to store.
    80     """
    88     """
    81     self.native = entity
    89     self.protobuf = entity
    82 
    90 
    83     self.encoded = entity.Encode()
    91     self.encoded_protobuf = entity.Encode()
    84 
    92 
    85 
    93     self.native = datastore.Entity._FromPb(entity)
    86 class DatastoreFileStub(object):
    94 
       
    95 
       
    96 class DatastoreFileStub(apiproxy_stub.APIProxyStub):
    87   """ Persistent stub for the Python datastore API.
    97   """ Persistent stub for the Python datastore API.
    88 
    98 
    89   Stores all entities in memory, and persists them to a file as pickled
    99   Stores all entities in memory, and persists them to a file as pickled
    90   protocol buffers. A DatastoreFileStub instance handles a single app's data
   100   protocol buffers. A DatastoreFileStub instance handles a single app's data
    91   and is backed by files on disk.
   101   and is backed by files on disk.
   112     type(None): 0,
   122     type(None): 0,
   113     unicode: entity_pb.PropertyValue.kstringValue,
   123     unicode: entity_pb.PropertyValue.kstringValue,
   114     users.User: entity_pb.PropertyValue.kUserValueGroup,
   124     users.User: entity_pb.PropertyValue.kUserValueGroup,
   115     }
   125     }
   116 
   126 
   117   def __init__(self, app_id, datastore_file, history_file,
   127   def __init__(self,
   118                require_indexes=False):
   128                app_id,
       
   129                datastore_file,
       
   130                history_file,
       
   131                require_indexes=False,
       
   132                service_name='datastore_v3'):
   119     """Constructor.
   133     """Constructor.
   120 
   134 
   121     Initializes and loads the datastore from the backing files, if they exist.
   135     Initializes and loads the datastore from the backing files, if they exist.
   122 
   136 
   123     Args:
   137     Args:
   126           not to use a file.
   140           not to use a file.
   127       history_file: string, stores query history.  Use None as with
   141       history_file: string, stores query history.  Use None as with
   128           datastore_file.
   142           datastore_file.
   129       require_indexes: bool, default False.  If True, composite indexes must
   143       require_indexes: bool, default False.  If True, composite indexes must
   130           exist in index.yaml for queries that need them.
   144           exist in index.yaml for queries that need them.
   131     """
   145       service_name: Service name expected for all calls.
       
   146     """
       
   147     super(DatastoreFileStub, self).__init__(service_name)
       
   148 
   132 
   149 
   133     assert isinstance(app_id, basestring) and app_id != ''
   150     assert isinstance(app_id, basestring) and app_id != ''
   134     self.__app_id = app_id
   151     self.__app_id = app_id
   135     self.__datastore_file = datastore_file
   152     self.__datastore_file = datastore_file
   136     self.__history_file = history_file
   153     self.__history_file = history_file
   137 
   154 
   138     self.__entities = {}
   155     self.__entities = {}
       
   156 
       
   157     self.__schema_cache = {}
   139 
   158 
   140     self.__tx_snapshot = {}
   159     self.__tx_snapshot = {}
   141 
   160 
   142     self.__queries = {}
   161     self.__queries = {}
   143 
   162 
   168     queries. """
   187     queries. """
   169     self.__entities = {}
   188     self.__entities = {}
   170     self.__queries = {}
   189     self.__queries = {}
   171     self.__transactions = {}
   190     self.__transactions = {}
   172     self.__query_history = {}
   191     self.__query_history = {}
       
   192     self.__schema_cache = {}
       
   193 
       
   194   def _AppKindForKey(self, key):
       
   195     """ Get (app, kind) tuple from given key.
       
   196 
       
   197     The (app, kind) tuple is used as an index into several internal
       
   198     dictionaries, e.g. __entities.
       
   199 
       
   200     Args:
       
   201       key: entity_pb.Reference
       
   202 
       
   203     Returns:
       
   204       Tuple (app, kind), both are unicode strings.
       
   205     """
       
   206     last_path = key.path().element_list()[-1]
       
   207     return key.app(), last_path.type()
       
   208 
       
   209   def _StoreEntity(self, entity):
       
   210     """ Store the given entity.
       
   211 
       
   212     Args:
       
   213       entity: entity_pb.EntityProto
       
   214     """
       
   215     key = entity.key()
       
   216     app_kind = self._AppKindForKey(key)
       
   217     if app_kind not in self.__entities:
       
   218       self.__entities[app_kind] = {}
       
   219     self.__entities[app_kind][key] = _StoredEntity(entity)
       
   220 
       
   221     if app_kind in self.__schema_cache:
       
   222       del self.__schema_cache[app_kind]
   173 
   223 
   174   def Read(self):
   224   def Read(self):
   175     """ Reads the datastore and history files into memory.
   225     """ Reads the datastore and history files into memory.
   176 
   226 
   177     The in-memory query history is cleared, but the datastore is *not*
   227     The in-memory query history is cleared, but the datastore is *not*
   196           entity = entity_pb.EntityProto(encoded_entity)
   246           entity = entity_pb.EntityProto(encoded_entity)
   197         except pb_exceptions, e:
   247         except pb_exceptions, e:
   198           raise datastore_errors.InternalError(error_msg %
   248           raise datastore_errors.InternalError(error_msg %
   199                                                (self.__datastore_file, e))
   249                                                (self.__datastore_file, e))
   200 
   250 
       
   251         self._StoreEntity(entity)
       
   252 
   201         last_path = entity.key().path().element_list()[-1]
   253         last_path = entity.key().path().element_list()[-1]
   202         app_kind = (entity.key().app(), last_path.type())
       
   203         kind_dict = self.__entities.setdefault(app_kind, {})
       
   204         kind_dict[entity.key()] = _StoredEntity(entity)
       
   205 
       
   206         if last_path.has_id() and last_path.id() >= self.__next_id:
   254         if last_path.has_id() and last_path.id() >= self.__next_id:
   207           self.__next_id = last_path.id() + 1
   255           self.__next_id = last_path.id() + 1
   208 
   256 
   209       self.__query_history = {}
   257       self.__query_history = {}
   210       for encoded_query, count in self.__ReadPickled(self.__history_file):
   258       for encoded_query, count in self.__ReadPickled(self.__history_file):
   232     """
   280     """
   233     if self.__datastore_file and self.__datastore_file != '/dev/null':
   281     if self.__datastore_file and self.__datastore_file != '/dev/null':
   234       encoded = []
   282       encoded = []
   235       for kind_dict in self.__entities.values():
   283       for kind_dict in self.__entities.values():
   236         for entity in kind_dict.values():
   284         for entity in kind_dict.values():
   237           encoded.append(entity.encoded)
   285           encoded.append(entity.encoded_protobuf)
   238 
   286 
   239       self.__WritePickled(encoded, self.__datastore_file)
   287       self.__WritePickled(encoded, self.__datastore_file)
   240 
   288 
   241   def __WriteHistory(self):
   289   def __WriteHistory(self):
   242     """ Writes out the history file. Be careful! If the file already exist,
   290     """ Writes out the history file. Be careful! If the file already exist,
   274     """
   322     """
   275     if not filename or filename == '/dev/null' or not obj:
   323     if not filename or filename == '/dev/null' or not obj:
   276       return
   324       return
   277 
   325 
   278     tmpfile = openfile(os.tempnam(os.path.dirname(filename)), 'wb')
   326     tmpfile = openfile(os.tempnam(os.path.dirname(filename)), 'wb')
   279     pickle.dump(obj, tmpfile, 1)
   327 
       
   328     pickler = pickle.Pickler(tmpfile, protocol=1)
       
   329     pickler.fast = True
       
   330     pickler.dump(obj)
       
   331 
   280     tmpfile.close()
   332     tmpfile.close()
   281 
   333 
   282     self.__file_lock.acquire()
   334     self.__file_lock.acquire()
   283     try:
   335     try:
   284       try:
   336       try:
   294 
   346 
   295   def MakeSyncCall(self, service, call, request, response):
   347   def MakeSyncCall(self, service, call, request, response):
   296     """ The main RPC entry point. service must be 'datastore_v3'. So far, the
   348     """ The main RPC entry point. service must be 'datastore_v3'. So far, the
   297     supported calls are 'Get', 'Put', 'RunQuery', 'Next', and 'Count'.
   349     supported calls are 'Get', 'Put', 'RunQuery', 'Next', and 'Count'.
   298     """
   350     """
   299 
   351     super(DatastoreFileStub, self).MakeSyncCall(service,
   300     assert service == 'datastore_v3'
   352                                                 call,
       
   353                                                 request,
       
   354                                                 response)
   301 
   355 
   302     explanation = []
   356     explanation = []
   303     assert request.IsInitialized(explanation), explanation
       
   304 
       
   305     (getattr(self, "_Dynamic_" + call))(request, response)
       
   306 
       
   307     assert response.IsInitialized(explanation), explanation
   357     assert response.IsInitialized(explanation), explanation
   308 
   358 
   309   def QueryHistory(self):
   359   def QueryHistory(self):
   310     """Returns a dict that maps Query PBs to times they've been run.
   360     """Returns a dict that maps Query PBs to times they've been run.
   311     """
   361     """
   320       clones.append(clone)
   370       clones.append(clone)
   321 
   371 
   322       assert clone.has_key()
   372       assert clone.has_key()
   323       assert clone.key().path().element_size() > 0
   373       assert clone.key().path().element_size() > 0
   324 
   374 
   325       app = clone.key().app()
       
   326       last_path = clone.key().path().element_list()[-1]
   375       last_path = clone.key().path().element_list()[-1]
   327       if last_path.id() == 0 and not last_path.has_name():
   376       if last_path.id() == 0 and not last_path.has_name():
   328         self.__id_lock.acquire()
   377         self.__id_lock.acquire()
   329         last_path.set_id(self.__next_id)
   378         last_path.set_id(self.__next_id)
   330         self.__next_id += 1
   379         self.__next_id += 1
   341 
   390 
   342     self.__entities_lock.acquire()
   391     self.__entities_lock.acquire()
   343 
   392 
   344     try:
   393     try:
   345       for clone in clones:
   394       for clone in clones:
   346         last_path = clone.key().path().element_list()[-1]
   395         self._StoreEntity(clone)
   347         kind_dict = self.__entities.setdefault((app, last_path.type()), {})
       
   348         kind_dict[clone.key()] = _StoredEntity(clone)
       
   349     finally:
   396     finally:
   350       self.__entities_lock.release()
   397       self.__entities_lock.release()
   351 
   398 
   352     if not put_request.has_transaction():
   399     if not put_request.has_transaction():
   353       self.__WriteDatastore()
   400       self.__WriteDatastore()
   355     put_response.key_list().extend([c.key() for c in clones])
   402     put_response.key_list().extend([c.key() for c in clones])
   356 
   403 
   357 
   404 
   358   def _Dynamic_Get(self, get_request, get_response):
   405   def _Dynamic_Get(self, get_request, get_response):
   359     for key in get_request.key_list():
   406     for key in get_request.key_list():
   360         app = key.app()
   407         app_kind = self._AppKindForKey(key)
   361         last_path = key.path().element_list()[-1]
       
   362 
   408 
   363         group = get_response.add_entity()
   409         group = get_response.add_entity()
   364         try:
   410         try:
   365           entity = self.__entities[app, last_path.type()][key].native
   411           entity = self.__entities[app_kind][key].protobuf
   366         except KeyError:
   412         except KeyError:
   367           entity = None
   413           entity = None
   368 
   414 
   369         if entity:
   415         if entity:
   370           group.mutable_entity().CopyFrom(entity)
   416           group.mutable_entity().CopyFrom(entity)
   372 
   418 
   373   def _Dynamic_Delete(self, delete_request, delete_response):
   419   def _Dynamic_Delete(self, delete_request, delete_response):
   374     self.__entities_lock.acquire()
   420     self.__entities_lock.acquire()
   375     try:
   421     try:
   376       for key in delete_request.key_list():
   422       for key in delete_request.key_list():
       
   423         app_kind = self._AppKindForKey(key)
   377         try:
   424         try:
   378           app = key.app()
   425           del self.__entities[app_kind][key]
   379           kind = key.path().element_list()[-1].type()
   426           if not self.__entities[app_kind]:
   380           del self.__entities[app, kind][key]
   427             del self.__entities[app_kind]
   381           if not self.__entities[app, kind]:
   428 
   382             del self.__entities[app, kind]
   429           del self.__schema_cache[app_kind]
   383         except KeyError:
   430         except KeyError:
   384           pass
   431           pass
   385 
   432 
   386         if not delete_request.has_transaction():
   433         if not delete_request.has_transaction():
   387           self.__WriteDatastore()
   434           self.__WriteDatastore()
   394       raise apiproxy_errors.ApplicationError(
   441       raise apiproxy_errors.ApplicationError(
   395         datastore_pb.Error.BAD_REQUEST, "Can't query inside a transaction.")
   442         datastore_pb.Error.BAD_REQUEST, "Can't query inside a transaction.")
   396     else:
   443     else:
   397       self.__tx_lock.release()
   444       self.__tx_lock.release()
   398 
   445 
       
   446     if query.has_offset() and query.offset() > _MAX_QUERY_OFFSET:
       
   447        raise apiproxy_errors.ApplicationError(
       
   448          datastore_pb.Error.BAD_REQUEST, "Too big query offset.")
       
   449 
   399     app = query.app()
   450     app = query.app()
   400 
   451 
   401     if self.__require_indexes:
   452     if self.__require_indexes:
   402       required_index = datastore_index.CompositeIndexForQuery(query)
   453       required, kind, ancestor, props, num_eq_filters = datastore_index.CompositeIndexForQuery(query)
   403       if required_index is not None:
   454       if required:
   404         kind, ancestor, props, num_eq_filters = required_index
       
   405         required_key = kind, ancestor, props
   455         required_key = kind, ancestor, props
   406         indexes = self.__indexes.get(app)
   456         indexes = self.__indexes.get(app)
   407         if not indexes:
   457         if not indexes:
   408           raise apiproxy_errors.ApplicationError(
   458           raise apiproxy_errors.ApplicationError(
   409               datastore_pb.Error.NEED_INDEX,
   459               datastore_pb.Error.NEED_INDEX,
   430               "You must update the index.yaml file in your application root.")
   480               "You must update the index.yaml file in your application root.")
   431 
   481 
   432     try:
   482     try:
   433       query.set_app(app)
   483       query.set_app(app)
   434       results = self.__entities[app, query.kind()].values()
   484       results = self.__entities[app, query.kind()].values()
   435       results = [datastore.Entity._FromPb(entity.native) for entity in results]
   485       results = [entity.native for entity in results]
   436     except KeyError:
   486     except KeyError:
   437       results = []
   487       results = []
   438 
   488 
   439     if query.has_ancestor():
   489     if query.has_ancestor():
   440       ancestor_path = query.ancestor().path().element_list()
   490       ancestor_path = query.ancestor().path().element_list()
   454       assert filt.op() != datastore_pb.Query_Filter.IN
   504       assert filt.op() != datastore_pb.Query_Filter.IN
   455 
   505 
   456       prop = filt.property(0).name().decode('utf-8')
   506       prop = filt.property(0).name().decode('utf-8')
   457       op = operators[filt.op()]
   507       op = operators[filt.op()]
   458 
   508 
       
   509       filter_val_list = [datastore_types.FromPropertyPb(filter_prop)
       
   510                          for filter_prop in filt.property_list()]
       
   511 
   459       def passes(entity):
   512       def passes(entity):
   460         """ Returns True if the entity passes the filter, False otherwise. """
   513         """ Returns True if the entity passes the filter, False otherwise. """
   461         entity_vals = entity.get(prop, [])
   514         if prop in datastore_types._SPECIAL_PROPERTIES:
       
   515           entity_vals = self.__GetSpecialPropertyValue(entity, prop)
       
   516         else:
       
   517           entity_vals = entity.get(prop, [])
       
   518 
   462         if not isinstance(entity_vals, list):
   519         if not isinstance(entity_vals, list):
   463           entity_vals = [entity_vals]
   520           entity_vals = [entity_vals]
   464 
   521 
   465         entity_property_list = [datastore_types.ToPropertyPb(prop, value)
   522         for fixed_entity_val in entity_vals:
   466                                 for value in entity_vals]
       
   467 
       
   468         for entity_prop in entity_property_list:
       
   469           fixed_entity_val = datastore_types.FromPropertyPb(entity_prop)
       
   470 
       
   471           if type(fixed_entity_val) in datastore_types._RAW_PROPERTY_TYPES:
   523           if type(fixed_entity_val) in datastore_types._RAW_PROPERTY_TYPES:
   472             continue
   524             continue
   473 
   525 
   474           for filter_prop in filt.property_list():
   526           for filter_val in filter_val_list:
   475             filter_val = datastore_types.FromPropertyPb(filter_prop)
       
   476 
       
   477             fixed_entity_type = self._PROPERTY_TYPE_TAGS.get(
   527             fixed_entity_type = self._PROPERTY_TYPE_TAGS.get(
   478               fixed_entity_val.__class__)
   528               fixed_entity_val.__class__)
   479             filter_type = self._PROPERTY_TYPE_TAGS.get(filter_val.__class__)
   529             filter_type = self._PROPERTY_TYPE_TAGS.get(filter_val.__class__)
   480             if fixed_entity_type == filter_type:
   530             if fixed_entity_type == filter_type:
   481               comp = u'%r %s %r' % (fixed_entity_val, op, filter_val)
   531               comp = u'%r %s %r' % (fixed_entity_val, op, filter_val)
   497         return False
   547         return False
   498 
   548 
   499       results = filter(passes, results)
   549       results = filter(passes, results)
   500 
   550 
   501     def has_prop_indexed(entity, prop):
   551     def has_prop_indexed(entity, prop):
   502       """Returns True if prop is in the entity and is not a raw property."""
   552       """Returns True if prop is in the entity and is not a raw property, or
       
   553       is a special property."""
       
   554       if prop in datastore_types._SPECIAL_PROPERTIES:
       
   555         return True
       
   556 
   503       values = entity.get(prop, [])
   557       values = entity.get(prop, [])
   504       if not isinstance(values, (tuple, list)):
   558       if not isinstance(values, (tuple, list)):
   505         values = [values]
   559         values = [values]
   506 
   560 
   507       for value in values:
   561       for value in values:
   521       for o in query.order_list():
   575       for o in query.order_list():
   522         prop = o.property().decode('utf-8')
   576         prop = o.property().decode('utf-8')
   523 
   577 
   524         reverse = (o.direction() is datastore_pb.Query_Order.DESCENDING)
   578         reverse = (o.direction() is datastore_pb.Query_Order.DESCENDING)
   525 
   579 
   526         a_val = a[prop]
   580         if prop in datastore_types._SPECIAL_PROPERTIES:
   527         if isinstance(a_val, list):
   581           a_val = self.__GetSpecialPropertyValue(a, prop)
   528           a_val = sorted(a_val, order_compare_properties, reverse=reverse)[0]
   582           b_val = self.__GetSpecialPropertyValue(b, prop)
   529 
   583         else:
   530         b_val = b[prop]
   584           a_val = a[prop]
   531         if isinstance(b_val, list):
   585           if isinstance(a_val, list):
   532           b_val = sorted(b_val, order_compare_properties, reverse=reverse)[0]
   586             a_val = sorted(a_val, order_compare_properties, reverse=reverse)[0]
       
   587 
       
   588           b_val = b[prop]
       
   589           if isinstance(b_val, list):
       
   590             b_val = sorted(b_val, order_compare_properties, reverse=reverse)[0]
   533 
   591 
   534         cmped = order_compare_properties(a_val, b_val)
   592         cmped = order_compare_properties(a_val, b_val)
   535 
   593 
   536         if o.direction() is datastore_pb.Query_Order.DESCENDING:
   594         if o.direction() is datastore_pb.Query_Order.DESCENDING:
   537           cmped = -cmped
   595           cmped = -cmped
   571     limit = len(results)
   629     limit = len(results)
   572     if query.has_offset():
   630     if query.has_offset():
   573       offset = query.offset()
   631       offset = query.offset()
   574     if query.has_limit():
   632     if query.has_limit():
   575       limit = query.limit()
   633       limit = query.limit()
       
   634     if limit > _MAXIMUM_RESULTS:
       
   635       limit = _MAXIMUM_RESULTS
   576     results = results[offset:limit + offset]
   636     results = results[offset:limit + offset]
   577 
   637 
   578     clone = datastore_pb.Query()
   638     clone = datastore_pb.Query()
   579     clone.CopyFrom(query)
   639     clone.CopyFrom(query)
   580     clone.clear_hint()
   640     clone.clear_hint()
   582       self.__query_history[clone] += 1
   642       self.__query_history[clone] += 1
   583     else:
   643     else:
   584       self.__query_history[clone] = 1
   644       self.__query_history[clone] = 1
   585     self.__WriteHistory()
   645     self.__WriteHistory()
   586 
   646 
   587     results = [e._ToPb() for e in results]
       
   588     self.__cursor_lock.acquire()
   647     self.__cursor_lock.acquire()
   589     cursor = self.__next_cursor
   648     cursor = self.__next_cursor
   590     self.__next_cursor += 1
   649     self.__next_cursor += 1
   591     self.__cursor_lock.release()
   650     self.__cursor_lock.release()
   592     self.__queries[cursor] = (results, len(results))
   651     self.__queries[cursor] = (results, len(results))
   593 
   652 
   594     query_result.mutable_cursor().set_cursor(cursor)
   653     query_result.mutable_cursor().set_cursor(cursor)
   595     query_result.set_more_results(len(results) > 0)
   654     query_result.set_more_results(len(results) > 0)
   596 
   655 
   597 
       
   598   def _Dynamic_Next(self, next_request, query_result):
   656   def _Dynamic_Next(self, next_request, query_result):
   599     cursor = next_request.cursor().cursor()
   657     cursor = next_request.cursor().cursor()
   600 
   658 
   601     try:
   659     try:
   602       results, orig_count = self.__queries[cursor]
   660       results, orig_count = self.__queries[cursor]
   603     except KeyError:
   661     except KeyError:
   604       raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST,
   662       raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST,
   605                                              'Cursor %d not found' % cursor)
   663                                              'Cursor %d not found' % cursor)
   606 
   664 
   607     count = next_request.count()
   665     count = next_request.count()
   608     for r in results[:count]:
   666     results_pb = [r._ToPb() for r in results[:count]]
   609       query_result.add_result().CopyFrom(r)
   667     query_result.result_list().extend(results_pb)
   610     del results[:count]
   668     del results[:count]
   611 
   669 
   612     query_result.set_more_results(len(results) > 0)
   670     query_result.set_more_results(len(results) > 0)
   613 
       
   614 
   671 
   615   def _Dynamic_Count(self, query, integer64proto):
   672   def _Dynamic_Count(self, query, integer64proto):
   616     query_result = datastore_pb.QueryResult()
   673     query_result = datastore_pb.QueryResult()
   617     self._Dynamic_RunQuery(query, query_result)
   674     self._Dynamic_RunQuery(query, query_result)
   618     cursor = query_result.cursor().cursor()
   675     cursor = query_result.cursor().cursor()
   619     results, count = self.__queries[cursor]
   676     results, count = self.__queries[cursor]
   620     integer64proto.set_value(count)
   677     integer64proto.set_value(count)
   621     del self.__queries[cursor]
   678     del self.__queries[cursor]
   622 
   679 
   623 
       
   624   def _Dynamic_BeginTransaction(self, request, transaction):
   680   def _Dynamic_BeginTransaction(self, request, transaction):
   625     self.__tx_handle_lock.acquire()
   681     self.__tx_handle_lock.acquire()
   626     handle = self.__next_tx_handle
   682     handle = self.__next_tx_handle
   627     self.__next_tx_handle += 1
   683     self.__next_tx_handle += 1
   628     self.__tx_handle_lock.release()
   684     self.__tx_handle_lock.release()
   668 
   724 
   669     kinds = []
   725     kinds = []
   670 
   726 
   671     for app, kind in self.__entities:
   727     for app, kind in self.__entities:
   672       if app == app_str:
   728       if app == app_str:
       
   729         app_kind = (app, kind)
       
   730         if app_kind in self.__schema_cache:
       
   731           kinds.append(self.__schema_cache[app_kind])
       
   732           continue
       
   733 
   673         kind_pb = entity_pb.EntityProto()
   734         kind_pb = entity_pb.EntityProto()
   674         kind_pb.mutable_key().set_app('')
   735         kind_pb.mutable_key().set_app('')
   675         kind_pb.mutable_key().mutable_path().add_element().set_type(kind)
   736         kind_pb.mutable_key().mutable_path().add_element().set_type(kind)
   676         kind_pb.mutable_entity_group()
   737         kind_pb.mutable_entity_group()
   677         kinds.append(kind_pb)
       
   678 
   738 
   679         props = {}
   739         props = {}
   680 
   740 
   681         for entity in self.__entities[(app, kind)].values():
   741         for entity in self.__entities[app_kind].values():
   682           for prop in entity.native.property_list():
   742           for prop in entity.protobuf.property_list():
   683             if prop.name() not in props:
   743             if prop.name() not in props:
   684               props[prop.name()] = entity_pb.PropertyValue()
   744               props[prop.name()] = entity_pb.PropertyValue()
   685             props[prop.name()].MergeFrom(prop.value())
   745             props[prop.name()].MergeFrom(prop.value())
   686 
   746 
   687         for value_pb in props.values():
   747         for value_pb in props.values():
   708         for name, value_pb in props.items():
   768         for name, value_pb in props.items():
   709           prop_pb = kind_pb.add_property()
   769           prop_pb = kind_pb.add_property()
   710           prop_pb.set_name(name)
   770           prop_pb.set_name(name)
   711           prop_pb.mutable_value().CopyFrom(value_pb)
   771           prop_pb.mutable_value().CopyFrom(value_pb)
   712 
   772 
   713     schema.kind_list().extend(kinds)
   773         kinds.append(kind_pb)
       
   774         self.__schema_cache[app_kind] = kind_pb
       
   775 
       
   776     for kind_pb in kinds:
       
   777       schema.add_kind().CopyFrom(kind_pb)
   714 
   778 
   715   def _Dynamic_CreateIndex(self, index, id_response):
   779   def _Dynamic_CreateIndex(self, index, id_response):
   716     if index.id() != 0:
   780     if index.id() != 0:
   717       raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST,
   781       raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST,
   718                                              'New index id must be 0.')
   782                                              'New index id must be 0.')
   788       for stored_index in self.__indexes[app]:
   852       for stored_index in self.__indexes[app]:
   789         if index.definition() == stored_index.definition():
   853         if index.definition() == stored_index.definition():
   790           return stored_index
   854           return stored_index
   791 
   855 
   792     return None
   856     return None
       
   857 
       
   858   @classmethod
       
   859   def __GetSpecialPropertyValue(cls, entity, property):
       
   860     """Returns an entity's value for a special property.
       
   861 
       
   862     Right now, the only special property is __key__, whose value is the
       
   863     entity's key.
       
   864 
       
   865     Args:
       
   866       entity: datastore.Entity
       
   867 
       
   868     Returns:
       
   869       property value. For __key__, a datastore_types.Key.
       
   870 
       
   871     Raises:
       
   872       AssertionError, if the given property is not special.
       
   873     """
       
   874     assert property in datastore_types._SPECIAL_PROPERTIES
       
   875     if property == datastore_types._KEY_SPECIAL_PROPERTY:
       
   876       return entity.key()