173 return None |
176 return None |
174 |
177 |
175 @classmethod |
178 @classmethod |
176 def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', |
179 def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', |
177 settype=True, ordermethod='fetch_order'): |
180 settype=True, ordermethod='fetch_order'): |
178 """return a rql to fetch all entities of the class type""" |
181 st = cls.fetch_rqlst(user, mainvar=mainvar, fetchattrs=fetchattrs, |
179 # XXX update api and implementation to AST manipulation (see unrelated rql) |
182 settype=settype, ordermethod=ordermethod) |
180 restrictions = restriction or [] |
183 rql = st.as_string() |
|
184 if restriction: |
|
185 # cannot use RQLRewriter API to insert 'X rtype %(x)s' restriction |
|
186 warn('[3.14] fetch_rql: use of `restriction` parameter is ' |
|
187 'deprecated, please use fetch_rqlst and supply a syntax' |
|
188 'tree with your restriction instead', DeprecationWarning) |
|
189 insert = ' WHERE ' + ','.join(restriction) |
|
190 if ' WHERE ' in rql: |
|
191 select, where = rql.split(' WHERE ', 1) |
|
192 rql = select + insert + ',' + where |
|
193 else: |
|
194 rql += insert |
|
195 return rql |
|
196 |
|
197 @classmethod |
|
198 def fetch_rqlst(cls, user, select=None, mainvar='X', fetchattrs=None, |
|
199 settype=True, ordermethod='fetch_order'): |
|
200 if select is None: |
|
201 select = Select() |
|
202 mainvar = select.get_variable(mainvar) |
|
203 select.add_selected(mainvar) |
|
204 elif isinstance(mainvar, basestring): |
|
205 assert mainvar in select.defined_vars |
|
206 mainvar = select.get_variable(mainvar) |
|
207 # eases string -> syntax tree test transition: please remove once stable |
|
208 select._varmaker = rqlvar_maker(defined=select.defined_vars, |
|
209 aliases=select.aliases, index=26) |
181 if settype: |
210 if settype: |
182 restrictions.append('%s is %s' % (mainvar, cls.__regid__)) |
211 select.add_type_restriction(mainvar, cls.__regid__) |
183 if fetchattrs is None: |
212 if fetchattrs is None: |
184 fetchattrs = cls.fetch_attrs |
213 fetchattrs = cls.fetch_attrs |
185 selection = [mainvar] |
214 cls._fetch_restrictions(mainvar, select, fetchattrs, user, ordermethod) |
186 orderby = [] |
215 return select |
187 # start from 26 to avoid possible conflicts with X |
|
188 # XXX not enough to be sure it'll be no conflicts |
|
189 varmaker = rqlvar_maker(index=26) |
|
190 cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection, |
|
191 orderby, restrictions, user, ordermethod) |
|
192 rql = 'Any %s' % ','.join(selection) |
|
193 if orderby: |
|
194 rql += ' ORDERBY %s' % ','.join(orderby) |
|
195 rql += ' WHERE %s' % ', '.join(restrictions) |
|
196 return rql |
|
197 |
216 |
198 @classmethod |
217 @classmethod |
199 def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs, |
218 def _fetch_restrictions(cls, mainvar, select, fetchattrs, |
200 selection, orderby, restrictions, user, |
219 user, ordermethod='fetch_order', visited=None): |
201 ordermethod='fetch_order', visited=None): |
|
202 eschema = cls.e_schema |
220 eschema = cls.e_schema |
203 if visited is None: |
221 if visited is None: |
204 visited = set((eschema.type,)) |
222 visited = set((eschema.type,)) |
205 elif eschema.type in visited: |
223 elif eschema.type in visited: |
206 # avoid infinite recursion |
224 # avoid infinite recursion |
216 attr, cls.__regid__) |
234 attr, cls.__regid__) |
217 continue |
235 continue |
218 rdef = eschema.rdef(attr) |
236 rdef = eschema.rdef(attr) |
219 if not user.matching_groups(rdef.get_groups('read')): |
237 if not user.matching_groups(rdef.get_groups('read')): |
220 continue |
238 continue |
221 var = varmaker.next() |
239 if rschema.final or rdef.cardinality[0] in '?1': |
222 selection.append(var) |
240 var = select.make_variable() |
223 restriction = '%s %s %s' % (mainvar, attr, var) |
241 select.add_selected(var) |
224 restrictions.append(restriction) |
242 rel = make_relation(mainvar, attr, (var,), VariableRef) |
|
243 select.add_restriction(rel) |
|
244 else: |
|
245 cls.warning('bad relation %s specified in fetch attrs for %s', |
|
246 attr, cls) |
|
247 continue |
225 if not rschema.final: |
248 if not rschema.final: |
226 card = rdef.cardinality[0] |
|
227 if card not in '?1': |
|
228 cls.warning('bad relation %s specified in fetch attrs for %s', |
|
229 attr, cls) |
|
230 selection.pop() |
|
231 restrictions.pop() |
|
232 continue |
|
233 # XXX we need outer join in case the relation is not mandatory |
249 # XXX we need outer join in case the relation is not mandatory |
234 # (card == '?') *or if the entity is being added*, since in |
250 # (card == '?') *or if the entity is being added*, since in |
235 # that case the relation may still be missing. As we miss this |
251 # that case the relation may still be missing. As we miss this |
236 # later information here, systematically add it. |
252 # later information here, systematically add it. |
237 restrictions[-1] += '?' |
253 rel.change_optional('right') |
238 targettypes = rschema.objects(eschema.type) |
254 targettypes = rschema.objects(eschema.type) |
239 # XXX user._cw.vreg iiiirk |
255 # XXX user._cw.vreg iiiirk |
240 etypecls = user._cw.vreg['etypes'].etype_class(targettypes[0]) |
256 etypecls = user._cw.vreg['etypes'].etype_class(targettypes[0]) |
241 if len(targettypes) > 1: |
257 if len(targettypes) > 1: |
242 # find fetch_attrs common to all destination types |
258 # find fetch_attrs common to all destination types |
243 fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes) |
259 fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes) |
244 remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema) |
260 remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema) |
245 else: |
261 else: |
246 fetchattrs = etypecls.fetch_attrs |
262 fetchattrs = etypecls.fetch_attrs |
247 etypecls._fetch_restrictions(var, varmaker, fetchattrs, |
263 etypecls._fetch_restrictions(var, select, fetchattrs, |
248 selection, orderby, restrictions, |
|
249 user, ordermethod, visited=visited) |
264 user, ordermethod, visited=visited) |
250 if ordermethod is not None: |
265 if ordermethod is not None: |
251 orderterm = getattr(cls, ordermethod)(attr, var) |
266 orderterm = getattr(cls, ordermethod)(attr, var.name) |
252 if orderterm: |
267 if orderterm: |
253 orderby.append(orderterm) |
268 var, order = orderterm.split() |
254 return selection, orderby, restrictions |
269 select.add_sort_var(select.get_variable(var), order=='ASC') |
255 |
270 |
256 @classmethod |
271 @classmethod |
257 @cached |
272 @cached |
258 def _rest_attr_info(cls): |
273 def _rest_attr_info(cls): |
259 mainattr, needcheck = 'eid', True |
274 mainattr, needcheck = 'eid', True |
755 self.cw_set_relation_cache(rtype, role, rset) |
771 self.cw_set_relation_cache(rtype, role, rset) |
756 return self.related(rtype, role, limit, entities) |
772 return self.related(rtype, role, limit, entities) |
757 |
773 |
758 def cw_related_rql(self, rtype, role='subject', targettypes=None): |
774 def cw_related_rql(self, rtype, role='subject', targettypes=None): |
759 rschema = self._cw.vreg.schema[rtype] |
775 rschema = self._cw.vreg.schema[rtype] |
|
776 select = Select() |
|
777 mainvar, evar = select.get_variable('X'), select.get_variable('E') |
|
778 select.add_selected(mainvar) |
|
779 select.add_eid_restriction(evar, 'x', 'Substitute') |
760 if role == 'subject': |
780 if role == 'subject': |
761 restriction = 'E eid %%(x)s, E %s X' % rtype |
781 rel = make_relation(evar, rtype, (mainvar,), VariableRef) |
|
782 select.add_restriction(rel) |
762 if targettypes is None: |
783 if targettypes is None: |
763 targettypes = rschema.objects(self.e_schema) |
784 targettypes = rschema.objects(self.e_schema) |
764 else: |
785 else: |
765 restriction += ', X is IN (%s)' % ','.join(targettypes) |
786 select.add_constant_restriction(mainvar, 'is', |
766 card = greater_card(rschema, (self.e_schema,), targettypes, 0) |
787 targettypes, 'etype') |
767 else: |
788 gcard = greater_card(rschema, (self.e_schema,), targettypes, 0) |
768 restriction = 'E eid %%(x)s, X %s E' % rtype |
789 else: |
|
790 rel = make_relation(mainvar, rtype, (evar,), VariableRef) |
|
791 select.add_restriction(rel) |
769 if targettypes is None: |
792 if targettypes is None: |
770 targettypes = rschema.subjects(self.e_schema) |
793 targettypes = rschema.subjects(self.e_schema) |
771 else: |
794 else: |
772 restriction += ', X is IN (%s)' % ','.join(targettypes) |
795 select.add_constant_restriction(mainvar, 'is', targettypes, |
773 card = greater_card(rschema, targettypes, (self.e_schema,), 1) |
796 'String') |
|
797 gcard = greater_card(rschema, targettypes, (self.e_schema,), 1) |
774 etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0]) |
798 etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0]) |
775 if len(targettypes) > 1: |
799 if len(targettypes) > 1: |
776 fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes) |
800 fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes) |
777 # XXX we should fetch ambiguous relation objects too but not |
801 # XXX we should fetch ambiguous relation objects too but not |
778 # recurse on them in _fetch_restrictions; it is easier to remove |
802 # recurse on them in _fetch_restrictions; it is easier to remove |
779 # them completely for now, as it would require an deeper api rewrite |
803 # them completely for now, as it would require a deeper api rewrite |
780 remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema) |
804 remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema) |
781 else: |
805 else: |
782 fetchattrs = etypecls.fetch_attrs |
806 fetchattrs = etypecls.fetch_attrs |
783 rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs, |
807 etypecls.fetch_rqlst(self._cw.user, select, mainvar, fetchattrs, |
784 settype=False) |
808 settype=False) |
785 # optimisation: remove ORDERBY if cardinality is 1 or ? (though |
809 # optimisation: remove ORDERBY if cardinality is 1 or ? (though |
786 # greater_card return 1 for those both cases) |
810 # greater_card return 1 for those both cases) |
787 if card == '1': |
811 if gcard == '1': |
788 if ' ORDERBY ' in rql: |
812 select.remove_sort_terms() |
789 rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0], |
813 elif not select.orderby: |
790 rql.split(' WHERE ', 1)[1]) |
814 # if modification_date is already retrieved, we use it instead |
791 elif not ' ORDERBY ' in rql: |
815 # of adding another variable for sorting. This should not be |
792 args = rql.split(' WHERE ', 1) |
816 # problematic, but it is with sqlserver, see ticket #694445 |
793 # if modification_date already retreived, we should use it instead |
817 for rel in select.where.get_nodes(RqlRelation): |
794 # of adding another variable for sort. This should be be problematic |
818 if (rel.r_type == 'modification_date' |
795 # but it's actually with sqlserver, see ticket #694445 |
819 and rel.children[0].variable == mainvar |
796 if 'X modification_date ' in args[1]: |
820 and rel.children[1].operator == '='): |
797 var = args[1].split('X modification_date ', 1)[1].split(',', 1)[0] |
821 var = rel.children[1].children[0].variable |
798 args.insert(1, var.strip()) |
822 select.add_sort_var(var, asc=False) |
799 rql = '%s ORDERBY %s DESC WHERE %s' % tuple(args) |
823 break |
800 else: |
824 else: |
801 rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % \ |
825 mdvar = select.make_variable() |
802 tuple(args) |
826 rel = make_relation(mainvar, 'modification_date', |
803 return rql |
827 (mdvar,), VariableRef) |
|
828 select.add_restriction(rel) |
|
829 select.add_sort_var(mdvar, asc=False) |
|
830 return select.as_string() |
804 |
831 |
805 # generic vocabulary methods ############################################## |
832 # generic vocabulary methods ############################################## |
806 |
833 |
807 def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None, |
834 def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None, |
808 vocabconstraints=True): |
835 vocabconstraints=True): |
815 ordermethod = ordermethod or 'fetch_unrelated_order' |
842 ordermethod = ordermethod or 'fetch_unrelated_order' |
816 if isinstance(rtype, basestring): |
843 if isinstance(rtype, basestring): |
817 rtype = self._cw.vreg.schema.rschema(rtype) |
844 rtype = self._cw.vreg.schema.rschema(rtype) |
818 rdef = rtype.role_rdef(self.e_schema, targettype, role) |
845 rdef = rtype.role_rdef(self.e_schema, targettype, role) |
819 rewriter = RQLRewriter(self._cw) |
846 rewriter = RQLRewriter(self._cw) |
|
847 select = Select() |
820 # initialize some variables according to the `role` of `self` in the |
848 # initialize some variables according to the `role` of `self` in the |
821 # relation: |
849 # relation (variable names must respect constraints conventions): |
822 # * variable for myself (`evar`) and searched entities (`searchvedvar`) |
850 # * variable for myself (`evar`) |
823 # * entity type of the subject (`subjtype`) and of the object |
851 # * variable for searched entities (`searchvedvar`) |
824 # (`objtype`) of the relation |
|
825 if role == 'subject': |
852 if role == 'subject': |
826 evar, searchedvar = 'S', 'O' |
853 evar = subjvar = select.get_variable('S') |
827 subjtype, objtype = self.e_schema, targettype |
854 searchedvar = objvar = select.get_variable('O') |
828 else: |
855 else: |
829 searchedvar, evar = 'S', 'O' |
856 searchedvar = subjvar = select.get_variable('S') |
830 objtype, subjtype = self.e_schema, targettype |
857 evar = objvar = select.get_variable('O') |
|
858 select.add_selected(searchedvar) |
831 # initialize some variables according to `self` existance |
859 # initialize some variables according to `self` existance |
832 if rdef.role_cardinality(neg_role(role)) in '?1': |
860 if rdef.role_cardinality(neg_role(role)) in '?1': |
833 # if cardinality in '1?', we want a target entity which isn't |
861 # if cardinality in '1?', we want a target entity which isn't |
834 # already linked using this relation |
862 # already linked using this relation |
835 if searchedvar == 'S': |
863 var = select.get_variable('ZZ') # XXX unname when tests pass |
836 restriction = ['NOT S %s ZZ' % rtype] |
864 if role == 'subject': |
837 else: |
865 rel = make_relation(var, rtype.type, (searchedvar,), VariableRef) |
838 restriction = ['NOT ZZ %s O' % rtype] |
866 else: |
|
867 rel = make_relation(searchedvar, rtype.type, (var,), VariableRef) |
|
868 select.add_restriction(Not(rel)) |
839 elif self.has_eid(): |
869 elif self.has_eid(): |
840 # elif we have an eid, we don't want a target entity which is |
870 # elif we have an eid, we don't want a target entity which is |
841 # already linked to ourself through this relation |
871 # already linked to ourself through this relation |
842 restriction = ['NOT S %s O' % rtype] |
872 rel = make_relation(subjvar, rtype.type, (objvar,), VariableRef) |
843 else: |
873 select.add_restriction(Not(rel)) |
844 restriction = [] |
|
845 if self.has_eid(): |
874 if self.has_eid(): |
846 restriction += ['%s eid %%(x)s' % evar] |
875 rel = make_relation(evar, 'eid', ('x', 'Substitute'), Constant) |
|
876 select.add_restriction(rel) |
847 args = {'x': self.eid} |
877 args = {'x': self.eid} |
848 if role == 'subject': |
878 if role == 'subject': |
849 sec_check_args = {'fromeid': self.eid} |
879 sec_check_args = {'fromeid': self.eid} |
850 else: |
880 else: |
851 sec_check_args = {'toeid': self.eid} |
881 sec_check_args = {'toeid': self.eid} |
852 existant = None # instead of 'SO', improve perfs |
882 existant = None # instead of 'SO', improve perfs |
853 else: |
883 else: |
854 args = {} |
884 args = {} |
855 sec_check_args = {} |
885 sec_check_args = {} |
856 existant = searchedvar |
886 existant = searchedvar.name |
857 # retreive entity class for targettype to compute base rql |
887 # undefine unused evar, or the type resolver will consider it |
|
888 select.undefine_variable(evar) |
|
889 # retrieve entity class for targettype to compute base rql |
858 etypecls = self._cw.vreg['etypes'].etype_class(targettype) |
890 etypecls = self._cw.vreg['etypes'].etype_class(targettype) |
859 rql = etypecls.fetch_rql(self._cw.user, restriction, |
891 etypecls.fetch_rqlst(self._cw.user, select, searchedvar, |
860 mainvar=searchedvar, ordermethod=ordermethod) |
892 ordermethod=ordermethod) |
861 select = self._cw.vreg.parse(self._cw, rql, args).children[0] |
893 # from now on, we need variable type resolving |
|
894 self._cw.vreg.solutions(self._cw, select, args) |
862 # insert RQL expressions for schema constraints into the rql syntax tree |
895 # insert RQL expressions for schema constraints into the rql syntax tree |
863 if vocabconstraints: |
896 if vocabconstraints: |
864 # RQLConstraint is a subclass for RQLVocabularyConstraint, so they |
897 # RQLConstraint is a subclass for RQLVocabularyConstraint, so they |
865 # will be included as well |
898 # will be included as well |
866 cstrcls = RQLVocabularyConstraint |
899 cstrcls = RQLVocabularyConstraint |
867 else: |
900 else: |
868 cstrcls = RQLConstraint |
901 cstrcls = RQLConstraint |
869 for cstr in rdef.constraints: |
902 for cstr in rdef.constraints: |
870 # consider constraint.mainvars to check if constraint apply |
903 # consider constraint.mainvars to check if constraint apply |
871 if isinstance(cstr, cstrcls) and searchedvar in cstr.mainvars: |
904 if isinstance(cstr, cstrcls) and searchedvar.name in cstr.mainvars: |
872 if not self.has_eid() and evar in cstr.mainvars: |
905 if not self.has_eid() and evar.name in cstr.mainvars: |
873 continue |
906 continue |
874 # compute a varmap suitable to RQLRewriter.rewrite argument |
907 # compute a varmap suitable to RQLRewriter.rewrite argument |
875 varmap = dict((v, v) for v in 'SO' if v in select.defined_vars |
908 varmap = dict((v, v) for v in (searchedvar.name, evar.name) |
876 and v in cstr.mainvars) |
909 if v in select.defined_vars and v in cstr.mainvars) |
877 # rewrite constraint by constraint since we want a AND between |
910 # rewrite constraint by constraint since we want a AND between |
878 # expressions. |
911 # expressions. |
879 rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions, |
912 rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions, |
880 args, existant) |
913 args, existant) |
881 # insert security RQL expressions granting the permission to 'add' the |
914 # insert security RQL expressions granting the permission to 'add' the |
882 # relation into the rql syntax tree, if necessary |
915 # relation into the rql syntax tree, if necessary |
883 rqlexprs = rdef.get_rqlexprs('add') |
916 rqlexprs = rdef.get_rqlexprs('add') |
884 if rqlexprs and not rdef.has_perm(self._cw, 'add', **sec_check_args): |
917 if rqlexprs and not rdef.has_perm(self._cw, 'add', **sec_check_args): |
885 # compute a varmap suitable to RQLRewriter.rewrite argument |
918 # compute a varmap suitable to RQLRewriter.rewrite argument |
886 varmap = dict((v, v) for v in 'SO' if v in select.defined_vars) |
919 varmap = dict((v, v) for v in (searchedvar.name, evar.name) |
|
920 if v in select.defined_vars) |
887 # rewrite all expressions at once since we want a OR between them. |
921 # rewrite all expressions at once since we want a OR between them. |
888 rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions, |
922 rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions, |
889 args, existant) |
923 args, existant) |
890 # ensure we have an order defined |
924 # ensure we have an order defined |
891 if not select.orderby: |
925 if not select.orderby: |
892 select.add_sort_var(select.defined_vars[searchedvar]) |
926 select.add_sort_var(select.defined_vars[searchedvar.name]) |
893 # we're done, turn the rql syntax tree as a string |
927 # we're done, turn the rql syntax tree as a string |
894 rql = select.as_string() |
928 rql = select.as_string() |
895 return rql, args |
929 return rql, args |
896 |
930 |
897 def unrelated(self, rtype, targettype, role='subject', limit=None, |
931 def unrelated(self, rtype, targettype, role='subject', limit=None, |