thirdparty/google_appengine/google/appengine/ext/gql/__init__.py
changeset 1278 a7766286a7be
parent 149 f2e327a7c5de
child 2273 e4cb9c53db3e
equal deleted inserted replaced
1277:5c931bd3dc1e 1278:a7766286a7be
    26 
    26 
    27 
    27 
    28 
    28 
    29 import calendar
    29 import calendar
    30 import datetime
    30 import datetime
    31 import heapq
       
    32 import logging
    31 import logging
    33 import re
    32 import re
    34 import time
    33 import time
    35 
    34 
    36 from google.appengine.api import datastore
    35 from google.appengine.api import datastore
    37 from google.appengine.api import datastore_errors
    36 from google.appengine.api import datastore_errors
    38 from google.appengine.api import datastore_types
    37 from google.appengine.api import datastore_types
    39 from google.appengine.api import users
    38 from google.appengine.api import users
    40 
    39 
       
    40 MultiQuery = datastore.MultiQuery
    41 
    41 
    42 LOG_LEVEL = logging.DEBUG - 1
    42 LOG_LEVEL = logging.DEBUG - 1
    43 
    43 
    44 _EPOCH = datetime.datetime.utcfromtimestamp(0)
    44 _EPOCH = datetime.datetime.utcfromtimestamp(0)
    45 
    45 
   159     \w+|
   159     \w+|
   160     \(|\)|
   160     \(|\)|
   161     \S+
   161     \S+
   162     """, re.VERBOSE | re.IGNORECASE)
   162     """, re.VERBOSE | re.IGNORECASE)
   163 
   163 
   164   MAX_ALLOWABLE_QUERIES = 30
   164   MAX_ALLOWABLE_QUERIES = datastore.MAX_ALLOWABLE_QUERIES
   165 
   165 
   166   __ANCESTOR = -1
   166   __ANCESTOR = -1
   167 
   167 
   168   def __init__(self, query_string, _app=None, _auth_domain=None):
   168   def __init__(self, query_string, _app=None, _auth_domain=None):
   169     """Ctor.
   169     """Ctor.
   345     if len(values) != 1:
   345     if len(values) != 1:
   346       self.__CastError(values, 'user', 'requires one and only one value')
   346       self.__CastError(values, 'user', 'requires one and only one value')
   347     else:
   347     else:
   348       return users.User(email=values[0], _auth_domain=self.__auth_domain)
   348       return users.User(email=values[0], _auth_domain=self.__auth_domain)
   349 
   349 
       
   350   def __EncodeIfNeeded(self, value):
       
   351     """Simple helper function to create an str from possibly unicode strings.
       
   352     Args:
       
   353       value: input string (should pass as an instance of str or unicode).
       
   354     """
       
   355     if isinstance(value, unicode):
       
   356       return value.encode('utf8')
       
   357     else:
       
   358       return value
       
   359 
   350   def __CastDate(self, values):
   360   def __CastDate(self, values):
   351     """Cast date values to DATETIME() class using ISO string or tuple inputs."""
   361     """Cast DATE values (year/month/day) from input (to datetime.datetime).
       
   362 
       
   363     Casts DATE input values formulated as ISO string or time tuple inputs.
       
   364 
       
   365     Args:
       
   366       values: either a single string with ISO time representation or 3
       
   367               integer valued date tuple (year, month, day).
       
   368 
       
   369     Returns:
       
   370       datetime.datetime value parsed from the input values.
       
   371     """
       
   372 
       
   373     if len(values) == 1:
       
   374       value = self.__EncodeIfNeeded(values[0])
       
   375       if isinstance(value, str):
       
   376         try:
       
   377           time_tuple = time.strptime(value, '%Y-%m-%d')[0:6]
       
   378         except ValueError, err:
       
   379           self.__CastError('DATE', values, err)
       
   380       else:
       
   381         self.__CastError('DATE', values, 'Single input value not a string')
       
   382     elif len(values) == 3:
       
   383       time_tuple = (values[0], values[1], values[2], 0, 0, 0)
       
   384     else:
       
   385       self.__CastError('DATE', values,
       
   386                        'function takes 1 string or 3 integer values')
       
   387 
   352     try:
   388     try:
   353       if len(values) == 1 and isinstance(values[0], str):
   389       return datetime.datetime(*time_tuple)
   354         time_tuple = time.strptime(values[0], '%Y-%m-%d')
       
   355         return datetime.datetime(*time_tuple[0:6])
       
   356       else:
       
   357         return datetime.datetime(values[0], values[1], values[2], 0, 0, 0)
       
   358     except ValueError, err:
   390     except ValueError, err:
   359       self.__CastError('DATE', values, err)
   391       self.__CastError('DATE', values, err)
   360 
   392 
   361   def __CastTime(self, values):
   393   def __CastTime(self, values):
   362     """Cast time values to DATETIME() class using ISO string or tuple inputs."""
   394     """Cast TIME values (hour/min/sec) from input (to datetime.datetime).
       
   395 
       
   396     Casts TIME input values formulated as ISO string or time tuple inputs.
       
   397 
       
   398     Args:
       
   399       values: either a single string with ISO time representation or 1-4
       
   400               integer valued time tuple (hour), (hour, minute),
       
   401               (hour, minute, second), (hour, minute, second, microsec).
       
   402 
       
   403     Returns:
       
   404       datetime.datetime value parsed from the input values.
       
   405     """
       
   406     if len(values) == 1:
       
   407       value = self.__EncodeIfNeeded(values[0])
       
   408       if isinstance(value, str):
       
   409         try:
       
   410           time_tuple = time.strptime(value, '%H:%M:%S')
       
   411         except ValueError, err:
       
   412           self.__CastError('TIME', values, err)
       
   413         time_tuple = (1970, 1, 1) + time_tuple[3:]
       
   414         time_tuple = time_tuple[0:6]
       
   415       elif isinstance(value, int):
       
   416         time_tuple = (1970, 1, 1, value)
       
   417       else:
       
   418         self.__CastError('TIME', values,
       
   419                          'Single input value not a string or integer hour')
       
   420     elif len(values) <= 4:
       
   421       time_tuple = (1970, 1, 1) + tuple(values)
       
   422     else:
       
   423       self.__CastError('TIME', values, err)
       
   424 
   363     try:
   425     try:
   364       if len(values) == 1 and isinstance(values[0], str):
   426       return datetime.datetime(*time_tuple)
   365         time_tuple = time.strptime(values[0], '%H:%M:%S')
       
   366         time_tuple = (1970, 1, 1) + time_tuple[3:]
       
   367         return datetime.datetime(*time_tuple[0:6])
       
   368       else:
       
   369         return datetime.datetime(1970, 1, 1, *values)
       
   370     except ValueError, err:
   427     except ValueError, err:
   371       self.__CastError('TIME', values, err)
   428       self.__CastError('TIME', values, err)
   372 
   429 
   373   def __CastDatetime(self, values):
   430   def __CastDatetime(self, values):
   374     """Cast values to DATETIME() class using ISO string or tuple inputs."""
   431     """Cast DATETIME values (string or tuple) from input (to datetime.datetime).
       
   432 
       
   433     Casts DATETIME input values formulated as ISO string or datetime tuple
       
   434     inputs.
       
   435 
       
   436     Args:
       
   437       values: either a single string with ISO representation or 3-7
       
   438               integer valued time tuple (year, month, day, ...).
       
   439 
       
   440     Returns:
       
   441       datetime.datetime value parsed from the input values.
       
   442     """
       
   443     if len(values) == 1:
       
   444       value = self.__EncodeIfNeeded(values[0])
       
   445       if isinstance(value, str):
       
   446         try:
       
   447           time_tuple = time.strptime(str(value), '%Y-%m-%d %H:%M:%S')[0:6]
       
   448         except ValueError, err:
       
   449           self.__CastError('DATETIME', values, err)
       
   450       else:
       
   451         self.__CastError('DATETIME', values, 'Single input value not a string')
       
   452     else:
       
   453       time_tuple = values
       
   454 
   375     try:
   455     try:
   376       if len(values) == 1 and isinstance(values[0], str):
   456       return datetime.datetime(*time_tuple)
   377         time_tuple = time.strptime(values[0], '%Y-%m-%d %H:%M:%S')
       
   378         return datetime.datetime(*time_tuple[0:6])
       
   379       else:
       
   380         return datetime.datetime(*values)
       
   381     except ValueError, err:
   457     except ValueError, err:
   382       self.__CastError('DATETIME', values, err)
   458       self.__CastError('DATETIME', values, err)
   383 
   459 
   384   def __Operate(self, args, keyword_args, used_args, operator, params):
   460   def __Operate(self, args, keyword_args, used_args, operator, params):
   385     """Create a single output value from params using the operator string given.
   461     """Create a single output value from params using the operator string given.
  1060     """Return the value of the literal."""
  1136     """Return the value of the literal."""
  1061     return self.__value
  1137     return self.__value
  1062 
  1138 
  1063   def __repr__(self):
  1139   def __repr__(self):
  1064     return 'Literal(%s)' % repr(self.__value)
  1140     return 'Literal(%s)' % repr(self.__value)
  1065 
       
  1066 
       
  1067 class MultiQuery(datastore.Query):
       
  1068   """Class representing a GQL query requiring multiple datastore queries.
       
  1069 
       
  1070   This class is actually a subclass of datastore.Query as it is intended to act
       
  1071   like a normal Query object (supporting the same interface).
       
  1072   """
       
  1073 
       
  1074   def __init__(self, bound_queries, orderings):
       
  1075     self.__bound_queries = bound_queries
       
  1076     self.__orderings = orderings
       
  1077 
       
  1078   def __str__(self):
       
  1079     res = 'MultiQuery: '
       
  1080     for query in self.__bound_queries:
       
  1081       res = '%s %s' % (res, str(query))
       
  1082     return res
       
  1083 
       
  1084   def Get(self, limit, offset=0):
       
  1085     """Get results of the query with a limit on the number of results.
       
  1086 
       
  1087     Args:
       
  1088       limit: maximum number of values to return.
       
  1089       offset: offset requested -- if nonzero, this will override the offset in
       
  1090               the original query
       
  1091 
       
  1092     Returns:
       
  1093       A list of entities with at most "limit" entries (less if the query
       
  1094       completes before reading limit values).
       
  1095     """
       
  1096     count = 1
       
  1097     result = []
       
  1098 
       
  1099     iterator = self.Run()
       
  1100 
       
  1101     try:
       
  1102       for i in xrange(offset):
       
  1103         val = iterator.next()
       
  1104     except StopIteration:
       
  1105       pass
       
  1106 
       
  1107     try:
       
  1108       while count <= limit:
       
  1109         val = iterator.next()
       
  1110         result.append(val)
       
  1111         count += 1
       
  1112     except StopIteration:
       
  1113       pass
       
  1114     return result
       
  1115 
       
  1116   class SortOrderEntity(object):
       
  1117     def __init__(self, entity_iterator, orderings):
       
  1118       self.__entity_iterator = entity_iterator
       
  1119       self.__entity = None
       
  1120       self.__min_max_value_cache = {}
       
  1121       try:
       
  1122         self.__entity = entity_iterator.next()
       
  1123       except StopIteration:
       
  1124         pass
       
  1125       else:
       
  1126         self.__orderings = orderings
       
  1127 
       
  1128     def __str__(self):
       
  1129       return str(self.__entity)
       
  1130 
       
  1131     def GetEntity(self):
       
  1132       return self.__entity
       
  1133 
       
  1134     def GetNext(self):
       
  1135       return MultiQuery.SortOrderEntity(self.__entity_iterator,
       
  1136                                         self.__orderings)
       
  1137 
       
  1138     def CmpProperties(self, that):
       
  1139       """Compare two entities and return their relative order.
       
  1140 
       
  1141       Compares self to that based on the current sort orderings and the
       
  1142       key orders between them. Returns negative, 0, or positive depending on
       
  1143       whether self is less, equal to, or greater than that. This
       
  1144       comparison returns as if all values were to be placed in ascending order
       
  1145       (highest value last).  Only uses the sort orderings to compare (ignores
       
  1146        keys).
       
  1147 
       
  1148       Args:
       
  1149         self: SortOrderEntity
       
  1150         that: SortOrderEntity
       
  1151 
       
  1152       Returns:
       
  1153         Negative if self < that
       
  1154         Zero if self == that
       
  1155         Positive if self > that
       
  1156       """
       
  1157       if not self.__entity:
       
  1158         return cmp(self.__entity, that.__entity)
       
  1159 
       
  1160       for (identifier, order) in self.__orderings:
       
  1161         value1 = self.__GetValueForId(self, identifier, order)
       
  1162         value2 = self.__GetValueForId(that, identifier, order)
       
  1163 
       
  1164         result = cmp(value1, value2)
       
  1165         if order == datastore.Query.DESCENDING:
       
  1166           result = -result
       
  1167         if result:
       
  1168           return result
       
  1169       return 0
       
  1170 
       
  1171     def __GetValueForId(self, sort_order_entity, identifier, sort_order):
       
  1172       value = sort_order_entity.__entity[identifier]
       
  1173       entity_key = sort_order_entity.__entity.key()
       
  1174       if self.__min_max_value_cache.has_key((entity_key, identifier)):
       
  1175         value = self.__min_max_value_cache[(entity_key, identifier)]
       
  1176       elif isinstance(value, list):
       
  1177         if sort_order == datastore.Query.DESCENDING:
       
  1178           value = min(value)
       
  1179         else:
       
  1180           value = max(value)
       
  1181         self.__min_max_value_cache[(entity_key, identifier)] = value
       
  1182 
       
  1183       return value
       
  1184 
       
  1185     def __cmp__(self, that):
       
  1186       """Compare self to that w.r.t. values defined in the sort order.
       
  1187 
       
  1188       Compare an entity with another, using sort-order first, then the key
       
  1189       order to break ties. This can be used in a heap to have faster min-value
       
  1190       lookup.
       
  1191 
       
  1192       Args:
       
  1193         that: other entity to compare to
       
  1194       Returns:
       
  1195         negative: if self is less than that in sort order
       
  1196         zero: if self is equal to that in sort order
       
  1197         positive: if self is greater than that in sort order
       
  1198       """
       
  1199       property_compare = self.CmpProperties(that)
       
  1200       if property_compare:
       
  1201         return property_compare
       
  1202       else:
       
  1203         return cmp(self.__entity.key(), that.__entity.key())
       
  1204 
       
  1205   def Run(self):
       
  1206     """Return an iterable output with all results in order."""
       
  1207     results = []
       
  1208     count = 1
       
  1209     for bound_query in self.__bound_queries:
       
  1210       logging.log(LOG_LEVEL, 'Running query #%i' % count)
       
  1211       results.append(bound_query.Run())
       
  1212       count += 1
       
  1213 
       
  1214     def IterateResults(results):
       
  1215       """Iterator function to return all results in sorted order.
       
  1216 
       
  1217       Iterate over the array of results, yielding the next element, in
       
  1218       sorted order. This function is destructive (results will be empty
       
  1219       when the operation is complete).
       
  1220 
       
  1221       Args:
       
  1222         results: list of result iterators to merge and iterate through
       
  1223 
       
  1224       Yields:
       
  1225         The next result in sorted order.
       
  1226       """
       
  1227       result_heap = []
       
  1228       for result in results:
       
  1229         heap_value = MultiQuery.SortOrderEntity(result, self.__orderings)
       
  1230         if heap_value.GetEntity():
       
  1231           heapq.heappush(result_heap, heap_value)
       
  1232 
       
  1233       used_keys = set()
       
  1234 
       
  1235       while result_heap:
       
  1236         top_result = heapq.heappop(result_heap)
       
  1237 
       
  1238         results_to_push = []
       
  1239         if top_result.GetEntity().key() not in used_keys:
       
  1240           yield top_result.GetEntity()
       
  1241         else:
       
  1242           pass
       
  1243 
       
  1244         used_keys.add(top_result.GetEntity().key())
       
  1245 
       
  1246         results_to_push = []
       
  1247         while result_heap:
       
  1248           next = heapq.heappop(result_heap)
       
  1249           if cmp(top_result, next):
       
  1250             results_to_push.append(next)
       
  1251             break
       
  1252           else:
       
  1253             results_to_push.append(next.GetNext())
       
  1254         results_to_push.append(top_result.GetNext())
       
  1255 
       
  1256         for popped_result in results_to_push:
       
  1257           if popped_result.GetEntity():
       
  1258             heapq.heappush(result_heap, popped_result)
       
  1259 
       
  1260     return IterateResults(results)
       
  1261 
       
  1262   def Count(self, limit=None):
       
  1263     """Return the number of matched entities for this query.
       
  1264 
       
  1265     Will return the de-duplicated count of results.  Will call the more
       
  1266     efficient Get() function if a limit is given.
       
  1267 
       
  1268     Args:
       
  1269       limit: maximum number of entries to count (for any result > limit, return
       
  1270       limit).
       
  1271     Returns:
       
  1272       count of the number of entries returned.
       
  1273     """
       
  1274     if limit is None:
       
  1275       count = 0
       
  1276       for value in self.Run():
       
  1277         count += 1
       
  1278       return count
       
  1279     else:
       
  1280       return len(self.Get(limit))
       
  1281