Repository: kayak/pypika
Pull request #40 (2017-04-25 12:04:20)

Quote char param
Added a quote_char parameter to the get_sql functions which allows the the quote character to be overridden or removed.


Patch:
diff --git a/README.rst b/README.rst
index 3bba5bd..32d9ee5 100644
--- a/README.rst
+++ b/README.rst
@@ -315,6 +315,31 @@ Example of a join using `ON`
 
     SELECT "history".* FROM "history" JOIN "customers" ON "history"."customer_id"="customers"."id" WHERE "customers"."id"=5
 
+As a shortcut, the ``Query.join().on_field()`` function is provided for joining the (first) table in the ``FROM`` clause
+with the joined table when the field name(s) are the same in both tables.
+
+Example of a join using `ON`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    history, customers = Tables('history', 'customers')
+    q = Query.from_(history).join(
+        customers
+    ).on_field(
+        'customer_id', 'group'
+    ).select(
+        history.star
+    ).where(
+        customers.group == 'A'
+    )
+
+
+.. code-block:: sql
+
+    SELECT "history".* FROM "history" JOIN "customers" ON "history"."customer_id"="customers"."customer_id" AND "history"."group"="customers"."group" WHERE "customers"."group"='A'
+
+
 
 Example of a join using `USING`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/pypika/queries.py b/pypika/queries.py
index 3feffe2..c57198d 100644
--- a/pypika/queries.py
+++ b/pypika/queries.py
@@ -1,8 +1,7 @@
 # coding: utf8
 
 from pypika.enums import JoinType, UnionType
-from pypika.utils import JoinException, UnionException, RollupException
-from pypika.utils import builder
+from pypika.utils import JoinException, UnionException, RollupException, builder, alias_sql
 from .terms import Field, Star, Term, Function, ArithmeticExpression, Rollup
 
 __author__ = "Timothy Heys"
@@ -37,30 +36,28 @@ class Table(Selectable):
         self.table_name = name
         self.schema = schema
 
-    def get_sql(self, **kwargs):
+    def get_sql(self, quote_char=None, **kwargs):
         # FIXME escape
 
         if self.schema:
