sql_beautifier 0.10.3 → 0.10.4

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: 35c33bf6ef1f1c6095643a9e6661aa16a93a29ff44534aab07fddd0befec5b43
4
- data.tar.gz: aab36afe287416dc61b4633f3d4c07975736d6111a427118bf975be2742325a6
3
+ metadata.gz: 7c92d6b96851b3e1d24b983a6cf8d3d01ee643f4418d7c66cdc92bdc32baa132
4
+ data.tar.gz: 8cf024876d629ff3a2beaae6604c4cc860a6652582b90bf1f3c6c4001acc3517
5
5
  SHA512:
6
- metadata.gz: 58f7b763cc122816a919b2caa1ba0281efb5fa8dc1bfbd4e97f27c6838728e8a8db2a6018d819d1f2314359851f1cd138f28bb0f0fb21986defa4a1e11ad724a
7
- data.tar.gz: db2e7de085a689a9331e831432f5ed31f8a58304c9f20f4cca2376acf68681da9ba84741c18bf0fb9b9002c85fb4f1d87c3e8bf92fdf31a384455caba9cccb23
6
+ metadata.gz: 0252dddc816463054f4bcd225485cadb58eac11d964f6fd1f18e1d5fa3b23c2619caa0dc38d8f3395f7616fcf5e0e7d5d52e60bfca4f7d96b83fbfec7d345f19
7
+ data.tar.gz: 6dfa3284884d2c4df58d93dab99b31dcebce6e07f00a71d499a71bd37f0030a680f905ca15565b23e97f860b53a108feb23d5a3d004e2967c328eace9ff51bfe
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [X.X.X] - YYYY-MM-DD
4
4
 
5
+ ## [0.10.4] - 2026-03-30
6
+
7
+ - Add `IN` list multiline formatting — `WHERE x IN (1, 2, 3)` value lists with 2+ items are now expanded to one item per line with proper indentation; single-item lists and `IN (SELECT ...)` subqueries are left unchanged
8
+ - Fix redundant parentheses after `NOT` not being stripped — `NOT ((a = 1 OR b = 2))` is now reduced to `NOT (a = 1 OR b = 2)`, removing unnecessary doubled parentheses while preserving semantically required grouping
9
+
5
10
  ## [0.10.3] - 2026-03-30
6
11
 
7
12
  - Add `DROP TABLE` formatting — `DROP TABLE [IF EXISTS] table_name` statements are now recognized and rendered with proper keyword casing and PascalCase table names instead of passing through as normalized text
data/README.md CHANGED
@@ -188,6 +188,41 @@ where active = true
188
188
  );
189
189
  ```
190
190
 
191
+ `IN` value lists with multiple items are expanded to one item per line. Single-item lists and `IN (SELECT ...)` subqueries are left inline:
192
+
193
+ ```ruby
194
+ SqlBeautifier.call("SELECT id FROM users WHERE status IN ('active', 'pending', 'banned')")
195
+ ```
196
+
197
+ Produces:
198
+
199
+ ```sql
200
+ select id
201
+ from Users u
202
+ where status in (
203
+ 'active',
204
+ 'pending',
205
+ 'banned'
206
+ );
207
+ ```
208
+
209
+ Redundant parentheses are removed, including after `NOT`:
210
+
211
+ ```ruby
212
+ SqlBeautifier.call("SELECT id FROM users WHERE NOT ((active = true OR role = 'guest')) AND verified = true")
213
+ ```
214
+
215
+ Produces:
216
+
217
+ ```sql
218
+ select id
219
+
220
+ from Users u
221
+
222
+ where not (active = true or role = 'guest')
223
+ and verified = true;
224
+ ```
225
+
191
226
  ### GROUP BY and HAVING
192
227
 
193
228
  ```ruby
@@ -7,8 +7,12 @@ module SqlBeautifier
7
7
  keyword_width = SqlBeautifier.config_for(:keyword_column_width)
8
8
  formatted_value = CaseExpression.format_in_text(@value, base_indent: keyword_width)
9
9
  unwrapped_value = strip_wrapping_parentheses(formatted_value)
10
+ unwrapped_value = Condition.unwrap_not_prefix_parens(unwrapped_value)
10
11
 
