42 |
42 |
43 cross RDMS note : read `Comparison of different SQL implementations`_ |
43 cross RDMS note : read `Comparison of different SQL implementations`_ |
44 by Troels Arvin. Features SQL ISO Standard, PG, mysql, Oracle, MS SQL, DB2 |
44 by Troels Arvin. Features SQL ISO Standard, PG, mysql, Oracle, MS SQL, DB2 |
45 and Informix. |
45 and Informix. |
46 |
46 |
47 .. _Comparison of different SQL implementations: http://www.troels.arvin.dk/db/rdbms |
47 .. _Comparison of different SQL implementations: http://www.troels.arvin.dk/db/rdbms |
48 |
48 |
49 |
49 |
50 """ |
50 """ |
51 __docformat__ = "restructuredtext en" |
51 __docformat__ = "restructuredtext en" |
52 |
52 |
110 continue |
110 continue |
111 modified = True |
111 modified = True |
112 unstable.remove(varname) |
112 unstable.remove(varname) |
113 torewrite.add(var) |
113 torewrite.add(var) |
114 newselect = Select() |
114 newselect = Select() |
115 newselect.need_distinct = newselect.need_intersect = False |
115 newselect.need_distinct = False |
116 myunion = Union() |
116 myunion = Union() |
117 myunion.append(newselect) |
117 myunion.append(newselect) |
118 # extract aliases / selection |
118 # extract aliases / selection |
119 newvar = _new_var(newselect, var.name) |
119 newvar = _new_var(newselect, var.name) |
120 newselect.selection = [VariableRef(newvar)] |
120 newselect.selection = [VariableRef(newvar)] |
314 |
314 |
315 |
315 |
316 # IGenerator implementation for RQL->SQL ####################################### |
316 # IGenerator implementation for RQL->SQL ####################################### |
317 |
317 |
318 class StateInfo(object): |
318 class StateInfo(object): |
319 def __init__(self, existssols, unstablevars): |
319 def __init__(self, select, existssols, unstablevars): |
320 self.existssols = existssols |
320 self.existssols = existssols |
321 self.unstablevars = unstablevars |
321 self.unstablevars = unstablevars |
322 self.subtables = {} |
322 self.subtables = {} |
323 self.needs_source_cb = None |
323 self.needs_source_cb = None |
324 self.subquery_source_cb = None |
324 self.subquery_source_cb = None |
325 self.source_cb_funcs = set() |
325 self.source_cb_funcs = set() |
|
326 self.scopes = {select: 0} |
|
327 self.scope_nodes = [] |
326 |
328 |
327 def reset(self, solution): |
329 def reset(self, solution): |
328 """reset some visit variables""" |
330 """reset some visit variables""" |
329 self.solution = solution |
331 self.solution = solution |
330 self.count = 0 |
332 self.count = 0 |
379 if rel in done: |
381 if rel in done: |
380 done.remove(rel) |
382 done.remove(rel) |
381 self.solution = origsol |
383 self.solution = origsol |
382 self.tables = origtables |
384 self.tables = origtables |
383 |
385 |
384 def push_scope(self): |
386 def push_scope(self, scope_node): |
|
387 self.scope_nodes.append(scope_node) |
|
388 self.scopes[scope_node] = len(self.actual_tables) |
385 self.actual_tables.append([]) |
389 self.actual_tables.append([]) |
386 self._restr_stack.append(self.restrictions) |
390 self._restr_stack.append(self.restrictions) |
387 self.restrictions = [] |
391 self.restrictions = [] |
388 |
392 |
389 def pop_scope(self): |
393 def pop_scope(self): |
|
394 del self.scopes[self.scope_nodes[-1]] |
|
395 self.scope_nodes.pop() |
390 restrictions = self.restrictions |
396 restrictions = self.restrictions |
391 self.restrictions = self._restr_stack.pop() |
397 self.restrictions = self._restr_stack.pop() |
392 return restrictions, self.actual_tables.pop() |
398 return restrictions, self.actual_tables.pop() |
393 |
399 |
394 |
400 |
440 self._lock.acquire() |
446 self._lock.acquire() |
441 self._args = args |
447 self._args = args |
442 self._varmap = varmap |
448 self._varmap = varmap |
443 self._query_attrs = {} |
449 self._query_attrs = {} |
444 self._state = None |
450 self._state = None |
445 self._not_scope_offset = 0 |
451 # self._not_scope_offset = 0 |
446 try: |
452 try: |
447 # union query for each rqlst / solution |
453 # union query for each rqlst / solution |
448 sql = self.union_sql(union) |
454 sql = self.union_sql(union) |
449 # we are done |
455 # we are done |
450 return sql, self._query_attrs, self._state.needs_source_cb |
456 return sql, self._query_attrs, self._state.needs_source_cb |
507 if groups or select.has_aggregat: |
513 if groups or select.has_aggregat: |
508 select.select_only_variables() |
514 select.select_only_variables() |
509 needwrap = True |
515 needwrap = True |
510 else: |
516 else: |
511 existssols, unstable = {}, () |
517 existssols, unstable = {}, () |
512 state = StateInfo(existssols, unstable) |
518 state = StateInfo(select, existssols, unstable) |
513 if self._state is not None: |
519 if self._state is not None: |
514 # state from a previous unioned select |
520 # state from a previous unioned select |
515 state.merge_source_cbs(self._state.needs_source_cb) |
521 state.merge_source_cbs(self._state.needs_source_cb) |
516 # treat subqueries |
522 # treat subqueries |
517 self._subqueries_sql(select, state) |
523 self._subqueries_sql(select, state) |
620 # sort for test predictability |
626 # sort for test predictability |
621 sql.insert(1, 'FROM %s' % ', '.join(sorted(tables))) |
627 sql.insert(1, 'FROM %s' % ', '.join(sorted(tables))) |
622 elif self._state.restrictions and self.dbhelper.needs_from_clause: |
628 elif self._state.restrictions and self.dbhelper.needs_from_clause: |
623 sql.insert(1, 'FROM (SELECT 1) AS _T') |
629 sql.insert(1, 'FROM (SELECT 1) AS _T') |
624 sqls.append('\n'.join(sql)) |
630 sqls.append('\n'.join(sql)) |
625 if select.need_intersect: |
631 if distinct: |
626 #if distinct or not self.dbhelper.intersect_all_support: |
|
627 return '\nINTERSECT\n'.join(sqls) |
|
628 #else: |
|
629 # return '\nINTERSECT ALL\n'.join(sqls) |
|
630 elif distinct: |
|
631 return '\nUNION\n'.join(sqls) |
632 return '\nUNION\n'.join(sqls) |
632 else: |
633 else: |
633 return '\nUNION ALL\n'.join(sqls) |
634 return '\nUNION ALL\n'.join(sqls) |
634 |
635 |
635 def _selection_sql(self, selected, distinct, needaliasing=False): |
636 def _selection_sql(self, selected, distinct, needaliasing=False): |
680 return '(%s)' % ' OR '.join(res) |
681 return '(%s)' % ' OR '.join(res) |
681 return res[0] |
682 return res[0] |
682 return '' |
683 return '' |
683 |
684 |
684 def visit_not(self, node): |
685 def visit_not(self, node): |
685 self._state.push_scope() |
|
686 if isinstance(node.children[0], Relation): |
|
687 self._not_scope_offset += 1 |
|
688 csql = node.children[0].accept(self) |
686 csql = node.children[0].accept(self) |
689 if isinstance(node.children[0], Relation): |
|
690 self._not_scope_offset -= 1 |
|
691 sqls, tables = self._state.pop_scope() |
|
692 if node in self._state.done or not csql: |
687 if node in self._state.done or not csql: |
693 # already processed or no sql generated by children |
688 # already processed or no sql generated by children |
694 self._state.actual_tables[-1] += tables |
|
695 self._state.restrictions += sqls |
|
696 return csql |
689 return csql |
697 if isinstance(node.children[0], Exists): |
690 return 'NOT (%s)' % csql |
698 assert not sqls, (sqls, str(node.stmt)) |
|
699 assert not tables, (tables, str(node.stmt)) |
|
700 return 'NOT %s' % csql |
|
701 sqls.append(csql) |
|
702 if tables: |
|
703 select = 'SELECT 1 FROM %s' % ','.join(tables) |
|
704 else: |
|
705 select = 'SELECT 1' |
|
706 if sqls: |
|
707 sql = 'NOT EXISTS(%s WHERE %s)' % (select, ' AND '.join(sqls)) |
|
708 else: |
|
709 sql = 'NOT EXISTS(%s)' % select |
|
710 return sql |
|
711 |
691 |
712 def visit_exists(self, exists): |
692 def visit_exists(self, exists): |
713 """generate SQL name for a exists subquery""" |
693 """generate SQL name for a exists subquery""" |
714 sqls = [] |
694 sqls = [] |
715 for dummy in self._state.iter_exists_sols(exists): |
695 for dummy in self._state.iter_exists_sols(exists): |
719 if not sqls: |
699 if not sqls: |
720 return '' |
700 return '' |
721 return 'EXISTS(%s)' % ' UNION '.join(sqls) |
701 return 'EXISTS(%s)' % ' UNION '.join(sqls) |
722 |
702 |
723 def _visit_exists(self, exists): |
703 def _visit_exists(self, exists): |
724 self._state.push_scope() |
704 self._state.push_scope(exists) |
725 restriction = exists.children[0].accept(self) |
705 restriction = exists.children[0].accept(self) |
726 restrictions, tables = self._state.pop_scope() |
706 restrictions, tables = self._state.pop_scope() |
727 if restriction: |
707 if restriction: |
728 restrictions.append(restriction) |
708 restrictions.append(restriction) |
729 restriction = ' AND '.join(restrictions) |
709 restriction = ' AND '.join(restrictions) |
760 # if variable(s) in the RHS |
740 # if variable(s) in the RHS |
761 sql = self._visit_var_attr_relation(relation, rhs_vars) |
741 sql = self._visit_var_attr_relation(relation, rhs_vars) |
762 else: |
742 else: |
763 # no variables in the RHS |
743 # no variables in the RHS |
764 sql = self._visit_attribute_relation(relation) |
744 sql = self._visit_attribute_relation(relation) |
765 if relation.neged(strict=True): |
|
766 self._state.done.add(relation.parent) |
|
767 sql = 'NOT (%s)' % sql |
|
768 else: |
745 else: |
769 if rtype == 'is' and rhs.operator == 'IS': |
746 if rtype == 'is' and rhs.operator == 'IS': |
770 # special case "C is NULL" |
747 # special case "C is NULL" |
771 if lhs.name in self._varmap: |
748 if lhs.name in self._varmap: |
772 lhssql = self._varmap[lhs.name] |
749 lhssql = self._varmap[lhs.name] |
831 implements optimization 1. |
808 implements optimization 1. |
832 """ |
809 """ |
833 if relation.r_type == 'identity': |
810 if relation.r_type == 'identity': |
834 # special case "X identity Y" |
811 # special case "X identity Y" |
835 lhs, rhs = relation.get_parts() |
812 lhs, rhs = relation.get_parts() |
836 if isinstance(relation.parent, Not): |
|
837 self._state.done.add(relation.parent) |
|
838 return 'NOT %s%s' % (lhs.accept(self), rhs.accept(self)) |
|
839 return '%s%s' % (lhs.accept(self), rhs.accept(self)) |
813 return '%s%s' % (lhs.accept(self), rhs.accept(self)) |
840 lhsvar, lhsconst, rhsvar, rhsconst = relation_info(relation) |
814 lhsvar, lhsconst, rhsvar, rhsconst = relation_info(relation) |
841 rid = self._relation_table(relation) |
815 rid = self._relation_table(relation) |
842 sqls = [] |
816 sqls = [] |
843 sqls += self._process_relation_term(relation, rid, lhsvar, lhsconst, 'eid_from') |
817 sqls += self._process_relation_term(relation, rid, lhsvar, lhsconst, 'eid_from') |
1039 self._state.done.add(rel.parent) |
1013 self._state.done.add(rel.parent) |
1040 not_ = True |
1014 not_ = True |
1041 else: |
1015 else: |
1042 not_ = False |
1016 not_ = False |
1043 return self.dbhelper.fti_restriction_sql(alias, const.eval(self._args), |
1017 return self.dbhelper.fti_restriction_sql(alias, const.eval(self._args), |
1044 jointo, not_) + restriction |
1018 jointo, not_) + restriction |
1045 |
1019 |
1046 def visit_comparison(self, cmp): |
1020 def visit_comparison(self, cmp): |
1047 """generate SQL for a comparison""" |
1021 """generate SQL for a comparison""" |
1048 if len(cmp.children) == 2: |
1022 if len(cmp.children) == 2: |
1049 # XXX occurs ? |
1023 # XXX occurs ? |
1202 # so nothing to return here |
1176 # so nothing to return here |
1203 pass |
1177 pass |
1204 return '' |
1178 return '' |
1205 |
1179 |
1206 def _var_info(self, var): |
1180 def _var_info(self, var): |
1207 # if current var or one of its attribute is selected , it *must* |
1181 scope = self._state.scopes[var.scope] |
1208 # appear in the toplevel's FROM even if we're currently visiting |
|
1209 # a EXISTS node |
|
1210 if var.sqlscope is var.stmt: |
|
1211 scope = 0 |
|
1212 # don't consider not_scope_offset if the variable is only used in one |
|
1213 # relation |
|
1214 elif len(var.stinfo['relations']) > 1: |
|
1215 scope = -1 - self._not_scope_offset |
|
1216 else: |
|
1217 scope = -1 |
|
1218 try: |
1182 try: |
1219 sql = self._varmap[var.name] |
1183 sql = self._varmap[var.name] |
1220 tablealias = sql.split('.', 1)[0] |
1184 tablealias = sql.split('.', 1)[0] |
1221 if scope < 0: |
|
1222 scope = self._varmap_table_scope(var.stmt, tablealias) |
|
1223 self.add_table(tablealias, scope=scope) |
1185 self.add_table(tablealias, scope=scope) |
1224 except KeyError: |
1186 except KeyError: |
1225 etype = self._state.solution[var.name] |
1187 etype = self._state.solution[var.name] |
1226 # XXX this check should be moved in rql.stcheck |
1188 # XXX this check should be moved in rql.stcheck |
1227 if self.schema.eschema(etype).final: |
1189 if self.schema.eschema(etype).final: |
1233 return sql, tablealias |
1195 return sql, tablealias |
1234 |
1196 |
1235 def _inlined_var_sql(self, var, rtype): |
1197 def _inlined_var_sql(self, var, rtype): |
1236 try: |
1198 try: |
1237 sql = self._varmap['%s.%s' % (var.name, rtype)] |
1199 sql = self._varmap['%s.%s' % (var.name, rtype)] |
1238 scope = var.sqlscope is var.stmt and 0 or -1 |
1200 scope = self._state.scopes[var.scope] |
1239 self.add_table(sql.split('.', 1)[0], scope=scope) |
1201 self.add_table(sql.split('.', 1)[0], scope=scope) |
1240 except KeyError: |
1202 except KeyError: |
1241 sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype) |
1203 sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype) |
1242 #self._state.done.add(var.name) |
1204 #self._state.done.add(var.name) |
1243 return sql |
1205 return sql |
1356 if isinstance(var, ColumnAlias): |
1318 if isinstance(var, ColumnAlias): |
1357 scope = 0 |
1319 scope = 0 |
1358 break |
1320 break |
1359 # XXX may have a principal without being invariant for this generation, |
1321 # XXX may have a principal without being invariant for this generation, |
1360 # not sure this is a pb or not |
1322 # not sure this is a pb or not |
1361 if var.stinfo.get('principal') is relation and var.sqlscope is var.stmt: |
1323 if var.stinfo.get('principal') is relation and var.scope is var.stmt: |
1362 scope = 0 |
1324 scope = 0 |
1363 break |
1325 break |
1364 else: |
1326 else: |
1365 scope = -1 |
1327 scope = -1 |
1366 self._state.count += 1 |
1328 self._state.count += 1 |
1377 pass |
1339 pass |
1378 self._state.done.add(relation) |
1340 self._state.done.add(relation) |
1379 alias = self.alias_and_add_table(self.dbhelper.fti_table) |
1341 alias = self.alias_and_add_table(self.dbhelper.fti_table) |
1380 relation._q_sqltable = alias |
1342 relation._q_sqltable = alias |
1381 return alias |
1343 return alias |
1382 |
|
1383 def _varmap_table_scope(self, select, table): |
|
1384 """since a varmap table may be used for multiple variable, its scope is |
|
1385 the most outer scope of each variables |
|
1386 """ |
|
1387 scope = -1 |
|
1388 for varname, alias in self._varmap.iteritems(): |
|
1389 # check '.' in varname since there are 'X.attribute' keys in varmap |
|
1390 if not '.' in varname and alias.split('.', 1)[0] == table: |
|
1391 if select.defined_vars[varname].sqlscope is select: |
|
1392 return 0 |
|
1393 return scope |
|