1 | from .._gae import ndb |
---|
2 | from ..adapters.google import GoogleDatastore |
---|
3 | from ..helpers.gae import NDBDecimalProperty |
---|
4 | from .base import NoSQLDialect |
---|
5 | from . import dialects, sqltype_for |
---|
6 | |
---|
7 | |
---|
8 | @dialects.register_for(GoogleDatastore) |
---|
9 | class GoogleDatastoreDialect(NoSQLDialect): |
---|
10 | FILTER_OPTIONS = { |
---|
11 | "=": lambda a, b: a == b, |
---|
12 | ">": lambda a, b: a > b, |
---|
13 | "<": lambda a, b: a < b, |
---|
14 | "<=": lambda a, b: a <= b, |
---|
15 | ">=": lambda a, b: a >= b, |
---|
16 | "!=": lambda a, b: a != b, |
---|
17 | "in": lambda a, b: a.IN(b), |
---|
18 | } |
---|
19 | |
---|
20 | @sqltype_for("string") |
---|
21 | def type_string(self): |
---|
22 | return lambda **kwargs: ndb.StringProperty(**kwargs) |
---|
23 | |
---|
24 | @sqltype_for("boolean") |
---|
25 | def type_boolean(self): |
---|
26 | return ndb.BooleanProperty |
---|
27 | |
---|
28 | @sqltype_for("text") |
---|
29 | def type_text(self): |
---|
30 | return ndb.TextProperty |
---|
31 | |
---|
32 | @sqltype_for("json") |
---|
33 | def type_json(self): |
---|
34 | return self.types["text"] |
---|
35 | |
---|
36 | @sqltype_for("password") |
---|
37 | def type_password(self): |
---|
38 | return ndb.StringProperty |
---|
39 | |
---|
40 | @sqltype_for("blob") |
---|
41 | def type_blob(self): |
---|
42 | return ndb.BlobProperty |
---|
43 | |
---|
44 | @sqltype_for("upload") |
---|
45 | def type_upload(self): |
---|
46 | return self.types["password"] |
---|
47 | |
---|
48 | @sqltype_for("integer") |
---|
49 | def type_integer(self): |
---|
50 | return ndb.IntegerProperty |
---|
51 | |
---|
52 | @sqltype_for("bigint") |
---|
53 | def type_bigint(self): |
---|
54 | return self.types["integer"] |
---|
55 | |
---|
56 | @sqltype_for("float") |
---|
57 | def type_float(self): |
---|
58 | return ndb.FloatProperty |
---|
59 | |
---|
60 | @sqltype_for("double") |
---|
61 | def type_double(self): |
---|
62 | return self.types["float"] |
---|
63 | |
---|
64 | @sqltype_for("decimal") |
---|
65 | def type_decimal(self): |
---|
66 | return NDBDecimalProperty |
---|
67 | |
---|
68 | @sqltype_for("date") |
---|
69 | def type_date(self): |
---|
70 | return ndb.DateProperty |
---|
71 | |
---|
72 | @sqltype_for("time") |
---|
73 | def type_time(self): |
---|
74 | return ndb.TimeProperty |
---|
75 | |
---|
76 | @sqltype_for("datetime") |
---|
77 | def type_datetime(self): |
---|
78 | return ndb.DateTimeProperty |
---|
79 | |
---|
80 | @sqltype_for("id") |
---|
81 | def type_id(self): |
---|
82 | return None |
---|
83 | |
---|
84 | @sqltype_for("reference") |
---|
85 | def type_reference(self): |
---|
86 | return ndb.IntegerProperty |
---|
87 | |
---|
88 | @sqltype_for("list:integer") |
---|
89 | def type_list_integer(self): |
---|
90 | return lambda **kwargs: ndb.IntegerProperty( |
---|
91 | repeated=True, default=None, **kwargs |
---|
92 | ) |
---|
93 | |
---|
94 | @sqltype_for("list:string") |
---|
95 | def type_list_string(self): |
---|
96 | return lambda **kwargs: ndb.StringProperty( |
---|
97 | repeated=True, default=None, **kwargs |
---|
98 | ) |
---|
99 | |
---|
100 | @sqltype_for("list:reference") |
---|
101 | def type_list_reference(self): |
---|
102 | return lambda **kwargs: ndb.IntegerProperty( |
---|
103 | repeated=True, default=None, **kwargs |
---|
104 | ) |
---|
105 | |
---|
106 | def _and(self, first, second, query_env={}): |
---|
107 | first = self.expand(first, query_env=query_env) |
---|
108 | second = self.expand(second, query_env=query_env) |
---|
109 | # none means lack of query (true) |
---|
110 | if first == None: |
---|
111 | return second |
---|
112 | return ndb.AND(first, second) |
---|
113 | |
---|
114 | def _or(self, first, second, query_env={}): |
---|
115 | first = self.expand(first, query_env=query_env) |
---|
116 | second = self.expand(second, query_env=query_env) |
---|
117 | # none means lack of query (true) |
---|
118 | if first == None or second == None: |
---|
119 | return None |
---|
120 | return ndb.OR(first, second) |
---|
121 | |
---|
122 | def __gaef(self, first, op, second): |
---|
123 | name = first.name if first.name != "id" else "key" |
---|
124 | if name == "key" and op in (">", "!=") and second in (0, "0", None): |
---|
125 | return None |
---|
126 | field = getattr(first.table._tableobj, name) |
---|
127 | value = self.adapter.represent(second, first.type, first._tablename) |
---|
128 | return self.FILTER_OPTIONS[op](field, value) |
---|
129 | |
---|
130 | def eq(self, first, second=None, query_env={}): |
---|
131 | return self.__gaef(first, "=", second) |
---|
132 | |
---|
133 | def ne(self, first, second=None, query_env={}): |
---|
134 | return self.__gaef(first, "!=", second) |
---|
135 | |
---|
136 | def lt(self, first, second=None, query_env={}): |
---|
137 | return self.__gaef(first, "<", second) |
---|
138 | |
---|
139 | def lte(self, first, second=None, query_env={}): |
---|
140 | return self.__gaef(first, "<=", second) |
---|
141 | |
---|
142 | def gt(self, first, second=None, query_env={}): |
---|
143 | return self.__gaef(first, ">", second) |
---|
144 | |
---|
145 | def gte(self, first, second=None, query_env={}): |
---|
146 | return self.__gaef(first, ">=", second) |
---|
147 | |
---|
148 | def invert(self, first, query_env={}): |
---|
149 | return "-%s" % first.name |
---|
150 | |
---|
151 | def comma(self, first, second, query_env={}): |
---|
152 | return "%s,%s" % (first, second) |
---|
153 | |
---|
154 | def belongs(self, first, second, query_env={}): |
---|
155 | if not isinstance(second, (list, tuple, set)): |
---|
156 | raise SyntaxError("Not supported") |
---|
157 | if not isinstance(second, list): |
---|
158 | second = list(second) |
---|
159 | if len(second) == 0: |
---|
160 | # return a filter which will return a null set |
---|
161 | f = self.eq(first, 0) |
---|
162 | f.filter_all = True |
---|
163 | return f |
---|
164 | return self.__gaef(first, "in", second) |
---|
165 | |
---|
166 | def contains(self, first, second, case_sensitive=True, query_env={}): |
---|
167 | # silently ignoring: GAE can only do case sensitive matches! |
---|
168 | if not first.type.startswith("list:"): |
---|
169 | raise SyntaxError("Not supported") |
---|
170 | return self.__gaef(first, "=", second) |
---|
171 | |
---|
172 | def _not(self, val, query_env={}): |
---|
173 | op, f, s = val.op, val.first, val.second |
---|
174 | if op in [self._or, self._and]: |
---|
175 | not_op = self._and if op == self._or else self._or |
---|
176 | rv = not_op(self._not(f), self._not(s)) |
---|
177 | elif op == self.eq: |
---|
178 | rv = self.__gaef(f, "!=", s) |
---|
179 | elif op == self.ne: |
---|
180 | rv = self.__gaef(f, "=", s) |
---|
181 | elif op == self.lt: |
---|
182 | rv = self.__gaef(f, ">=", s) |
---|
183 | elif op == self.lte: |
---|
184 | rv = self.__gaef(f, ">", s) |
---|
185 | elif op == self.gt: |
---|
186 | rv = self.__gaef(f, "<=", s) |
---|
187 | elif op == self.gte: |
---|
188 | rv = self.__gaef(f, "<", s) |
---|
189 | else: |
---|
190 | # TODO the IN operator must be split into a sequence of |
---|
191 | # (field!=value) AND (field!=value) AND ... |
---|
192 | raise NotImplementedError |
---|
193 | return rv |
---|