Skip to content

Commit 39ddba7

Browse files
committed
Improve handling of quoted indentifiers (fixes issue78).
1 parent 813782f commit 39ddba7

4 files changed

Lines changed: 66 additions & 10 deletions

File tree

CHANGES

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
Development Version
2+
-------------------
3+
4+
Bug Fixes
5+
* Improve handling of quoted identifiers (issue78).
6+
7+
Other
8+
* Deprecated sqlparse.SQLParseError. Please use
9+
sqlparse.exceptions.SQLParseError instead.
10+
11+
12+
113
Release 0.1.4 (Apr 20, 2012)
214
----------------------------
315

@@ -14,10 +26,6 @@ Bug Fixes
1426
* Pretty format comments in identifier lists (issue59).
1527
* Several minor bug fixes and improvements.
1628

17-
Other
18-
* Deprecated sqlparse.SQLParseError. Please use
19-
sqlparse.exceptions.SQLParseError instead.
20-
2129

2230
Release 0.1.3 (Jul 29, 2011)
2331
----------------------------

sqlparse/sql.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ def _pprint_tree(self, max_depth=None, depth=0):
169169
if (token.is_group() and (max_depth is None or depth < max_depth)):
170170
token._pprint_tree(max_depth, depth + 1)
171171

172+
def _remove_quotes(self, val):
173+
"""Helper that removes surrounding quotes from strings."""
174+
if not val:
175+
return val
176+
if val[0] in ('"', '\'') and val[-1] == val[0]:
177+
val = val[1:-1]
178+
return val
179+
172180
def flatten(self):
173181
"""Generator yielding ungrouped tokens.
174182
@@ -362,11 +370,13 @@ def get_alias(self):
362370
else:
363371
next_ = self.token_next_by_instance(0, Identifier)
364372
if next_ is None:
365-
return None
373+
next_ = self.token_next_by_type(0, T.String.Symbol)
374+
if next_ is None:
375+
return None
366376
alias = next_
367377
if isinstance(alias, Identifier):
368378
return alias.get_name()
369-
return unicode(alias)
379+
return self._remove_quotes(unicode(alias))
370380

371381
def get_name(self):
372382
"""Returns the name of this identifier.
@@ -385,13 +395,16 @@ def get_real_name(self):
385395
# a.b
386396
dot = self.token_next_match(0, T.Punctuation, '.')
387397
if dot is None:
388-
return self.token_next_by_type(0, T.Name).value
398+
next_ = self.token_next_by_type(0, T.Name)
399+
if next_ is not None:
400+
return self._remove_quotes(next_.value)
401+
return None
389402

390403
next_ = self.token_next_by_type(self.token_index(dot),
391-
(T.Name, T.Wildcard))
404+
(T.Name, T.Wildcard, T.String.Symbol))
392405
if next_ is None: # invalid identifier, e.g. "a."
393406
return None
394-
return next_.value
407+
return self._remove_quotes(next_.value)
395408

396409

397410
class Statement(TokenList):
@@ -437,7 +450,7 @@ def get_parent_name(self):
437450
prev_ = self.token_prev(self.token_index(dot))
438451
if prev_ is None: # something must be verry wrong here..
439452
return None
440-
return prev_.value
453+
return self._remove_quotes(prev_.value)
441454

442455
def is_wildcard(self):
443456
"""Return ``True`` if this identifier contains a wildcard."""

tests/test_parse.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,10 @@ def test_keyword_like_identifier(self): # see issue47
106106
t = sqlparse.parse('foo.key')[0].tokens
107107
self.assertEqual(len(t), 1)
108108
self.assert_(isinstance(t[0], sqlparse.sql.Identifier))
109+
110+
111+
def test_quoted_identifier():
112+
t = sqlparse.parse('select x.y as "z" from foo')[0].tokens
113+
assert isinstance(t[2], sqlparse.sql.Identifier)
114+
assert t[2].get_name() == 'z'
115+
assert t[2].get_real_name() == 'y'

tests/test_regressions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,31 @@ def test_issue40(self):
106106
' (SELECT id,\n'
107107
' name\n'
108108
' FROM bar) as foo'))
109+
110+
111+
def test_issue78():
112+
# the bug author provided this nice examples, let's use them!
113+
def _get_identifier(sql):
114+
p = sqlparse.parse(sql)[0]
115+
return p.tokens[2]
116+
results = (('get_name', 'z'),
117+
('get_real_name', 'y'),
118+
('get_parent_name', 'x'),
119+
('get_alias', 'z'),
120+
('get_typecast', 'text'))
121+
variants = (
122+
'select x.y::text as z from foo',
123+
'select x.y::text as "z" from foo',
124+
'select x."y"::text as z from foo',
125+
'select x."y"::text as "z" from foo',
126+
'select "x".y::text as z from foo',
127+
'select "x".y::text as "z" from foo',
128+
'select "x"."y"::text as z from foo',
129+
'select "x"."y"::text as "z" from foo',
130+
)
131+
for variant in variants:
132+
i = _get_identifier(variant)
133+
assert isinstance(i, sql.Identifier)
134+
for func_name, result in results:
135+
func = getattr(i, func_name)
136+
assert func() == result

0 commit comments

Comments
 (0)