sql_beautifier 0.4.0 → 0.5.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 +6 -0
- data/lib/sql_beautifier/condition_formatter.rb +2 -1
- data/lib/sql_beautifier/create_table_as_formatter.rb +177 -0
- data/lib/sql_beautifier/cte_formatter.rb +2 -1
- 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: 74ce2777ba2e1a7635aa92623cbb6e252bef41abafa62fff7b3f9f651aad05af
|
|
4
|
+
data.tar.gz: 83f543391d4e60f2ab928eb44c6975d4f935b84c7e63bcbb05685331bcdf59a2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bbfe00044b32a468fb019a9d32010ce355803000d627d6b16146398b54515531d0a6b76a84022facaec39b1afb849307b08ee96b8859b00ad99f7f2895b2668
|
|
7
|
+
data.tar.gz: 2252b3c944667589b17c0bde79e2eca176ced558c6bf8d19795607490788dc468200a00a2dedbce667adee966956389b3957f70cc39fa317d865435ceee22007
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [X.X.X] - YYYY-MM-DD
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-03-28
|
|
6
|
+
|
|
7
|
+
- Add support for Create Table As (CTA) formatting
|
|
8
|
+
|
|
5
9
|
## [0.4.0] - 2026-03-27
|
|
6
10
|
|
|
11
|
+
- Add CTE (Common Table Expression) formatting with recursive indentation
|
|
12
|
+
|
|
7
13
|
## [0.3.0] - 2026-03-27
|
|
8
14
|
|
|
9
15
|
- Add configuration system with `SqlBeautifier.configure` block and `SqlBeautifier.reset_configuration!`
|
|
@@ -4,7 +4,8 @@ module SqlBeautifier
|
|
|
4
4
|
module ConditionFormatter
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def format(text,
|
|
7
|
+
def format(text, args = {})
|
|
8
|
+
indent_width = args.fetch(:indent_width, 0)
|
|
8
9
|
conditions = Tokenizer.split_top_level_conditions(text)
|
|
9
10
|
return text.strip if conditions.length <= 1 && !parse_condition_group(conditions.dig(0, 1))
|
|
10
11
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
module CreateTableAsFormatter
|
|
5
|
+
MODIFIERS = %w[
|
|
6
|
+
temp
|
|
7
|
+
temporary
|
|
8
|
+
unlogged
|
|
9
|
+
local
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
WITH_DATA_SUFFIX_REGEX = %r{\s+(with\s+(?:no\s+)?data)\s*\z}i
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def format(normalized_sql, _args = {})
|
|
17
|
+
return nil unless create_table_as_query?(normalized_sql)
|
|
18
|
+
|
|
19
|
+
parsed = parse(normalized_sql)
|
|
20
|
+
return nil unless parsed
|
|
21
|
+
|
|
22
|
+
format_statement(parsed[:preamble], parsed[:body], parsed[:suffix])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_table_as_query?(sql)
|
|
26
|
+
Tokenizer.keyword_at?(sql, 0, "create")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parse(sql)
|
|
30
|
+
position = 0
|
|
31
|
+
return nil unless Tokenizer.keyword_at?(sql, position, "create")
|
|
32
|
+
|
|
33
|
+
position = skip_past_keyword(sql, position, "create")
|
|
34
|
+
|
|
35
|
+
modifier = detect_modifier(sql, position)
|
|
36
|
+
position = skip_past_keyword(sql, position, modifier) if modifier
|
|
37
|
+
|
|
38
|
+
return nil unless Tokenizer.keyword_at?(sql, position, "table")
|
|
39
|
+
|
|
40
|
+
position = skip_past_keyword(sql, position, "table")
|
|
41
|
+
|
|
42
|
+
if_not_exists = detect_if_not_exists?(sql, position)
|
|
43
|
+
position = skip_past_if_not_exists(sql, position) if if_not_exists
|
|
44
|
+
|
|
45
|
+
table_name, position = read_identifier(sql, position)
|
|
46
|
+
return nil unless table_name
|
|
47
|
+
|
|
48
|
+
position = skip_whitespace(sql, position)
|
|
49
|
+
return nil unless Tokenizer.keyword_at?(sql, position, "as")
|
|
50
|
+
|
|
51
|
+
position = skip_past_keyword(sql, position, "as")
|
|
52
|
+
|
|
53
|
+
result = extract_body(sql, position)
|
|
54
|
+
return nil unless result
|
|
55
|
+
|
|
56
|
+
body_sql, suffix = result
|
|
57
|
+
return nil unless body_sql
|
|
58
|
+
|
|
59
|
+
preamble = build_preamble(modifier, if_not_exists, table_name)
|
|
60
|
+
{ preamble: preamble, body: body_sql, suffix: suffix }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def detect_modifier(sql, position)
|
|
64
|
+
MODIFIERS.detect { |modifier| Tokenizer.keyword_at?(sql, position, modifier) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def detect_if_not_exists?(sql, position)
|
|
68
|
+
Tokenizer.keyword_at?(sql, position, "if") && Tokenizer.keyword_at?(sql, skip_past_keyword(sql, position, "if"), "not") && Tokenizer.keyword_at?(sql, skip_past_keyword(sql, skip_past_keyword(sql, position, "if"), "not"), "exists")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def skip_past_if_not_exists(sql, position)
|
|
72
|
+
position = skip_past_keyword(sql, position, "if")
|
|
73
|
+
position = skip_past_keyword(sql, position, "not")
|
|
74
|
+
skip_past_keyword(sql, position, "exists")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def extract_body(sql, position)
|
|
78
|
+
position = skip_whitespace(sql, position)
|
|
79
|
+
return nil if position >= sql.length
|
|
80
|
+
|
|
81
|
+
if sql[position] == Constants::OPEN_PARENTHESIS
|
|
82
|
+
closing = Tokenizer.find_matching_parenthesis(sql, position)
|
|
83
|
+
return nil unless closing
|
|
84
|
+
|
|
85
|
+
body = sql[(position + 1)...closing].strip
|
|
86
|
+
suffix = sql[(closing + 1)..].strip.presence
|
|
87
|
+
[body, suffix]
|
|
88
|
+
else
|
|
89
|
+
extract_unparenthesized_body(sql[position..].strip)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def extract_unparenthesized_body(raw_body)
|
|
94
|
+
return nil unless raw_body.present?
|
|
95
|
+
|
|
96
|
+
match = raw_body.match(WITH_DATA_SUFFIX_REGEX)
|
|
97
|
+
|
|
98
|
+
if match
|
|
99
|
+
body = raw_body[0...match.begin(0)].strip
|
|
100
|
+
return nil unless body.present?
|
|
101
|
+
|
|
102
|
+
[body, match[1]]
|
|
103
|
+
else
|
|
104
|
+
[raw_body, nil]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def build_preamble(modifier, if_not_exists, table_name)
|
|
109
|
+
parts = [Util.format_keyword("create")]
|
|
110
|
+
parts << Util.format_keyword(modifier) if modifier
|
|
111
|
+
parts << Util.format_keyword("table")
|
|
112
|
+
parts << "#{Util.format_keyword('if')} #{Util.format_keyword('not')} #{Util.format_keyword('exists')}" if if_not_exists
|
|
113
|
+
parts << Util.format_table_name(table_name)
|
|
114
|
+
parts << Util.format_keyword("as")
|
|
115
|
+
parts.join(" ")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def format_statement(preamble, body_sql, suffix)
|
|
119
|
+
indent_spaces = SqlBeautifier.config_for(:indent_spaces) || 4
|
|
120
|
+
formatted = Formatter.new(body_sql, depth: 0).call
|
|
121
|
+
return "#{preamble}\n" unless formatted
|
|
122
|
+
|
|
123
|
+
indentation = Util.whitespace(indent_spaces)
|
|
124
|
+
indented_lines = formatted.chomp.lines.map { |line| line.strip.empty? ? "\n" : "#{indentation}#{line}" }.join
|
|
125
|
+
|
|
126
|
+
formatted_suffix = suffix ? " #{format_suffix(suffix)}" : ""
|
|
127
|
+
"#{preamble} (\n#{indented_lines}\n)#{formatted_suffix}\n"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def format_suffix(suffix)
|
|
131
|
+
suffix.strip.split(%r{\s+}).map { |word| Util.format_keyword(word) }.join(" ")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def read_identifier(sql, position)
|
|
135
|
+
position = skip_whitespace(sql, position)
|
|
136
|
+
return nil if position >= sql.length
|
|
137
|
+
|
|
138
|
+
if sql[position] == Constants::DOUBLE_QUOTE
|
|
139
|
+
start = position
|
|
140
|
+
position += 1
|
|
141
|
+
|
|
142
|
+
while position < sql.length
|
|
143
|
+
if sql[position] == Constants::DOUBLE_QUOTE
|
|
144
|
+
if position + 1 < sql.length && sql[position + 1] == Constants::DOUBLE_QUOTE
|
|
145
|
+
position += 2
|
|
146
|
+
next
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
position += 1
|
|
150
|
+
break
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
position += 1
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
return nil unless position <= sql.length && sql[position - 1] == Constants::DOUBLE_QUOTE
|
|
157
|
+
|
|
158
|
+
return [sql[start...position], position]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
start = position
|
|
162
|
+
position += 1 while position < sql.length && sql[position] =~ Tokenizer::IDENTIFIER_CHARACTER
|
|
163
|
+
return nil if position == start
|
|
164
|
+
|
|
165
|
+
[sql[start...position], position]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def skip_whitespace(sql, position)
|
|
169
|
+
position += 1 while position < sql.length && sql[position] =~ Constants::WHITESPACE_CHARACTER_REGEX
|
|
170
|
+
position
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def skip_past_keyword(sql, position, keyword)
|
|
174
|
+
skip_whitespace(sql, position + keyword.length)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -4,7 +4,8 @@ module SqlBeautifier
|
|
|
4
4
|
module CteFormatter
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def format(normalized_sql,
|
|
7
|
+
def format(normalized_sql, args = {})
|
|
8
|
+
depth = args.fetch(:depth, 0)
|
|
8
9
|
return nil unless cte_query?(normalized_sql)
|
|
9
10
|
|
|
10
11
|
recursive, definitions, main_query_sql = parse(normalized_sql)
|
|
@@ -20,6 +20,9 @@ module SqlBeautifier
|
|
|
20
20
|
cte_result = CteFormatter.format(@normalized_value, depth: @depth)
|
|
21
21
|
return cte_result if cte_result
|
|
22
22
|
|
|
23
|
+
create_table_as_result = CreateTableAsFormatter.format(@normalized_value, depth: @depth)
|
|
24
|
+
return create_table_as_result if create_table_as_result
|
|
25
|
+
|
|
23
26
|
first_clause_position = Tokenizer.first_clause_position(@normalized_value)
|
|
24
27
|
return "#{@normalized_value}\n" if first_clause_position.nil? || first_clause_position.positive?
|
|
25
28
|
|
data/lib/sql_beautifier.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative "sql_beautifier/table_registry"
|
|
|
13
13
|
require_relative "sql_beautifier/condition_formatter"
|
|
14
14
|
require_relative "sql_beautifier/subquery_formatter"
|
|
15
15
|
require_relative "sql_beautifier/cte_formatter"
|
|
16
|
+
require_relative "sql_beautifier/create_table_as_formatter"
|
|
16
17
|
require_relative "sql_beautifier/clauses/base"
|
|
17
18
|
require_relative "sql_beautifier/clauses/condition_clause"
|
|
18
19
|
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.5.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/create_table_as_formatter.rb
|
|
52
53
|
- lib/sql_beautifier/cte_formatter.rb
|
|
53
54
|
- lib/sql_beautifier/formatter.rb
|
|
54
55
|
- lib/sql_beautifier/normalizer.rb
|