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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 928801124a1241f4b12dc2e7e7966cd95dcba5754cc6ee88a08c9f641f2e32e4
4
- data.tar.gz: eb1db005fb6923f5f99aaacc44caeeed14b6c7f6e4d8cd174fb3fcff92cf944e
3
+ metadata.gz: 74ce2777ba2e1a7635aa92623cbb6e252bef41abafa62fff7b3f9f651aad05af
4
+ data.tar.gz: 83f543391d4e60f2ab928eb44c6975d4f935b84c7e63bcbb05685331bcdf59a2
5
5
  SHA512:
6
- metadata.gz: c0ee8ca9569e7409f989cce52070112bb61af2c064dec679a96e7f26bb74837b8f2ae207617f619280140548fb5cc8804a1996464265415efc96d0e0fafc7085
7
- data.tar.gz: b1cff6570a5137fd7ff26fd11eed662d7002c9d242ca03ea303c0c1711f4a44f12dbdc3958ce3657b0988742abd351792476ab0fc7171ccafebe1c28a70be595
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, indent_width:)
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, depth: 0)
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SqlBeautifier
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -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.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