11
- return "#{keyword_prefix}#{unwrapped_value}" unless multiple_conditions?(unwrapped_value)
12
+ unless multiple_conditions?(unwrapped_value)
13
+ formatted_leaf = InList.format_in_text(unwrapped_value, base_indent: keyword_width)
14
+ return "#{keyword_prefix}#{formatted_leaf}"
15
+ end
12
16
 
13
17
  formatted_conditions = Condition.format(unwrapped_value, indent_width: keyword_width)
14
18
  formatted_conditions.sub(continuation_indent, keyword_prefix)
@@ -2,6 +2,8 @@
2
2
 
3
3
  module SqlBeautifier
4
4
  class Condition < Base
5
+ NOT_PREFIX_PATTERN = %r{\Anot([[:space:]]+)}i
6
+
5
7
  option :conjunction, default: -> {}
6
8
  option :expression, default: -> {}
7
9
  option :children, default: -> {}
@@ -52,7 +54,7 @@ module SqlBeautifier
52
54
  end
53
55
 
54
56
  def render(indent_width:)
55
- return @expression if leaf?
57
+ return InList.format_in_text(@expression, base_indent: indent_width) if leaf?
56
58
 
57
59
  inline_version = render_inline
58
60
  return inline_version if inline_version.length <= SqlBeautifier.config_for(:inline_group_threshold)
@@ -89,7 +91,28 @@ module SqlBeautifier
89
91
  output = inner_content
90
92
  end
91
93
 
92
- output
94
+ unwrap_not_prefix_parens(output)
95
+ end
96
+
97
+ def self.unwrap_not_prefix_parens(text)
98
+ prefix_match = text.match(NOT_PREFIX_PATTERN)
99
+ return text unless prefix_match
100
+
101
+ remainder = text[prefix_match[0].length..]
102
+
103
+ unwrapped_remainder = remainder
104
+
105
+ while Tokenizer.outer_parentheses_wrap_all?(unwrapped_remainder)
106
+ inner_content = Util.strip_outer_parentheses(unwrapped_remainder)
107
+ inner_conditions = Tokenizer.split_top_level_conditions(inner_content)
108
+ break if inner_conditions.length > 1
109
+
110
+ unwrapped_remainder = inner_content
111
+ end
112
+
113
+ return text if unwrapped_remainder == remainder
114
+
115
+ "not #{unwrapped_remainder}"
93
116
  end
94
117
 
95
118
  def self.parse_condition_group(condition_text)
@@ -10,7 +10,8 @@ module SqlBeautifier
10
10
 
11
11
  formatted_where_text = begin
12
12
  if conditions.length <= 1 && conditions.first&.leaf?
13
- "\n#{Util.keyword_padding('where')}#{@where_clause.strip}"
13
+ formatted_expression = InList.format_in_text(conditions.first.expression, base_indent: keyword_column_width)
14
+ "\n#{Util.keyword_padding('where')}#{formatted_expression}"
14
15
  else
15
16
  formatted_conditions = Condition.render_all(conditions, indent_width: keyword_column_width)
