nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: kayak/pypika
Pull request #94 (2017-12-29 11:27:30)

Fixed null wrapping in update set


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index 873ac3f..6b9d79c 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -211,6 +211,8 @@ class ValueWrapper(Term):
             return "'%s'" % self.value
         if isinstance(self.value, bool):
             return str.lower(str(self.value))
+        if self.value is None:
+            return 'null'
 
         return str(self.value)
 


Test patch:
diff --git a/pypika/tests/test_updates.py b/pypika/tests/test_updates.py
index b6079ec..50d6c44 100644
--- a/pypika/tests/test_updates.py
+++ b/pypika/tests/test_updates.py
@@ -25,3 +25,7 @@ class UpdateTests(unittest.TestCase):
         q = Query.update(table).set(table.foo, 1).where(table.foo == 0)
 
         self.assertEqual('UPDATE "schema1"."abc" SET "foo"=1 WHERE "foo"=0', str(q))
+
+    def test_update_with_none(self):
+        q = Query.update('abc').set('foo', None)
+        self.assertEqual('UPDATE "abc" SET "foo"=null', str(q))
Repository: kayak/pypika
Pull request #102 (2018-03-08 16:12:22)

Added not like function for negating wildcard queries
Added not like function for negating wildcard queries


Patch:
diff --git a/pypika/enums.py b/pypika/enums.py
index e96a283..66684da 100644
--- a/pypika/enums.py
+++ b/pypika/enums.py
@@ -26,6 +26,7 @@ class Equality(Comparator):
 
 
 class Matching(Comparator):
+    not_like = ' NOT LIKE '
     like = ' LIKE '
     regex = ' REGEX '
     bin_regex = ' REGEX BINARY '
diff --git a/pypika/terms.py b/pypika/terms.py
index 795caaf..b0723cf 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -99,6 +99,9 @@ class Term(object):
     def like(self, expr):
         return BasicCriterion(Matching.like, self, self._wrap(expr))
 
+    def not_like(self, expr):
+        return BasicCriterion(Matching.not_like, self, self._wrap(expr))
+
     def regex(self, pattern):
         return BasicCriterion(Matching.regex, self, self._wrap(pattern))
 


Test patch:
diff --git a/pypika/tests/test_selects.py b/pypika/tests/test_selects.py
index 73fe402..f3e926f 100644
--- a/pypika/tests/test_selects.py
+++ b/pypika/tests/test_selects.py
@@ -223,6 +223,26 @@ class WhereTests(unittest.TestCase):
 
         self.assertEqual("SELECT * FROM \"abc\" WHERE \"foo\" LIKE '___'", str(q))
 
+    def test_where_field_does_not_start_with(self):
+        q = Query.from_(self.t).select(self.t.star).where(self.t.foo.not_like('ab%'))
+
+        self.assertEqual("SELECT * FROM \"abc\" WHERE \"foo\" NOT LIKE 'ab%'", str(q))
+
+    def test_where_field_does_not_contain(self):
+        q = Query.from_(self.t).select(self.t.star).where(self.t.foo.not_like('%fg%'))
+
+        self.assertEqual("SELECT * FROM \"abc\" WHERE \"foo\" NOT LIKE '%fg%'", str(q))
+
+    def test_where_field_does_not_end_with(self):
+        q = Query.from_(self.t).select(self.t.star).where(self.t.foo.not_like('%yz'))
+
+        self.assertEqual("SELECT * FROM \"abc\" WHERE \"foo\" NOT LIKE '%yz'", str(q))
+
+    def test_where_field_is_not_n_chars_long(self):
+        q = Query.from_(self.t).select(self.t.star).where(self.t.foo.not_like('___'))
+
+        self.assertEqual("SELECT * FROM \"abc\" WHERE \"foo\" NOT LIKE '___'", str(q))
+
     def test_where_field_matches_regex(self):
         q = Query.from_(self.t).select(self.t.star).where(self.t.foo.regex(r'^b'))
Repository: kayak/pypika
Pull request #110 (2018-03-26 10:15:47)

Fixed single quote escaping
I looked through supported dialects and it looks like all of them escape single quote by doubling it, so it should be okay without any dialect passing to .get_sql() shenanigans


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index e808c11..326babd 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -222,7 +222,8 @@ class ValueWrapper(Term):
         if isinstance(self.value, date):
             return "'%s'" % self.value.isoformat()
         if isinstance(self.value, basestring):
