sql_beautifier 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +224 -10
- data/lib/sql_beautifier/clauses/base.rb +10 -0
- data/lib/sql_beautifier/clauses/condition_clause.rb +20 -0
- data/lib/sql_beautifier/clauses/from.rb +139 -2
- data/lib/sql_beautifier/clauses/group_by.rb +2 -2
- data/lib/sql_beautifier/clauses/having.rb +2 -6
- data/lib/sql_beautifier/clauses/limit.rb +8 -2
- data/lib/sql_beautifier/clauses/order_by.rb +2 -2
- data/lib/sql_beautifier/clauses/select.rb +60 -6
- data/lib/sql_beautifier/clauses/where.rb +2 -6
- data/lib/sql_beautifier/condition_formatter.rb +126 -0
- data/lib/sql_beautifier/configuration.rb +33 -0
- data/lib/sql_beautifier/constants.rb +27 -0
- data/lib/sql_beautifier/formatter.rb +57 -3
- data/lib/sql_beautifier/normalizer.rb +84 -15
- data/lib/sql_beautifier/subquery_formatter.rb +113 -0
- data/lib/sql_beautifier/table_registry.rb +229 -0
- data/lib/sql_beautifier/tokenizer.rb +223 -20
- data/lib/sql_beautifier/util.rb +67 -0
- data/lib/sql_beautifier/version.rb +1 -1
- data/lib/sql_beautifier.rb +23 -0
- metadata +7 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
module ConditionFormatter
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def format(text, indent_width:)
|
|
8
|
+
conditions = Tokenizer.split_top_level_conditions(text)
|
|
9
|
+
return text.strip if conditions.length <= 1 && !parse_condition_group(conditions.dig(0, 1))
|
|
10
|
+
|
|
11
|
+
conditions = flatten_same_conjunction_groups(conditions)
|
|
12
|
+
indentation = Util.whitespace(indent_width)
|
|
13
|
+
lines = []
|
|
14
|
+
|
|
15
|
+
conditions.each_with_index do |(conjunction, condition_text), index|
|
|
16
|
+
unwrapped_condition = unwrap_single_condition(condition_text)
|
|
17
|
+
formatted_condition_text = format_single_condition(unwrapped_condition, indent_width: indent_width)
|
|
18
|
+
|
|
19
|
+
line = begin
|
|
20
|
+
if index.zero?
|
|
21
|
+
"#{indentation}#{formatted_condition_text}"
|
|
22
|
+
else
|
|
23
|
+
"#{indentation}#{conjunction} #{formatted_condition_text}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
lines << line
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
lines.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def flatten_same_conjunction_groups(conditions)
|
|
34
|
+
return conditions if conditions.length <= 1
|
|
35
|
+
|
|
36
|
+
outer_conjunction = conditions[1]&.first
|
|
37
|
+
return conditions unless outer_conjunction
|
|
38
|
+
return conditions unless conditions.drop(1).all? { |pair| pair[0] == outer_conjunction }
|
|
39
|
+
|
|
40
|
+
flattened_conditions = []
|
|
41
|
+
|
|
42
|
+
conditions.each do |conjunction, condition_text|
|
|
43
|
+
inner_conditions = parse_condition_group(condition_text)
|
|
44
|
+
|
|
45
|
+
if inner_conditions && flattenable_into_conjunction?(inner_conditions, outer_conjunction)
|
|
46
|
+
flatten_inner_conditions_into!(flattened_conditions, inner_conditions, conjunction, outer_conjunction)
|
|
47
|
+
else
|
|
48
|
+
flattened_conditions << [conjunction, condition_text]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
flattened_conditions
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def rebuild_inline(inner_conditions)
|
|
56
|
+
parts = inner_conditions.map.with_index do |(conjunction, condition_text), index|
|
|
57
|
+
index.zero? ? condition_text : "#{conjunction} #{condition_text}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
"(#{parts.join(' ')})"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def unwrap_single_condition(condition)
|
|
64
|
+
output = condition.strip
|
|
65
|
+
|
|
66
|
+
while Tokenizer.outer_parentheses_wrap_all?(output)
|
|
67
|
+
inner_content = Util.strip_outer_parentheses(output)
|
|
68
|
+
inner_conditions = Tokenizer.split_top_level_conditions(inner_content)
|
|
69
|
+
break if inner_conditions.length > 1
|
|
70
|
+
|
|
71
|
+
output = inner_content
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
output
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_condition_group(condition_text)
|
|
78
|
+
return unless condition_text
|
|
79
|
+
|
|
80
|
+
trimmed_condition = condition_text.strip
|
|
81
|
+
return unless Tokenizer.outer_parentheses_wrap_all?(trimmed_condition)
|
|
82
|
+
|
|
83
|
+
inner_content = Util.strip_outer_parentheses(trimmed_condition)
|
|
84
|
+
inner_conditions = Tokenizer.split_top_level_conditions(inner_content)
|
|
85
|
+
return unless inner_conditions.length > 1
|
|
86
|
+
|
|
87
|
+
inner_conditions
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def format_single_condition(condition_text, indent_width:)
|
|
91
|
+
inner_conditions = parse_condition_group(condition_text)
|
|
92
|
+
return condition_text unless inner_conditions
|
|
93
|
+
|
|
94
|
+
inline_version = rebuild_inline(inner_conditions)
|
|
95
|
+
return inline_version if inline_version.length <= SqlBeautifier.config_for(:inline_group_threshold)
|
|
96
|
+
|
|
97
|
+
inner_content = Util.strip_outer_parentheses(condition_text.strip)
|
|
98
|
+
formatted_inner_content = format(inner_content, indent_width: indent_width + 4)
|
|
99
|
+
indentation = Util.whitespace(indent_width)
|
|
100
|
+
|
|
101
|
+
"(\n#{formatted_inner_content}\n#{indentation})"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def flattenable_into_conjunction?(inner_conditions, outer_conjunction)
|
|
105
|
+
inner_conjunction = inner_conditions[1]&.first
|
|
106
|
+
|
|
107
|
+
inner_conjunction == outer_conjunction && inner_conditions.drop(1).all? { |pair| pair[0] == outer_conjunction }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def flatten_inner_conditions_into!(flattened_conditions, inner_conditions, conjunction, outer_conjunction)
|
|
111
|
+
inner_conditions.each_with_index do |inner_pair, inner_index|
|
|
112
|
+
condition_pair = begin
|
|
113
|
+
if flattened_conditions.empty?
|
|
114
|
+
[nil, inner_pair[1]]
|
|
115
|
+
elsif inner_index.zero?
|
|
116
|
+
[conjunction || outer_conjunction, inner_pair[1]]
|
|
117
|
+
else
|
|
118
|
+
[outer_conjunction, inner_pair[1]]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
flattened_conditions << condition_pair
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
class Configuration
|
|
5
|
+
DEFAULTS = {
|
|
6
|
+
keyword_case: :lower,
|
|
7
|
+
keyword_column_width: 8,
|
|
8
|
+
indent_spaces: 4,
|
|
9
|
+
clause_spacing_mode: :compact,
|
|
10
|
+
table_name_format: :pascal_case,
|
|
11
|
+
inline_group_threshold: 100,
|
|
12
|
+
alias_strategy: :initials,
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
attr_accessor :keyword_case
|
|
16
|
+
attr_accessor :keyword_column_width
|
|
17
|
+
attr_accessor :indent_spaces
|
|
18
|
+
attr_accessor :clause_spacing_mode
|
|
19
|
+
attr_accessor :table_name_format
|
|
20
|
+
attr_accessor :inline_group_threshold
|
|
21
|
+
attr_accessor :alias_strategy
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
reset!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset!
|
|
28
|
+
DEFAULTS.each do |key, value|
|
|
29
|
+
public_send(:"#{key}=", value)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -11,5 +11,32 @@ module SqlBeautifier
|
|
|
11
11
|
"order by",
|
|
12
12
|
"limit",
|
|
13
13
|
].freeze
|
|
14
|
+
|
|
15
|
+
JOIN_KEYWORDS = [
|
|
16
|
+
"inner join",
|
|
17
|
+
"left outer join",
|
|
18
|
+
"right outer join",
|
|
19
|
+
"full outer join",
|
|
20
|
+
"left join",
|
|
21
|
+
"right join",
|
|
22
|
+
"full join",
|
|
23
|
+
"cross join",
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
JOIN_KEYWORDS_BY_LENGTH = JOIN_KEYWORDS.sort_by { |keyword| -keyword.length }.freeze
|
|
27
|
+
JOIN_KEYWORD_PATTERN = %r{\b(#{JOIN_KEYWORDS.map { |keyword| Regexp.escape(keyword) }.join('|')})\b}i
|
|
28
|
+
|
|
29
|
+
CONJUNCTIONS = %w[and or].freeze
|
|
30
|
+
BETWEEN_KEYWORD = "between"
|
|
31
|
+
|
|
32
|
+
OPEN_PARENTHESIS = "("
|
|
33
|
+
CLOSE_PARENTHESIS = ")"
|
|
34
|
+
COMMA = ","
|
|
35
|
+
|
|
36
|
+
WHITESPACE_REGEX = %r{\s+}
|
|
37
|
+
WHITESPACE_CHARACTER_REGEX = %r{\s}
|
|
38
|
+
SINGLE_QUOTE = "'"
|
|
39
|
+
DOUBLE_QUOTE = '"'
|
|
40
|
+
ESCAPED_DOUBLE_QUOTE = '""'
|
|
14
41
|
end
|
|
15
42
|
end
|
|
@@ -6,8 +6,9 @@ module SqlBeautifier
|
|
|
6
6
|
new(value).call
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def initialize(value)
|
|
9
|
+
def initialize(value, depth: 0)
|
|
10
10
|
@value = value
|
|
11
|
+
@depth = depth
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def call
|
|
@@ -20,19 +21,22 @@ module SqlBeautifier
|
|
|
20
21
|
return "#{@normalized_value}\n" if first_clause_position.nil? || first_clause_position.positive?
|
|
21
22
|
|
|
22
23
|
@clauses = Tokenizer.split_into_clauses(@normalized_value)
|
|
24
|
+
@table_registry = TableRegistry.new(@clauses[:from]) if @clauses[:from].present?
|
|
23
25
|
@parts = []
|
|
24
26
|
|
|
25
27
|
append_clause!(:select, Clauses::Select)
|
|
26
|
-
|
|
28
|
+
append_from_clause!
|
|
27
29
|
append_clause!(:where, Clauses::Where)
|
|
28
30
|
append_clause!(:group_by, Clauses::GroupBy)
|
|
29
31
|
append_clause!(:having, Clauses::Having)
|
|
30
32
|
append_clause!(:order_by, Clauses::OrderBy)
|
|
31
33
|
append_clause!(:limit, Clauses::Limit)
|
|
32
34
|
|
|
33
|
-
output = @parts.join(
|
|
35
|
+
output = @parts.join(clause_separator)
|
|
34
36
|
return "#{@normalized_value}\n" if output.empty?
|
|
35
37
|
|
|
38
|
+
output = SubqueryFormatter.format(output, @depth)
|
|
39
|
+
output = @table_registry.apply_aliases(output) if @table_registry
|
|
36
40
|
"#{output}\n"
|
|
37
41
|
end
|
|
38
42
|
|
|
@@ -44,5 +48,55 @@ module SqlBeautifier
|
|
|
44
48
|
|
|
45
49
|
@parts << formatter_class.call(value)
|
|
46
50
|
end
|
|
51
|
+
|
|
52
|
+
def append_from_clause!
|
|
53
|
+
value = @clauses[:from]
|
|
54
|
+
return unless value.present?
|
|
55
|
+
|
|
56
|
+
@parts << Clauses::From.call(value, table_registry: @table_registry)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clause_separator
|
|
60
|
+
return "\n\n" if SqlBeautifier.config_for(:clause_spacing_mode) == :spacious
|
|
61
|
+
return "\n\n" unless compact_query?
|
|
62
|
+
|
|
63
|
+
"\n"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def compact_query?
|
|
67
|
+
compact_clause_set? && single_select_column? && single_from_table? && one_or_fewer_conditions?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def compact_clause_set?
|
|
71
|
+
clause_keys = @clauses.keys
|
|
72
|
+
allowed_keys = %i[select from where order_by limit]
|
|
73
|
+
|
|
74
|
+
clause_keys.all? { |key| allowed_keys.include?(key) } && clause_keys.include?(:select) && clause_keys.include?(:from)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def single_select_column?
|
|
78
|
+
select_value = @clauses[:select]
|
|
79
|
+
return false unless select_value.present?
|
|
80
|
+
|
|
81
|
+
formatted_select = Clauses::Select.call(select_value)
|
|
82
|
+
formatted_select.lines.length == 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def single_from_table?
|
|
86
|
+
from_value = @clauses[:from]
|
|
87
|
+
return false unless from_value.present?
|
|
88
|
+
|
|
89
|
+
join_keywords = Constants::JOIN_KEYWORDS_BY_LENGTH.any? { |keyword| Tokenizer.find_top_level_keyword(from_value, keyword) }
|
|
90
|
+
return false if join_keywords
|
|
91
|
+
|
|
92
|
+
!from_value.match?(%r{,})
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def one_or_fewer_conditions?
|
|
96
|
+
where_value = @clauses[:where]
|
|
97
|
+
return true unless where_value.present?
|
|
98
|
+
|
|
99
|
+
Tokenizer.split_top_level_conditions(where_value).length <= 1
|
|
100
|
+
end
|
|
47
101
|
end
|
|
48
102
|
end
|
|
@@ -18,18 +18,23 @@ module SqlBeautifier
|
|
|
18
18
|
@source = @value.strip
|
|
19
19
|
return unless @source.present?
|
|
20
20
|
|
|
21
|
+
@source = strip_comments(@source)
|
|
22
|
+
@source = strip_trailing_semicolons(@source)
|
|
23
|
+
@source = @source.strip
|
|
24
|
+
return unless @source.present?
|
|
25
|
+
|
|
21
26
|
@output = +""
|
|
22
27
|
@position = 0
|
|
23
28
|
|
|
24
29
|
while @position < @source.length
|
|
25
30
|
case current_character
|
|
26
|
-
when
|
|
31
|
+
when Constants::SINGLE_QUOTE
|
|
27
32
|
consume_string_literal!
|
|
28
33
|
|
|
29
|
-
when
|
|
34
|
+
when Constants::DOUBLE_QUOTE
|
|
30
35
|
consume_quoted_identifier!
|
|
31
36
|
|
|
32
|
-
when
|
|
37
|
+
when Constants::WHITESPACE_CHARACTER_REGEX
|
|
33
38
|
collapse_whitespace!
|
|
34
39
|
|
|
35
40
|
else
|
|
@@ -50,7 +55,7 @@ module SqlBeautifier
|
|
|
50
55
|
def collapse_whitespace!
|
|
51
56
|
@output << " "
|
|
52
57
|
@position += 1
|
|
53
|
-
@position += 1 while @position < @source.length && @source[@position] =~
|
|
58
|
+
@position += 1 while @position < @source.length && @source[@position] =~ Constants::WHITESPACE_CHARACTER_REGEX
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def consume_string_literal!
|
|
@@ -61,10 +66,10 @@ module SqlBeautifier
|
|
|
61
66
|
character = current_character
|
|
62
67
|
@output << character
|
|
63
68
|
|
|
64
|
-
if character ==
|
|
69
|
+
if character == Constants::SINGLE_QUOTE && @source[@position + 1] == Constants::SINGLE_QUOTE
|
|
65
70
|
@position += 1
|
|
66
71
|
@output << current_character
|
|
67
|
-
elsif character ==
|
|
72
|
+
elsif character == Constants::SINGLE_QUOTE
|
|
68
73
|
@position += 1
|
|
69
74
|
return
|
|
70
75
|
end
|
|
@@ -81,10 +86,10 @@ module SqlBeautifier
|
|
|
81
86
|
while @position < @source.length
|
|
82
87
|
character = current_character
|
|
83
88
|
|
|
84
|
-
if character ==
|
|
85
|
-
identifier <<
|
|
89
|
+
if character == Constants::DOUBLE_QUOTE && @source[@position + 1] == Constants::DOUBLE_QUOTE
|
|
90
|
+
identifier << Constants::DOUBLE_QUOTE
|
|
86
91
|
@position += 2
|
|
87
|
-
elsif character ==
|
|
92
|
+
elsif character == Constants::DOUBLE_QUOTE
|
|
88
93
|
@position += 1
|
|
89
94
|
@output << format_identifier(identifier)
|
|
90
95
|
return
|
|
@@ -100,17 +105,81 @@ module SqlBeautifier
|
|
|
100
105
|
end
|
|
101
106
|
|
|
102
107
|
def format_identifier(identifier)
|
|
103
|
-
|
|
108
|
+
downcased_identifier = identifier.downcase
|
|
109
|
+
return downcased_identifier unless requires_quoting?(downcased_identifier)
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
else
|
|
108
|
-
lowercased
|
|
109
|
-
end
|
|
111
|
+
escaped_identifier = Util.escape_double_quote(downcased_identifier)
|
|
112
|
+
Util.double_quote_string(escaped_identifier)
|
|
110
113
|
end
|
|
111
114
|
|
|
112
115
|
def requires_quoting?(identifier)
|
|
113
116
|
identifier !~ SAFE_UNQUOTED_IDENTIFIER
|
|
114
117
|
end
|
|
118
|
+
|
|
119
|
+
def strip_trailing_semicolons(sql)
|
|
120
|
+
sql.sub(%r{;[[:space:]]*\z}, "")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def strip_comments(sql)
|
|
124
|
+
output = +""
|
|
125
|
+
position = 0
|
|
126
|
+
in_single_quoted_string = false
|
|
127
|
+
in_double_quoted_identifier = false
|
|
128
|
+
|
|
129
|
+
while position < sql.length
|
|
130
|
+
character = sql[position]
|
|
131
|
+
|
|
132
|
+
if in_single_quoted_string
|
|
133
|
+
output << character
|
|
134
|
+
|
|
135
|
+
if character == Constants::SINGLE_QUOTE && sql[position + 1] == Constants::SINGLE_QUOTE
|
|
136
|
+
position += 1
|
|
137
|
+
output << sql[position]
|
|
138
|
+
elsif character == Constants::SINGLE_QUOTE
|
|
139
|
+
in_single_quoted_string = false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
position += 1
|
|
143
|
+
next
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if in_double_quoted_identifier
|
|
147
|
+
output << character
|
|
148
|
+
|
|
149
|
+
if character == Constants::DOUBLE_QUOTE && sql[position + 1] == Constants::DOUBLE_QUOTE
|
|
150
|
+
position += 1
|
|
151
|
+
output << sql[position]
|
|
152
|
+
elsif character == Constants::DOUBLE_QUOTE
|
|
153
|
+
in_double_quoted_identifier = false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
position += 1
|
|
157
|
+
next
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if character == Constants::SINGLE_QUOTE
|
|
161
|
+
in_single_quoted_string = true
|
|
162
|
+
output << character
|
|
163
|
+
position += 1
|
|
164
|
+
elsif character == Constants::DOUBLE_QUOTE
|
|
165
|
+
in_double_quoted_identifier = true
|
|
166
|
+
output << character
|
|
167
|
+
position += 1
|
|
168
|
+
elsif character == "-" && sql[position + 1] == "-"
|
|
169
|
+
position += 2
|
|
170
|
+
position += 1 while position < sql.length && sql[position] != "\n"
|
|
171
|
+
elsif character == "/" && sql[position + 1] == "*"
|
|
172
|
+
output << " " unless output.empty? || output[-1] =~ Constants::WHITESPACE_CHARACTER_REGEX
|
|
173
|
+
position += 2
|
|
174
|
+
position += 1 while position < sql.length && !(sql[position] == "*" && sql[position + 1] == "/")
|
|
175
|
+
position += 2 if position < sql.length
|
|
176
|
+
else
|
|
177
|
+
output << character
|
|
178
|
+
position += 1
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
output
|
|
183
|
+
end
|
|
115
184
|
end
|
|
116
185
|
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
module SubqueryFormatter
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def format(text, base_indent)
|
|
8
|
+
output = +""
|
|
9
|
+
position = 0
|
|
10
|
+
|
|
11
|
+
while position < text.length
|
|
12
|
+
subquery_position = find_top_level_subquery(text, position)
|
|
13
|
+
|
|
14
|
+
unless subquery_position
|
|
15
|
+
output << text[position..]
|
|
16
|
+
break
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
output << text[position...subquery_position]
|
|
20
|
+
|
|
21
|
+
closing_position = Tokenizer.find_matching_parenthesis(text, subquery_position)
|
|
22
|
+
|
|
23
|
+
unless closing_position
|
|
24
|
+
output << text[subquery_position..]
|
|
25
|
+
break
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
inner_sql = text[(subquery_position + 1)...closing_position].strip
|
|
29
|
+
subquery_base_indent = subquery_base_indent_for(text, subquery_position, base_indent)
|
|
30
|
+
output << format_subquery(inner_sql, subquery_base_indent)
|
|
31
|
+
position = closing_position + 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
output
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_top_level_subquery(text, start_position)
|
|
38
|
+
position = start_position
|
|
39
|
+
in_single_quoted_string = false
|
|
40
|
+
in_double_quoted_identifier = false
|
|
41
|
+
while position < text.length
|
|
42
|
+
character = text[position]
|
|
43
|
+
|
|
44
|
+
if in_single_quoted_string
|
|
45
|
+
if character == Constants::SINGLE_QUOTE && text[position + 1] == Constants::SINGLE_QUOTE
|
|
46
|
+
position += 2
|
|
47
|
+
elsif character == Constants::SINGLE_QUOTE
|
|
48
|
+
in_single_quoted_string = false
|
|
49
|
+
position += 1
|
|
50
|
+
else
|
|
51
|
+
position += 1
|
|
52
|
+
end
|
|
53
|
+
next
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if in_double_quoted_identifier
|
|
57
|
+
if character == Constants::DOUBLE_QUOTE && text[position + 1] == Constants::DOUBLE_QUOTE
|
|
58
|
+
position += 2
|
|
59
|
+
elsif character == Constants::DOUBLE_QUOTE
|
|
60
|
+
in_double_quoted_identifier = false
|
|
61
|
+
position += 1
|
|
62
|
+
else
|
|
63
|
+
position += 1
|
|
64
|
+
end
|
|
65
|
+
next
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
case character
|
|
69
|
+
when Constants::SINGLE_QUOTE
|
|
70
|
+
in_single_quoted_string = true
|
|
71
|
+
when Constants::DOUBLE_QUOTE
|
|
72
|
+
in_double_quoted_identifier = true
|
|
73
|
+
when Constants::OPEN_PARENTHESIS
|
|
74
|
+
return position if select_follows?(text, position)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
position += 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def format_subquery(inner_sql, base_indent)
|
|
84
|
+
indent_spaces = SqlBeautifier.config_for(:indent_spaces) || 4
|
|
85
|
+
subquery_indent = base_indent + indent_spaces
|
|
86
|
+
formatted = Formatter.new(inner_sql, depth: subquery_indent).call
|
|
87
|
+
return "(#{inner_sql})" unless formatted
|
|
88
|
+
|
|
89
|
+
indentation = Util.whitespace(subquery_indent)
|
|
90
|
+
indented_lines = formatted.chomp.lines.map { |line| line.strip.empty? ? "\n" : "#{indentation}#{line}" }.join
|
|
91
|
+
|
|
92
|
+
"(\n#{indented_lines}\n#{Util.whitespace(base_indent)})"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def subquery_base_indent_for(text, subquery_position, default_base_indent)
|
|
96
|
+
line_start_position = text.rindex("\n", subquery_position - 1)
|
|
97
|
+
line_start_position = line_start_position ? line_start_position + 1 : 0
|
|
98
|
+
line_before_subquery = text[line_start_position...subquery_position]
|
|
99
|
+
line_leading_spaces = line_before_subquery[%r{\A[[:space:]]*}].to_s.length
|
|
100
|
+
|
|
101
|
+
return default_base_indent unless line_before_subquery.lstrip.match?(%r{\Awhere(?:[[:space:]]|$)}i)
|
|
102
|
+
|
|
103
|
+
default_base_indent + line_leading_spaces + SqlBeautifier.config_for(:keyword_column_width)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def select_follows?(text, position)
|
|
107
|
+
remaining_text = text[(position + 1)..]
|
|
108
|
+
return false unless remaining_text
|
|
109
|
+
|
|
110
|
+
remaining_text.match?(%r{\A[[:space:]]*select(?:[[:space:]]|\()}i)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|