16
17
  "\n#{formatted_conditions.sub(Util.continuation_padding, Util.keyword_padding('where'))}"
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SqlBeautifier
4
+ module InList
5
+ IN_KEYWORD = "in"
6
+ NOT_IN_KEYWORD = "not in"
7
+ SUBQUERY_PATTERN = %r{\A[[:space:]]*select(?:[[:space:]]|\()}i
8
+
9
+ module_function
10
+
11
+ def format_in_text(text, base_indent: 0)
12
+ output = +""
13
+ scanner = Scanner.new(text)
14
+
15
+ while scanner.position < text.length
16
+ consumed = scanner.scan_quoted_or_sentinel!
17
+ if consumed
18
+ output << consumed
19
+ next
20
+ end
21
+
22
+ if scanner.current_char == Constants::OPEN_PARENTHESIS
23
+ scanner.increment_depth!
24
+ output << scanner.current_char
25
+ scanner.advance!
26
+ next
27
+ end
28
+
29
+ if scanner.current_char == Constants::CLOSE_PARENTHESIS
30
+ scanner.decrement_depth!
31
+ output << scanner.current_char
32
+ scanner.advance!
33
+ next
34
+ end
35
+
36
+ if scanner.parenthesis_depth.zero? && in_keyword_at?(scanner)
37
+ keyword_length = in_keyword_length_at(scanner)
38
+ parenthesis_position = find_opening_parenthesis(text, scanner.position + keyword_length)
39
+
40
+ if parenthesis_position
41
+ expansion = format_in_list(text, keyword_position: scanner.position, parenthesis_position: parenthesis_position, base_indent: base_indent)
42
+
43
+ if expansion
44
+ output << expansion[:text]
45
+ scanner.advance!(expansion[:consumed] - scanner.position)
46
+ next
47
+ end
48
+ end
49
+ end
50
+
51
+ output << scanner.current_char
52
+ scanner.advance!
53
+ end
54
+
55
+ output
56
+ end
57
+
58
+ def in_keyword_at?(scanner)
59
+ scanner.keyword_at?(NOT_IN_KEYWORD) || scanner.keyword_at?(IN_KEYWORD)
60
+ end
61
+
62
+ def in_keyword_length_at(scanner)
63
+ scanner.keyword_at?(NOT_IN_KEYWORD) ? NOT_IN_KEYWORD.length : IN_KEYWORD.length
64
+ end
65
+
66
+ def find_opening_parenthesis(text, from_position)
67
+ position = from_position
68
+
69
+ while position < text.length
70
+ return position if text[position] == Constants::OPEN_PARENTHESIS
71
+ return nil unless text[position] =~ Constants::WHITESPACE_CHARACTER_REGEX
72
+
73
+ position += 1
74
+ end
75
+
76
+ nil
77
+ end
78
+
79
+ def format_in_list(text, keyword_position:, parenthesis_position:, base_indent:)
80
+ closing_position = Scanner.new(text).find_matching_parenthesis(parenthesis_position)
81
+ return nil unless closing_position
82
+
83
+ inner_text = text[(parenthesis_position + 1)...closing_position]
84
+ return nil if inner_text.match?(SUBQUERY_PATTERN)
85
+
86
+ items = Tokenizer.split_by_top_level_commas(inner_text)
87
+ return nil if items.length <= 1
88
+
89
+ keyword_text = text[keyword_position...(parenthesis_position + 1)]
90
+ indent_spaces = SqlBeautifier.config_for(:indent_spaces)
91
+ item_indent = Util.whitespace(base_indent + indent_spaces)
92
+ closing_indent = Util.whitespace(base_indent)
93
+
94
+ formatted_items = items.map.with_index do |item, index|
95
+ trailing_comma = index < items.length - 1 ? "," : ""
96
+ "#{item_indent}#{item}#{trailing_comma}"
97
+ end
98
+
99
+ {
100
+ text: "#{keyword_text}\n#{formatted_items.join("\n")}\n#{closing_indent})",
101
+ consumed: closing_position + 1,
102
+ }
103
+ end
104
+
105
+ private_class_method :in_keyword_at?, :in_keyword_length_at, :find_opening_parenthesis, :format_in_list
106
+ end
107
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SqlBeautifier
4
- VERSION = "0.10.3"
4
+ VERSION = "0.10.4"
5
5
  end
@@ -19,6 +19,7 @@ require_relative "sql_beautifier/table_reference"
19
19
  require_relative "sql_beautifier/table_registry"
20
20
  require_relative "sql_beautifier/join"
21
21
  require_relative "sql_beautifier/case_expression"
22
+ require_relative "sql_beautifier/in_list"
22
23
  require_relative "sql_beautifier/expression"
23
24
  require_relative "sql_beautifier/sort_expression"
24
25
  require_relative "sql_beautifier/condition"
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.10.3
4
+ version: 0.10.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kinnell Shah
@@ -92,6 +92,7 @@ files:
92
92
  - lib/sql_beautifier/drop_table.rb
93
93
  - lib/sql_beautifier/expression.rb
94
94
  - lib/sql_beautifier/formatter.rb
95
+ - lib/sql_beautifier/in_list.rb
95
96
  - lib/sql_beautifier/insert_query.rb
96
97
  - lib/sql_beautifier/join.rb
97
98
  - lib/sql_beautifier/normalizer.rb