sql_beautifier 0.3.0 → 0.4.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 +2 -0
- data/lib/sql_beautifier/cte_formatter.rb +191 -0
- data/lib/sql_beautifier/formatter.rb +3 -0
- data/lib/sql_beautifier/version.rb +1 -1
- data/lib/sql_beautifier.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 928801124a1241f4b12dc2e7e7966cd95dcba5754cc6ee88a08c9f641f2e32e4
|
|
4
|
+
data.tar.gz: eb1db005fb6923f5f99aaacc44caeeed14b6c7f6e4d8cd174fb3fcff92cf944e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0ee8ca9569e7409f989cce52070112bb61af2c064dec679a96e7f26bb74837b8f2ae207617f619280140548fb5cc8804a1996464265415efc96d0e0fafc7085
|
|
7
|
+
data.tar.gz: b1cff6570a5137fd7ff26fd11eed662d7002c9d242ca03ea303c0c1711f4a44f12dbdc3958ce3657b0988742abd351792476ab0fc7171ccafebe1c28a70be595
|
data/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
module CteFormatter
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def format(normalized_sql, depth: 0)
|
|
8
|
+
return nil unless cte_query?(normalized_sql)
|
|
9
|
+
|
|
10
|
+
recursive, definitions, main_query_sql = parse(normalized_sql)
|
|
11
|
+
return nil unless definitions.any? && main_query_sql.present?
|
|
12
|
+
|
|
13
|
+
format_cte_statement(recursive, definitions, main_query_sql, depth)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cte_query?(sql)
|
|
17
|
+
Tokenizer.keyword_at?(sql, 0, "with")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse(sql)
|
|
21
|
+
position = skip_past_keyword(sql, 0, "with")
|
|
22
|
+
|
|
23
|
+
recursive = Tokenizer.keyword_at?(sql, position, "recursive")
|
|
24
|
+
position = skip_past_keyword(sql, position, "recursive") if recursive
|
|
25
|
+
|
|
26
|
+
definitions = []
|
|
27
|
+
|
|
28
|
+
loop do
|
|
29
|
+
definition, new_position = parse_definition(sql, position)
|
|
30
|
+
break unless definition
|
|
31
|
+
|
|
32
|
+
definitions << definition
|
|
33
|
+
position = skip_whitespace(sql, new_position)
|
|
34
|
+
|
|
35
|
+
break unless position < sql.length && sql[position] == Constants::COMMA
|
|
36
|
+
|
|
37
|
+
position = skip_whitespace(sql, position + 1)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
main_query_sql = sql[position..].strip
|
|
41
|
+
|
|
42
|
+
[recursive, definitions, main_query_sql]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_definition(sql, position)
|
|
46
|
+
name, position = read_identifier(sql, position)
|
|
47
|
+
return nil unless name
|
|
48
|
+
|
|
49
|
+
position = skip_whitespace(sql, position)
|
|
50
|
+
|
|
51
|
+
column_list = parse_column_list(sql, position)
|
|
52
|
+
position = column_list[:next_position] if column_list
|
|
53
|
+
|
|
54
|
+
return nil unless Tokenizer.keyword_at?(sql, position, "as")
|
|
55
|
+
|
|
56
|
+
position = skip_past_keyword(sql, position, "as")
|
|
57
|
+
materialization, position = parse_materialization(sql, position)
|
|
58
|
+
|
|
59
|
+
return nil unless position < sql.length && sql[position] == Constants::OPEN_PARENTHESIS
|
|
60
|
+
|
|
61
|
+
closing = Tokenizer.find_matching_parenthesis(sql, position)
|
|
62
|
+
return nil unless closing
|
|
63
|
+
|
|
64
|
+
body_sql = sql[(position + 1)...closing].strip
|
|
65
|
+
definition = { name: name, body: body_sql }
|
|
66
|
+
definition[:column_list] = column_list[:text] if column_list
|
|
67
|
+
definition[:materialization] = materialization if materialization
|
|
68
|
+
|
|
69
|
+
[definition, closing + 1]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse_column_list(sql, position)
|
|
73
|
+
return nil unless position < sql.length && sql[position] == Constants::OPEN_PARENTHESIS
|
|
74
|
+
|
|
75
|
+
closing = Tokenizer.find_matching_parenthesis(sql, position)
|
|
76
|
+
return nil unless closing
|
|
77
|
+
|
|
78
|
+
after_paren = skip_whitespace(sql, closing + 1)
|
|
79
|
+
return nil unless Tokenizer.keyword_at?(sql, after_paren, "as")
|
|
80
|
+
|
|
81
|
+
{ text: sql[(position + 1)...closing].strip, next_position: after_paren }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def format_cte_statement(recursive, definitions, main_query_sql, depth)
|
|
85
|
+
keyword_width = SqlBeautifier.config_for(:keyword_column_width)
|
|
86
|
+
cte_name_column = keyword_width
|
|
87
|
+
continuation_indent = Util.continuation_padding
|
|
88
|
+
|
|
89
|
+
output = +""
|
|
90
|
+
|
|
91
|
+
definitions.each_with_index do |definition, index|
|
|
92
|
+
if index.zero?
|
|
93
|
+
output << Util.keyword_padding("with")
|
|
94
|
+
output << "#{Util.format_keyword('recursive')} " if recursive
|
|
95
|
+
else
|
|
96
|
+
output << continuation_indent
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
output << definition_header(definition)
|
|
100
|
+
output << format_body(definition[:body], cte_name_column)
|
|
101
|
+
output << (index < definitions.length - 1 ? ",\n" : "\n\n")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
formatted_main = Formatter.new(main_query_sql, depth: depth).call
|
|
105
|
+
output << formatted_main if formatted_main
|
|
106
|
+
|
|
107
|
+
output
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def definition_header(definition)
|
|
111
|
+
header = +definition[:name].to_s
|
|
112
|
+
header << " (#{definition[:column_list]})" if definition[:column_list]
|
|
113
|
+
header << " #{Util.format_keyword('as')}"
|
|
114
|
+
header << " #{format_materialization(definition[:materialization])}" if definition[:materialization]
|
|
115
|
+
header << " "
|
|
116
|
+
header
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse_materialization(sql, position)
|
|
120
|
+
position = skip_whitespace(sql, position)
|
|
121
|
+
return ["materialized", skip_past_keyword(sql, position, "materialized")] if Tokenizer.keyword_at?(sql, position, "materialized")
|
|
122
|
+
return [nil, position] unless Tokenizer.keyword_at?(sql, position, "not")
|
|
123
|
+
|
|
124
|
+
materialized_position = skip_past_keyword(sql, position, "not")
|
|
125
|
+
return [nil, position] unless Tokenizer.keyword_at?(sql, materialized_position, "materialized")
|
|
126
|
+
|
|
127
|
+
["not materialized", skip_past_keyword(sql, materialized_position, "materialized")]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def format_materialization(materialization)
|
|
131
|
+
return Util.format_keyword("materialized") if materialization == "materialized"
|
|
132
|
+
|
|
133
|
+
[Util.format_keyword("not"), Util.format_keyword("materialized")].join(" ")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def format_body(body_sql, base_indent)
|
|
137
|
+
indent_spaces = SqlBeautifier.config_for(:indent_spaces) || 4
|
|
138
|
+
body_indent = base_indent + indent_spaces
|
|
139
|
+
formatted = Formatter.new(body_sql, depth: 0).call
|
|
140
|
+
return "(#{body_sql})" unless formatted
|
|
141
|
+
|
|
142
|
+
indentation = Util.whitespace(body_indent)
|
|
143
|
+
indented_lines = formatted.chomp.lines.map { |line| line.strip.empty? ? "\n" : "#{indentation}#{line}" }.join
|
|
144
|
+
|
|
145
|
+
"(\n#{indented_lines}\n#{Util.whitespace(base_indent)})"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def read_identifier(sql, position)
|
|
149
|
+
position = skip_whitespace(sql, position)
|
|
150
|
+
return nil if position >= sql.length
|
|
151
|
+
|
|
152
|
+
if sql[position] == Constants::DOUBLE_QUOTE
|
|
153
|
+
start = position
|
|
154
|
+
position += 1
|
|
155
|
+
|
|
156
|
+
while position < sql.length
|
|
157
|
+
if sql[position] == Constants::DOUBLE_QUOTE
|
|
158
|
+
if position + 1 < sql.length && sql[position + 1] == Constants::DOUBLE_QUOTE
|
|
159
|
+
position += 2
|
|
160
|
+
next
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
position += 1
|
|
164
|
+
break
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
position += 1
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
return nil unless position <= sql.length && sql[position - 1] == Constants::DOUBLE_QUOTE
|
|
171
|
+
|
|
172
|
+
return [sql[start...position], position]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
start = position
|
|
176
|
+
position += 1 while position < sql.length && sql[position] =~ Tokenizer::IDENTIFIER_CHARACTER
|
|
177
|
+
return nil if position == start
|
|
178
|
+
|
|
179
|
+
[sql[start...position], position]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def skip_whitespace(sql, position)
|
|
183
|
+
position += 1 while position < sql.length && sql[position] =~ Constants::WHITESPACE_CHARACTER_REGEX
|
|
184
|
+
position
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def skip_past_keyword(sql, position, keyword)
|
|
188
|
+
skip_whitespace(sql, position + keyword.length)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -17,6 +17,9 @@ module SqlBeautifier
|
|
|
17
17
|
@normalized_value = Normalizer.call(@value)
|
|
18
18
|
return unless @normalized_value.present?
|
|
19
19
|
|
|
20
|
+
cte_result = CteFormatter.format(@normalized_value, depth: @depth)
|
|
21
|
+
return cte_result if cte_result
|
|
22
|
+
|
|
20
23
|
first_clause_position = Tokenizer.first_clause_position(@normalized_value)
|
|
21
24
|
return "#{@normalized_value}\n" if first_clause_position.nil? || first_clause_position.positive?
|
|
22
25
|
|
data/lib/sql_beautifier.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "sql_beautifier/tokenizer"
|
|
|
12
12
|
require_relative "sql_beautifier/table_registry"
|
|
13
13
|
require_relative "sql_beautifier/condition_formatter"
|
|
14
14
|
require_relative "sql_beautifier/subquery_formatter"
|
|
15
|
+
require_relative "sql_beautifier/cte_formatter"
|
|
15
16
|
require_relative "sql_beautifier/clauses/base"
|
|
16
17
|
require_relative "sql_beautifier/clauses/condition_clause"
|
|
17
18
|
require_relative "sql_beautifier/clauses/select"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sql_beautifier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kinnell Shah
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- lib/sql_beautifier/condition_formatter.rb
|
|
50
50
|
- lib/sql_beautifier/configuration.rb
|
|
51
51
|
- lib/sql_beautifier/constants.rb
|
|
52
|
+
- lib/sql_beautifier/cte_formatter.rb
|
|
52
53
|
- lib/sql_beautifier/formatter.rb
|
|
53
54
|
- lib/sql_beautifier/normalizer.rb
|
|
54
55
|
- lib/sql_beautifier/subquery_formatter.rb
|