-            return "'%s'" % self.value
+            value = self.value.replace("'", "''")
+            return "'%s'" % value
         if isinstance(self.value, bool):
             return str.lower(str(self.value))
         if self.value is None:


Test patch:
diff --git a/pypika/tests/test_selects.py b/pypika/tests/test_selects.py
index f3e926f..ab0f96a 100644
--- a/pypika/tests/test_selects.py
+++ b/pypika/tests/test_selects.py
@@ -188,6 +188,11 @@ class WhereTests(unittest.TestCase):
 
         self.assertEqual('SELECT * FROM "abc" WHERE "foo"=1 AND "bar"="baz"', str(q))
 
+    def test_where_single_quote(self):
+        q1 = Query.from_(self.t).select('*').where(self.t.foo == "bar'foo")
+
+        self.assertEqual('SELECT * FROM "abc" WHERE "foo"=\'bar\'\'foo\'', str(q1))
+
     def test_where_field_equals_and(self):
         q = Query.from_(self.t).select('*').where((self.t.foo == 1) & (self.t.bar == self.t.baz))
 
diff --git a/pypika/tests/test_updates.py b/pypika/tests/test_updates.py
index 87a183e..4866893 100644
--- a/pypika/tests/test_updates.py
+++ b/pypika/tests/test_updates.py
@@ -20,6 +20,11 @@ class UpdateTests(unittest.TestCase):
 
         self.assertEqual('UPDATE "abc" SET "foo"=\'bar\'', str(q))
 
+    def test_single_quote_escape_in_set(self):
+        q = Query.update(self.table_abc).set('foo', "bar'foo")
+
+        self.assertEqual('UPDATE "abc" SET "foo"=\'bar\'\'foo\'', str(q))
+
     def test_update__table_schema(self):
         table = Table('abc', 'schema1')
         q = Query.update(table).set(table.foo, 1).where(table.foo == 0)
Repository: kayak/pypika
Pull request #117 (2018-04-04 08:06:54)

add UTC_TIMESTAMP function


Patch:
diff --git a/pypika/functions.py b/pypika/functions.py
index d1b06c5..fb7b7b7 100644
--- a/pypika/functions.py
+++ b/pypika/functions.py
@@ -200,12 +200,17 @@ class RegexpLike(Function):
         super(RegexpLike, self).__init__('REGEXP_LIKE', term, pattern, modifiers, alias=alias)
 
 
-# Date Functions
+# Date/Time Functions
 class Now(Function):
     def __init__(self, alias=None):
         super(Now, self).__init__('NOW', alias=alias)
 
 
+class UtcTimestamp(Function):
+    def __init__(self, alias=None):
+        super(UtcTimestamp, self).__init__('UTC_TIMESTAMP', alias=alias)
+
+
 class CurDate(Function):
     def __init__(self, alias=None):
         super(CurDate, self).__init__('CURRENT_DATE', alias=alias)


