Repository: kayak/pypika
Pull request #74 (2017-09-27 15:43:59)

Added Amazon Redshift dialect and timestamp/date add functions


Patch:
diff --git a/pypika/__init__.py b/pypika/__init__.py
index ee1f3b3..662fbcd 100644
--- a/pypika/__init__.py
+++ b/pypika/__init__.py
@@ -31,28 +31,40 @@ pypika.utils
 This contains all of the utility classes such as exceptions and decorators.
 
 """
-from .enums import (Order,
-                    JoinType,
-                    DatePart)
-from .queries import (Query,
-                      Table,
-                      make_tables as Tables,
-                      Tuple)
-from .terms import (Field,
-                    Case,
-                    Not,
-                    Interval,
-                    Rollup)
-from .utils import (JoinException,
-                    GroupingException,
-                    CaseException,
-                    UnionException,
-                    RollupException)
-from .dialects import (MSSQLQuery,
-                       MySQLQuery,
-                       OracleQuery,
-                       PostgreSQLQuery,
-                       VerticaQuery)
+from .dialects import (
+    Dialects,
+    MSSQLQuery,
+    MySQLQuery,
+    OracleQuery,
+    PostgreSQLQuery,
+    RedshiftQuery,
+    VerticaQuery,
+)
+from .enums import (
+    DatePart,
+    JoinType,
+    Order,
+)
+from .queries import (
+    Query,
+    Table,
+    Tuple,
+    make_tables as Tables,
+)
+from .terms import (
+    Case,
+    Field,
+    Interval,
+    Not,
+    Rollup,
+)
+from .utils import (
+    CaseException,
+    GroupingException,
+    JoinException,
+    RollupException,
+    UnionException,
+)
 
 __author__ = "Timothy Heys"
 __email__ = "theys@kayak.com"
diff --git a/pypika/dialects.py b/pypika/dialects.py
index e9758fc..75dc4bb 100644
--- a/pypika/dialects.py
+++ b/pypika/dialects.py
@@ -45,6 +45,16 @@ class PostgreSQLQuery(Query):
         return QueryBuilder(dialect=Dialects.POSTGRESQL)
 
 
+class RedshiftQuery(Query):
+    """
+    Defines a query class for use with Amazon Redshift.
+    """
+
+    @classmethod
+    def _builder(cls):
+        return QueryBuilder(dialect=Dialects.REDSHIFT)
+
+
 class MSSQLQuery(Query):
     """
     Defines a query class for use with Microsoft SQL Server.
diff --git a/pypika/enums.py b/pypika/enums.py
index 3945289..9679664 100644
--- a/pypika/enums.py
+++ b/pypika/enums.py
@@ -81,3 +81,4 @@ class Dialects(Enum):
     MSSQL = 'mssql'
     MYSQL = 'mysql'
     POSTGRESQL = 'postgressql'
+    REDSHIFT = 'redshift'
diff --git a/pypika/functions.py b/pypika/functions.py
index f21a083..2414796 100644
--- a/pypika/functions.py
+++ b/pypika/functions.py
@@ -107,11 +107,21 @@ class DateDiff(Function):
         super(DateDiff, self).__init__('DATEDIFF', interval, start_date, end_date, alias=alias)
 
 
+class DateAdd(Function):
+    def __init__(self, date_part, interval, term, alias=None):
+        super(DateAdd, self).__init__('DATE_ADD', date_part, interval, term, alias=alias)
+
+
 class Timestamp(Function):
     def __init__(self, term, alias=None):
         super(Timestamp, self).__init__('TIMESTAMP', term, alias=alias)
 
 
+class TimestampAdd(Function):
+    def __init__(self, date_part, interval, term, alias=None):
+        super(TimestampAdd, self).__init__('TIMESTAMPADD', date_part, interval, term, alias=alias)
+
+
 # String Functions
 class Ascii(Function):
     def __init__(self, term, alias=None):
diff --git a/pypika/terms.py b/pypika/terms.py
index dc56f3d..f34c5a7 100644
--- a/pypika/terms.py
+++ b/pypika/terms.py
@@ -821,7 +821,9 @@ class Interval(object):
 
     trim_pattern = re.compile(r'^[0\-\.: ]+|[0\-\.: ]+$')
 
-    def __init__(self, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0, quarters=0, weeks=0):
+    def __init__(self, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0, quarters=0, weeks=0,
+                 dialect=None):
+        self.dialect = dialect
         self.largest = None
         self.smallest = None
 
@@ -847,7 +849,7 @@ class Interval(object):
         return []
 
     def get_sql(self, **kwargs):
-        dialect = kwargs.get('dialect')
+        dialect = self.dialect or kwargs.get('dialect')
 
         if hasattr(self, 'quarters'):
             expr = getattr(self, 'quarters')
@@ -880,6 +882,11 @@ class Interval(object):
             # MySQL requires no single quotes around the expr and unit
             Dialects.MYSQL: 'INTERVAL {expr} {unit}',
 
