1#
2# Copyright (C) 2009-2020 the sqlparse authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of python-sqlparse and is released under
6# the BSD License: https://opensource.org/licenses/BSD-3-Clause
7
8"""SQL formatter"""
9
10from sqlparse import filters
11from sqlparse.exceptions import SQLParseError
12
13
14def validate_options(options): # noqa: C901
15 """Validates options."""
16 kwcase = options.get('keyword_case')
17 if kwcase not in [None, 'upper', 'lower', 'capitalize']:
18 raise SQLParseError('Invalid value for keyword_case: '
19 '{!r}'.format(kwcase))
20
21 idcase = options.get('identifier_case')
22 if idcase not in [None, 'upper', 'lower', 'capitalize']:
23 raise SQLParseError('Invalid value for identifier_case: '
24 '{!r}'.format(idcase))
25
26 ofrmt = options.get('output_format')
27 if ofrmt not in [None, 'sql', 'python', 'php']:
28 raise SQLParseError('Unknown output format: '
29 '{!r}'.format(ofrmt))
30
31 strip_comments = options.get('strip_comments', False)
32 if strip_comments not in [True, False]:
33 raise SQLParseError('Invalid value for strip_comments: '
34 '{!r}'.format(strip_comments))
35
36 space_around_operators = options.get('use_space_around_operators', False)
37 if space_around_operators not in [True, False]:
38 raise SQLParseError('Invalid value for use_space_around_operators: '
39 '{!r}'.format(space_around_operators))
40
41 strip_ws = options.get('strip_whitespace', False)
42 if strip_ws not in [True, False]:
43 raise SQLParseError('Invalid value for strip_whitespace: '
44 '{!r}'.format(strip_ws))
45
46 truncate_strings = options.get('truncate_strings')
47 if truncate_strings is not None:
48 try:
49 truncate_strings = int(truncate_strings)
50 except (ValueError, TypeError):
51 raise SQLParseError('Invalid value for truncate_strings: '
52 '{!r}'.format(truncate_strings))
53 if truncate_strings <= 1:
54 raise SQLParseError('Invalid value for truncate_strings: '
55 '{!r}'.format(truncate_strings))
56 options['truncate_strings'] = truncate_strings
57 options['truncate_char'] = options.get('truncate_char', '[...]')
58
59 indent_columns = options.get('indent_columns', False)
60 if indent_columns not in [True, False]:
61 raise SQLParseError('Invalid value for indent_columns: '
62 '{!r}'.format(indent_columns))
63 elif indent_columns:
64 options['reindent'] = True # enforce reindent
65 options['indent_columns'] = indent_columns
66
67 reindent = options.get('reindent', False)
68 if reindent not in [True, False]:
69 raise SQLParseError('Invalid value for reindent: '
70 '{!r}'.format(reindent))
71 elif reindent:
72 options['strip_whitespace'] = True
73
74 reindent_aligned = options.get('reindent_aligned', False)
75 if reindent_aligned not in [True, False]:
76 raise SQLParseError('Invalid value for reindent_aligned: '
77 '{!r}'.format(reindent))
78 elif reindent_aligned:
79 options['strip_whitespace'] = True
80
81 indent_after_first = options.get('indent_after_first', False)
82 if indent_after_first not in [True, False]:
83 raise SQLParseError('Invalid value for indent_after_first: '
84 '{!r}'.format(indent_after_first))
85 options['indent_after_first'] = indent_after_first
86
87 indent_tabs = options.get('indent_tabs', False)
88 if indent_tabs not in [True, False]:
89 raise SQLParseError('Invalid value for indent_tabs: '
90 '{!r}'.format(indent_tabs))
91 elif indent_tabs:
92 options['indent_char'] = '\t'
93 else:
94 options['indent_char'] = ' '
95
96 indent_width = options.get('indent_width', 2)
97 try:
98 indent_width = int(indent_width)
99 except (TypeError, ValueError):
100 raise SQLParseError('indent_width requires an integer')
101 if indent_width < 1:
102 raise SQLParseError('indent_width requires a positive integer')
103 options['indent_width'] = indent_width
104
105 wrap_after = options.get('wrap_after', 0)
106 try:
107 wrap_after = int(wrap_after)
108 except (TypeError, ValueError):
109 raise SQLParseError('wrap_after requires an integer')
110 if wrap_after < 0:
111 raise SQLParseError('wrap_after requires a positive integer')
112 options['wrap_after'] = wrap_after
113
114 comma_first = options.get('comma_first', False)
115 if comma_first not in [True, False]:
116 raise SQLParseError('comma_first requires a boolean value')
117 options['comma_first'] = comma_first
118
119 compact = options.get('compact', False)
120 if compact not in [True, False]:
121 raise SQLParseError('compact requires a boolean value')
122 options['compact'] = compact
123
124 right_margin = options.get('right_margin')
125 if right_margin is not None:
126 try:
127 right_margin = int(right_margin)
128 except (TypeError, ValueError):
129 raise SQLParseError('right_margin requires an integer')
130 if right_margin < 10:
131 raise SQLParseError('right_margin requires an integer > 10')
132 options['right_margin'] = right_margin
133
134 return options
135
136
137def build_filter_stack(stack, options):
138 """Setup and return a filter stack.
139
140 Args:
141 stack: :class:`~sqlparse.filters.FilterStack` instance
142 options: Dictionary with options validated by validate_options.
143 """
144 # Token filter
145 if options.get('keyword_case'):
146 stack.preprocess.append(
147 filters.KeywordCaseFilter(options['keyword_case']))
148
149 if options.get('identifier_case'):
150 stack.preprocess.append(
151 filters.IdentifierCaseFilter(options['identifier_case']))
152
153 if options.get('truncate_strings'):
154 stack.preprocess.append(filters.TruncateStringFilter(
155 width=options['truncate_strings'], char=options['truncate_char']))
156
157 if options.get('use_space_around_operators', False):
158 stack.enable_grouping()
159 stack.stmtprocess.append(filters.SpacesAroundOperatorsFilter())
160
161 # After grouping
162 if options.get('strip_comments'):
163 stack.enable_grouping()
164 stack.stmtprocess.append(filters.StripCommentsFilter())
165
166 if options.get('strip_whitespace') or options.get('reindent'):
167 stack.enable_grouping()
168 stack.stmtprocess.append(filters.StripWhitespaceFilter())
169
170 if options.get('reindent'):
171 stack.enable_grouping()
172 stack.stmtprocess.append(
173 filters.ReindentFilter(
174 char=options['indent_char'],
175 width=options['indent_width'],
176 indent_after_first=options['indent_after_first'],
177 indent_columns=options['indent_columns'],
178 wrap_after=options['wrap_after'],
179 comma_first=options['comma_first'],
180 compact=options['compact'],))
181
182 if options.get('reindent_aligned', False):
183 stack.enable_grouping()
184 stack.stmtprocess.append(
185 filters.AlignedIndentFilter(char=options['indent_char']))
186
187 if options.get('right_margin'):
188 stack.enable_grouping()
189 stack.stmtprocess.append(
190 filters.RightMarginFilter(width=options['right_margin']))
191
192 # Serializer
193 if options.get('output_format'):
194 frmt = options['output_format']
195 if frmt.lower() == 'php':
196 fltr = filters.OutputPHPFilter()
197 elif frmt.lower() == 'python':
198 fltr = filters.OutputPythonFilter()
199 else:
200 fltr = None
201 if fltr is not None:
202 stack.postprocess.append(fltr)
203
204 return stack