80 if related is not None: |
80 if related is not None: |
81 related = [key for key in related if not key == targetkey] |
81 related = [key for key in related if not key == targetkey] |
82 gaeentity[relation] = related or None |
82 gaeentity[relation] = related or None |
83 _mark_modified(session, gaeentity) |
83 _mark_modified(session, gaeentity) |
84 |
84 |
85 |
85 |
86 class DatastorePutOp(SingleOperation): |
86 class DatastorePutOp(SingleOperation): |
87 """delayed put of entities to have less datastore write api calls |
87 """delayed put of entities to have less datastore write api calls |
88 |
88 |
89 * save all modified entities at precommit (should be the first operation |
89 * save all modified entities at precommit (should be the first operation |
90 processed, hence the 0 returned by insert_index()) |
90 processed, hence the 0 returned by insert_index()) |
91 |
91 |
92 * in case others precommit operations modify some entities, resave modified |
92 * in case others precommit operations modify some entities, resave modified |
93 entities at commit. This suppose that no db changes will occurs during |
93 entities at commit. This suppose that no db changes will occurs during |
94 commit event but it should be the case. |
94 commit event but it should be the case. |
95 """ |
95 """ |
96 def insert_index(self): |
96 def insert_index(self): |
101 modified = self.session.query_data('modifiedentities', {}) |
101 modified = self.session.query_data('modifiedentities', {}) |
102 for eid, gaeentity in modified.iteritems(): |
102 for eid, gaeentity in modified.iteritems(): |
103 assert not eid in pending |
103 assert not eid in pending |
104 Put(gaeentity) |
104 Put(gaeentity) |
105 modified.clear() |
105 modified.clear() |
106 |
106 |
107 def commit_event(self): |
107 def commit_event(self): |
108 self._put_entities() |
108 self._put_entities() |
109 |
109 |
110 def precommit_event(self): |
110 def precommit_event(self): |
111 self._put_entities() |
111 self._put_entities() |
112 |
112 |
113 |
113 |
114 class GAESource(AbstractSource): |
114 class GAESource(AbstractSource): |
115 """adapter for a system source on top of google appengine datastore""" |
115 """adapter for a system source on top of google appengine datastore""" |
116 |
116 |
117 passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P" |
117 passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P" |
118 auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s" |
118 auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s" |
119 _sols = ({'X': 'CWUser', 'P': 'Password'},) |
119 _sols = ({'X': 'CWUser', 'P': 'Password'},) |
120 |
120 |
121 options = () |
121 options = () |
122 |
122 |
123 def __init__(self, repo, appschema, source_config, *args, **kwargs): |
123 def __init__(self, repo, appschema, source_config, *args, **kwargs): |
124 AbstractSource.__init__(self, repo, appschema, source_config, |
124 AbstractSource.__init__(self, repo, appschema, source_config, |
125 *args, **kwargs) |
125 *args, **kwargs) |
126 if repo.config['use-google-auth']: |
126 if repo.config['use-google-auth']: |
127 self.info('using google authentication service') |
127 self.info('using google authentication service') |
128 self.authenticate = self.authenticate_gauth |
128 self.authenticate = self.authenticate_gauth |
129 else: |
129 else: |
130 self.authenticate = self.authenticate_local |
130 self.authenticate = self.authenticate_local |
131 |
131 |
132 def reset_caches(self): |
132 def reset_caches(self): |
133 """method called during test to reset potential source caches""" |
133 """method called during test to reset potential source caches""" |
134 pass |
134 pass |
135 |
135 |
136 def init_creating(self): |
136 def init_creating(self): |
137 pass |
137 pass |
138 |
138 |
139 def init(self): |
139 def init(self): |
140 # XXX unregister unsupported hooks |
140 # XXX unregister unsupported hooks |
142 self.repo.hm.unregister_hook(sync_owner_after_add_composite_relation, |
142 self.repo.hm.unregister_hook(sync_owner_after_add_composite_relation, |
143 'after_add_relation', '') |
143 'after_add_relation', '') |
144 |
144 |
145 def get_connection(self): |
145 def get_connection(self): |
146 return ConnectionWrapper() |
146 return ConnectionWrapper() |
147 |
147 |
148 # ISource interface ####################################################### |
148 # ISource interface ####################################################### |
149 |
149 |
150 def compile_rql(self, rql): |
150 def compile_rql(self, rql): |
151 rqlst = self.repo.querier._rqlhelper.parse(rql) |
151 rqlst = self.repo.querier._rqlhelper.parse(rql) |
152 rqlst.restricted_vars = () |
152 rqlst.restricted_vars = () |
153 rqlst.children[0].solutions = self._sols |
153 rqlst.children[0].solutions = self._sols |
154 return rqlst |
154 return rqlst |
155 |
155 |
156 def set_schema(self, schema): |
156 def set_schema(self, schema): |
157 """set the application'schema""" |
157 """set the application'schema""" |
158 self.interpreter = RQLInterpreter(schema) |
158 self.interpreter = RQLInterpreter(schema) |
159 self.schema = schema |
159 self.schema = schema |
160 if 'CWUser' in schema and not self.repo.config['use-google-auth']: |
160 if 'CWUser' in schema and not self.repo.config['use-google-auth']: |
161 # rql syntax trees used to authenticate users |
161 # rql syntax trees used to authenticate users |
162 self._passwd_rqlst = self.compile_rql(self.passwd_rql) |
162 self._passwd_rqlst = self.compile_rql(self.passwd_rql) |
163 self._auth_rqlst = self.compile_rql(self.auth_rql) |
163 self._auth_rqlst = self.compile_rql(self.auth_rql) |
164 |
164 |
165 def support_entity(self, etype, write=False): |
165 def support_entity(self, etype, write=False): |
166 """return true if the given entity's type is handled by this adapter |
166 """return true if the given entity's type is handled by this adapter |
167 if write is true, return true only if it's a RW support |
167 if write is true, return true only if it's a RW support |
168 """ |
168 """ |
169 return True |
169 return True |
170 |
170 |
171 def support_relation(self, rtype, write=False): |
171 def support_relation(self, rtype, write=False): |
172 """return true if the given relation's type is handled by this adapter |
172 """return true if the given relation's type is handled by this adapter |
173 if write is true, return true only if it's a RW support |
173 if write is true, return true only if it's a RW support |
174 """ |
174 """ |
175 return True |
175 return True |
198 euser = Entity('CWUser', name='key_' + login) |
198 euser = Entity('CWUser', name='key_' + login) |
199 euser['s_login'] = login |
199 euser['s_login'] = login |
200 _init_groups(guser, euser) |
200 _init_groups(guser, euser) |
201 Put(euser) |
201 Put(euser) |
202 return str(euser.key()) |
202 return str(euser.key()) |
203 |
203 |
204 def authenticate_local(self, session, login, password): |
204 def authenticate_local(self, session, login, password): |
205 """return CWUser eid for the given login/password if this account is |
205 """return CWUser eid for the given login/password if this account is |
206 defined in this source, else raise `AuthenticationError` |
206 defined in this source, else raise `AuthenticationError` |
207 |
207 |
208 two queries are needed since passwords are stored crypted, so we have |
208 two queries are needed since passwords are stored crypted, so we have |
222 rset = self.syntax_tree_search(session, self._auth_rqlst, args) |
222 rset = self.syntax_tree_search(session, self._auth_rqlst, args) |
223 try: |
223 try: |
224 return rset[0][0] |
224 return rset[0][0] |
225 except IndexError: |
225 except IndexError: |
226 raise AuthenticationError('bad password') |
226 raise AuthenticationError('bad password') |
227 |
227 |
228 def syntax_tree_search(self, session, union, args=None, cachekey=None, |
228 def syntax_tree_search(self, session, union, args=None, cachekey=None, |
229 varmap=None): |
229 varmap=None): |
230 """return result from this source for a rql query (actually from a rql |
230 """return result from this source for a rql query (actually from a rql |
231 syntax tree and a solution dictionary mapping each used variable to a |
231 syntax tree and a solution dictionary mapping each used variable to a |
232 possible type). If cachekey is given, the query necessary to fetch the |
232 possible type). If cachekey is given, the query necessary to fetch the |
233 results (but not the results themselves) may be cached using this key. |
233 results (but not the results themselves) may be cached using this key. |
234 """ |
234 """ |
235 results, description = self.interpreter.interpret(union, args, |
235 results, description = self.interpreter.interpret(union, args, |
236 session.datastore_get) |
236 session.datastore_get) |
237 return results # XXX description |
237 return results # XXX description |
238 |
238 |
239 def flying_insert(self, table, session, union, args=None, varmap=None): |
239 def flying_insert(self, table, session, union, args=None, varmap=None): |
240 raise NotImplementedError |
240 raise NotImplementedError |
241 |
241 |
242 def add_entity(self, session, entity): |
242 def add_entity(self, session, entity): |
243 """add a new entity to the source""" |
243 """add a new entity to the source""" |
244 # do not delay add_entity as other modifications, new created entity |
244 # do not delay add_entity as other modifications, new created entity |
245 # needs an eid |
245 # needs an eid |
246 entity.put() |
246 entity.put() |
247 |
247 |
248 def update_entity(self, session, entity): |
248 def update_entity(self, session, entity): |
249 """replace an entity in the source""" |
249 """replace an entity in the source""" |
250 gaeentity = entity.to_gae_model() |
250 gaeentity = entity.to_gae_model() |
251 _mark_modified(session, entity.to_gae_model()) |
251 _mark_modified(session, entity.to_gae_model()) |
252 if gaeentity.kind() == 'CWUser': |
252 if gaeentity.kind() == 'CWUser': |
253 for asession in self.repo._sessions.itervalues(): |
253 for asession in self.repo._sessions.itervalues(): |
254 if asession.user.eid == entity.eid: |
254 if asession.user.eid == entity.eid: |
255 asession.user.update(dict(gaeentity)) |
255 asession.user.update(dict(gaeentity)) |
256 |
256 |
257 def delete_entity(self, session, etype, eid): |
257 def delete_entity(self, session, etype, eid): |
258 """delete an entity from the source""" |
258 """delete an entity from the source""" |
259 # do not delay delete_entity as other modifications to ensure |
259 # do not delay delete_entity as other modifications to ensure |
260 # consistency |
260 # consistency |
261 key = Key(eid) |
261 key = Key(eid) |
268 """add a relation to the source""" |
268 """add a relation to the source""" |
269 gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object) |
269 gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object) |
270 _radd(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0]) |
270 _radd(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0]) |
271 _radd(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1]) |
271 _radd(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1]) |
272 _clear_related_cache(session, gaesubj, rtype, gaeobj) |
272 _clear_related_cache(session, gaesubj, rtype, gaeobj) |
273 |
273 |
274 def delete_relation(self, session, subject, rtype, object): |
274 def delete_relation(self, session, subject, rtype, object): |
275 """delete a relation from the source""" |
275 """delete a relation from the source""" |
276 gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object) |
276 gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object) |
277 pending = session.query_data('pendingeids', set(), setdefault=True) |
277 pending = session.query_data('pendingeids', set(), setdefault=True) |
278 if not subject in pending: |
278 if not subject in pending: |
279 _rdel(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0]) |
279 _rdel(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0]) |
280 if not object in pending: |
280 if not object in pending: |
281 _rdel(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1]) |
281 _rdel(session, gaeobj, gaesubj.key(), 'o_' + rtype, cards[1]) |
282 _clear_related_cache(session, gaesubj, rtype, gaeobj) |
282 _clear_related_cache(session, gaesubj, rtype, gaeobj) |
283 |
283 |
284 # system source interface ################################################# |
284 # system source interface ################################################# |
285 |
285 |
286 def eid_type_source(self, session, eid): |
286 def eid_type_source(self, session, eid): |
287 """return a tuple (type, source, extid) for the entity with id <eid>""" |
287 """return a tuple (type, source, extid) for the entity with id <eid>""" |
288 try: |
288 try: |
289 key = Key(eid) |
289 key = Key(eid) |
290 except datastore_errors.BadKeyError: |
290 except datastore_errors.BadKeyError: |
291 raise UnknownEid(eid) |
291 raise UnknownEid(eid) |
292 return key.kind(), 'system', None |
292 return key.kind(), 'system', None |
293 |
293 |
294 def create_eid(self, session): |
294 def create_eid(self, session): |
295 return None # let the datastore generating key |
295 return None # let the datastore generating key |
296 |
296 |
297 def add_info(self, session, entity, source, extid=None): |
297 def add_info(self, session, entity, source, extid=None): |
298 """add type and source info for an eid into the system table""" |
298 """add type and source info for an eid into the system table""" |
301 def delete_info(self, session, eid, etype, uri, extid): |
301 def delete_info(self, session, eid, etype, uri, extid): |
302 """delete system information on deletion of an entity by transfering |
302 """delete system information on deletion of an entity by transfering |
303 record from the entities table to the deleted_entities table |
303 record from the entities table to the deleted_entities table |
304 """ |
304 """ |
305 pass |
305 pass |
306 |
306 |
307 def fti_unindex_entity(self, session, eid): |
307 def fti_unindex_entity(self, session, eid): |
308 """remove text content for entity with the given eid from the full text |
308 """remove text content for entity with the given eid from the full text |
309 index |
309 index |
310 """ |
310 """ |
311 pass |
311 pass |
312 |
312 |
313 def fti_index_entity(self, session, entity): |
313 def fti_index_entity(self, session, entity): |
314 """add text content of a created/modified entity to the full text index |
314 """add text content of a created/modified entity to the full text index |
315 """ |
315 """ |
316 pass |
316 pass |
317 |
|