from .._gae import ndb from ..adapters.google import GoogleDatastore from ..helpers.gae import NDBDecimalProperty from .base import NoSQLDialect from . import dialects, sqltype_for @dialects.register_for(GoogleDatastore) class GoogleDatastoreDialect(NoSQLDialect): FILTER_OPTIONS = { '=': lambda a, b: a == b, '>': lambda a, b: a > b, '<': lambda a, b: a < b, '<=': lambda a, b: a <= b, '>=': lambda a, b: a >= b, '!=': lambda a, b: a != b, 'in': lambda a, b: a.IN(b) } @sqltype_for('string') def type_string(self): return (lambda **kwargs: ndb.StringProperty(**kwargs)) @sqltype_for('boolean') def type_boolean(self): return ndb.BooleanProperty @sqltype_for('text') def type_text(self): return ndb.TextProperty @sqltype_for('json') def type_json(self): return self.types['text'] @sqltype_for('password') def type_password(self): return ndb.StringProperty @sqltype_for('blob') def type_blob(self): return ndb.BlobProperty @sqltype_for('upload') def type_upload(self): return self.types['password'] @sqltype_for('integer') def type_integer(self): return ndb.IntegerProperty @sqltype_for('bigint') def type_bigint(self): return self.types['integer'] @sqltype_for('float') def type_float(self): return ndb.FloatProperty @sqltype_for('double') def type_double(self): return self.types['float'] @sqltype_for('decimal') def type_decimal(self): return NDBDecimalProperty @sqltype_for('date') def type_date(self): return ndb.DateProperty @sqltype_for('time') def type_time(self): return ndb.TimeProperty @sqltype_for('datetime') def type_datetime(self): return ndb.DateTimeProperty @sqltype_for('id') def type_id(self): return None @sqltype_for('reference') def type_reference(self): return ndb.IntegerProperty @sqltype_for('list:integer') def type_list_integer(self): return (lambda **kwargs: ndb.IntegerProperty( repeated=True, default=None, **kwargs)) @sqltype_for('list:string') def type_list_string(self): return (lambda **kwargs: ndb.StringProperty( repeated=True, default=None, **kwargs)) @sqltype_for('list:reference') def type_list_reference(self): return (lambda **kwargs: ndb.IntegerProperty( repeated=True, default=None, **kwargs)) def _and(self, first, second, query_env={}): first = self.expand(first, query_env=query_env) second = self.expand(second, query_env=query_env) # none means lack of query (true) if first == None: return second return ndb.AND(first, second) def _or(self, first, second, query_env={}): first = self.expand(first, query_env=query_env) second = self.expand(second, query_env=query_env) # none means lack of query (true) if first == None or second == None: return None return ndb.OR(first, second) def __gaef(self, first, op, second): name = first.name if first.name != 'id' else 'key' if name == 'key' and op in ('>', '!=') and second in (0, '0', None): return None field = getattr(first.table._tableobj, name) value = self.adapter.represent(second, first.type, first._tablename) return self.FILTER_OPTIONS[op](field, value) def eq(self, first, second=None, query_env={}): return self.__gaef(first, '=', second) def ne(self, first, second=None, query_env={}): return self.__gaef(first, '!=', second) def lt(self, first, second=None, query_env={}): return self.__gaef(first, '<', second) def lte(self, first, second=None, query_env={}): return self.__gaef(first, '<=', second) def gt(self, first, second=None, query_env={}): return self.__gaef(first, '>', second) def gte(self, first, second=None, query_env={}): return self.__gaef(first, '>=', second) def invert(self, first, query_env={}): return '-%s' % first.name def comma(self, first, second, query_env={}): return '%s,%s' % (first, second) def belongs(self, first, second, query_env={}): if not isinstance(second, (list, tuple, set)): raise SyntaxError("Not supported") if not isinstance(second, list): second = list(second) if len(second) == 0: # return a filter which will return a null set f = self.eq(first, 0) f.filter_all = True return f return self.__gaef(first, 'in', second) def contains(self, first, second, case_sensitive=True, query_env={}): # silently ignoring: GAE can only do case sensitive matches! if not first.type.startswith('list:'): raise SyntaxError("Not supported") return self.__gaef(first, '=', second) def _not(self, val, query_env={}): op, f, s = val.op, val.first, val.second if op in [self._or, self._and]: not_op = self._and if op == self._or else self._or rv = not_op(self._not(f), self._not(s)) elif op == self.eq: rv = self.__gaef(f, '!=', s) elif op == self.ne: rv = self.__gaef(f, '=', s) elif op == self.lt: rv = self.__gaef(f, '>=', s) elif op == self.lte: rv = self.__gaef(f, '>', s) elif op == self.gt: rv = self.__gaef(f, '<=', s) elif op == self.gte: rv = self.__gaef(f, '<', s) else: # TODO the IN operator must be split into a sequence of # (field!=value) AND (field!=value) AND ... raise NotImplementedError return rv