Repository: andialbrecht/sqlparse

Issue:
Space removed in the extract presto function after query formatting. 
```
sqlparse.format( "SELECT extract(HOUR from from_unixtime(hour_ts) AT TIME ZONE 'America/Los_Angeles') from table", reindent=True)
```
removes space between `from` and `from_unixtime(hour_ts` in the 0.3.1 version, works fine in 0.3.0

Related superset PR: https://github.com/apache/incubator-superset/pull/10165

Expected
```
SELECT extract(HOUR
               from from_unixtime(hour_ts) AT TIME ZONE 'America/Los_Angeles')
from table
```

Actual
```
SELECT extract(HOUR
               fromfrom_unixtime(hour_ts) AT TIME ZONE 'America/Los_Angeles')
from table
```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/andialbrecht/sqlparse/pull/676?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht) Report
> Merging [#676](https://codecov.io/gh/andialbrecht/sqlparse/pull/676?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht) (38d4210) into [master](https://codecov.io/gh/andialbrecht/sqlparse/commit/9d2cb6fc950386e9e59f29faf0d3742c4b12572c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht) (9d2cb6f) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master     #676   +/-   ##
=======================================
  Coverage   96.82%   96.83%           
=======================================
  Files          20       20           
  Lines        1514     1516    +2     
=======================================
+ Hits         1466     1468    +2     
  Misses         48       48           
```


| [Impacted Files](https://codecov.io/gh/andialbrecht/sqlparse/pull/676?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht) | Coverage Δ | |
|---|---|---|
| [sqlparse/engine/grouping.py](https://codecov.io/gh/andialbrecht/sqlparse/pull/676/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht#diff-c3FscGFyc2UvZW5naW5lL2dyb3VwaW5nLnB5) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Andi+Albrecht)


Patch:
diff --git a/sqlparse/engine/grouping.py b/sqlparse/engine/grouping.py
index 175ae8e..2fb0a4c 100644
--- a/sqlparse/engine/grouping.py
+++ b/sqlparse/engine/grouping.py
@@ -91,13 +91,20 @@ def group_tzcasts(tlist):
     def match(token):
         return token.ttype == T.Keyword.TZCast
 
-    def valid(token):
+    def valid_prev(token):
         return token is not None
 
+    def valid_next(token):
+        return token is not None and (
+            token.is_whitespace
+            or token.match(T.Keyword, 'AS')
+            or token.match(*sql.TypedLiteral.M_CLOSE)
+        )
+
     def post(tlist, pidx, tidx, nidx):
         return pidx, nidx
 
-    _group(tlist, sql.Identifier, match, valid, valid, post)
+    _group(tlist, sql.Identifier, match, valid_prev, valid_next, post)
 
 
 def group_typed_literal(tlist):


Test patch:
diff --git a/tests/test_regressions.py b/tests/test_regressions.py
index 38d1840..4ffc69f 100644
--- a/tests/test_regressions.py
+++ b/tests/test_regressions.py
@@ -401,6 +401,15 @@ def test_issue489_tzcasts():
     assert p.tokens[-1].get_alias() == 'foo'
 
 
+def test_issue562_tzcasts():
+    # Test that whitespace between 'from' and 'bar' is retained
+    formatted = sqlparse.format(
+        'SELECT f(HOUR from bar AT TIME ZONE \'UTC\') from foo', reindent=True
+    )
+    assert formatted == \
+           'SELECT f(HOUR\n         from bar AT TIME ZONE \'UTC\')\nfrom foo'
+
+
 def test_as_in_parentheses_indents():
     # did raise NoneType has no attribute is_group in _process_parentheses
     formatted = sqlparse.format('(as foo)', reindent=True)