15 |
15 |
16 2. Any X WHERE X nonfinal1 Y, Y nonfinal2 Z |
16 2. Any X WHERE X nonfinal1 Y, Y nonfinal2 Z |
17 |
17 |
18 -> direct join between nonfinal1 and nonfinal2, whatever X,Y, Z (unless |
18 -> direct join between nonfinal1 and nonfinal2, whatever X,Y, Z (unless |
19 inlined...) |
19 inlined...) |
20 |
20 |
21 NOT IMPLEMENTED (and quite hard to implement) |
21 NOT IMPLEMENTED (and quite hard to implement) |
22 |
22 |
23 Potential optimization information is collected by the querier, sql generation |
23 Potential optimization information is collected by the querier, sql generation |
24 is done according to this information |
24 is done according to this information |
25 |
25 |
39 |
39 |
40 from cubicweb import server |
40 from cubicweb import server |
41 from cubicweb.server.sqlutils import SQL_PREFIX |
41 from cubicweb.server.sqlutils import SQL_PREFIX |
42 from cubicweb.server.utils import cleanup_solutions |
42 from cubicweb.server.utils import cleanup_solutions |
43 |
43 |
44 def _new_var(select, varname): |
44 def _new_var(select, varname): |
45 newvar = select.get_variable(varname) |
45 newvar = select.get_variable(varname) |
46 if not 'relations' in newvar.stinfo: |
46 if not 'relations' in newvar.stinfo: |
47 # not yet initialized |
47 # not yet initialized |
48 newvar.prepare_annotation() |
48 newvar.prepare_annotation() |
49 newvar.stinfo['scope'] = select |
49 newvar.stinfo['scope'] = select |
59 newivar = _new_var(newselect, vref.name) |
59 newivar = _new_var(newselect, vref.name) |
60 newselect.selection.append(VariableRef(newivar)) |
60 newselect.selection.append(VariableRef(newivar)) |
61 _fill_to_wrap_rel(vref.variable, newselect, towrap, schema) |
61 _fill_to_wrap_rel(vref.variable, newselect, towrap, schema) |
62 elif rschema.is_final(): |
62 elif rschema.is_final(): |
63 towrap.add( (var, rel) ) |
63 towrap.add( (var, rel) ) |
64 |
64 |
65 def rewrite_unstable_outer_join(select, solutions, unstable, schema): |
65 def rewrite_unstable_outer_join(select, solutions, unstable, schema): |
66 """if some optional variables are unstable, they should be selected in a |
66 """if some optional variables are unstable, they should be selected in a |
67 subquery. This function check this and rewrite the rql syntax tree if |
67 subquery. This function check this and rewrite the rql syntax tree if |
68 necessary (in place). Return a boolean telling if the tree has been modified |
68 necessary (in place). Return a boolean telling if the tree has been modified |
69 """ |
69 """ |
102 for vref in newrel.children[1].iget_nodes(VariableRef): |
102 for vref in newrel.children[1].iget_nodes(VariableRef): |
103 var = vref.variable |
103 var = vref.variable |
104 var.stinfo['relations'].add(newrel) |
104 var.stinfo['relations'].add(newrel) |
105 var.stinfo['rhsrelations'].add(newrel) |
105 var.stinfo['rhsrelations'].add(newrel) |
106 if rel.optional in ('right', 'both'): |
106 if rel.optional in ('right', 'both'): |
107 var.stinfo['optrelations'].add(newrel) |
107 var.stinfo['optrelations'].add(newrel) |
108 # extract subquery solutions |
108 # extract subquery solutions |
109 solutions = [sol.copy() for sol in solutions] |
109 solutions = [sol.copy() for sol in solutions] |
110 cleanup_solutions(newselect, solutions) |
110 cleanup_solutions(newselect, solutions) |
111 newselect.set_possible_types(solutions) |
111 newselect.set_possible_types(solutions) |
112 # full sub-query |
112 # full sub-query |
203 append(term) |
203 append(term) |
204 if groups: |
204 if groups: |
205 for vref in term.iget_nodes(VariableRef): |
205 for vref in term.iget_nodes(VariableRef): |
206 if not vref in groups: |
206 if not vref in groups: |
207 groups.append(vref) |
207 groups.append(vref) |
208 |
208 |
209 def fix_selection(rqlst, selectedidx, needwrap, sorts, groups, having): |
209 def fix_selection(rqlst, selectedidx, needwrap, sorts, groups, having): |
210 if sorts: |
210 if sorts: |
211 sort_term_selection(sorts, selectedidx, rqlst, not needwrap and groups) |
211 sort_term_selection(sorts, selectedidx, rqlst, not needwrap and groups) |
212 if needwrap: |
212 if needwrap: |
213 if groups: |
213 if groups: |
228 class StateInfo(object): |
228 class StateInfo(object): |
229 def __init__(self, existssols, unstablevars): |
229 def __init__(self, existssols, unstablevars): |
230 self.existssols = existssols |
230 self.existssols = existssols |
231 self.unstablevars = unstablevars |
231 self.unstablevars = unstablevars |
232 self.subtables = {} |
232 self.subtables = {} |
233 |
233 |
234 def reset(self, solution): |
234 def reset(self, solution): |
235 """reset some visit variables""" |
235 """reset some visit variables""" |
236 self.solution = solution |
236 self.solution = solution |
237 self.count = 0 |
237 self.count = 0 |
238 self.done = set() |
238 self.done = set() |
244 self.duplicate_switches = [] |
244 self.duplicate_switches = [] |
245 self.attr_vars = {} |
245 self.attr_vars = {} |
246 self.aliases = {} |
246 self.aliases = {} |
247 self.restrictions = [] |
247 self.restrictions = [] |
248 self._restr_stack = [] |
248 self._restr_stack = [] |
249 |
249 |
250 def add_restriction(self, restr): |
250 def add_restriction(self, restr): |
251 if restr: |
251 if restr: |
252 self.restrictions.append(restr) |
252 self.restrictions.append(restr) |
253 |
253 |
254 def iter_exists_sols(self, exists): |
254 def iter_exists_sols(self, exists): |
255 if not exists in self.existssols: |
255 if not exists in self.existssols: |
256 yield 1 |
256 yield 1 |
257 return |
257 return |
258 thisexistssols, thisexistsvars = self.existssols[exists] |
258 thisexistssols, thisexistsvars = self.existssols[exists] |
284 |
284 |
285 def pop_scope(self): |
285 def pop_scope(self): |
286 restrictions = self.restrictions |
286 restrictions = self.restrictions |
287 self.restrictions = self._restr_stack.pop() |
287 self.restrictions = self._restr_stack.pop() |
288 return restrictions, self.actual_tables.pop() |
288 return restrictions, self.actual_tables.pop() |
289 |
289 |
290 |
290 |
291 class SQLGenerator(object): |
291 class SQLGenerator(object): |
292 """ |
292 """ |
293 generation of SQL from the fully expanded RQL syntax tree |
293 generation of SQL from the fully expanded RQL syntax tree |
294 SQL is designed to be used with a CubicWeb SQL schema |
294 SQL is designed to be used with a CubicWeb SQL schema |
295 |
295 |
296 Groups and sort are not handled here since they should not be handled at |
296 Groups and sort are not handled here since they should not be handled at |
297 this level (see cubicweb.server.querier) |
297 this level (see cubicweb.server.querier) |
298 |
298 |
299 we should not have errors here ! |
299 we should not have errors here ! |
300 |
300 |
301 WARNING: a CubicWebSQLGenerator instance is not thread safe, but generate is |
301 WARNING: a CubicWebSQLGenerator instance is not thread safe, but generate is |
302 protected by a lock |
302 protected by a lock |
303 """ |
303 """ |
304 |
304 |
305 def __init__(self, schema, dbms_helper, dbencoding='UTF-8'): |
305 def __init__(self, schema, dbms_helper, dbencoding='UTF-8'): |
306 self.schema = schema |
306 self.schema = schema |
307 self.dbms_helper = dbms_helper |
307 self.dbms_helper = dbms_helper |
308 self.dbencoding = dbencoding |
308 self.dbencoding = dbencoding |
309 self.keyword_map = {'NOW' : self.dbms_helper.sql_current_timestamp, |
309 self.keyword_map = {'NOW' : self.dbms_helper.sql_current_timestamp, |
310 'TODAY': self.dbms_helper.sql_current_date, |
310 'TODAY': self.dbms_helper.sql_current_date, |
311 } |
311 } |
312 if not self.dbms_helper.union_parentheses_support: |
312 if not self.dbms_helper.union_parentheses_support: |
313 self.union_sql = self.noparen_union_sql |
313 self.union_sql = self.noparen_union_sql |
314 self._lock = threading.Lock() |
314 self._lock = threading.Lock() |
315 |
315 |
316 def generate(self, union, args=None, varmap=None): |
316 def generate(self, union, args=None, varmap=None): |
317 """return SQL queries and a variable dictionnary from a RQL syntax tree |
317 """return SQL queries and a variable dictionnary from a RQL syntax tree |
318 |
318 |
319 :partrqls: a list of couple (rqlst, solutions) |
319 :partrqls: a list of couple (rqlst, solutions) |
320 :args: optional dictionary with values of substitutions used in the query |
320 :args: optional dictionary with values of substitutions used in the query |
353 # the subquery) but will work in most case |
353 # the subquery) but will work in most case |
354 # see http://www.sqlite.org/cvstrac/tktview?tn=3074 |
354 # see http://www.sqlite.org/cvstrac/tktview?tn=3074 |
355 sqls = (self.select_sql(select, needalias) |
355 sqls = (self.select_sql(select, needalias) |
356 for i, select in enumerate(union.children)) |
356 for i, select in enumerate(union.children)) |
357 return '\nUNION ALL\n'.join(sqls) |
357 return '\nUNION ALL\n'.join(sqls) |
358 |
358 |
359 def select_sql(self, select, needalias=False): |
359 def select_sql(self, select, needalias=False): |
360 """return SQL queries and a variable dictionnary from a RQL syntax tree |
360 """return SQL queries and a variable dictionnary from a RQL syntax tree |
361 |
361 |
362 :select: a selection statement of the syntax tree (`rql.stmts.Select`) |
362 :select: a selection statement of the syntax tree (`rql.stmts.Select`) |
363 :solution: a dictionnary containing variables binding. |
363 :solution: a dictionnary containing variables binding. |
386 selectsortterms = True |
386 selectsortterms = True |
387 # and if select is using group by or aggregat, a wrapping |
387 # and if select is using group by or aggregat, a wrapping |
388 # query will be necessary |
388 # query will be necessary |
389 if groups or select.has_aggregat: |
389 if groups or select.has_aggregat: |
390 select.select_only_variables() |
390 select.select_only_variables() |
391 needwrap = True |
391 needwrap = True |
392 else: |
392 else: |
393 existssols, unstable = {}, () |
393 existssols, unstable = {}, () |
394 state = StateInfo(existssols, unstable) |
394 state = StateInfo(existssols, unstable) |
395 # treat subqueries |
395 # treat subqueries |
396 self._subqueries_sql(select, state) |
396 self._subqueries_sql(select, state) |
439 sql += '\nGROUP BY %s' % groups |
439 sql += '\nGROUP BY %s' % groups |
440 if having: |
440 if having: |
441 sql += '\nHAVING %s' % having |
441 sql += '\nHAVING %s' % having |
442 # sort |
442 # sort |
443 if sorts: |
443 if sorts: |
444 sql += '\nORDER BY %s' % ','.join(self._sortterm_sql(sortterm, |
444 sql += '\nORDER BY %s' % ','.join(self._sortterm_sql(sortterm, |
445 fselectidx) |
445 fselectidx) |
446 for sortterm in sorts) |
446 for sortterm in sorts) |
447 if fneedwrap: |
447 if fneedwrap: |
448 selection = ['T1.C%s' % i for i in xrange(len(origselection))] |
448 selection = ['T1.C%s' % i for i in xrange(len(origselection))] |
449 sql = 'SELECT %s FROM (%s) AS T1' % (','.join(selection), sql) |
449 sql = 'SELECT %s FROM (%s) AS T1' % (','.join(selection), sql) |
495 return '\nINTERSECT ALL\n'.join(sqls) |
495 return '\nINTERSECT ALL\n'.join(sqls) |
496 elif distinct: |
496 elif distinct: |
497 return '\nUNION\n'.join(sqls) |
497 return '\nUNION\n'.join(sqls) |
498 else: |
498 else: |
499 return '\nUNION ALL\n'.join(sqls) |
499 return '\nUNION ALL\n'.join(sqls) |
500 |
500 |
501 def _selection_sql(self, selected, distinct, needaliasing=False): |
501 def _selection_sql(self, selected, distinct, needaliasing=False): |
502 clause = [] |
502 clause = [] |
503 for term in selected: |
503 for term in selected: |
504 sql = term.accept(self) |
504 sql = term.accept(self) |
505 if needaliasing: |
505 if needaliasing: |
544 if res: |
544 if res: |
545 if len(res) > 1: |
545 if len(res) > 1: |
546 return '(%s)' % ' OR '.join(res) |
546 return '(%s)' % ' OR '.join(res) |
547 return res[0] |
547 return res[0] |
548 return '' |
548 return '' |
549 |
549 |
550 def visit_not(self, node): |
550 def visit_not(self, node): |
551 self._state.push_scope() |
551 self._state.push_scope() |
552 csql = node.children[0].accept(self) |
552 csql = node.children[0].accept(self) |
553 sqls, tables = self._state.pop_scope() |
553 sqls, tables = self._state.pop_scope() |
554 if node in self._state.done or not csql: |
554 if node in self._state.done or not csql: |
579 if sql: |
579 if sql: |
580 sqls.append(sql) |
580 sqls.append(sql) |
581 if not sqls: |
581 if not sqls: |
582 return '' |
582 return '' |
583 return 'EXISTS(%s)' % ' UNION '.join(sqls) |
583 return 'EXISTS(%s)' % ' UNION '.join(sqls) |
584 |
584 |
585 def _visit_exists(self, exists): |
585 def _visit_exists(self, exists): |
586 self._state.push_scope() |
586 self._state.push_scope() |
587 restriction = exists.children[0].accept(self) |
587 restriction = exists.children[0].accept(self) |
588 restrictions, tables = self._state.pop_scope() |
588 restrictions, tables = self._state.pop_scope() |
589 if restriction: |
589 if restriction: |
591 restriction = ' AND '.join(restrictions) |
591 restriction = ' AND '.join(restrictions) |
592 if not restriction: |
592 if not restriction: |
593 return '' |
593 return '' |
594 if not tables: |
594 if not tables: |
595 # XXX could leave surrounding EXISTS() in this case no? |
595 # XXX could leave surrounding EXISTS() in this case no? |
596 sql = 'SELECT 1 WHERE %s' % restriction |
596 sql = 'SELECT 1 WHERE %s' % restriction |
597 else: |
597 else: |
598 sql = 'SELECT 1 FROM %s WHERE %s' % (', '.join(tables), restriction) |
598 sql = 'SELECT 1 FROM %s WHERE %s' % (', '.join(tables), restriction) |
599 return sql |
599 return sql |
600 |
600 |
601 |
601 |
602 def visit_relation(self, relation): |
602 def visit_relation(self, relation): |
603 """generate SQL for a relation""" |
603 """generate SQL for a relation""" |
604 rtype = relation.r_type |
604 rtype = relation.r_type |
605 # don't care of type constraint statement (i.e. relation_type = 'is') |
605 # don't care of type constraint statement (i.e. relation_type = 'is') |
606 if relation.is_types_restriction(): |
606 if relation.is_types_restriction(): |
689 termsql = termvar.accept(self) |
689 termsql = termvar.accept(self) |
690 yield '%s.%s=%s' % (rid, relfield, termsql) |
690 yield '%s.%s=%s' % (rid, relfield, termsql) |
691 extrajoin = self._extra_join_sql(relation, '%s.%s' % (rid, relfield), termvar) |
691 extrajoin = self._extra_join_sql(relation, '%s.%s' % (rid, relfield), termvar) |
692 if extrajoin: |
692 if extrajoin: |
693 yield extrajoin |
693 yield extrajoin |
694 |
694 |
695 def _visit_relation(self, relation, rschema): |
695 def _visit_relation(self, relation, rschema): |
696 """generate SQL for a relation |
696 """generate SQL for a relation |
697 |
697 |
698 implements optimization 1. |
698 implements optimization 1. |
699 """ |
699 """ |
716 |
716 |
717 def _visit_outer_join_relation(self, relation, rschema): |
717 def _visit_outer_join_relation(self, relation, rschema): |
718 """ |
718 """ |
719 left outer join syntax (optional=='right'): |
719 left outer join syntax (optional=='right'): |
720 X relation Y? |
720 X relation Y? |
721 |
721 |
722 right outer join syntax (optional=='left'): |
722 right outer join syntax (optional=='left'): |
723 X? relation Y |
723 X? relation Y |
724 |
724 |
725 full outer join syntaxes (optional=='both'): |
725 full outer join syntaxes (optional=='both'): |
726 X? relation Y? |
726 X? relation Y? |
727 |
727 |
728 if relation is inlined: |
728 if relation is inlined: |
729 if it's a left outer join: |
729 if it's a left outer join: |
832 # at least one variable is already in attr_vars, this means we have to |
832 # at least one variable is already in attr_vars, this means we have to |
833 # generate unification expression |
833 # generate unification expression |
834 lhssql = self._inlined_var_sql(relation.children[0].variable, |
834 lhssql = self._inlined_var_sql(relation.children[0].variable, |
835 relation.r_type) |
835 relation.r_type) |
836 return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) |
836 return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) |
837 |
837 |
838 def _visit_attribute_relation(self, relation): |
838 def _visit_attribute_relation(self, relation): |
839 """generate SQL for an attribute relation""" |
839 """generate SQL for an attribute relation""" |
840 lhs, rhs = relation.get_parts() |
840 lhs, rhs = relation.get_parts() |
841 rhssql = rhs.accept(self) |
841 rhssql = rhs.accept(self) |
842 table = self._var_table(lhs.variable) |
842 table = self._var_table(lhs.variable) |
895 not_ = True |
895 not_ = True |
896 else: |
896 else: |
897 not_ = False |
897 not_ = False |
898 return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args), |
898 return self.dbms_helper.fti_restriction_sql(alias, const.eval(self._args), |
899 jointo, not_) + restriction |
899 jointo, not_) + restriction |
900 |
900 |
901 def visit_comparison(self, cmp, contextrels=None): |
901 def visit_comparison(self, cmp, contextrels=None): |
902 """generate SQL for a comparaison""" |
902 """generate SQL for a comparaison""" |
903 if len(cmp.children) == 2: |
903 if len(cmp.children) == 2: |
904 lhs, rhs = cmp.children |
904 lhs, rhs = cmp.children |
905 else: |
905 else: |
916 operator = ' ' |
916 operator = ' ' |
917 if lhs is None: |
917 if lhs is None: |
918 return '%s%s'% (operator, rhs.accept(self, contextrels)) |
918 return '%s%s'% (operator, rhs.accept(self, contextrels)) |
919 return '%s%s%s'% (lhs.accept(self, contextrels), operator, |
919 return '%s%s%s'% (lhs.accept(self, contextrels), operator, |
920 rhs.accept(self, contextrels)) |
920 rhs.accept(self, contextrels)) |
921 |
921 |
922 def visit_mathexpression(self, mexpr, contextrels=None): |
922 def visit_mathexpression(self, mexpr, contextrels=None): |
923 """generate SQL for a mathematic expression""" |
923 """generate SQL for a mathematic expression""" |
924 lhs, rhs = mexpr.get_parts() |
924 lhs, rhs = mexpr.get_parts() |
925 # check for string concatenation |
925 # check for string concatenation |
926 operator = mexpr.operator |
926 operator = mexpr.operator |
929 operator = '||' |
929 operator = '||' |
930 except CoercionError: |
930 except CoercionError: |
931 pass |
931 pass |
932 return '(%s %s %s)'% (lhs.accept(self, contextrels), operator, |
932 return '(%s %s %s)'% (lhs.accept(self, contextrels), operator, |
933 rhs.accept(self, contextrels)) |
933 rhs.accept(self, contextrels)) |
934 |
934 |
935 def visit_function(self, func, contextrels=None): |
935 def visit_function(self, func, contextrels=None): |
936 """generate SQL name for a function""" |
936 """generate SQL name for a function""" |
937 # function_description will check function is supported by the backend |
937 # function_description will check function is supported by the backend |
938 sqlname = self.dbms_helper.func_sqlname(func.name) |
938 sqlname = self.dbms_helper.func_sqlname(func.name) |
939 return '%s(%s)' % (sqlname, ', '.join(c.accept(self, contextrels) |
939 return '%s(%s)' % (sqlname, ', '.join(c.accept(self, contextrels) |
940 for c in func.children)) |
940 for c in func.children)) |
941 |
941 |
942 def visit_constant(self, constant, contextrels=None): |
942 def visit_constant(self, constant, contextrels=None): |
943 """generate SQL name for a constant""" |
943 """generate SQL name for a constant""" |
961 _id = str(id(constant)).replace('-', '', 1) |
961 _id = str(id(constant)).replace('-', '', 1) |
962 if isinstance(value, unicode): |
962 if isinstance(value, unicode): |
963 value = value.encode(self.dbencoding) |
963 value = value.encode(self.dbencoding) |
964 self._query_attrs[_id] = value |
964 self._query_attrs[_id] = value |
965 return '%%(%s)s' % _id |
965 return '%%(%s)s' % _id |
966 |
966 |
967 def visit_variableref(self, variableref, contextrels=None): |
967 def visit_variableref(self, variableref, contextrels=None): |
968 """get the sql name for a variable reference""" |
968 """get the sql name for a variable reference""" |
969 # use accept, .variable may be a variable or a columnalias |
969 # use accept, .variable may be a variable or a columnalias |
970 return variableref.variable.accept(self, contextrels) |
970 return variableref.variable.accept(self, contextrels) |
971 |
971 |
977 colalias._q_sqltable = table |
977 colalias._q_sqltable = table |
978 colalias._q_sql = sql |
978 colalias._q_sql = sql |
979 self.add_table(table) |
979 self.add_table(table) |
980 return sql |
980 return sql |
981 return colalias._q_sql |
981 return colalias._q_sql |
982 |
982 |
983 def visit_variable(self, variable, contextrels=None): |
983 def visit_variable(self, variable, contextrels=None): |
984 """get the table name and sql string for a variable""" |
984 """get the table name and sql string for a variable""" |
985 if contextrels is None and variable.name in self._state.done: |
985 if contextrels is None and variable.name in self._state.done: |
986 if self._in_wrapping_query: |
986 if self._in_wrapping_query: |
987 return 'T1.%s' % self._state.aliases[variable.name] |
987 return 'T1.%s' % self._state.aliases[variable.name] |
988 return variable._q_sql |
988 return variable._q_sql |
989 self._state.done.add(variable.name) |
989 self._state.done.add(variable.name) |
990 vtablename = None |
990 vtablename = None |
991 if contextrels is None and variable.name in self._varmap: |
991 if contextrels is None and variable.name in self._varmap: |
992 sql, vtablename = self._var_info(variable) |
992 sql, vtablename = self._var_info(variable) |
993 elif variable.stinfo['attrvar']: |
993 elif variable.stinfo['attrvar']: |
994 # attribute variable (systematically used in rhs of final |
994 # attribute variable (systematically used in rhs of final |
995 # relation(s)), get table name and sql from any rhs relation |
995 # relation(s)), get table name and sql from any rhs relation |
996 sql = self._linked_var_sql(variable, contextrels) |
996 sql = self._linked_var_sql(variable, contextrels) |
997 elif variable._q_invariant: |
997 elif variable._q_invariant: |
1041 except KeyError: |
1041 except KeyError: |
1042 # no principal defined, relation is necessarily the principal and |
1042 # no principal defined, relation is necessarily the principal and |
1043 # so nothing to return here |
1043 # so nothing to return here |
1044 pass |
1044 pass |
1045 return '' |
1045 return '' |
1046 |
1046 |
1047 def _var_info(self, var): |
1047 def _var_info(self, var): |
1048 # if current var or one of its attribute is selected , it *must* |
1048 # if current var or one of its attribute is selected , it *must* |
1049 # appear in the toplevel's FROM even if we're currently visiting |
1049 # appear in the toplevel's FROM even if we're currently visiting |
1050 # a EXISTS node |
1050 # a EXISTS node |
1051 if var.sqlscope is var.stmt: |
1051 if var.sqlscope is var.stmt: |
1065 raise BadRQLQuery(var.stmt.root) |
1065 raise BadRQLQuery(var.stmt.root) |
1066 table = var.name |
1066 table = var.name |
1067 sql = '%s.%seid' % (table, SQL_PREFIX) |
1067 sql = '%s.%seid' % (table, SQL_PREFIX) |
1068 self.add_table('%s%s AS %s' % (SQL_PREFIX, etype, table), table, scope=scope) |
1068 self.add_table('%s%s AS %s' % (SQL_PREFIX, etype, table), table, scope=scope) |
1069 return sql, table |
1069 return sql, table |
1070 |
1070 |
1071 def _inlined_var_sql(self, var, rtype): |
1071 def _inlined_var_sql(self, var, rtype): |
1072 try: |
1072 try: |
1073 sql = self._varmap['%s.%s' % (var.name, rtype)] |
1073 sql = self._varmap['%s.%s' % (var.name, rtype)] |
1074 scope = var.sqlscope is var.stmt and 0 or -1 |
1074 scope = var.sqlscope is var.stmt and 0 or -1 |
1075 self.add_table(sql.split('.', 1)[0], scope=scope) |
1075 self.add_table(sql.split('.', 1)[0], scope=scope) |
1076 except KeyError: |
1076 except KeyError: |
1077 sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype) |
1077 sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype) |
1078 #self._state.done.add(var.name) |
1078 #self._state.done.add(var.name) |
1079 return sql |
1079 return sql |
1080 |
1080 |
1081 def _linked_var_sql(self, variable, contextrels=None): |
1081 def _linked_var_sql(self, variable, contextrels=None): |
1082 if contextrels is None: |
1082 if contextrels is None: |
1083 try: |
1083 try: |
1084 return self._varmap[variable.name] |
1084 return self._varmap[variable.name] |
1085 except KeyError: |
1085 except KeyError: |
1086 pass |
1086 pass |
1087 rel = (contextrels and contextrels.get(variable.name) or |
1087 rel = (contextrels and contextrels.get(variable.name) or |
1088 variable.stinfo.get('principal') or |
1088 variable.stinfo.get('principal') or |
1089 iter(variable.stinfo['rhsrelations']).next()) |
1089 iter(variable.stinfo['rhsrelations']).next()) |
1090 linkedvar = rel.children[0].variable |
1090 linkedvar = rel.children[0].variable |
1091 if rel.r_type == 'eid': |
1091 if rel.r_type == 'eid': |
1092 return linkedvar.accept(self) |
1092 return linkedvar.accept(self) |
1094 raise BadRQLQuery('variable %s should be selected by the subquery' |
1094 raise BadRQLQuery('variable %s should be selected by the subquery' |
1095 % variable.name) |
1095 % variable.name) |
1096 try: |
1096 try: |
1097 sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] |
1097 sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] |
1098 except KeyError: |
1098 except KeyError: |
1099 linkedvar.accept(self) |
1099 linkedvar.accept(self) |
1100 sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type) |
1100 sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type) |
1101 return sql |
1101 return sql |
1102 |
1102 |
1103 # tables handling ######################################################### |
1103 # tables handling ######################################################### |
1104 |
1104 |
1105 def alias_and_add_table(self, tablename): |
1105 def alias_and_add_table(self, tablename): |
1106 alias = '%s%s' % (tablename, self._state.count) |
1106 alias = '%s%s' % (tablename, self._state.count) |
1107 self._state.count += 1 |
1107 self._state.count += 1 |
1108 self.add_table('%s AS %s' % (tablename, alias), alias) |
1108 self.add_table('%s AS %s' % (tablename, alias), alias) |
1109 return alias |
1109 return alias |
1110 |
1110 |
1111 def add_table(self, table, key=None, scope=-1): |
1111 def add_table(self, table, key=None, scope=-1): |
1112 if key is None: |
1112 if key is None: |
1113 key = table |
1113 key = table |
1114 if key in self._state.tables: |
1114 if key in self._state.tables: |
1115 return |
1115 return |
1116 self._state.tables[key] = (len(self._state.actual_tables) - 1, table) |
1116 self._state.tables[key] = (len(self._state.actual_tables) - 1, table) |
1117 self._state.actual_tables[scope].append(table) |
1117 self._state.actual_tables[scope].append(table) |
1118 |
1118 |
1119 def replace_tables_by_outer_join(self, substitute, lefttable, *tables): |
1119 def replace_tables_by_outer_join(self, substitute, lefttable, *tables): |
1120 for table in tables: |
1120 for table in tables: |
1121 try: |
1121 try: |
1122 scope, alias = self._state.tables[table] |
1122 scope, alias = self._state.tables[table] |
1123 self._state.actual_tables[scope].remove(alias) |
1123 self._state.actual_tables[scope].remove(alias) |
1158 # by <oldalias> may not be reused here, though their associated value |
1158 # by <oldalias> may not be reused here, though their associated value |
1159 # in the outer_tables dict has to be updated as well |
1159 # in the outer_tables dict has to be updated as well |
1160 for table, outerexpr in self._state.outer_tables.iteritems(): |
1160 for table, outerexpr in self._state.outer_tables.iteritems(): |
1161 if outerexpr == oldalias: |
1161 if outerexpr == oldalias: |
1162 self._state.outer_tables[table] = newalias |
1162 self._state.outer_tables[table] = newalias |
1163 self._state.outer_tables[table] = newalias |
1163 self._state.outer_tables[table] = newalias |
1164 |
1164 |
1165 def _var_table(self, var): |
1165 def _var_table(self, var): |
1166 var.accept(self)#.visit_variable(var) |
1166 var.accept(self)#.visit_variable(var) |
1167 return var._q_sqltable |
1167 return var._q_sqltable |
1168 |
1168 |
1169 def _relation_table(self, relation): |
1169 def _relation_table(self, relation): |
1171 if relation in self._state.done: |
1171 if relation in self._state.done: |
1172 return relation._q_sqltable |
1172 return relation._q_sqltable |
1173 assert not self.schema.rschema(relation.r_type).is_final(), relation.r_type |
1173 assert not self.schema.rschema(relation.r_type).is_final(), relation.r_type |
1174 rid = 'rel_%s%s' % (relation.r_type, self._state.count) |
1174 rid = 'rel_%s%s' % (relation.r_type, self._state.count) |
1175 # relation's table is belonging to the root scope if it is the principal |
1175 # relation's table is belonging to the root scope if it is the principal |
1176 # table of one of it's variable and if that variable belong's to parent |
1176 # table of one of it's variable and if that variable belong's to parent |
1177 # scope |
1177 # scope |
1178 for varref in relation.iget_nodes(VariableRef): |
1178 for varref in relation.iget_nodes(VariableRef): |
1179 var = varref.variable |
1179 var = varref.variable |
1180 if isinstance(var, ColumnAlias): |
1180 if isinstance(var, ColumnAlias): |
1181 scope = 0 |
1181 scope = 0 |
1190 self._state.count += 1 |
1190 self._state.count += 1 |
1191 self.add_table('%s_relation AS %s' % (relation.r_type, rid), rid, scope=scope) |
1191 self.add_table('%s_relation AS %s' % (relation.r_type, rid), rid, scope=scope) |
1192 relation._q_sqltable = rid |
1192 relation._q_sqltable = rid |
1193 self._state.done.add(relation) |
1193 self._state.done.add(relation) |
1194 return rid |
1194 return rid |
1195 |
1195 |
1196 def _fti_table(self, relation): |
1196 def _fti_table(self, relation): |
1197 if relation in self._state.done: |
1197 if relation in self._state.done: |
1198 try: |
1198 try: |
1199 return relation._q_sqltable |
1199 return relation._q_sqltable |
1200 except AttributeError: |
1200 except AttributeError: |
1201 pass |
1201 pass |
1202 self._state.done.add(relation) |
1202 self._state.done.add(relation) |
1203 alias = self.alias_and_add_table(self.dbms_helper.fti_table) |
1203 alias = self.alias_and_add_table(self.dbms_helper.fti_table) |
1204 relation._q_sqltable = alias |
1204 relation._q_sqltable = alias |
1205 return alias |
1205 return alias |
1206 |
1206 |
1207 def _varmap_table_scope(self, select, table): |
1207 def _varmap_table_scope(self, select, table): |
1208 """since a varmap table may be used for multiple variable, its scope is |
1208 """since a varmap table may be used for multiple variable, its scope is |
1209 the most outer scope of each variables |
1209 the most outer scope of each variables |
1210 """ |
1210 """ |
1211 scope = -1 |
1211 scope = -1 |