-            name = "\"{schema}\".\"{name}\"".format(
+            table_sql = "{quote}{schema}{quote}.{quote}{name}{quote}".format(
                 schema=self.schema,
                 name=self.table_name,
+                quote=quote_char or '',
             )
 
         else:
-            name = "\"{name}\"".format(
-                name=self.table_name
-            )
-
-        if self.alias:
-            return "{name} \"{alias}\"".format(
-                name=name,
-                alias=self.alias
+            table_sql = "{quote}{name}{quote}".format(
+                name=self.table_name,
+                quote=quote_char or '',
             )
 
-        return name
+        if self.alias is None:
+            return table_sql
+        return alias_sql(table_sql, self.alias, quote_char)
 
     def __str__(self):
-        return self.get_sql()
+        return self.get_sql(quote_char='"')
 
     def __eq__(self, other):
         if not isinstance(other, Table):
@@ -350,8 +347,8 @@ class QueryBuilder(Selectable, Term):
         self._limit = item.stop
 
     @staticmethod
-    def _list_aliases(field_set):
-        return [field.alias or field.get_sql(with_quotes=False)
+    def _list_aliases(field_set, quote_char=None):
+        return [field.alias or field.get_sql(quote_char=quote_char)
                 for field in field_set]
 
     def _select_field_str(self, term):
@@ -389,26 +386,6 @@ class QueryBuilder(Selectable, Term):
         # Don't return anything here. Subqueries have their own fields.
         return []
 
-    def select_aliases(self):
-        """
-        Gets a list of aliases for the columns in this query's SELECT clause.  If a field does not have an alias, the
-        field name is returned instead.
-
-        :return:
-            A list[str] of aliases.
-        """
-        return self._list_aliases(self._selects)
-
-    def groupby_aliases(self):
-        """
-        Gets a list of aliases for the columns in this query's GROUP BY clause.  If a field does not have an alias, the
-        field name is returned instead.
-
-        :return:
-            A list[str] of aliases.
-        """
-        return self._list_aliases(self._groupbys)
-
     def do_join(self, join):
         join.validate(self._from, self._joins)
 
@@ -439,7 +416,7 @@ class QueryBuilder(Selectable, Term):
         self._subquery_count += 1
 
     def __str__(self):
-        return self.get_sql(with_unions=True)
+        return self.get_sql(with_unions=True, quote_char='"')
 
     def __eq__(self, other):
         if not isinstance(other, QueryBuilder):
@@ -462,16 +439,16 @@ class QueryBuilder(Selectable, Term):
         if self._insert_table and not (self._selects or self._values):
             return ''
 
-        kwargs = {'with_namespace': bool(self._joins) or 1 < len(self._from)}
+        kwargs['with_namespace'] = bool(self._joins) or 1 < len(self._from)
 
         if not self._select_into and self._insert_table:
-            querystring = self._insert_sql()
+            querystring = self._insert_sql(**kwargs)
 
             if self._columns:
-                querystring += self._columns_sql()
+                querystring += self._columns_sql(**kwargs)
 
             if self._values:
-                return querystring + self._values_sql()
+                return querystring + self._values_sql(**kwargs)
             else:
                 querystring += ' ' + self._select_sql(**kwargs)
 
@@ -479,7 +456,7 @@ class QueryBuilder(Selectable, Term):
             querystring = self._select_sql(**kwargs)
 
             if self._insert_table:
-                querystring += self._into_sql()
+                querystring += self._into_sql(**kwargs)
 
         if self._from:
             querystring += self._from_sql(**kwargs)
@@ -509,103 +486,100 @@ class QueryBuilder(Selectable, Term):
             querystring += self._limit_sql()
 
         if subquery:
-            querystring = '({query})'.format(
-                query=querystring,
-            )
+            querystring = '({query})'.format(query=querystring)
 
         if with_alias:
-            return self._queryalias_sql(querystring)
+            return alias_sql(querystring, self.alias or self.table_name, kwargs.get('quote_char'))
 
         if with_unions:
-            querystring = self._union_sql(querystring)
+            querystring = self._union_sql(querystring, **kwargs)
 
         return querystring
 
     def _select_sql(self, **kwargs):
         return 'SELECT {distinct}{select}'.format(
             distinct='distinct ' if self._distinct else '',
-            select=','.join(term.get_sql(with_quotes=True, with_alias=True, **kwargs)
+            select=','.join(term.get_sql(with_alias=True, **kwargs)
                             for term in self._selects),
         )
 
-    def _insert_sql(self):
+    def _insert_sql(self, **kwargs):
         return 'INSERT {ignore}INTO {table}'.format(
-            table=self._insert_table,
+            table=self._insert_table.get_sql(**kwargs),
             ignore='IGNORE ' if self._ignore else ''
         )
 
-    def _columns_sql(self):
+    def _columns_sql(self, with_namespace=False, **kwargs):
+        """
+        SQL for Columns clause for INSERT queries 
+        :param with_namespace:
+            Remove from kwargs, never format the column terms with namespaces since only one table can be inserted into 
+        """
         return ' ({columns})'.format(
-            columns=','.join(term.get_sql(with_quotes=True, with_alias=True)
+            columns=','.join(term.get_sql(with_namespace=False, **kwargs)
                              for term in self._columns)
         )
 
-    def _values_sql(self):
+    def _values_sql(self, **kwargs):
         return ' VALUES ({values})'.format(
-            values='),('.join(','.join(term.get_sql(with_quotes=True, with_alias=True)
+            values='),('.join(','.join(term.get_sql(with_alias=True, **kwargs)
                                        for term in row)
                               for row in self._values)
 
         )
 
-    def _into_sql(self):
+    def _into_sql(self, **kwargs):
         return ' INTO {table}'.format(
-            table=self._insert_table.get_sql(with_quotes=True, with_alias=False),
+            table=self._insert_table.get_sql(with_alias=False, **kwargs),
         )
 
-    def _from_sql(self, with_namespace=False, **kwargs):
+    def _from_sql(self, **kwargs):
         return ' FROM {selectable}'.format(selectable=','.join(
-            clause.get_sql(with_quotes=True, subquery=True, with_alias=with_namespace)
+            clause.get_sql(subquery=True, with_alias=kwargs['with_namespace'], **kwargs)
             for clause in self._from
         ))
 
-    def _where_sql(self, **kwargs):
-        return ' WHERE {where}'.format(where=self._wheres.get_sql(with_quotes=True, subquery=True, **kwargs))
+    def _where_sql(self, quote_char=None, **kwargs):
+        return ' WHERE {where}'.format(where=self._wheres.get_sql(quote_char=quote_char, subquery=True, **kwargs))
 
-    def _group_sql(self, **kwargs):
+    def _group_sql(self, quote_char=None, **kwargs):
         return ' GROUP BY {groupby}'.format(
-            groupby=','.join(term.get_sql(with_quotes=True, **kwargs)
+            groupby=','.join(term.get_sql(quote_char=quote_char, **kwargs)
                              for term in self._groupbys)
         )
 
     def _rollup_sql(self):
         return ' WITH ROLLUP'
 
-    def _having_sql(self, **kwargs):
-        return ' HAVING {having}'.format(having=self._havings.get_sql(with_quotes=True, **kwargs))
+    def _having_sql(self, quote_char=None, **kwargs):
+        return ' HAVING {having}'.format(having=self._havings.get_sql(quote_char=quote_char, **kwargs))
 
-    def _orderby_sql(self, **kwargs):
+    def _orderby_sql(self, quote_char=None, **kwargs):
         return ' ORDER BY {orderby}'.format(
             orderby=','.join(
                 '{field} {orient}'.format(
-                    field=field.get_sql(with_quotes=True),
+                    field=field.get_sql(quote_char=quote_char),
                     orient=orient.value,
                 ) if orient is not None else
-                field.get_sql(with_quotes=True, **kwargs)
+                field.get_sql(quote_char=quote_char, **kwargs)
                 for field, orient in self._orderbys
             )
         )
 
-    def _queryalias_sql(self, querystring):
-        return '{query} \"{alias}\"'.format(
-            query=querystring,
-            alias=self.alias or self.table_name,
-        )
-
-    def _union_sql(self, querystring):
+    def _union_sql(self, querystring, quote_char=None, **kwargs):
         if self._unions:
             # Some queries require brackets for unions so easier to just always use them
             querystring = "({})".format(querystring)
 
             for (union_type, other) in self._unions:
+                other_querystring = other.get_sql(quote_char=quote_char)
                 if len(self._selects) != len(other._selects):
                     raise UnionException("Queries must have an equal number of select statements in a union."
-                                         "\n\nMain Query:\n{query1}"
-                                         "\n\nUnion Query:\n{query2}".format(query1=querystring,
-                                                                             query2=other.get_sql()))
+                                         "\n\nMain Query:\n{query1}\n\nUnion Query:\n{query2}" \
+                                         .format(query1=querystring, query2=other_querystring))
 
                 querystring += ' UNION{type} ({query})' \
-                    .format(type=union_type.value, query=other.get_sql())
+                    .format(type=union_type.value, query=other_querystring)
 
         return querystring
 
@@ -625,8 +599,21 @@ class Joiner(object):
 
     def on(self, criterion):
         if criterion is None:
-            raise JoinException("Parameter 'on' is required when joining a "
-                                "{type} but was not supplied.".format(type=self.type_label))
+            raise JoinException("Parameter 'criterion' is required for a "
+                                "{type} JOIN but was not supplied.".format(type=self.type_label))
+
+        self.query.do_join(JoinOn(self.item, self.how, criterion))
+        return self.query
+
+    def on_field(self, *fields):
+        if not fields:
+            raise JoinException("Parameter 'fields' is required for a "
+                                "{type} JOIN but was not supplied.".format(type=self.type_label))
+
+        criterion = None
+        for field in fields:
+            consituent = Field(field, table=self.query._from[0]) == Field(field, table=self.item)
+            criterion = consituent if criterion is None else criterion & consituent
 
         self.query.do_join(JoinOn(self.item, self.how, criterion))
         return self.query
@@ -647,7 +634,7 @@ class Join(object):
 
     def get_sql(self, **kwargs):
         sql = 'JOIN {table}'.format(
-            table=self.item.get_sql(with_quotes=True, subquery=True, with_alias=True),
+            table=self.item.get_sql(subquery=True, with_alias=True, **kwargs),
         )
 
         if self.how.value:
@@ -664,7 +651,7 @@ class JoinOn(Join):
         join_sql = super(JoinOn, self).get_sql(**kwargs)
         return '{join} ON {criterion}'.format(
             join=join_sql,
-            criterion=self.criterion.get_sql(with_quotes=True, **kwargs),
+            criterion=self.criterion.get_sql(**kwargs),
         )
 
     def validate(self, _from, _joins):
diff --git a/pypika/terms.py b/pypika/terms.py
index acad034..cac8df4 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -5,7 +5,7 @@ from datetime import date
 from aenum import Enum
 
 from pypika.enums import Boolean, Equality, Arithmetic, Matching
-from pypika.utils import CaseException, builder, resolve_is_aggregate
+from pypika.utils import CaseException, builder, resolve_is_aggregate, alias_sql
 
 __author__ = "Timothy Heys"
 __email__ = "theys@kayak.com"
@@ -157,7 +157,7 @@ class Term(object):
         return self.between(item.start, item.stop)
 
     def __str__(self):
-        return self.get_sql()
+        return self.get_sql(quote_char='"')
 
     def __hash__(self):
         return hash(self.get_sql())
@@ -221,40 +221,36 @@ class Field(Term):
         self.table = table
         return self
 
-    def get_sql(self, with_alias=False, with_quotes=True, with_namespace=False, **kwargs):
-        quote_char = '\"' if with_quotes else ''
-
+    def get_sql(self, with_alias=False, with_namespace=False, quote_char=None, **kwargs):
         # Need to add namespace if the table has an alias
         if self.table and (with_namespace or self.table.alias):
-            name = "{quote}{namespace}{quote}.{quote}{name}{quote}".format(
+            field_sql = "{quote}{namespace}{quote}.{quote}{name}{quote}".format(
                 namespace=self.table.alias or self.table.table_name,
                 name=self.name,
-                quote=quote_char,
+                quote=quote_char or '',
             )
         else:
-            name = "{quote}{name}{quote}".format(
+            field_sql = "{quote}{name}{quote}".format(
                 name=self.name,
-                quote=quote_char,
+                quote=quote_char or '',
             )
 
-        alias = getattr(self, 'alias', None)
-        if with_alias and alias:
-            return name + ' {quote}{alias}{quote}'.format(
-                alias=alias,
-                quote=quote_char,
-            )
+        field_alias = getattr(self, 'alias', None)
+        if not with_alias or field_alias is None:
+            return field_sql
 
-        return name
+        return alias_sql(field_sql, field_alias, quote_char)
 
 
 class Star(Field):
     def __init__(self, table=None):
         super(Star, self).__init__('*', table=table)
 
-    def get_sql(self, with_alias=False, with_namespace=False, **kwargs):
+    def get_sql(self, with_alias=False, with_namespace=False, quote_char=None, **kwargs):
         if self.table and (with_namespace or self.table.alias):
-            return "\"{namespace}\".*".format(
+            return "{quote}{namespace}{quote}.*".format(
                 namespace=self.table.alias or self.table.table_name,
+                quote=quote_char or ''
             )
 
         return '*'
@@ -309,7 +305,7 @@ class BasicCriterion(Criterion):
 
         :param comparator:
             Type: Comparator
-            This defines the type of comparison, such as \"=\" or \">\".
+            This defines the type of comparison, such as {quote}={quote} or {quote}>{quote}.
         :param left:
             The term on the left side of the expression.
         :param right:
@@ -462,7 +458,7 @@ class ArithmeticExpression(Term):
 
         :param operator:
             Type: Arithmetic
-            An operator for the expression such as \"+\" or \"/\"
+            An operator for the expression such as {quote}+{quote} or {quote}/{quote}
 
         :param left:
             The term on the left side of the expression.
@@ -507,13 +503,19 @@ class ArithmeticExpression(Term):
         is_mul = self.operator in self.mul_order
         is_left_add, is_right_add = [getattr(side, 'operator', None) in self.add_order
                                      for side in [self.left, self.right]]
-        return '{left}{operator}{right}{alias}'.format(
+
+        quote_char = kwargs.get('quote_char', None)
+        arithmatic_sql = '{left}{operator}{right}'.format(
             operator=self.operator.value,
             left=("({})" if is_mul and is_left_add else "{}").format(self.left.get_sql(**kwargs)),
             right=("({})" if is_mul and is_right_add else "{}").format(self.right.get_sql(**kwargs)),
-            alias=' \"{}\"'.format(self.alias) if with_alias and self.alias is not None else ''
         )
 
+        if not with_alias or self.alias is None:
+            return arithmatic_sql
+
+        return alias_sql(arithmatic_sql, self.alias, quote_char)
+
 
 class Case(Term):
     def __init__(self, alias=None):
@@ -553,11 +555,14 @@ class Case(Term):
         else_ = (' ELSE {}'.format(self._else.get_sql(**kwargs))
                  if self._else
                  else '')
-        alias = (' \"{}\"'.format(self.alias)
-                 if self.alias is not None and with_alias
-                 else '')
 
-        return 'CASE {cases}{else_} END{alias}'.format(cases=cases, else_=else_, alias=alias)
+        case_sql = 'CASE {cases}{else_} END'.format(cases=cases, else_=else_)
+
+        if self.alias is None:
+            return case_sql
+
+        return alias_sql(case_sql, self.alias, kwargs.get('quote_char'))
+
 
     def fields(self):
         fields = []
@@ -599,21 +604,24 @@ class Function(Term):
                        for param in self.params]
         return self
 
-    def get_function_sql(self, with_namespace=False):
+    def get_function_sql(self, with_namespace=False, quote_char=None):
         return '{name}({params})'.format(
             name=self.name,
-            params=','.join(p.get_sql(with_quotes=True, with_alias=False, with_namespace=with_namespace)
+            params=','.join(p.get_sql(with_alias=False, with_namespace=with_namespace, quote_char=quote_char)
                             if hasattr(p, 'get_sql')
                             else str(p)
                             for p in self.params),
         )
 
-    def get_sql(self, with_alias=False, with_namespace=False, **kwargs):
+    def get_sql(self, with_alias=False, with_namespace=False, quote_char=None, **kwargs):
         # FIXME escape
-        return '{function}{alias}'.format(
-            function=self.get_function_sql(with_namespace=with_namespace),
-            alias=' \"{}\"'.format(self.alias) if self.alias is not None and with_alias else ''
-        )
+
+        function_sql = self.get_function_sql(with_namespace=with_namespace, quote_char=quote_char)
+
+        if self.alias is None:
+            return function_sql
+
+        return alias_sql(function_sql, self.alias, quote_char)
 
     def fields(self):
         return [field
diff --git a/pypika/utils.py b/pypika/utils.py
index 4ab525a..1e9048c 100644
--- a/pypika/utils.py
+++ b/pypika/utils.py
@@ -53,3 +53,7 @@ def resolve_is_aggregate(values):
     if result:
         return all(result)
     return None
+
+
+def alias_sql(sql, alias, quote_char=None):
+    return '{sql} {quote}{alias}{quote}'.format(sql=sql, alias=alias, quote=quote_char or '')


Test patch:
diff --git a/pypika/tests/test_criterions.py b/pypika/tests/test_criterions.py
index 88f2b98..bfc3cf8 100644
--- a/pypika/tests/test_criterions.py
+++ b/pypika/tests/test_criterions.py
@@ -16,7 +16,7 @@ class CriterionTests(unittest.TestCase):
         c1 = (Field('foo') == Field('bar')).as_('criterion')
 
         self.assertEqual('"foo"="bar"', str(c1))
-        self.assertEqual('"foo"="bar" "criterion"', c1.get_sql(with_alias=True))
+        self.assertEqual('"foo"="bar" "criterion"', c1.get_sql(with_alias=True, quote_char='"'))
 
     def test__criterion_eq_number(self):
         c1 = Field('foo') == 1
diff --git a/pypika/tests/test_formats.py b/pypika/tests/test_formats.py
new file mode 100644
index 0000000..f89477a
--- /dev/null
+++ b/pypika/tests/test_formats.py
@@ -0,0 +1,59 @@
+# coding: utf-8
+import unittest
+
+from pypika import Query, Tables, functions as fn
+
+
+class QuoteTests(unittest.TestCase):
+    maxDiff = None
+
+    table_abc, table_efg = Tables('abc', 'efg')
+
+    def setUp(self):
+        subquery1 = Query.from_(self.table_abc).select(
+            self.table_abc.foo,
+            fn.Sum(self.table_abc.fizz + self.table_abc.buzz).as_('fizzbuzz'),
+        ).groupby(
+            self.table_abc.foo
+        )
+
+        subquery2 = Query.from_(self.table_efg).select(
+            self.table_efg.foo.as_('foo_two'),
+            self.table_efg.bar,
+        )
+
+        self.query = Query.from_(subquery1).select(
+            subquery1.foo, subquery1.fizzbuzz
+        ).join(subquery2).on(subquery1.foo == subquery2.foo_two).select(
+            subquery2.foo_two, subquery2.bar
+        )
+
+    def test_replace_quote_char_in_complex_query(self):
+        self.assertEqual('SELECT '
+                         '`sq0`.`foo`,`sq0`.`fizzbuzz`,'
+                         '`sq1`.`foo_two`,`sq1`.`bar` '
+                         'FROM ('
+                         'SELECT '
+                         '`foo`,SUM(`fizz`+`buzz`) `fizzbuzz` '
+                         'FROM `abc` '
+                         'GROUP BY `foo`'
+                         ') `sq0` JOIN ('
+                         'SELECT '
+                         '`foo` `foo_two`,`bar` '
+                         'FROM `efg`'
+                         ') `sq1` ON `sq0`.`foo`=`sq1`.`foo_two`', self.query.get_sql(quote_char='`'))
+
+    def test_remove_quote_char_in_complex_query(self):
+        self.assertEqual('SELECT '
+                         'sq0.foo,sq0.fizzbuzz,'
+                         'sq1.foo_two,sq1.bar '
+                         'FROM ('
+                         'SELECT '
+                         'foo,SUM(fizz+buzz) fizzbuzz '
+                         'FROM abc '
+                         'GROUP BY foo'
+                         ') sq0 JOIN ('
+                         'SELECT '
+                         'foo foo_two,bar '
+                         'FROM efg'
+                         ') sq1 ON sq0.foo=sq1.foo_two', self.query.get_sql(quote_char=None))
diff --git a/pypika/tests/test_inserts.py b/pypika/tests/test_inserts.py
index ff01b45..a3761ae 100644
--- a/pypika/tests/test_inserts.py
+++ b/pypika/tests/test_inserts.py
@@ -96,6 +96,11 @@ class InsertSelectFromTests(unittest.TestCase):
 
         self.assertEqual('INSERT INTO "abc" SELECT * FROM "efg"', str(query))
 
+    def test_insert_ignore_star(self):
+        query = Query.into(self.table_abc).from_(self.table_efg).select('*').ignore()
+
+        self.assertEqual('INSERT IGNORE INTO "abc" SELECT * FROM "efg"', str(query))
+
     def test_insert_from_columns(self):
         query = Query.into(self.table_abc).from_(self.table_efg).select(
             self.table_efg.fiz, self.table_efg.buz, self.table_efg.baz
@@ -137,11 +142,6 @@ class InsertSelectFromTests(unittest.TestCase):
                          'SELECT "efg"."foo","efg"."bar","hij"."fiz","hij"."buz" FROM "efg" '
                          'JOIN "hij" ON "efg"."id"="hij"."abc_id"', str(query))
 
-    def test_insert_star(self):
-        query = Query.into(self.table_abc).from_(self.table_efg).select('*').ignore()
-
-        self.assertEqual('INSERT IGNORE INTO "abc" SELECT * FROM "efg"', str(query))
-
 
 class SelectIntoTests(unittest.TestCase):
     table_abc, table_efg, table_hij = Tables('abc', 'efg', 'hij')
diff --git a/pypika/tests/test_joins.py b/pypika/tests/test_joins.py
index 7667ced..405e8f2 100644
--- a/pypika/tests/test_joins.py
+++ b/pypika/tests/test_joins.py
@@ -31,6 +31,23 @@ class JoinTypeTests(unittest.TestCase):
         self.assertEqual('SELECT * FROM "abc" JOIN "efg" ON "abc"."foo"="efg"."bar"', str(query))
         self.assertEqual('SELECT * FROM "abc" JOIN "efg" ON "abc"."foo"="efg"."bar"', str(query_explicit))
 
+    def test_join_on_field_single(self):
+        query = Query.from_(self.table0).join(self.table1).on_field("foo").select('*')
+        self.assertEqual('SELECT * FROM "abc" JOIN "efg" ON "abc"."foo"="efg"."foo"', str(query))
+
+    def test_join_on_field_multi(self):
+        query = Query.from_(self.table0).join(self.table1).on_field("foo", "bar").select('*')
+        self.assertEqual('SELECT * FROM "abc" JOIN "efg" ON "abc"."foo"="efg"."foo" '
+                         'AND "abc"."bar"="efg"."bar"', str(query))
+
+    def test_join_on_field_multi_with_extra_join(self):
+        query = Query.from_(self.table0)\
+            .join(self.hij).on_field("buzz")\
+            .join(self.table1).on_field("foo", "bar").select('*')
+
+        self.assertEqual('SELECT * FROM "abc" JOIN "hij" ON "abc"."buzz"="hij"."buzz" '
+                         'JOIN "efg" ON "abc"."foo"="efg"."foo" AND "abc"."bar"="efg"."bar"', str(query))
+
     def test_join_using_string_field_name(self):
         query = Query.from_(self.table0).join(self.table1).using('id').select('*')
 
diff --git a/pypika/tests/test_selects.py b/pypika/tests/test_selects.py
index e705b29..c48381d 100644
--- a/pypika/tests/test_selects.py
+++ b/pypika/tests/test_selects.py
@@ -353,52 +353,7 @@ class AliasTests(unittest.TestCase):
     def test_case_using_constructor_param(self):
         q = Query.from_(self.t).select(Case(alias='bar').when(self.t.foo == 1, 'a').else_('b'))
 
-        self.assertEqual("SELECT CASE WHEN \"foo\"=1 THEN 'a' ELSE 'b' END \"bar\" FROM \"abc\"", str(q))
-
-    def test_select_aliases(self):
-        test_query = Query.from_(self.t).select(
-            self.t.foo.as_('fiz1'), self.t.bar.as_('buz1')
-        ).groupby(
-            self.t.foo.as_('fiz2'), self.t.bar.as_('buz2')
-        )
-
-        self.assertListEqual(['fiz1', 'buz1'], test_query.select_aliases())
-
-    def test_select_aliases_mixed_with_fields(self):
-        test_query = Query.from_(self.t).select(
-            self.t.foo.as_('fiz1'), self.t.bar
-        ).groupby(
-            self.t.foo, self.t.bar.as_('buz2')
-        )
-
-        self.assertListEqual(['fiz1', 'bar'], test_query.select_aliases())
-
-    def test_select_aliases_mixed_with_complex_fields(self):
-        test_query = Query.from_(self.t).select(
-            self.t.foo.as_('foobar'), fn.Count(self.t.fiz + self.t.buz)
-        ).groupby(
-            self.t.foo
-        )
-
-        self.assertListEqual(['foobar', 'COUNT("fiz"+"buz")'], test_query.select_aliases())
-
-    def test_groupby_aliases(self):
-        test_query = Query.from_(self.t).select(
-            self.t.foo.as_('fiz1'), self.t.bar.as_('buz1')
-        ).groupby(
-            self.t.foo.as_('fiz2'), self.t.bar.as_('buz2')
-        )
-
-        self.assertListEqual(['fiz2', 'buz2'], test_query.groupby_aliases())
-
-    def test_groupby_aliases_mixed_with_fields(self):
-        test_query = Query.from_(self.t).select(
-            self.t.foo.as_('fiz1'), self.t.bar
-        ).groupby(
-            self.t.foo, self.t.bar.as_('buz2')
-        )
-
-        self.assertListEqual(['foo', 'buz2'], test_query.groupby_aliases())
+        self.assertEqual("SELECT CASE WHEN \"foo\"=1 THEN 'a' ELSE 'b' END \"bar\" FROM \"abc\"", str(q)) \
 
     def test_select__multiple_tables(self):
         table_abc, table_efg = Table('abc', alias='q0'), Table('efg', alias='q1')