165 asol[vname] = origsol[vname] |
165 asol[vname] = origsol[vname] |
166 if not asol in newsolutions: |
166 if not asol in newsolutions: |
167 newsolutions.append(asol) |
167 newsolutions.append(asol) |
168 return newsolutions |
168 return newsolutions |
169 |
169 |
170 def remove_unused_solutions(rqlst, solutions, varmap, schema): |
170 def remove_unused_solutions(rqlst, solutions, schema): |
171 """cleanup solutions: remove solutions where invariant variables are taking |
171 """cleanup solutions: remove solutions where invariant variables are taking |
172 different types |
172 different types |
173 """ |
173 """ |
174 newsols = _new_solutions(rqlst, solutions) |
174 newsols = _new_solutions(rqlst, solutions) |
175 existssols = {} |
175 existssols = {} |
176 unstable = set() |
176 unstable = set() |
177 invariants = {} |
177 invariants = {} |
178 for vname, var in rqlst.defined_vars.items(): |
178 for vname, var in rqlst.defined_vars.items(): |
179 vtype = newsols[0][vname] |
179 vtype = newsols[0][vname] |
180 if var._q_invariant or vname in varmap: |
180 if var._q_invariant: |
181 # remove invariant variable from solutions to remove duplicates |
181 # remove invariant variable from solutions to remove duplicates |
182 # later, then reinserting a type for the variable even later |
182 # later, then reinserting a type for the variable even later |
183 for sol in newsols: |
183 for sol in newsols: |
184 invariants.setdefault(id(sol), {})[vname] = sol.pop(vname) |
184 invariants.setdefault(id(sol), {})[vname] = sol.pop(vname) |
185 elif var.scope is not rqlst: |
185 elif var.scope is not rqlst: |
713 self._lock = threading.Lock() |
712 self._lock = threading.Lock() |
714 if attrmap is None: |
713 if attrmap is None: |
715 attrmap = {} |
714 attrmap = {} |
716 self.attr_map = attrmap |
715 self.attr_map = attrmap |
717 |
716 |
718 def generate(self, union, args=None, varmap=None): |
717 def generate(self, union, args=None): |
719 """return SQL queries and a variable dictionary from a RQL syntax tree |
718 """return SQL queries and a variable dictionary from a RQL syntax tree |
720 |
719 |
721 :partrqls: a list of couple (rqlst, solutions) |
720 :partrqls: a list of couple (rqlst, solutions) |
722 :args: optional dictionary with values of substitutions used in the query |
721 :args: optional dictionary with values of substitutions used in the query |
723 :varmap: optional dictionary mapping variable name to a special table |
|
724 name, in case the query as to fetch data from temporary tables |
|
725 |
722 |
726 return an sql string and a dictionary with substitutions values |
723 return an sql string and a dictionary with substitutions values |
727 """ |
724 """ |
728 if args is None: |
725 if args is None: |
729 args = {} |
726 args = {} |
730 if varmap is None: |
|
731 varmap = {} |
|
732 self._lock.acquire() |
727 self._lock.acquire() |
733 self._args = args |
728 self._args = args |
734 self._varmap = varmap |
|
735 self._query_attrs = {} |
729 self._query_attrs = {} |
736 self._state = None |
730 self._state = None |
737 # self._not_scope_offset = 0 |
731 # self._not_scope_offset = 0 |
738 try: |
732 try: |
739 # union query for each rqlst / solution |
733 # union query for each rqlst / solution |
1046 else: |
1040 else: |
1047 # no variables in the RHS |
1041 # no variables in the RHS |
1048 sql = self._visit_attribute_relation(relation) |
1042 sql = self._visit_attribute_relation(relation) |
1049 elif (rtype == 'is' and isinstance(rhs.children[0], Constant) |
1043 elif (rtype == 'is' and isinstance(rhs.children[0], Constant) |
1050 and rhs.children[0].eval(self._args) is None): |
1044 and rhs.children[0].eval(self._args) is None): |
1051 # special case "C is NULL" |
1045 lhssql = lhs.accept(self) |
1052 if lhs.name in self._varmap: |
|
1053 lhssql = self._varmap[lhs.name] |
|
1054 else: |
|
1055 lhssql = lhs.accept(self) |
|
1056 return '%s%s' % (lhssql, rhs.accept(self)) |
1046 return '%s%s' % (lhssql, rhs.accept(self)) |
1057 elif '%s.%s' % (lhs, relation.r_type) in self._varmap: |
|
1058 # relation has already been processed by a previous step |
|
1059 return '' |
|
1060 elif relation.optional: |
1047 elif relation.optional: |
1061 # OPTIONAL relation, generate a left|right outer join |
1048 # OPTIONAL relation, generate a left|right outer join |
1062 if rtype == 'identity' or rschema.inlined: |
1049 if rtype == 'identity' or rschema.inlined: |
1063 sql = self._visit_outer_join_inlined_relation(relation, rschema) |
1050 sql = self._visit_outer_join_inlined_relation(relation, rschema) |
1064 else: |
1051 else: |
1105 def _process_relation_term(self, relation, rid, termvar, termconst, relfield): |
1091 def _process_relation_term(self, relation, rid, termvar, termconst, relfield): |
1106 if termconst or not termvar._q_invariant: |
1092 if termconst or not termvar._q_invariant: |
1107 termsql = termconst and termconst.accept(self) or termvar.accept(self) |
1093 termsql = termconst and termconst.accept(self) or termvar.accept(self) |
1108 yield '%s.%s=%s' % (rid, relfield, termsql) |
1094 yield '%s.%s=%s' % (rid, relfield, termsql) |
1109 elif termvar._q_invariant: |
1095 elif termvar._q_invariant: |
1110 # if the variable is mapped, generate restriction anyway |
|
1111 if termvar.name in self._varmap: |
|
1112 termsql = termvar.accept(self) |
|
1113 yield '%s.%s=%s' % (rid, relfield, termsql) |
|
1114 extrajoin = self._extra_join_sql(relation, '%s.%s' % (rid, relfield), termvar) |
1096 extrajoin = self._extra_join_sql(relation, '%s.%s' % (rid, relfield), termvar) |
1115 if extrajoin is not None: |
1097 if extrajoin is not None: |
1116 yield extrajoin |
1098 yield extrajoin |
1117 |
1099 |
1118 def _visit_relation(self, relation, rschema): |
1100 def _visit_relation(self, relation, rschema): |
1234 lhsvar, lhsconst, rhsvar, rhsconst = relation_info(relation) |
1216 lhsvar, lhsconst, rhsvar, rhsconst = relation_info(relation) |
1235 assert not (lhsconst and rhsconst), "doesn't make sense" |
1217 assert not (lhsconst and rhsconst), "doesn't make sense" |
1236 attr = 'eid' if relation.r_type == 'identity' else relation.r_type |
1218 attr = 'eid' if relation.r_type == 'identity' else relation.r_type |
1237 lhsalias = self._var_table(lhsvar) |
1219 lhsalias = self._var_table(lhsvar) |
1238 rhsalias = rhsvar and self._var_table(rhsvar) |
1220 rhsalias = rhsvar and self._var_table(rhsvar) |
1239 try: |
1221 if lhsalias is None: |
1240 lhssql = self._varmap['%s.%s' % (lhsvar.name, attr)] |
1222 lhssql = lhsconst.accept(self) |
1241 except KeyError: |
1223 elif attr == 'eid': |
1242 if lhsalias is None: |
1224 lhssql = lhsvar.accept(self) |
1243 lhssql = lhsconst.accept(self) |
1225 else: |
1244 elif attr == 'eid': |
1226 lhssql = '%s.%s%s' % (lhsalias, SQL_PREFIX, attr) |
1245 lhssql = lhsvar.accept(self) |
|
1246 else: |
|
1247 lhssql = '%s.%s%s' % (lhsalias, SQL_PREFIX, attr) |
|
1248 condition = '%s=%s' % (lhssql, (rhsconst or rhsvar).accept(self)) |
1227 condition = '%s=%s' % (lhssql, (rhsconst or rhsvar).accept(self)) |
1249 # this is not a typo, rhs optional variable means lhs outer join and vice-versa |
1228 # this is not a typo, rhs optional variable means lhs outer join and vice-versa |
1250 if relation.optional == 'left': |
1229 if relation.optional == 'left': |
1251 lhsvar, rhsvar = rhsvar, lhsvar |
1230 lhsvar, rhsvar = rhsvar, lhsvar |
1252 lhsconst, rhsconst = rhsconst, lhsconst |
1231 lhsconst, rhsconst = rhsconst, lhsconst |
1329 table = self._var_table(lhs.variable) |
1301 table = self._var_table(lhs.variable) |
1330 if table is None: |
1302 if table is None: |
1331 assert rel.r_type == 'eid' |
1303 assert rel.r_type == 'eid' |
1332 lhssql = lhs.accept(self) |
1304 lhssql = lhs.accept(self) |
1333 else: |
1305 else: |
1334 try: |
1306 mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type) |
1335 lhssql = self._varmap['%s.%s' % (lhs.name, rel.r_type)] |
1307 if mapkey in self.attr_map: |
1336 except KeyError: |
1308 cb, sourcecb = self.attr_map[mapkey] |
1337 mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type) |
1309 if sourcecb: |
1338 if mapkey in self.attr_map: |
1310 # callback is a source callback, we can't use this |
1339 cb, sourcecb = self.attr_map[mapkey] |
1311 # attribute in restriction |
1340 if sourcecb: |
1312 raise QueryError("can't use %s (%s) in restriction" |
1341 # callback is a source callback, we can't use this |
1313 % (mapkey, rel.as_string())) |
1342 # attribute in restriction |
1314 lhssql = cb(self, lhs.variable, rel) |
1343 raise QueryError("can't use %s (%s) in restriction" |
1315 elif rel.r_type == 'eid': |
1344 % (mapkey, rel.as_string())) |
1316 lhssql = lhs.variable._q_sql |
1345 lhssql = cb(self, lhs.variable, rel) |
1317 else: |
1346 elif rel.r_type == 'eid': |
1318 lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type) |
1347 lhssql = lhs.variable._q_sql |
|
1348 else: |
|
1349 lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type) |
|
1350 try: |
1319 try: |
1351 if rel._q_needcast == 'TODAY': |
1320 if rel._q_needcast == 'TODAY': |
1352 sql = 'DATE(%s)%s' % (lhssql, rhssql) |
1321 sql = 'DATE(%s)%s' % (lhssql, rhssql) |
1353 # XXX which cast function should be used |
1322 # XXX which cast function should be used |
1354 #elif rel._q_needcast == 'NOW': |
1323 #elif rel._q_needcast == 'NOW': |
1608 # no principal defined, relation is necessarily the principal and |
1564 # no principal defined, relation is necessarily the principal and |
1609 # so nothing to return here |
1565 # so nothing to return here |
1610 pass |
1566 pass |
1611 return None |
1567 return None |
1612 |
1568 |
1613 def _temp_table_scope(self, select, table): |
1569 def _var_info(self, var): |
1614 scope = 9999 |
1570 scope = self._state.scopes[var.scope] |
1615 for var, sql in self._varmap.items(): |
1571 etype = self._state.solution[var.name] |
1616 # skip "attribute variable" in varmap (such 'T.login') |
1572 # XXX this check should be moved in rql.stcheck |
1617 if not '.' in var and table == sql.split('.', 1)[0]: |
1573 if self.schema.eschema(etype).final: |
1618 try: |
1574 raise BadRQLQuery(var.stmt.root) |
1619 scope = min(scope, self._state.scopes[select.defined_vars[var].scope]) |
1575 tablealias = '_' + var.name |
1620 except KeyError: |
1576 sql = '%s.%seid' % (tablealias, SQL_PREFIX) |
1621 scope = 0 # XXX |
1577 self._state.add_table('%s%s AS %s' % (SQL_PREFIX, etype, tablealias), |
1622 if scope == 0: |
1578 tablealias, scope=scope) |
1623 break |
|
1624 return scope |
|
1625 |
|
1626 def _mapped_term(self, term, key): |
|
1627 """return sql and table alias to the `term`, mapped as `key` or raise |
|
1628 KeyError when the key is not found in the varmap |
|
1629 """ |
|
1630 sql = self._varmap[key] |
|
1631 tablealias = sql.split('.', 1)[0] |
|
1632 scope = self._temp_table_scope(term.stmt, tablealias) |
|
1633 self._state.add_table(tablealias, scope=scope) |
|
1634 return sql, tablealias |
1579 return sql, tablealias |
1635 |
1580 |
1636 def _var_info(self, var): |
|
1637 try: |
|
1638 return self._mapped_term(var, var.name) |
|
1639 except KeyError: |
|
1640 scope = self._state.scopes[var.scope] |
|
1641 etype = self._state.solution[var.name] |
|
1642 # XXX this check should be moved in rql.stcheck |
|
1643 if self.schema.eschema(etype).final: |
|
1644 raise BadRQLQuery(var.stmt.root) |
|
1645 tablealias = '_' + var.name |
|
1646 sql = '%s.%seid' % (tablealias, SQL_PREFIX) |
|
1647 self._state.add_table('%s%s AS %s' % (SQL_PREFIX, etype, tablealias), |
|
1648 tablealias, scope=scope) |
|
1649 return sql, tablealias |
|
1650 |
|
1651 def _inlined_var_sql(self, var, rtype): |
1581 def _inlined_var_sql(self, var, rtype): |
1652 try: |
1582 # rtype may be an attribute relation when called from |
1653 sql = self._varmap['%s.%s' % (var.name, rtype)] |
1583 # _visit_var_attr_relation. take care about 'eid' rtype, since in |
1654 scope = self._state.scopes[var.scope] |
1584 # some case we may use the `entities` table, so in that case we've |
1655 self._state.add_table(sql.split('.', 1)[0], scope=scope) |
1585 # to properly use variable'sql |
1656 except KeyError: |
1586 if rtype == 'eid': |
1657 # rtype may be an attribute relation when called from |
1587 sql = var.accept(self) |
1658 # _visit_var_attr_relation. take care about 'eid' rtype, since in |
1588 else: |
1659 # some case we may use the `entities` table, so in that case we've |
1589 sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype) |
1660 # to properly use variable'sql |
|
1661 if rtype == 'eid': |
|
1662 sql = var.accept(self) |
|
1663 else: |
|
1664 sql = '%s.%s%s' % (self._var_table(var), SQL_PREFIX, rtype) |
|
1665 return sql |
1590 return sql |
1666 |
1591 |
1667 def _linked_var_sql(self, variable): |
1592 def _linked_var_sql(self, variable): |
1668 if not self._state.ignore_varmap: |
|
1669 try: |
|
1670 return self._varmap[variable.name] |
|
1671 except KeyError: |
|
1672 pass |
|
1673 rel = (variable.stinfo.get('principal') or |
1593 rel = (variable.stinfo.get('principal') or |
1674 next(iter(variable.stinfo['rhsrelations']))) |
1594 next(iter(variable.stinfo['rhsrelations']))) |
1675 linkedvar = rel.children[0].variable |
1595 linkedvar = rel.children[0].variable |
1676 if rel.r_type == 'eid': |
1596 if rel.r_type == 'eid': |
1677 return linkedvar.accept(self) |
1597 return linkedvar.accept(self) |
1678 if isinstance(linkedvar, ColumnAlias): |
1598 if isinstance(linkedvar, ColumnAlias): |
1679 raise BadRQLQuery('variable %s should be selected by the subquery' |
1599 raise BadRQLQuery('variable %s should be selected by the subquery' |
1680 % variable.name) |
1600 % variable.name) |
1681 try: |
1601 mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type) |
1682 sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] |
1602 if mapkey in self.attr_map: |
1683 except KeyError: |
1603 cb, sourcecb = self.attr_map[mapkey] |
1684 mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type) |
1604 if not sourcecb: |
1685 if mapkey in self.attr_map: |
1605 return cb(self, linkedvar, rel) |
1686 cb, sourcecb = self.attr_map[mapkey] |
1606 # attribute mapped at the source level (bfss for instance) |
1687 if not sourcecb: |
1607 stmt = rel.stmt |
1688 return cb(self, linkedvar, rel) |
1608 for selectidx, vref in iter_mapped_var_sels(stmt, variable): |
1689 # attribute mapped at the source level (bfss for instance) |
1609 stack = [cb] |
1690 stmt = rel.stmt |
1610 update_source_cb_stack(self._state, stmt, vref, stack) |
1691 for selectidx, vref in iter_mapped_var_sels(stmt, variable): |
1611 self._state._needs_source_cb[selectidx] = stack |
1692 stack = [cb] |
1612 linkedvar.accept(self) |
1693 update_source_cb_stack(self._state, stmt, vref, stack) |
1613 return '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type) |
1694 self._state._needs_source_cb[selectidx] = stack |
|
1695 linkedvar.accept(self) |
|
1696 sql = '%s.%s%s' % (linkedvar._q_sqltable, SQL_PREFIX, rel.r_type) |
|
1697 return sql |
|
1698 |
1614 |
1699 # tables handling ######################################################### |
1615 # tables handling ######################################################### |
1700 |
1616 |
1701 def _var_table(self, var): |
1617 def _var_table(self, var): |
1702 var.accept(self)#.visit_variable(var) |
1618 var.accept(self)#.visit_variable(var) |