+            # PostgreSQL, Redshift and Vertica require quotes around the expr and unit e.g. INTERVAL '1 week'
+            Dialects.POSTGRESQL: 'INTERVAL \'{expr} {unit}\'',
+            Dialects.REDSHIFT: 'INTERVAL \'{expr} {unit}\'',
+            Dialects.VERTICA: 'INTERVAL \'{expr} {unit}\'',
+
             # Oracle requires just single quotes around the expr
             Dialects.ORACLE: 'INTERVAL \'{expr}\' {unit}'
         }


Test patch:
diff --git a/pypika/tests/test_functions.py b/pypika/tests/test_functions.py
index e46cc7b..a9c50db 100644
--- a/pypika/tests/test_functions.py
+++ b/pypika/tests/test_functions.py
@@ -531,6 +531,14 @@ class DateFunctionsTests(unittest.TestCase):
         c = Interval(days=1).get_sql(dialect=Dialects.VERTICA)
         self.assertEqual("INTERVAL '1 DAY'", str(c))
 
+    def test_redshift_dialect_uses_single_quotes_around_interval(self):
+        c = Interval(days=1).get_sql(dialect=Dialects.REDSHIFT)
+        self.assertEqual("INTERVAL '1 DAY'", str(c))
+
+    def test_postgresql_dialect_uses_single_quotes_around_interval(self):
+        c = Interval(days=1).get_sql(dialect=Dialects.POSTGRESQL)
+        self.assertEqual("INTERVAL '1 DAY'", str(c))
+
     def _test_extract_datepart(self, date_part):
         q = Q.from_(self.t).select(fn.Extract(date_part, self.t.foo))
 
@@ -562,3 +570,11 @@ class DateFunctionsTests(unittest.TestCase):
 
     def test_extract_year(self):
         self._test_extract_datepart(DatePart.year)
+
+    def test_timestampadd(self):
+        a = fn.TimestampAdd('year', 1, '2017-10-01')
+        self.assertEqual(str(a), "TIMESTAMPADD('year',1,'2017-10-01')")
+
+    def test_date_add(self):
+        a = fn.DateAdd('year', 1, '2017-10-01')
+        self.assertEqual(str(a), "DATE_ADD('year',1,'2017-10-01')")
diff --git a/pypika/tests/test_selects.py b/pypika/tests/test_selects.py
index 45baea3..b01dd18 100644
--- a/pypika/tests/test_selects.py
+++ b/pypika/tests/test_selects.py
@@ -1,18 +1,21 @@
 # coding: utf8
 import unittest
 
-from pypika import (Query,
-                    Table,
-                    Tables,
-                    Field as F,
-                    Case,
-                    functions as fn,
-                    Order,
-                    MySQLQuery,
-                    VerticaQuery,
-                    MSSQLQuery,
-                    PostgreSQLQuery,
-                    OracleQuery)
+from pypika import (
+    Query,
+    Table,
+    Tables,
+    Field as F,
+    Case,
+    functions as fn,
+    Order,
+    MySQLQuery,
+    VerticaQuery,
+    MSSQLQuery,
+    PostgreSQLQuery,
+    OracleQuery,
+    RedshiftQuery,
+)
 
 __author__ = "Timothy Heys"
 __email__ = "theys@kayak.com"
@@ -159,11 +162,16 @@ class SelectTests(unittest.TestCase):
 
         self.assertEqual('SELECT "foo","bar" FROM "abc"', str(q))
 
-    def test_postgres_query_uses_double_quote_chars(self):
+    def test_postgresql_query_uses_double_quote_chars(self):
         q = PostgreSQLQuery.from_('abc').select('foo', 'bar')
 
         self.assertEqual('SELECT "foo","bar" FROM "abc"', str(q))
 
+    def test_redshift_query_uses_double_quote_chars(self):
+        q = RedshiftQuery.from_('abc').select('foo', 'bar')
+
+        self.assertEqual('SELECT "foo","bar" FROM "abc"', str(q))
+
 
 class WhereTests(unittest.TestCase):
     t = Table('abc')
@@ -307,6 +315,11 @@ class GroupByTests(unittest.TestCase):
 
         self.assertEqual('SELECT "foo" FROM "abc" GROUP BY "foo"', str(q))
 
+    def test_redshift_query_uses_double_quote_chars(self):
+        q = RedshiftQuery.from_(self.t).groupby(self.t.foo).select(self.t.foo)
+
+        self.assertEqual('SELECT "foo" FROM "abc" GROUP BY "foo"', str(q))
+
 
 class HavingTests(unittest.TestCase):
     table_abc, table_efg = Tables('abc', 'efg')
@@ -404,6 +417,16 @@ class HavingTests(unittest.TestCase):
         )
         self.assertEqual("SELECT \"foo\" FROM \"abc\" GROUP BY \"foo\" HAVING \"buz\"='fiz'", str(q))
 
+    def test_redshift_query_uses_double_quote_chars(self):
+        q = RedshiftQuery.from_(self.table_abc).select(
+            self.table_abc.foo
+        ).groupby(
+            self.table_abc.foo
+        ).having(
+            self.table_abc.buz == 'fiz'
+        )
+        self.assertEqual("SELECT \"foo\" FROM \"abc\" GROUP BY \"foo\" HAVING \"buz\"='fiz'", str(q))
+
 
 class OrderByTests(unittest.TestCase):
     t = Table('abc')