301 |
301 |
302 WARNING: a CubicWebSQLGenerator instance is not thread safe, but generate is |
302 WARNING: a CubicWebSQLGenerator instance is not thread safe, but generate is |
303 protected by a lock |
303 protected by a lock |
304 """ |
304 """ |
305 |
305 |
306 def __init__(self, schema, dbms_helper, dbencoding='UTF-8'): |
306 def __init__(self, schema, dbms_helper, dbencoding='UTF-8', attrmap=None): |
307 self.schema = schema |
307 self.schema = schema |
308 self.dbms_helper = dbms_helper |
308 self.dbms_helper = dbms_helper |
309 self.dbencoding = dbencoding |
309 self.dbencoding = dbencoding |
310 self.keyword_map = {'NOW' : self.dbms_helper.sql_current_timestamp, |
310 self.keyword_map = {'NOW' : self.dbms_helper.sql_current_timestamp, |
311 'TODAY': self.dbms_helper.sql_current_date, |
311 'TODAY': self.dbms_helper.sql_current_date, |
312 } |
312 } |
313 if not self.dbms_helper.union_parentheses_support: |
313 if not self.dbms_helper.union_parentheses_support: |
314 self.union_sql = self.noparen_union_sql |
314 self.union_sql = self.noparen_union_sql |
315 self._lock = threading.Lock() |
315 self._lock = threading.Lock() |
|
316 if attrmap is None: |
|
317 attrmap = {} |
|
318 self.attr_map = attrmap |
316 |
319 |
317 def generate(self, union, args=None, varmap=None): |
320 def generate(self, union, args=None, varmap=None): |
318 """return SQL queries and a variable dictionnary from a RQL syntax tree |
321 """return SQL queries and a variable dictionnary from a RQL syntax tree |
319 |
322 |
320 :partrqls: a list of couple (rqlst, solutions) |
323 :partrqls: a list of couple (rqlst, solutions) |
851 # generate unification expression |
854 # generate unification expression |
852 lhssql = self._inlined_var_sql(relation.children[0].variable, |
855 lhssql = self._inlined_var_sql(relation.children[0].variable, |
853 relation.r_type) |
856 relation.r_type) |
854 return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) |
857 return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) |
855 |
858 |
856 def _visit_attribute_relation(self, relation): |
859 def _visit_attribute_relation(self, rel): |
857 """generate SQL for an attribute relation""" |
860 """generate SQL for an attribute relation""" |
858 lhs, rhs = relation.get_parts() |
861 lhs, rhs = rel.get_parts() |
859 rhssql = rhs.accept(self) |
862 rhssql = rhs.accept(self) |
860 table = self._var_table(lhs.variable) |
863 table = self._var_table(lhs.variable) |
861 if table is None: |
864 if table is None: |
862 assert relation.r_type == 'eid' |
865 assert rel.r_type == 'eid' |
863 lhssql = lhs.accept(self) |
866 lhssql = lhs.accept(self) |
864 else: |
867 else: |
865 try: |
868 try: |
866 lhssql = self._varmap['%s.%s' % (lhs.name, relation.r_type)] |
869 lhssql = self._varmap['%s.%s' % (lhs.name, rel.r_type)] |
867 except KeyError: |
870 except KeyError: |
868 if relation.r_type == 'eid': |
871 mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type) |
|
872 if mapkey in self.attr_map: |
|
873 lhssql = self.attr_map[mapkey](self, lhs.variable, rel) |
|
874 elif rel.r_type == 'eid': |
869 lhssql = lhs.variable._q_sql |
875 lhssql = lhs.variable._q_sql |
870 else: |
876 else: |
871 lhssql = '%s.%s%s' % (table, SQL_PREFIX, relation.r_type) |
877 lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type) |
872 try: |
878 try: |
873 if relation._q_needcast == 'TODAY': |
879 if rel._q_needcast == 'TODAY': |
874 sql = 'DATE(%s)%s' % (lhssql, rhssql) |
880 sql = 'DATE(%s)%s' % (lhssql, rhssql) |
875 # XXX which cast function should be used |
881 # XXX which cast function should be used |
876 #elif relation._q_needcast == 'NOW': |
882 #elif rel._q_needcast == 'NOW': |
877 # sql = 'TIMESTAMP(%s)%s' % (lhssql, rhssql) |
883 # sql = 'TIMESTAMP(%s)%s' % (lhssql, rhssql) |
878 else: |
884 else: |
879 sql = '%s%s' % (lhssql, rhssql) |
885 sql = '%s%s' % (lhssql, rhssql) |
880 except AttributeError: |
886 except AttributeError: |
881 sql = '%s%s' % (lhssql, rhssql) |
887 sql = '%s%s' % (lhssql, rhssql) |
882 if lhs.variable.stinfo['optrelations']: |
888 if lhs.variable.stinfo['optrelations']: |
883 self.add_outer_join_condition(lhs.variable, table, sql) |
889 self.add_outer_join_condition(lhs.variable, table, sql) |
884 else: |
890 else: |
885 return sql |
891 return sql |
886 |
892 |
887 def _visit_has_text_relation(self, relation): |
893 def _visit_has_text_relation(self, rel): |
888 """generate SQL for a has_text relation""" |
894 """generate SQL for a has_text relation""" |
889 lhs, rhs = relation.get_parts() |
895 lhs, rhs = rel.get_parts() |
890 const = rhs.children[0] |
896 const = rhs.children[0] |
891 alias = self._fti_table(relation) |
897 alias = self._fti_table(rel) |
892 jointo = lhs.accept(self) |
898 jointo = lhs.accept(self) |
893 restriction = '' |
899 restriction = '' |
894 lhsvar = lhs.variable |
900 lhsvar = lhs.variable |
895 me_is_principal = lhsvar.stinfo.get('principal') is relation |
901 me_is_principal = lhsvar.stinfo.get('principal') is rel |
896 if me_is_principal: |
902 if me_is_principal: |
897 if not lhsvar.stinfo['typerels']: |
903 if not lhsvar.stinfo['typerels']: |
898 # the variable is using the fti table, no join needed |
904 # the variable is using the fti table, no join needed |
899 jointo = None |
905 jointo = None |
900 elif not lhsvar.name in self._varmap: |
906 elif not lhsvar.name in self._varmap: |
906 if not lhsvar._q_invariant or len(lhsvar.stinfo['possibletypes']) == 1: |
912 if not lhsvar._q_invariant or len(lhsvar.stinfo['possibletypes']) == 1: |
907 restriction = " AND %s.type='%s'" % (ealias, self._state.solution[lhs.name]) |
913 restriction = " AND %s.type='%s'" % (ealias, self._state.solution[lhs.name]) |
908 else: |
914 else: |
909 etypes = ','.join("'%s'" % etype for etype in lhsvar.stinfo['possibletypes']) |
915 etypes = ','.join("'%s'" % etype for etype in lhsvar.stinfo['possibletypes']) |
910 restriction = " AND %s.type IN (%s)" % (ealias, etypes) |
916 restriction = " AND %s.type IN (%s)" % (ealias, etypes) |
911 if isinstance(relation.parent, Not): |
917 if isinstance(rel.parent, Not): |
912 self._state.done.add(relation.parent) |
918 self._state.done.add(rel.parent) |
913 not_ = True |
919 not_ = True |
914 else: |
920 else: |
915 not_ = False |
921 not_ = False |
916 return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args), |
922 return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args), |
917 jointo, not_) + restriction |
923 jointo, not_) + restriction |
1115 if rel.r_type == 'eid': |
1121 if rel.r_type == 'eid': |
1116 return linkedvar.accept(self) |
1122 return linkedvar.accept(self) |
1117 if isinstance(linkedvar, ColumnAlias): |
1123 if isinstance(linkedvar, ColumnAlias): |
1118 raise BadRQLQuery('variable %s should be selected by the subquery' |
1124 raise BadRQLQuery('variable %s should be selected by the subquery' |
1119 % variable.name) |
1125 % variable.name) |
|
1126 mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type) |
|
1127 if mapkey in self.attr_map: |
|
1128 return self.attr_map[mapkey](self, linkedvar, rel) |
1120 try: |
1129 try: |
1121 sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] |
1130 sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] |
1122 except KeyError: |
1131 except KeyError: |
1123 linkedvar.accept(self) |
1132 linkedvar.accept(self) |
1124 sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type) |
1133 sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type) |