Skip to content

Commit e1b5482

Browse files
committed
(postgresql) Add support for square bracket array indexing, by darikg.
Pull request andialbrecht#170 with trivial conflicts resolved.
1 parent 1ebad53 commit e1b5482

5 files changed

Lines changed: 61 additions & 1 deletion

File tree

sqlparse/engine/grouping.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,12 @@ def _consume_cycle(tl, i):
156156
x = itertools.cycle((
157157
lambda y: (y.match(T.Punctuation, '.')
158158
or y.ttype is T.Operator
159-
or y.ttype is T.Wildcard),
159+
or y.ttype is T.Wildcard
160+
or y.ttype is T.ArrayIndex),
160161
lambda y: (y.ttype in (T.String.Symbol,
161162
T.Name,
162163
T.Wildcard,
164+
T.ArrayIndex,
163165
T.Literal.String.Single,
164166
T.Literal.Number.Integer,
165167
T.Literal.Number.Float)

sqlparse/lexer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class Lexer(object):
195195
(r"'(''|\\'|[^'])*'", tokens.String.Single),
196196
# not a real string literal in ANSI SQL:
197197
(r'(""|".*?[^\\]")', tokens.String.Symbol),
198+
(r'(?<=[\w\]])(\[[^\]]*?\])', tokens.Punctuation.ArrayIndex),
198199
(r'(\[[^\]]+\])', tokens.Name),
199200
(r'((LEFT\s+|RIGHT\s+|FULL\s+)?(INNER\s+|OUTER\s+|STRAIGHT\s+)?|(CROSS\s+|NATURAL\s+)?)?JOIN\b', tokens.Keyword),
200201
(r'END(\s+IF|\s+LOOP)?\b', tokens.Keyword),

sqlparse/sql.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,13 @@ def get_ordering(self):
502502
return None
503503
return ordering.value.upper()
504504

505+
def get_array_indices(self):
506+
"""Returns an iterator of index expressions as strings"""
507+
508+
# Use [1:-1] index to discard the square brackets
509+
return (tok.value[1:-1] for tok in self.tokens
510+
if tok.ttype in T.ArrayIndex)
511+
505512

506513
class IdentifierList(TokenList):
507514
"""A list of :class:`~sqlparse.sql.Identifier`\'s."""

sqlparse/tokens.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __repr__(self):
5757
String = Literal.String
5858
Number = Literal.Number
5959
Punctuation = Token.Punctuation
60+
ArrayIndex = Punctuation.ArrayIndex
6061
Operator = Token.Operator
6162
Comparison = Operator.Comparison
6263
Wildcard = Token.Wildcard

tests/test_parse.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,52 @@ def test_single_quotes_with_linebreaks(): # issue118
205205
assert len(p) == 1
206206
assert p[0].ttype is T.String.Single
207207

208+
209+
def test_array_indexed_column():
210+
# Make sure we still parse sqlite style escapes
211+
p = sqlparse.parse('[col1],[col2]')[0].tokens
212+
assert (len(p) == 1
213+
and isinstance(p[0], sqlparse.sql.IdentifierList)
214+
and [id.get_name() for id in p[0].get_identifiers()]
215+
== ['[col1]', '[col2]'])
216+
217+
p = sqlparse.parse('[col1]+[col2]')[0]
218+
types = [tok.ttype for tok in p.flatten()]
219+
assert types == [T.Name, T.Operator, T.Name]
220+
221+
p = sqlparse.parse('col[1]')[0].tokens
222+
assert (len(p) == 1
223+
and tuple(p[0].get_array_indices()) == ('1',)
224+
and p[0].get_name() == 'col')
225+
226+
p = sqlparse.parse('col[1][1:5] as mycol')[0].tokens
227+
assert (len(p) == 1
228+
and tuple(p[0].get_array_indices()) == ('1', '1:5')
229+
and p[0].get_name() == 'mycol'
230+
and p[0].get_real_name() == 'col')
231+
232+
p = sqlparse.parse('col[1][other_col]')[0].tokens
233+
assert len(p) == 1 and tuple(p[0].get_array_indices()) == ('1', 'other_col')
234+
235+
sql = 'SELECT col1, my_1d_array[2] as alias1, my_2d_array[2][5] as alias2'
236+
p = sqlparse.parse(sql)[0].tokens
237+
assert len(p) == 3 and isinstance(p[2], sqlparse.sql.IdentifierList)
238+
ids = list(p[2].get_identifiers())
239+
assert (ids[0].get_name() == 'col1'
240+
and tuple(ids[0].get_array_indices()) == ()
241+
and ids[1].get_name() == 'alias1'
242+
and ids[1].get_real_name() == 'my_1d_array'
243+
and tuple(ids[1].get_array_indices()) == ('2',)
244+
and ids[2].get_name() == 'alias2'
245+
and ids[2].get_real_name() == 'my_2d_array'
246+
and tuple(ids[2].get_array_indices()) == ('2', '5'))
247+
248+
249+
def test_typed_array_definition():
250+
# array indices aren't grouped with builtins, but make sure we can extract
251+
# indentifer names
252+
p = sqlparse.parse('x int, y int[], z int')[0]
253+
names = [x.get_name() for x in p.get_sublists()]
254+
assert names == ['x', 'y', 'z']
255+
256+

0 commit comments

Comments
 (0)