1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """functions for schema / permissions (de)serialization using RQL""" |
|
19 from __future__ import print_function |
|
20 |
|
21 __docformat__ = "restructuredtext en" |
|
22 |
|
23 import os |
|
24 import json |
|
25 import sys |
|
26 |
|
27 from six import PY2, text_type, string_types |
|
28 |
|
29 from logilab.common.shellutils import ProgressBar, DummyProgressBar |
|
30 |
|
31 from yams import BadSchemaDefinition, schema as schemamod, buildobjs as ybo |
|
32 |
|
33 from cubicweb import Binary |
|
34 from cubicweb.schema import (KNOWN_RPROPERTIES, CONSTRAINTS, ETYPE_NAME_MAP, |
|
35 VIRTUAL_RTYPES) |
|
36 from cubicweb.server import sqlutils, schema2sql as y2sql |
|
37 |
|
38 |
|
39 def group_mapping(cnx, interactive=True): |
|
40 """create a group mapping from an rql cursor |
|
41 |
|
42 A group mapping has standard group names as key (managers, owners at least) |
|
43 and the actual CWGroup entity's eid as associated value. |
|
44 In interactive mode (the default), missing groups'eid will be prompted |
|
45 from the user. |
|
46 """ |
|
47 res = {} |
|
48 for eid, name in cnx.execute('Any G, N WHERE G is CWGroup, G name N', |
|
49 build_descr=False): |
|
50 res[name] = eid |
|
51 if not interactive: |
|
52 return res |
|
53 missing = [g for g in ('owners', 'managers', 'users', 'guests') if not g in res] |
|
54 if missing: |
|
55 print('some native groups are missing but the following groups have been found:') |
|
56 print('\n'.join('* %s (%s)' % (n, eid) for n, eid in res.items())) |
|
57 print() |
|
58 print('enter the eid of a to group to map to each missing native group') |
|
59 print('or just type enter to skip permissions granted to a group') |
|
60 for group in missing: |
|
61 while True: |
|
62 value = raw_input('eid for group %s: ' % group).strip() |
|
63 if not value: |
|
64 continue |
|
65 try: |
|
66 eid = int(value) |
|
67 except ValueError: |
|
68 print('eid should be an integer') |
|
69 continue |
|
70 for eid_ in res.values(): |
|
71 if eid == eid_: |
|
72 break |
|
73 else: |
|
74 print('eid is not a group eid') |
|
75 continue |
|
76 res[name] = eid |
|
77 break |
|
78 return res |
|
79 |
|
80 def cstrtype_mapping(cnx): |
|
81 """cached constraint types mapping""" |
|
82 map = dict(cnx.execute('Any T, X WHERE X is CWConstraintType, X name T')) |
|
83 return map |
|
84 |
|
85 # schema / perms deserialization ############################################## |
|
86 |
|
87 def deserialize_schema(schema, cnx): |
|
88 """return a schema according to information stored in an rql database |
|
89 as CWRType and CWEType entities |
|
90 """ |
|
91 repo = cnx.repo |
|
92 dbhelper = repo.system_source.dbhelper |
|
93 |
|
94 # Computed Rtype |
|
95 with cnx.ensure_cnx_set: |
|
96 tables = set(t.lower() for t in dbhelper.list_tables(cnx.cnxset.cu)) |
|
97 has_computed_relations = 'cw_cwcomputedrtype' in tables |
|
98 # computed attribute |
|
99 try: |
|
100 cnx.system_sql("SELECT cw_formula FROM cw_CWAttribute") |
|
101 has_computed_attributes = True |
|
102 except Exception: |
|
103 cnx.rollback() |
|
104 has_computed_attributes = False |
|
105 |
|
106 # XXX bw compat (3.6 migration) |
|
107 sqlcu = cnx.system_sql("SELECT * FROM cw_CWRType WHERE cw_name='symetric'") |
|
108 if sqlcu.fetchall(): |
|
109 sql = dbhelper.sql_rename_col('cw_CWRType', 'cw_symetric', 'cw_symmetric', |
|
110 dbhelper.TYPE_MAPPING['Boolean'], True) |
|
111 sqlcu.execute(sql) |
|
112 sqlcu.execute("UPDATE cw_CWRType SET cw_name='symmetric' WHERE cw_name='symetric'") |
|
113 cnx.commit() |
|
114 ertidx = {} |
|
115 copiedeids = set() |
|
116 permsidx = deserialize_ertype_permissions(cnx) |
|
117 schema.reading_from_database = True |
|
118 # load every entity types |
|
119 for eid, etype, desc in cnx.execute( |
|
120 'Any X, N, D WHERE X is CWEType, X name N, X description D', |
|
121 build_descr=False): |
|
122 # base types are already in the schema, skip them |
|
123 if etype in schemamod.BASE_TYPES: |
|
124 # just set the eid |
|
125 eschema = schema.eschema(etype) |
|
126 eschema.eid = eid |
|
127 ertidx[eid] = etype |
|
128 continue |
|
129 if etype in ETYPE_NAME_MAP: |
|
130 needcopy = False |
|
131 netype = ETYPE_NAME_MAP[etype] |
|
132 # can't use write rql queries at this point, use raw sql |
|
133 sqlexec = cnx.system_sql |
|
134 if sqlexec('SELECT 1 FROM %(p)sCWEType WHERE %(p)sname=%%(n)s' |
|
135 % {'p': sqlutils.SQL_PREFIX}, {'n': netype}).fetchone(): |
|
136 # the new type already exists, we should copy (eg make existing |
|
137 # instances of the old type instances of the new type) |
|
138 assert etype.lower() != netype.lower() |
|
139 needcopy = True |
|
140 else: |
|
141 # the new type doesn't exist, we should rename |
|
142 sqlexec('UPDATE %(p)sCWEType SET %(p)sname=%%(n)s WHERE %(p)seid=%%(x)s' |
|
143 % {'p': sqlutils.SQL_PREFIX}, {'x': eid, 'n': netype}) |
|
144 if etype.lower() != netype.lower(): |
|
145 alter_table_sql = dbhelper.sql_rename_table(sqlutils.SQL_PREFIX+etype, |
|
146 sqlutils.SQL_PREFIX+netype) |
|
147 sqlexec(alter_table_sql) |
|
148 sqlexec('UPDATE entities SET type=%(n)s WHERE type=%(x)s', |
|
149 {'x': etype, 'n': netype}) |
|
150 cnx.commit(False) |
|
151 tocleanup = [eid] |
|
152 tocleanup += (eid for eid, cached in repo._type_source_cache.items() |
|
153 if etype == cached[0]) |
|
154 repo.clear_caches(tocleanup) |
|
155 cnx.commit(False) |
|
156 if needcopy: |
|
157 ertidx[eid] = netype |
|
158 copiedeids.add(eid) |
|
159 # copy / CWEType entity removal expected to be done through |
|
160 # rename_entity_type in a migration script |
|
161 continue |
|
162 etype = netype |
|
163 ertidx[eid] = etype |
|
164 eschema = schema.add_entity_type( |
|
165 ybo.EntityType(name=etype, description=desc, eid=eid)) |
|
166 set_perms(eschema, permsidx) |
|
167 # load inheritance relations |
|
168 for etype, stype in cnx.execute( |
|
169 'Any XN, ETN WHERE X is CWEType, X name XN, X specializes ET, ET name ETN', |
|
170 build_descr=False): |
|
171 etype = ETYPE_NAME_MAP.get(etype, etype) |
|
172 stype = ETYPE_NAME_MAP.get(stype, stype) |
|
173 schema.eschema(etype)._specialized_type = stype |
|
174 schema.eschema(stype)._specialized_by.append(etype) |
|
175 if has_computed_relations: |
|
176 rset = cnx.execute( |
|
177 'Any X, N, R, D WHERE X is CWComputedRType, X name N, ' |
|
178 'X rule R, X description D') |
|
179 for eid, rule_name, rule, description in rset.rows: |
|
180 rtype = ybo.ComputedRelation(name=rule_name, rule=rule, eid=eid, |
|
181 description=description) |
|
182 rschema = schema.add_relation_type(rtype) |
|
183 set_perms(rschema, permsidx) |
|
184 # load every relation types |
|
185 for eid, rtype, desc, sym, il, ftc in cnx.execute( |
|
186 'Any X,N,D,S,I,FTC WHERE X is CWRType, X name N, X description D, ' |
|
187 'X symmetric S, X inlined I, X fulltext_container FTC', build_descr=False): |
|
188 ertidx[eid] = rtype |
|
189 rschema = schema.add_relation_type( |
|
190 ybo.RelationType(name=rtype, description=desc, |
|
191 symmetric=bool(sym), inlined=bool(il), |
|
192 fulltext_container=ftc, eid=eid)) |
|
193 # remains to load every relation definitions (ie relations and attributes) |
|
194 cstrsidx = deserialize_rdef_constraints(cnx) |
|
195 pendingrdefs = [] |
|
196 # closure to factorize common code of attribute/relation rdef addition |
|
197 def _add_rdef(rdefeid, seid, reid, oeid, **kwargs): |
|
198 rdef = ybo.RelationDefinition(ertidx[seid], ertidx[reid], ertidx[oeid], |
|
199 constraints=cstrsidx.get(rdefeid, ()), |
|
200 eid=rdefeid, **kwargs) |
|
201 if seid in copiedeids or oeid in copiedeids: |
|
202 # delay addition of this rdef. We'll insert them later if needed. We |
|
203 # have to do this because: |
|
204 # |
|
205 # * on etype renaming, we want relation of the old entity type being |
|
206 # redirected to the new type during migration |
|
207 # |
|
208 # * in the case of a copy, we've to take care that rdef already |
|
209 # existing in the schema are not overwritten by a redirected one, |
|
210 # since we want correct eid on them (redirected rdef will be |
|
211 # removed in rename_entity_type) |
|
212 pendingrdefs.append(rdef) |
|
213 else: |
|
214 # add_relation_def return a RelationDefinitionSchema if it has been |
|
215 # actually added (can be None on duplicated relation definitions, |
|
216 # e.g. if the relation type is marked as beeing symmetric) |
|
217 rdefs = schema.add_relation_def(rdef) |
|
218 if rdefs is not None: |
|
219 ertidx[rdefeid] = rdefs |
|
220 set_perms(rdefs, permsidx) |
|
221 # Get the type parameters for additional base types. |
|
222 try: |
|
223 extra_props = dict(cnx.execute('Any X, XTP WHERE X is CWAttribute, ' |
|
224 'X extra_props XTP')) |
|
225 except Exception: |
|
226 cnx.critical('Previous CRITICAL notification about extra_props is not ' |
|
227 'a problem if you are migrating to cubicweb 3.17') |
|
228 extra_props = {} # not yet in the schema (introduced by 3.17 migration) |
|
229 |
|
230 # load attributes |
|
231 rql = ('Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT%(fm)s ' |
|
232 'WHERE X is CWAttribute, X relation_type RT, X cardinality CARD,' |
|
233 ' X ordernum ORD, X indexed IDX, X description DESC, ' |
|
234 ' X internationalizable I18N, X defaultval DFLT,%(fmsnip)s' |
|
235 ' X fulltextindexed FTIDX, X from_entity SE, X to_entity OE') |
|
236 if has_computed_attributes: |
|
237 rql = rql % {'fm': ',FM', 'fmsnip': 'X formula FM,'} |
|
238 else: |
|
239 rql = rql % {'fm': '', 'fmsnip': ''} |
|
240 for values in cnx.execute(rql, build_descr=False): |
|
241 attrs = dict(zip( |
|
242 ('rdefeid', 'seid', 'reid', 'oeid', 'cardinality', |
|
243 'order', 'description', 'indexed', 'fulltextindexed', |
|
244 'internationalizable', 'default', 'formula'), values)) |
|
245 typeparams = extra_props.get(attrs['rdefeid']) |
|
246 attrs.update(json.loads(typeparams.getvalue().decode('ascii')) if typeparams else {}) |
|
247 default = attrs['default'] |
|
248 if default is not None: |
|
249 if isinstance(default, Binary): |
|
250 # while migrating from 3.17 to 3.18, we still have to |
|
251 # handle String defaults |
|
252 attrs['default'] = default.unzpickle() |
|
253 _add_rdef(**attrs) |
|
254 # load relations |
|
255 for values in cnx.execute( |
|
256 'Any X,SE,RT,OE,CARD,ORD,DESC,C WHERE X is CWRelation, X relation_type RT,' |
|
257 'X cardinality CARD, X ordernum ORD, X description DESC, ' |
|
258 'X from_entity SE, X to_entity OE, X composite C', build_descr=False): |
|
259 rdefeid, seid, reid, oeid, card, ord, desc, comp = values |
|
260 _add_rdef(rdefeid, seid, reid, oeid, |
|
261 cardinality=card, description=desc, order=ord, |
|
262 composite=comp) |
|
263 for rdef in pendingrdefs: |
|
264 try: |
|
265 rdefs = schema.add_relation_def(rdef) |
|
266 except BadSchemaDefinition: |
|
267 continue |
|
268 if rdefs is not None: |
|
269 set_perms(rdefs, permsidx) |
|
270 unique_togethers = {} |
|
271 rset = cnx.execute( |
|
272 'Any X,E,R WHERE ' |
|
273 'X is CWUniqueTogetherConstraint, ' |
|
274 'X constraint_of E, X relations R', build_descr=False) |
|
275 for values in rset: |
|
276 uniquecstreid, eeid, releid = values |
|
277 eschema = schema.schema_by_eid(eeid) |
|
278 relations = unique_togethers.setdefault(uniquecstreid, (eschema, [])) |
|
279 rel = ertidx[releid] |
|
280 if isinstance(rel, schemamod.RelationDefinitionSchema): |
|
281 # not yet migrated 3.9 database ('relations' target type changed |
|
282 # to CWRType in 3.10) |
|
283 rtype = rel.rtype.type |
|
284 else: |
|
285 rtype = str(rel) |
|
286 relations[1].append(rtype) |
|
287 for eschema, unique_together in unique_togethers.values(): |
|
288 eschema._unique_together.append(tuple(sorted(unique_together))) |
|
289 schema.infer_specialization_rules() |
|
290 cnx.commit() |
|
291 schema.finalize() |
|
292 schema.reading_from_database = False |
|
293 |
|
294 |
|
295 def deserialize_ertype_permissions(cnx): |
|
296 """return sect action:groups associations for the given |
|
297 entity or relation schema with its eid, according to schema's |
|
298 permissions stored in the database as [read|add|delete|update]_permission |
|
299 relations between CWEType/CWRType and CWGroup entities |
|
300 """ |
|
301 res = {} |
|
302 for action in ('read', 'add', 'update', 'delete'): |
|
303 rql = 'Any E,N WHERE G is CWGroup, G name N, E %s_permission G' % action |
|
304 for eid, gname in cnx.execute(rql, build_descr=False): |
|
305 res.setdefault(eid, {}).setdefault(action, []).append(gname) |
|
306 rql = ('Any E,X,EXPR,V WHERE X is RQLExpression, X expression EXPR, ' |
|
307 'E %s_permission X, X mainvars V' % action) |
|
308 for eid, expreid, expr, mainvars in cnx.execute(rql, build_descr=False): |
|
309 # we don't know yet if it's a rql expr for an entity or a relation, |
|
310 # so append a tuple to differentiate from groups and so we'll be |
|
311 # able to instantiate it later |
|
312 res.setdefault(eid, {}).setdefault(action, []).append( (expr, mainvars, expreid) ) |
|
313 return res |
|
314 |
|
315 def deserialize_rdef_constraints(cnx): |
|
316 """return the list of relation definition's constraints as instances""" |
|
317 res = {} |
|
318 for rdefeid, ceid, ct, val in cnx.execute( |
|
319 'Any E, X,TN,V WHERE E constrained_by X, X is CWConstraint, ' |
|
320 'X cstrtype T, T name TN, X value V', build_descr=False): |
|
321 cstr = CONSTRAINTS[ct].deserialize(val) |
|
322 cstr.eid = ceid |
|
323 res.setdefault(rdefeid, []).append(cstr) |
|
324 return res |
|
325 |
|
326 def set_perms(erschema, permsidx): |
|
327 """set permissions on the given erschema according to the permission |
|
328 definition dictionary as built by deserialize_ertype_permissions for a |
|
329 given erschema's eid |
|
330 """ |
|
331 # reset erschema permissions here to avoid getting yams default anyway |
|
332 erschema.permissions = dict((action, ()) for action in erschema.ACTIONS) |
|
333 try: |
|
334 thispermsdict = permsidx[erschema.eid] |
|
335 except KeyError: |
|
336 return |
|
337 for action, somethings in thispermsdict.items(): |
|
338 erschema.permissions[action] = tuple( |
|
339 isinstance(p, tuple) and erschema.rql_expression(*p) or p |
|
340 for p in somethings) |
|
341 |
|
342 |
|
343 # schema / perms serialization ################################################ |
|
344 |
|
345 def serialize_schema(cnx, schema): |
|
346 """synchronize schema and permissions in the database according to |
|
347 current schema |
|
348 """ |
|
349 _title = '-> storing the schema in the database ' |
|
350 print(_title, end=' ') |
|
351 execute = cnx.execute |
|
352 eschemas = schema.entities() |
|
353 pb_size = (len(eschemas + schema.relations()) |
|
354 + len(CONSTRAINTS) |
|
355 + len([x for x in eschemas if x.specializes()])) |
|
356 if sys.stdout.isatty(): |
|
357 pb = ProgressBar(pb_size, title=_title) |
|
358 else: |
|
359 pb = DummyProgressBar() |
|
360 groupmap = group_mapping(cnx, interactive=False) |
|
361 # serialize all entity types, assuring CWEType is serialized first for proper |
|
362 # is / is_instance_of insertion |
|
363 eschemas.remove(schema.eschema('CWEType')) |
|
364 eschemas.insert(0, schema.eschema('CWEType')) |
|
365 for eschema in eschemas: |
|
366 execschemarql(execute, eschema, eschema2rql(eschema, groupmap)) |
|
367 pb.update() |
|
368 # serialize constraint types |
|
369 cstrtypemap = {} |
|
370 rql = 'INSERT CWConstraintType X: X name %(ct)s' |
|
371 for cstrtype in CONSTRAINTS: |
|
372 cstrtypemap[cstrtype] = execute(rql, {'ct': text_type(cstrtype)}, |
|
373 build_descr=False)[0][0] |
|
374 pb.update() |
|
375 # serialize relations |
|
376 for rschema in schema.relations(): |
|
377 # skip virtual relations such as eid, has_text and identity |
|
378 if rschema in VIRTUAL_RTYPES: |
|
379 pb.update() |
|
380 continue |
|
381 if rschema.rule: |
|
382 execschemarql(execute, rschema, crschema2rql(rschema, groupmap)) |
|
383 pb.update() |
|
384 continue |
|
385 execschemarql(execute, rschema, rschema2rql(rschema, addrdef=False)) |
|
386 if rschema.symmetric: |
|
387 rdefs = [rdef for k, rdef in rschema.rdefs.items() |
|
388 if (rdef.subject, rdef.object) == k] |
|
389 else: |
|
390 rdefs = rschema.rdefs.values() |
|
391 for rdef in rdefs: |
|
392 execschemarql(execute, rdef, |
|
393 rdef2rql(rdef, cstrtypemap, groupmap)) |
|
394 pb.update() |
|
395 # serialize unique_together constraints |
|
396 for eschema in eschemas: |
|
397 if eschema._unique_together: |
|
398 execschemarql(execute, eschema, uniquetogether2rqls(eschema)) |
|
399 # serialize yams inheritance relationships |
|
400 for rql, kwargs in specialize2rql(schema): |
|
401 execute(rql, kwargs, build_descr=False) |
|
402 pb.update() |
|
403 print() |
|
404 |
|
405 |
|
406 # high level serialization functions |
|
407 |
|
408 def execschemarql(execute, schema, rqls): |
|
409 for rql, kwargs in rqls: |
|
410 kwargs['x'] = schema.eid |
|
411 rset = execute(rql, kwargs, build_descr=False) |
|
412 if schema.eid is None: |
|
413 schema.eid = rset[0][0] |
|
414 else: |
|
415 assert rset |
|
416 |
|
417 def erschema2rql(erschema, groupmap): |
|
418 if isinstance(erschema, schemamod.EntitySchema): |
|
419 return eschema2rql(erschema, groupmap=groupmap) |
|
420 return rschema2rql(erschema, groupmap=groupmap) |
|
421 |
|
422 def specialize2rql(schema): |
|
423 for eschema in schema.entities(): |
|
424 if eschema.final: |
|
425 continue |
|
426 for rql, kwargs in eschemaspecialize2rql(eschema): |
|
427 yield rql, kwargs |
|
428 |
|
429 # etype serialization |
|
430 |
|
431 def eschema2rql(eschema, groupmap=None): |
|
432 """return a list of rql insert statements to enter an entity schema |
|
433 in the database as an CWEType entity |
|
434 """ |
|
435 relations, values = eschema_relations_values(eschema) |
|
436 # NOTE: 'specializes' relation can't be inserted here since there's no |
|
437 # way to make sure the parent type is inserted before the child type |
|
438 yield 'INSERT CWEType X: %s' % ','.join(relations) , values |
|
439 # entity permissions |
|
440 if groupmap is not None: |
|
441 for rql, args in _erperms2rql(eschema, groupmap): |
|
442 yield rql, args |
|
443 |
|
444 def eschema_relations_values(eschema): |
|
445 values = _ervalues(eschema) |
|
446 relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] |
|
447 return relations, values |
|
448 |
|
449 def eschemaspecialize2rql(eschema): |
|
450 specialized_type = eschema.specializes() |
|
451 if specialized_type: |
|
452 values = {'x': eschema.eid, 'et': specialized_type.eid} |
|
453 yield 'SET X specializes ET WHERE X eid %(x)s, ET eid %(et)s', values |
|
454 |
|
455 def uniquetogether2rqls(eschema): |
|
456 rql_args = [] |
|
457 # robustness against duplicated CWUniqueTogetherConstraint (pre 3.18) |
|
458 columnset = set() |
|
459 for columns in eschema._unique_together: |
|
460 if columns in columnset: |
|
461 print('schemaserial: skipping duplicate unique together %r %r' % |
|
462 (eschema.type, columns)) |
|
463 continue |
|
464 columnset.add(columns) |
|
465 rql, args = _uniquetogether2rql(eschema, columns) |
|
466 args['name'] = y2sql.unique_index_name(eschema, columns) |
|
467 rql_args.append((rql, args)) |
|
468 return rql_args |
|
469 |
|
470 def _uniquetogether2rql(eschema, unique_together): |
|
471 relations = [] |
|
472 restrictions = [] |
|
473 substs = {} |
|
474 for i, name in enumerate(unique_together): |
|
475 rschema = eschema.schema.rschema(name) |
|
476 rtype = 'T%d' % i |
|
477 substs[rtype] = text_type(rschema.type) |
|
478 relations.append('C relations %s' % rtype) |
|
479 restrictions.append('%(rtype)s name %%(%(rtype)s)s' % {'rtype': rtype}) |
|
480 relations = ', '.join(relations) |
|
481 restrictions = ', '.join(restrictions) |
|
482 rql = ('INSERT CWUniqueTogetherConstraint C: C name %%(name)s, C constraint_of X, %s ' |
|
483 'WHERE X eid %%(x)s, %s') |
|
484 return rql % (relations, restrictions), substs |
|
485 |
|
486 |
|
487 def _ervalues(erschema): |
|
488 try: |
|
489 type_ = text_type(erschema.type) |
|
490 except UnicodeDecodeError as e: |
|
491 raise Exception("can't decode %s [was %s]" % (erschema.type, e)) |
|
492 try: |
|
493 desc = text_type(erschema.description) or u'' |
|
494 except UnicodeDecodeError as e: |
|
495 raise Exception("can't decode %s [was %s]" % (erschema.description, e)) |
|
496 return { |
|
497 'name': type_, |
|
498 'final': erschema.final, |
|
499 'description': desc, |
|
500 } |
|
501 |
|
502 # rtype serialization |
|
503 |
|
504 def rschema2rql(rschema, cstrtypemap=None, addrdef=True, groupmap=None): |
|
505 """generate rql insert statements to enter a relation schema |
|
506 in the database as an CWRType entity |
|
507 """ |
|
508 if rschema.type == 'has_text': |
|
509 return |
|
510 relations, values = rschema_relations_values(rschema) |
|
511 yield 'INSERT CWRType X: %s' % ','.join(relations), values |
|
512 if addrdef: |
|
513 assert cstrtypemap |
|
514 # sort for testing purpose |
|
515 for rdef in sorted(rschema.rdefs.values(), |
|
516 key=lambda x: (x.subject, x.object)): |
|
517 for rql, values in rdef2rql(rdef, cstrtypemap, groupmap): |
|
518 yield rql, values |
|
519 |
|
520 def rschema_relations_values(rschema): |
|
521 values = _ervalues(rschema) |
|
522 values['final'] = rschema.final |
|
523 values['symmetric'] = rschema.symmetric |
|
524 values['inlined'] = rschema.inlined |
|
525 if PY2 and isinstance(rschema.fulltext_container, str): |
|
526 values['fulltext_container'] = unicode(rschema.fulltext_container) |
|
527 else: |
|
528 values['fulltext_container'] = rschema.fulltext_container |
|
529 relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] |
|
530 return relations, values |
|
531 |
|
532 def crschema2rql(crschema, groupmap): |
|
533 relations, values = crschema_relations_values(crschema) |
|
534 yield 'INSERT CWComputedRType X: %s' % ','.join(relations), values |
|
535 if groupmap: |
|
536 for rql, args in _erperms2rql(crschema, groupmap): |
|
537 yield rql, args |
|
538 |
|
539 def crschema_relations_values(crschema): |
|
540 values = _ervalues(crschema) |
|
541 values['rule'] = text_type(crschema.rule) |
|
542 # XXX why oh why? |
|
543 del values['final'] |
|
544 relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] |
|
545 return relations, values |
|
546 |
|
547 # rdef serialization |
|
548 |
|
549 def rdef2rql(rdef, cstrtypemap, groupmap=None): |
|
550 # don't serialize inferred relations |
|
551 if rdef.infered: |
|
552 return |
|
553 relations, values = _rdef_values(rdef) |
|
554 relations.append('X relation_type ER,X from_entity SE,X to_entity OE') |
|
555 values.update({'se': rdef.subject.eid, 'rt': rdef.rtype.eid, 'oe': rdef.object.eid}) |
|
556 if rdef.final: |
|
557 etype = 'CWAttribute' |
|
558 else: |
|
559 etype = 'CWRelation' |
|
560 yield 'INSERT %s X: %s WHERE SE eid %%(se)s,ER eid %%(rt)s,OE eid %%(oe)s' % ( |
|
561 etype, ','.join(relations), ), values |
|
562 for rql, values in constraints2rql(cstrtypemap, rdef.constraints): |
|
563 yield rql, values |
|
564 # no groupmap means "no security insertion" |
|
565 if groupmap: |
|
566 for rql, args in _erperms2rql(rdef, groupmap): |
|
567 yield rql, args |
|
568 |
|
569 _IGNORED_PROPS = ['eid', 'constraints', 'uid', 'infered', 'permissions'] |
|
570 |
|
571 def _rdef_values(rdef): |
|
572 amap = {'order': 'ordernum', 'default': 'defaultval'} |
|
573 values = {} |
|
574 extra = {} |
|
575 for prop in rdef.rproperty_defs(rdef.object): |
|
576 if prop in _IGNORED_PROPS: |
|
577 continue |
|
578 value = getattr(rdef, prop) |
|
579 if prop not in KNOWN_RPROPERTIES: |
|
580 extra[prop] = value |
|
581 continue |
|
582 # XXX type cast really necessary? |
|
583 if prop in ('indexed', 'fulltextindexed', 'internationalizable'): |
|
584 value = bool(value) |
|
585 elif prop == 'ordernum': |
|
586 value = int(value) |
|
587 elif PY2 and isinstance(value, str): |
|
588 value = unicode(value) |
|
589 if value is not None and prop == 'default': |
|
590 value = Binary.zpickle(value) |
|
591 values[amap.get(prop, prop)] = value |
|
592 if extra: |
|
593 values['extra_props'] = Binary(json.dumps(extra).encode('ascii')) |
|
594 relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] |
|
595 return relations, values |
|
596 |
|
597 def constraints2rql(cstrtypemap, constraints, rdefeid=None): |
|
598 for constraint in constraints: |
|
599 values = {'ct': cstrtypemap[constraint.type()], |
|
600 'value': text_type(constraint.serialize()), |
|
601 'x': rdefeid} # when not specified, will have to be set by the caller |
|
602 yield 'INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE \ |
|
603 CT eid %(ct)s, EDEF eid %(x)s', values |
|
604 |
|
605 |
|
606 def _erperms2rql(erschema, groupmap): |
|
607 """return rql insert statements to enter the entity or relation |
|
608 schema's permissions in the database as |
|
609 [read|add|delete|update]_permission relations between CWEType/CWRType |
|
610 and CWGroup entities |
|
611 """ |
|
612 for action in erschema.ACTIONS: |
|
613 try: |
|
614 grantedto = erschema.action_permissions(action) |
|
615 except KeyError: |
|
616 # may occurs when modifying persistent schema |
|
617 continue |
|
618 for group_or_rqlexpr in grantedto: |
|
619 if isinstance(group_or_rqlexpr, string_types): |
|
620 # group |
|
621 try: |
|
622 yield ('SET X %s_permission Y WHERE Y eid %%(g)s, X eid %%(x)s' % action, |
|
623 {'g': groupmap[group_or_rqlexpr]}) |
|
624 except KeyError: |
|
625 print("WARNING: group %s used in permissions for %s was ignored because it doesn't exist." |
|
626 " You may want to add it into a precreate.py file" % (group_or_rqlexpr, erschema)) |
|
627 continue |
|
628 else: |
|
629 # rqlexpr |
|
630 rqlexpr = group_or_rqlexpr |
|
631 yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, ' |
|
632 'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action, |
|
633 {'e': text_type(rqlexpr.expression), |
|
634 'v': text_type(','.join(sorted(rqlexpr.mainvars))), |
|
635 't': text_type(rqlexpr.__class__.__name__)}) |
|
636 |
|
637 # update functions |
|
638 |
|
639 def updateeschema2rql(eschema, eid): |
|
640 relations, values = eschema_relations_values(eschema) |
|
641 values['x'] = eid |
|
642 yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values |
|
643 |
|
644 def updaterschema2rql(rschema, eid): |
|
645 if rschema.rule: |
|
646 yield ('SET X rule %(r)s WHERE X eid %(x)s', |
|
647 {'x': eid, 'r': text_type(rschema.rule)}) |
|
648 else: |
|
649 relations, values = rschema_relations_values(rschema) |
|
650 values['x'] = eid |
|
651 yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values |
|
652 |
|
653 def updaterdef2rql(rdef, eid): |
|
654 relations, values = _rdef_values(rdef) |
|
655 values['x'] = eid |
|
656 yield 'SET %s WHERE X eid %%(x)s' % ','.join(relations), values |
|