diff --git a/orator/query/builder.py b/orator/query/builder.py index 6e8aaa6c..9c7e2e59 100644 --- a/orator/query/builder.py +++ b/orator/query/builder.py @@ -63,7 +63,7 @@ def __init__(self, connection, grammar, processor): self._processor = processor self._connection = connection self._bindings = OrderedDict() - for type in ["select", "join", "where", "having", "order"]: + for type in ["select", "from", "join", "where", "having", "order"]: self._bindings[type] = [] self.aggregate_ = None @@ -188,8 +188,31 @@ def from_(self, table): :return: The current QueryBuilder instance :rtype: QueryBuilder """ + self.set_bindings([], "from") self.from__ = table + return self + + def from_sub(self, query, as_): + """ + Set the query target table to a subquery + + :param query: The QueryBuilder to set as from expression + :type query: QueryBuilder + + :param as_: The alias name for the subquery + :type as_: str + + :return: The current QueryBuilder instance + :rtype: QueryBuilder + """ + if not isinstance(query, QueryBuilder): + raise ArgumentError("From expression must be a QueryBuilder") + + bindings = query.get_bindings() + query = "(%s) AS %s" % (query.to_sql(), self._grammar.wrap(as_)) + self.set_bindings(bindings, "from") + self.from__ = QueryExpression(query) return self def join(self, table, one=None, operator=None, two=None, type="inner", where=False): diff --git a/tests/query/test_query_builder.py b/tests/query/test_query_builder.py index 26c1c5e3..faf4fc1f 100644 --- a/tests/query/test_query_builder.py +++ b/tests/query/test_query_builder.py @@ -1618,6 +1618,29 @@ def test_sub_select(self): self.assertEqual(expected_sql, builder.to_sql()) self.assertEqual(expected_bindings, builder.get_bindings()) + def test_from_sub_query(self): + builder = self.get_builder() + marker = builder.get_grammar().get_marker() + expected_sql = ( + 'SELECT "foo", "bar" FROM ' + '(SELECT "a" AS "foo", "b" AS "bar" FROM "one" WHERE "key" = %s ORDER BY "foo" ASC) AS "two" ' + 'WHERE "foo" = %s ORDER BY "bar" DESC' % (marker, marker) + ) + expected_bindings = ["innerval", "outerval"] + inner_query = ( + builder.new_query() + .from_("one") + .select("a as foo", "b as bar") + .where("key", "=", "innerval") + .order_by("foo", "asc") + ) + builder.from_sub(inner_query, "two").select("foo", "bar").where( + "foo", "=", "outerval" + ).order_by("bar", "desc") + + self.assertEqual(expected_sql, builder.to_sql()) + self.assertEqual(expected_bindings, builder.get_bindings()) + def test_chunk(self): builder = self.get_builder() results = [{"foo": "bar"}, {"foo": "baz"}, {"foo": "bam"}, {"foo": "boom"}]