Test patch:
diff --git a/pypika/tests/test_functions.py b/pypika/tests/test_functions.py
index 8b4eb45..867bbab 100644
--- a/pypika/tests/test_functions.py
+++ b/pypika/tests/test_functions.py
@@ -11,6 +11,7 @@ from pypika import (
     Table as T,
     VerticaQuery,
     functions as fn,
+    Query,
 )
 from pypika.enums import (
     Dialects,
@@ -601,3 +602,23 @@ class DateFunctionsTests(unittest.TestCase):
     def test_date_add(self):
         a = fn.DateAdd('year', 1, '2017-10-01')
         self.assertEqual(str(a), "DATE_ADD('year',1,'2017-10-01')")
+
+    def test_now(self):
+        query = Query.select(fn.Now())
+
+        self.assertEqual("SELECT NOW()", str(query))
+
+    def test_utc_timestamp(self):
+        query = Query.select(fn.UtcTimestamp())
+
+        self.assertEqual("SELECT UTC_TIMESTAMP()", str(query))
+
+    def test_current_date(self):
+        query = Query.select(fn.CurDate())
+
+        self.assertEqual("SELECT CURRENT_DATE()", str(query))
+
+    def test_current_time(self):
+        query = Query.select(fn.CurTime())
+
+        self.assertEqual("SELECT CURRENT_TIME()", str(query))
Repository: kayak/pypika
Pull request #118 (2018-04-16 11:09:44)

Changed `Not` to inherit from Criterion
Currently `Not` does not inherit from `Criterion`, and because of it you can't build complex criterion from negated term, even though it is valid in sql


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index d90e3a5..afe07de 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -622,7 +622,7 @@ class Case(Term):
         return fields
 
 
-class Not(object):
+class Not(Criterion):
     def __init__(self, term):
         self.term = term
 


Test patch:
diff --git a/pypika/tests/test_selects.py b/pypika/tests/test_selects.py
index ab0f96a..589dd88 100644
--- a/pypika/tests/test_selects.py
+++ b/pypika/tests/test_selects.py
@@ -188,6 +188,18 @@ class WhereTests(unittest.TestCase):
 
         self.assertEqual('SELECT * FROM "abc" WHERE "foo"=1 AND "bar"="baz"', str(q))
 
+    def test_where_field_equals_where_not(self):
+        q = Query.from_(self.t).select('*').where((self.t.foo == 1).negate()).where(self.t.bar == self.t.baz)
+
+        self.assertEqual('SELECT * FROM "abc" WHERE NOT "foo"=1 AND "bar"="baz"', str(q))
+
+    def test_where_field_equals_where_two_not(self):
+        q = Query.from_(self.t).select('*').where(
+            (self.t.foo == 1).negate()
+        ).where((self.t.bar == self.t.baz).negate())
+
+        self.assertEqual('SELECT * FROM "abc" WHERE NOT "foo"=1 AND NOT "bar"="baz"', str(q))
+
     def test_where_single_quote(self):
         q1 = Query.from_(self.t).select('*').where(self.t.foo == "bar'foo")
Repository: kayak/pypika
Pull request #131 (2018-06-14 03:51:50)

Add for_ to base term class.
for_ is not defined for all terms, like ValueWrapper. This adds a noop
to the base for_ definition for convenience.

When running for_ on a criterion, Field("x") > 1, this crashes due to
ValueWrapper not having it implemented.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/17483534/badge)](https://coveralls.io/builds/17483534)

Coverage decreased (-0.2%) to 97.917% when pulling **c6b2effaa2ca1054ec81ba0a14c75e75f7a74922 on tobymao:safer_for** into **3df72fc725039593f9b469127a5e25264b6a8f96 on kayak:master**.


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index c0c7707..45c8da0 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -69,6 +69,17 @@ class Term(object):
 
         return ValueWrapper(val)
 
+    def for_(self, table):
+        """
+        Replaces the tables of this term for the table parameter provided.  The base implementation returns self because not all terms have a table property.
+
+        :param table:
+            The table to replace with.
+        :return:
+            Self.
+        """
+        return self
+
     def fields(self):
         return [self]
 
@@ -678,9 +689,7 @@ class Function(Term):
         :return:
             A copy of the field with it's table value replaced.
         """
-        self.args = [param.for_(table) if hasattr(param, 'for_')
-                     else param
-                     for param in self.args]
+        self.args = [param.for_(table) for param in self.args]
 
     def get_special_params_sql(self, **kwargs):
         pass


Test patch:
diff --git a/pypika/tests/test_criterions.py b/pypika/tests/test_criterions.py
index aa2f901..7b96b9d 100644
--- a/pypika/tests/test_criterions.py
+++ b/pypika/tests/test_criterions.py
@@ -237,6 +237,11 @@ class CriterionTests(unittest.TestCase):
         self.assertEqual('"foo">=1', str(c1))
         self.assertEqual('"crit"."foo">=-1', str(c2))
 
+    def test__criterion_for_with_value(self):
+        c = (Field('foo') > 1).for_(self.t)
+        self.assertEqual(c.left, self.t)
+        self.assertEqual(c.tables_, {self.t})
+
 
 class NotTests(unittest.TestCase):
     table_abc = Table('abc', alias='cx0')
Repository: kayak/pypika
Pull request #142 (2018-07-19 16:37:21)

Fixed the Not term to cause complex criterion to be wrapped in brackets
Changed the behavior of complex criterions when enclosed in a Not term to be wrapped in brackets


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index 799a5fa..193203f 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -139,6 +139,9 @@ class Term(object):
     def negate(self):
         return Not(self)
 
+    def __invert__(self):
+        return Not(self)
+
     def __add__(self, other):
         return ArithmeticExpression(Arithmetic.add, self, self._wrap(other))
 
@@ -666,7 +669,9 @@ class Not(Criterion):
         return self.term.fields() if self.term.fields else []
 
     def get_sql(self, quote_char=None, **kwargs):
-        sql = "NOT {term}".format(term=self.term.get_sql(quote_char=quote_char, **kwargs))
+        kwargs['subcriterion'] = True
+        sql = "NOT {term}".format(term=self.term.get_sql(quote_char=quote_char,
+                                                         **kwargs))
         return alias_sql(sql, self.alias, quote_char=quote_char)
 
     def __str__(self):


Test patch:
diff --git a/pypika/tests/test_criterions.py b/pypika/tests/test_criterions.py
index fbe1928..5a2902a 100644
--- a/pypika/tests/test_criterions.py
+++ b/pypika/tests/test_criterions.py
@@ -293,6 +293,16 @@ class NotTests(unittest.TestCase):
 
         self.assertEqual('NOT "cx0"."foo" IS NULL', str(f))
 
+    def test_not_with_or_criterion(self):
+        self.assertEqual('NOT ("foo" OR "bar")', str(~(Field('foo') | Field('bar'))))
+
+    def test_not_with_and_criterion(self):
+        self.assertEqual('NOT ("foo" AND "bar")', str(~(Field('foo') & Field('bar'))))
+
+    def test_not_with_complex_criterion(self):
+        self.assertEqual('NOT ("foo" AND "bar" AND "fizz" AND "buzz")',
+                         str(~(Field('foo') & Field('bar') & Field('fizz') & Field('buzz'))))
+
 
 class BetweenTests(unittest.TestCase):
     t = Table('abc', alias='btw')
Repository: kayak/pypika
Pull request #150 (2018-08-10 14:36:43)

Mark constants as aggregate values
Changing the denotation of constants and expressions composed of only constants to aggregate.

The purpose of this flag is to know if an expression can be used in a group by query, which constants can.


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index a0d1fc4..127de17 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -216,7 +216,7 @@ class Term(object):
 
 
 class ValueWrapper(Term):
-    is_aggregate = None
+    is_aggregate = True
 
     def __init__(self, value, alias=None):
         super(ValueWrapper, self).__init__(alias)


Test patch:
diff --git a/pypika/tests/test_aggregate.py b/pypika/tests/test_aggregate.py
index e7802aa..e4c2cdb 100644
--- a/pypika/tests/test_aggregate.py
+++ b/pypika/tests/test_aggregate.py
@@ -10,13 +10,13 @@ class IsAggregateTests(unittest.TestCase):
         v = Field('foo')
         self.assertFalse(v.is_aggregate)
 
-    def test__constant_is_neither_aggr_or_not(self):
+    def test__constant_is_aggregate(self):
         v = ValueWrapper(100)
-        self.assertIsNone(v.is_aggregate)
+        self.assertTrue(v.is_aggregate)
 
-    def test__constant_arithmetic_is_neither_aggr_or_not(self):
+    def test__constant_arithmetic_is_aggregate(self):
         v = ValueWrapper(100) + ValueWrapper(100)
-        self.assertIsNone(v.is_aggregate)
+        self.assertTrue(v.is_aggregate)
 
     def test__field_arithmetic_is_not_aggregate(self):
         v = Field('foo') + Field('bar')
@@ -73,10 +73,10 @@ class IsAggregateTests(unittest.TestCase):
 
         self.assertTrue(v.is_aggregate)
 
-    def test__case_all_constants_is_neither_aggr_or_not(self):
+    def test__case_all_constants_is_aggregate(self):
         v = Case() \
             .when(Field('foo') == 1, 1) \
             .when(Field('foo') == 2, 2) \
             .else_(3)
 
-        self.assertIsNone(v.is_aggregate)
+        self.assertTrue(v.is_aggregate)
Repository: kayak/pypika
Pull request #154 (2018-08-27 11:39:26)

tables_ impl in Case
fixed the implementation of tables_ in Case to use sets and add values to it correctly


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index 287df28..2d6fcb8 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -654,16 +654,16 @@ class Case(Term):
 
     @property
     def tables_(self):
-        tables = {}
+        tables = set()
         if self._cases:
-            tables &= {table
+            tables |= {table
                        for case in self._cases
                        for part in case
                        for table in part.tables_
                        if hasattr(part, 'tables_')}
 
         if self._else and hasattr(self._else, 'tables_'):
-            tables &= {table
+            tables |= {table
                        for table in self._else.tables_}
 
         return tables


Test patch:
diff --git a/pypika/tests/test_criterions.py b/pypika/tests/test_criterions.py
index 5a2902a..c9f15d2 100644
--- a/pypika/tests/test_criterions.py
+++ b/pypika/tests/test_criterions.py
@@ -245,11 +245,6 @@ class CriterionTests(unittest.TestCase):
         self.assertEqual('"foo">=1', str(c1))
         self.assertEqual('"crit"."foo">=-1', str(c2))
 
-    def test__criterion_for_with_value(self):
-        c = (Field('foo') > 1).for_(self.t)
-        self.assertEqual(c.left, self.t)
-        self.assertEqual(c.tables_, {self.t})
-
 
 class NotTests(unittest.TestCase):
     table_abc = Table('abc', alias='cx0')
diff --git a/pypika/tests/test_internals.py b/pypika/tests/test_internals.py
new file mode 100644
index 0000000..635a225
--- /dev/null
+++ b/pypika/tests/test_internals.py
@@ -0,0 +1,24 @@
+import unittest
+
+from pypika import (
+    Case,
+    Field,
+    Table,
+)
+
+
+class TablesTests(unittest.TestCase):
+
+    def test__criterion_for_with_value(self):
+        table = Table('a')
+
+        c = (Field('foo') > 1).for_(table)
+        self.assertEqual(c.left, table)
+        self.assertEqual(c.tables_, {table})
+
+    def test__case_tables(self):
+        table = Table('a')
+
+        c = Case().when(table.a == 1, 2 * table.a)
+        self.assertIsInstance(c.tables_, set)
+        self.assertSetEqual(c.tables_, {table})
Repository: kayak/pypika
Pull request #155 (2018-08-28 10:53:15)

Reverted is_aggregate for ValueWrapper to be None
This flag is used to determine if a pypika expression can be or must be part of the group by clause. There are cases where it is ambiguous, as with constants, and that needs to be modeled as well. I had forgotten this and set `is_aggregate=True` when it can be used in the group by clause. Since I need to be able to determine if it can also not be aggregatable in these cases, setting it back to `None`


Patch:
diff --git a/pypika/terms.py b/pypika/terms.py
index 2d6fcb8..c83389b 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -216,7 +216,7 @@ class Term(object):
 
 
 class ValueWrapper(Term):
-    is_aggregate = True
+    is_aggregate = None
 
     def __init__(self, value, alias=None):
         super(ValueWrapper, self).__init__(alias)
@@ -669,7 +669,6 @@ class Case(Term):
         return tables
 
 
-
 class Not(Criterion):
     def __init__(self, term, alias=None):
         super(Not, self).__init__(alias=alias)


Test patch:
diff --git a/pypika/tests/test_aggregate.py b/pypika/tests/test_aggregate.py
index e4c2cdb..1f567cb 100644
--- a/pypika/tests/test_aggregate.py
+++ b/pypika/tests/test_aggregate.py
@@ -10,13 +10,13 @@ class IsAggregateTests(unittest.TestCase):
         v = Field('foo')
         self.assertFalse(v.is_aggregate)
 
-    def test__constant_is_aggregate(self):
+    def test__constant_is_aggregate_none(self):
         v = ValueWrapper(100)
-        self.assertTrue(v.is_aggregate)
+        self.assertIsNone(v.is_aggregate)
 
-    def test__constant_arithmetic_is_aggregate(self):
+    def test__constant_arithmetic_is_aggregate_none(self):
         v = ValueWrapper(100) + ValueWrapper(100)
-        self.assertTrue(v.is_aggregate)
+        self.assertIsNone(v.is_aggregate)
 
     def test__field_arithmetic_is_not_aggregate(self):
         v = Field('foo') + Field('bar')
@@ -73,10 +73,10 @@ class IsAggregateTests(unittest.TestCase):
 
         self.assertTrue(v.is_aggregate)
 
-    def test__case_all_constants_is_aggregate(self):
+    def test__case_all_constants_is_aggregate_none(self):
         v = Case() \
             .when(Field('foo') == 1, 1) \
             .when(Field('foo') == 2, 2) \
             .else_(3)
 
-        self.assertTrue(v.is_aggregate)
+        self.assertIsNone(v.is_aggregate)
Repository: kayak/pypika
Pull request #189 (2018-11-19 17:44:14)

Fix table sql with schema and alias not showing alias.


Patch:
diff --git a/pypika/queries.py b/pypika/queries.py
index 89835ec..75621fb 100644
--- a/pypika/queries.py
+++ b/pypika/queries.py
@@ -113,7 +113,7 @@ class Table(Selectable):
         table_sql = format_quotes(self._table_name, quote_char)
 
         if self._schema is not None:
-            return '{schema}.{table}' \
+            table_sql = '{schema}.{table}' \
                 .format(schema=self._schema.get_sql(quote_char=quote_char, **kwargs),
                         table=table_sql)
         return alias_sql(table_sql, self.alias, quote_char)


Test patch:
diff --git a/pypika/tests/test_selects.py b/pypika/tests/test_selects.py
index 02b7bb9..dea79e9 100644
--- a/pypika/tests/test_selects.py
+++ b/pypika/tests/test_selects.py
@@ -664,6 +664,10 @@ class AliasTests(unittest.TestCase):
                          'GROUP BY "my_foo" '
                          'ORDER BY "my_foo"', str(q))
 
+    def test_table_with_schema_and_alias(self):
+        table = Table('abc', schema='schema', alias='alias')
+        self.assertEqual('"schema"."abc" "alias"', str(table))
+
 
 class SubqueryTests(unittest.TestCase):
     maxDiff = None
Repository: kayak/pypika
Pull request #197 (2019-01-04 13:19:07)

Bracket term
Added a term for holding brackets in order to maintain order of operations in expressions


Patch:
diff --git a/pypika/__init__.py b/pypika/__init__.py
index 0e3cd2f..a917fd3 100644
--- a/pypika/__init__.py
+++ b/pypika/__init__.py
@@ -52,16 +52,18 @@ from .queries import (
     Query,
     Schema,
     Table,
-    Tuple,
     make_tables as Tables,
 )
 # noinspection PyUnresolvedReferences
 from .terms import (
+    Array,
+    Bracket,
     Case,
     Field,
     Interval,
     Not,
     Rollup,
+    Tuple,
 )
 # noinspection PyUnresolvedReferences
 from .utils import (
@@ -75,4 +77,4 @@ from .utils import (
 
 __author__ = "Timothy Heys"
 __email__ = "theys@kayak.com"
-__version__ = "0.18.4"
+__version__ = "0.18.3"
diff --git a/pypika/terms.py b/pypika/terms.py
index bb441af..0ddb67e 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -368,7 +368,7 @@ class Star(Field):
         return '*'
 
 
-class Tuple(Term):
+class Tuple(Criterion):
     def __init__(self, *values):
         super(Tuple, self).__init__()
         self.values = [self.wrap_constant(value) for value in values]
@@ -394,6 +394,11 @@ class Array(Tuple):
         )
 
 
+class Bracket(Tuple):
+    def __init__(self, term):
+        super(Bracket, self).__init__(term)
+
+
 class BasicCriterion(Criterion):
     def __init__(self, comparator, left, right, alias=None):
         """
diff --git a/requirements.txt b/requirements.txt
index a9b7146..5cefa40 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,1 +1,1 @@
-aenum==2.1.2
+aenum==2.0.8


Test patch:
diff --git a/pypika/tests/test_tuples.py b/pypika/tests/test_tuples.py
index d9855fd..3f09fce 100644
--- a/pypika/tests/test_tuples.py
+++ b/pypika/tests/test_tuples.py
@@ -1,8 +1,11 @@
-# coding: utf-8
-
 import unittest
 
-from pypika import Query, Tables, Tuple
+from pypika import (
+    Bracket,
+    Query,
+    Tables,
+    Tuple,
+)
 
 
 class TupleTests(unittest.TestCase):
@@ -66,3 +69,14 @@ class TupleTests(unittest.TestCase):
 
         self.assertEqual('SELECT * FROM "abc" JOIN "efg" ON "abc"."foo"="efg"."bar" '
                          'WHERE ("abc"."foo","efg"."bar") IN ((1,1),(2,2),(3,3))', str(query))
+
+
+class BracketTests(unittest.TestCase):
+    table_abc, table_efg = Tables('abc', 'efg')
+
+    def test_arithmetic_with_brackets(self):
+        q = Query \
+            .from_(self.table_abc) \
+            .select(Bracket(self.table_abc.foo / 2) / 2)
+
+        self.assertEqual('SELECT ("foo"/2)/2 FROM "abc"', str(q))