Skip to content

Commit 2d72b7a

Browse files
committed
Merge pull request andialbrecht#171 from darikg/alias_bugfix
Fix andialbrecht#167
2 parents 51871a8 + 6f134c6 commit 2d72b7a

3 files changed

Lines changed: 92 additions & 42 deletions

File tree

sqlparse/engine/grouping.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,10 @@ def _consume_cycle(tl, i):
155155
# TODO: Usage of Wildcard token is ambivalent here.
156156
x = itertools.cycle((
157157
lambda y: (y.match(T.Punctuation, '.')
158-
or y.ttype is T.Operator
159-
or y.ttype is T.Wildcard
160-
or y.ttype is T.ArrayIndex),
158+
or y.ttype in (T.Operator,
159+
T.Wildcard,
160+
T.ArrayIndex,
161+
T.Name)),
161162
lambda y: (y.ttype in (T.String.Symbol,
162163
T.Name,
163164
T.Wildcard,

sqlparse/sql.py

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -390,21 +390,17 @@ def has_alias(self):
390390

391391
def get_alias(self):
392392
"""Returns the alias for this identifier or ``None``."""
393+
394+
# "name AS alias"
393395
kw = self.token_next_match(0, T.Keyword, 'AS')
394396
if kw is not None:
395-
alias = self.token_next(self.token_index(kw))
396-
if alias is None:
397-
return None
398-
else:
399-
next_ = self.token_next_by_instance(0, Identifier)
400-
if next_ is None:
401-
next_ = self.token_next_by_type(0, T.String.Symbol)
402-
if next_ is None:
403-
return None
404-
alias = next_
405-
if isinstance(alias, Identifier):
406-
return alias.get_name()
407-
return self._remove_quotes(unicode(alias))
397+
return self._get_first_name(kw, keywords=True)
398+
399+
# "name alias" or "complicated column expression alias"
400+
if len(self.tokens) > 2:
401+
return self._get_first_name(reverse=True)
402+
403+
return None
408404

409405
def get_name(self):
410406
"""Returns the name of this identifier.
@@ -422,18 +418,43 @@ def get_real_name(self):
422418
"""Returns the real name (object name) of this identifier."""
423419
# a.b
424420
dot = self.token_next_match(0, T.Punctuation, '.')
421+
if dot is not None:
422+
return self._get_first_name(self.token_index(dot))
423+
424+
return self._get_first_name()
425+
426+
def get_parent_name(self):
427+
"""Return name of the parent object if any.
428+
429+
A parent object is identified by the first occuring dot.
430+
"""
431+
dot = self.token_next_match(0, T.Punctuation, '.')
425432
if dot is None:
426-
next_ = self.token_next_by_type(0, T.Name)
427-
if next_ is not None:
428-
return self._remove_quotes(next_.value)
429433
return None
430-
431-
next_ = self.token_next_by_type(self.token_index(dot),
432-
(T.Name, T.Wildcard, T.String.Symbol))
433-
if next_ is None: # invalid identifier, e.g. "a."
434+
prev_ = self.token_prev(self.token_index(dot))
435+
if prev_ is None: # something must be verry wrong here..
434436
return None
435-
return self._remove_quotes(next_.value)
437+
return self._remove_quotes(prev_.value)
438+
439+
def _get_first_name(self, idx=None, reverse=False, keywords=False):
440+
"""Returns the name of the first token with a name"""
436441

442+
if idx and not isinstance(idx, int):
443+
idx = self.token_index(idx) + 1
444+
445+
tokens = self.tokens[idx:] if idx else self.tokens
446+
tokens = reversed(tokens) if reverse else tokens
447+
types = [T.Name, T.Wildcard, T.String.Symbol]
448+
449+
if keywords:
450+
types.append(T.Keyword)
451+
452+
for tok in tokens:
453+
if tok.ttype in types:
454+
return self._remove_quotes(tok.value)
455+
elif isinstance(tok, Identifier) or isinstance(tok, Function):
456+
return tok.get_name()
457+
return None
437458

438459
class Statement(TokenList):
439460
"""Represents a SQL statement."""
@@ -467,19 +488,6 @@ class Identifier(TokenList):
467488

468489
__slots__ = ('value', 'ttype', 'tokens')
469490

470-
def get_parent_name(self):
471-
"""Return name of the parent object if any.
472-
473-
A parent object is identified by the first occuring dot.
474-
"""
475-
dot = self.token_next_match(0, T.Punctuation, '.')
476-
if dot is None:
477-
return None
478-
prev_ = self.token_prev(self.token_index(dot))
479-
if prev_ is None: # something must be verry wrong here..
480-
return None
481-
return self._remove_quotes(prev_.value)
482-
483491
def is_wildcard(self):
484492
"""Return ``True`` if this identifier contains a wildcard."""
485493
token = self.token_next_by_type(0, T.Wildcard)

tests/test_grouping.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ def test_parenthesis(self):
1515
s = 'select (select (x3) x2) and (y2) bar'
1616
parsed = sqlparse.parse(s)[0]
1717
self.ndiffAssertEqual(s, str(parsed))
18-
self.assertEqual(len(parsed.tokens), 9)
18+
self.assertEqual(len(parsed.tokens), 7)
1919
self.assert_(isinstance(parsed.tokens[2], sql.Parenthesis))
20-
self.assert_(isinstance(parsed.tokens[-3], sql.Parenthesis))
21-
self.assertEqual(len(parsed.tokens[2].tokens), 7)
22-
self.assert_(isinstance(parsed.tokens[2].tokens[3], sql.Parenthesis))
20+
self.assert_(isinstance(parsed.tokens[-1], sql.Identifier))
21+
self.assertEqual(len(parsed.tokens[2].tokens), 5)
22+
self.assert_(isinstance(parsed.tokens[2].tokens[3], sql.Identifier))
23+
self.assert_(isinstance(parsed.tokens[2].tokens[3].tokens[0], sql.Parenthesis))
2324
self.assertEqual(len(parsed.tokens[2].tokens[3].tokens), 3)
2425

2526
def test_comments(self):
@@ -145,7 +146,7 @@ def test_where(self):
145146
s = 'select x from (select y from foo where bar = 1) z'
146147
p = sqlparse.parse(s)[0]
147148
self.ndiffAssertEqual(s, unicode(p))
148-
self.assertTrue(isinstance(p.tokens[-3].tokens[-2], sql.Where))
149+
self.assertTrue(isinstance(p.tokens[-1].tokens[0].tokens[-2], sql.Where))
149150

150151
def test_typecast(self):
151152
s = 'select foo::integer from bar'
@@ -345,3 +346,43 @@ def test_nested_begin():
345346
assert inner.tokens[0].value == 'BEGIN'
346347
assert inner.tokens[-1].value == 'END'
347348
assert isinstance(inner, sql.Begin)
349+
350+
351+
def test_aliased_column_without_as():
352+
p = sqlparse.parse('foo bar')[0].tokens
353+
assert len(p) == 1
354+
assert p[0].get_real_name() == 'foo'
355+
assert p[0].get_alias() == 'bar'
356+
357+
p = sqlparse.parse('foo.bar baz')[0].tokens[0]
358+
assert p.get_parent_name() == 'foo'
359+
assert p.get_real_name() == 'bar'
360+
assert p.get_alias() == 'baz'
361+
362+
363+
def test_qualified_function():
364+
p = sqlparse.parse('foo()')[0].tokens[0]
365+
assert p.get_parent_name() is None
366+
assert p.get_real_name() == 'foo'
367+
368+
p = sqlparse.parse('foo.bar()')[0].tokens[0]
369+
assert p.get_parent_name() == 'foo'
370+
assert p.get_real_name() == 'bar'
371+
372+
373+
def test_aliased_function_without_as():
374+
p = sqlparse.parse('foo() bar')[0].tokens[0]
375+
assert p.get_parent_name() is None
376+
assert p.get_real_name() == 'foo'
377+
assert p.get_alias() == 'bar'
378+
379+
p = sqlparse.parse('foo.bar() baz')[0].tokens[0]
380+
assert p.get_parent_name() == 'foo'
381+
assert p.get_real_name() == 'bar'
382+
assert p.get_alias() == 'baz'
383+
384+
385+
def test_aliased_literal_without_as():
386+
p = sqlparse.parse('1 foo')[0].tokens
387+
assert len(p) == 1
388+
assert p[0].get_alias() == 'foo'

0 commit comments

Comments
 (0)