sql_beautifier 0.2.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 +10 -0
- data/README.md +86 -8
- data/lib/sql_beautifier/clauses/base.rb +10 -0
- data/lib/sql_beautifier/clauses/condition_clause.rb +3 -3
- data/lib/sql_beautifier/clauses/from.rb +10 -8
- data/lib/sql_beautifier/clauses/group_by.rb +2 -2
- data/lib/sql_beautifier/clauses/having.rb +1 -1
- 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 +4 -5
- data/lib/sql_beautifier/clauses/where.rb +1 -1
- data/lib/sql_beautifier/condition_formatter.rb +3 -3
- data/lib/sql_beautifier/configuration.rb +33 -0
- data/lib/sql_beautifier/constants.rb +0 -5
- data/lib/sql_beautifier/cte_formatter.rb +191 -0
- data/lib/sql_beautifier/formatter.rb +50 -2
- data/lib/sql_beautifier/normalizer.rb +71 -0
- data/lib/sql_beautifier/subquery_formatter.rb +113 -0
- data/lib/sql_beautifier/table_registry.rb +17 -3
- data/lib/sql_beautifier/util.rb +33 -0
- data/lib/sql_beautifier/version.rb +1 -1
- data/lib/sql_beautifier.rb +19 -0
- metadata +4 -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
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [X.X.X] - YYYY-MM-DD
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-03-27
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-03-27
|
|
8
|
+
|
|
9
|
+
- Add configuration system with `SqlBeautifier.configure` block and `SqlBeautifier.reset_configuration!`
|
|
10
|
+
- Add configurable keyword case (`:lower` / `:upper`), keyword column width, indent spaces, table name format (`:pascal_case` / `:lowercase`), inline group threshold, and alias strategy (`:initials` / `:none` / callable)
|
|
11
|
+
- Add semicolon stripping in normalizer (trailing `;` removed before formatting)
|
|
12
|
+
- Add comment stripping in normalizer (`--` line comments and `/* */` block comments, string-aware)
|
|
13
|
+
- Add subquery formatting with recursive indentation (`(select ...)` expanded to multiline)
|
|
14
|
+
|
|
5
15
|
## [0.2.0] - 2026-03-27
|
|
6
16
|
|
|
7
17
|
- Add JOIN support (inner, left, right, full outer, cross) with formatted continuation lines
|
data/README.md
CHANGED
|
@@ -48,16 +48,16 @@ where active = true
|
|
|
48
48
|
order by name
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
Single-word keywords are lowercased and padded so their clause bodies start at an 8-character column. Multi-word clauses such as `order by` and `group by`, and short clauses like `limit`, use a single space between the keyword and the clause body instead of padding.
|
|
51
|
+
Single-word keywords are lowercased and padded so their clause bodies start at an 8-character column. Multi-word clauses such as `order by` and `group by`, and short clauses like `limit`, use a single space between the keyword and the clause body instead of padding. Clause spacing is compact by default for simple one-column / one-table / one-condition queries, and otherwise uses blank lines between top-level clauses. Multi-column SELECT lists place each column on its own line with continuation indentation. Table names are PascalCased and automatically aliased.
|
|
52
52
|
|
|
53
53
|
### Table Aliasing
|
|
54
54
|
|
|
55
55
|
Tables are automatically aliased using their initials. Underscore-separated table names use the first letter of each segment:
|
|
56
56
|
|
|
57
|
-
| Table Name
|
|
58
|
-
|
|
|
59
|
-
| `users`
|
|
60
|
-
| `active_storage_blobs`
|
|
57
|
+
| Table Name | PascalCase | Alias |
|
|
58
|
+
| -------------------------- | -------------------------- | ----- |
|
|
59
|
+
| `users` | `Users` | `u` |
|
|
60
|
+
| `active_storage_blobs` | `Active_Storage_Blobs` | `asb` |
|
|
61
61
|
| `person_event_invitations` | `Person_Event_Invitations` | `pei` |
|
|
62
62
|
|
|
63
63
|
All `table.column` references throughout the query are replaced with `alias.column`:
|
|
@@ -219,11 +219,8 @@ Produces:
|
|
|
219
219
|
|
|
220
220
|
```sql
|
|
221
221
|
select id
|
|
222
|
-
|
|
223
222
|
from Users u
|
|
224
|
-
|
|
225
223
|
order by created_at desc
|
|
226
|
-
|
|
227
224
|
limit 25
|
|
228
225
|
```
|
|
229
226
|
|
|
@@ -263,6 +260,87 @@ select user_id,
|
|
|
263
260
|
from Users u
|
|
264
261
|
```
|
|
265
262
|
|
|
263
|
+
### Subqueries
|
|
264
|
+
|
|
265
|
+
Subqueries are automatically detected and recursively formatted with indentation:
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
SqlBeautifier.call("SELECT id FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 100)")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Produces:
|
|
272
|
+
|
|
273
|
+
```sql
|
|
274
|
+
select id
|
|
275
|
+
from Users u
|
|
276
|
+
where id in (
|
|
277
|
+
select user_id
|
|
278
|
+
from Orders o
|
|
279
|
+
where total > 100
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Nested subqueries increase indentation at each level.
|
|
284
|
+
|
|
285
|
+
### Comments and Semicolons
|
|
286
|
+
|
|
287
|
+
SQL comments (`--` line comments and `/* */` block comments) and trailing semicolons are automatically stripped during normalization. Comments inside string literals are preserved:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
SqlBeautifier.call("SELECT id /* primary key */ FROM users -- main table\nWHERE active = true;")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Produces:
|
|
294
|
+
|
|
295
|
+
```sql
|
|
296
|
+
select id
|
|
297
|
+
from Users u
|
|
298
|
+
where active = true
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Configuration
|
|
302
|
+
|
|
303
|
+
Customize formatting behavior with `SqlBeautifier.configure`:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
SqlBeautifier.configure do |config|
|
|
307
|
+
config.keyword_case = :upper # :lower (default), :upper
|
|
308
|
+
config.keyword_column_width = 10 # default: 8
|
|
309
|
+
config.indent_spaces = 4 # default: 4
|
|
310
|
+
config.clause_spacing_mode = :spacious # :compact (default), :spacious
|
|
311
|
+
config.table_name_format = :lowercase # :pascal_case (default), :lowercase
|
|
312
|
+
config.inline_group_threshold = 80 # default: 100
|
|
313
|
+
config.alias_strategy = :none # :initials (default), :none, or a callable
|
|
314
|
+
end
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### Clause Spacing Modes
|
|
318
|
+
|
|
319
|
+
- `:compact` (default) keeps top-level clauses on single newlines only when the query is simple:
|
|
320
|
+
- exactly one SELECT column
|
|
321
|
+
- exactly one FROM table (no JOINs)
|
|
322
|
+
- zero or one top-level WHERE condition
|
|
323
|
+
- only `select`, `from`, optional `where`, optional `order by`, and optional `limit`
|
|
324
|
+
- `:spacious` always separates top-level clauses with blank lines
|
|
325
|
+
|
|
326
|
+
Reset to defaults:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
SqlBeautifier.reset_configuration!
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### Alias Strategies
|
|
333
|
+
|
|
334
|
+
- `:initials` (default) — automatic aliases using table initials (`users` → `u`, `active_storage_blobs` → `asb`)
|
|
335
|
+
- `:none` — no automatic aliases (explicit aliases in the SQL are still preserved)
|
|
336
|
+
- Callable — provide a proc/lambda for custom alias generation:
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
SqlBeautifier.configure do |config|
|
|
340
|
+
config.alias_strategy = ->(table_name) { "t_#{table_name[0..2]}" }
|
|
341
|
+
end
|
|
342
|
+
```
|
|
343
|
+
|
|
266
344
|
### Callable Interface
|
|
267
345
|
|
|
268
346
|
`SqlBeautifier.call` is the public API, making it a valid callable for Rails `normalizes` and anywhere a proc-like object is expected:
|
|
@@ -10,6 +10,16 @@ module SqlBeautifier
|
|
|
10
10
|
def initialize(value)
|
|
11
11
|
@value = value
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def keyword_prefix
|
|
17
|
+
Util.keyword_padding(self.class::KEYWORD)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def continuation_indent
|
|
21
|
+
Util.continuation_padding
|
|
22
|
+
end
|
|
13
23
|
end
|
|
14
24
|
end
|
|
15
25
|
end
|
|
@@ -4,10 +4,10 @@ module SqlBeautifier
|
|
|
4
4
|
module Clauses
|
|
5
5
|
class ConditionClause < Base
|
|
6
6
|
def call
|
|
7
|
-
return "#{
|
|
7
|
+
return "#{keyword_prefix}#{@value.strip}" unless multiple_conditions?
|
|
8
8
|
|
|
9
|
-
formatted_conditions = ConditionFormatter.format(@value, indent_width:
|
|
10
|
-
formatted_conditions.sub(
|
|
9
|
+
formatted_conditions = ConditionFormatter.format(@value, indent_width: SqlBeautifier.config_for(:keyword_column_width))
|
|
10
|
+
formatted_conditions.sub(continuation_indent, keyword_prefix)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
private
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
module SqlBeautifier
|
|
4
4
|
module Clauses
|
|
5
5
|
class From < Base
|
|
6
|
-
|
|
7
|
-
CONTINUATION_INDENTATION = " "
|
|
8
|
-
JOIN_CONDITION_INDENTATION = " "
|
|
6
|
+
KEYWORD = "from"
|
|
9
7
|
|
|
10
8
|
def self.call(value, table_registry:)
|
|
11
9
|
new(value, table_registry: table_registry).call
|
|
@@ -22,7 +20,7 @@ module SqlBeautifier
|
|
|
22
20
|
join_parts = split_join_parts
|
|
23
21
|
primary_table_text = join_parts.shift.strip
|
|
24
22
|
formatted_primary_table_name = format_table_with_alias(primary_table_text)
|
|
25
|
-
add_line!("#{
|
|
23
|
+
add_line!("#{keyword_prefix}#{formatted_primary_table_name}")
|
|
26
24
|
|
|
27
25
|
join_parts.each { |join_part| format_join_part(join_part) }
|
|
28
26
|
|
|
@@ -35,6 +33,10 @@ module SqlBeautifier
|
|
|
35
33
|
@lines << line
|
|
36
34
|
end
|
|
37
35
|
|
|
36
|
+
def join_condition_indentation
|
|
37
|
+
Util.whitespace(SqlBeautifier.config_for(:keyword_column_width) + 4)
|
|
38
|
+
end
|
|
39
|
+
|
|
38
40
|
def format_join_part(join_part)
|
|
39
41
|
join_keyword, remaining_join_content = extract_join_keyword(join_part)
|
|
40
42
|
return unless join_keyword && remaining_join_content
|
|
@@ -45,7 +47,7 @@ module SqlBeautifier
|
|
|
45
47
|
format_join_with_conditions(join_keyword, remaining_join_content, on_keyword_position)
|
|
46
48
|
else
|
|
47
49
|
formatted_table_name = format_table_with_alias(remaining_join_content)
|
|
48
|
-
add_line!("#{
|
|
50
|
+
add_line!("#{continuation_indent}#{join_keyword} #{formatted_table_name}")
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
|
|
@@ -56,10 +58,10 @@ module SqlBeautifier
|
|
|
56
58
|
|
|
57
59
|
formatted_table_name = format_table_with_alias(table_text)
|
|
58
60
|
first_condition = on_conditions.first[1]
|
|
59
|
-
add_line!("#{
|
|
61
|
+
add_line!("#{continuation_indent}#{join_keyword} #{formatted_table_name} on #{first_condition}")
|
|
60
62
|
|
|
61
63
|
on_conditions.drop(1).each do |conjunction, additional_condition|
|
|
62
|
-
add_line!("#{
|
|
64
|
+
add_line!("#{join_condition_indentation}#{conjunction} #{additional_condition}")
|
|
63
65
|
end
|
|
64
66
|
end
|
|
65
67
|
|
|
@@ -137,7 +139,7 @@ module SqlBeautifier
|
|
|
137
139
|
|
|
138
140
|
def format_table_with_alias(table_text)
|
|
139
141
|
table_name = Util.first_word(table_text)
|
|
140
|
-
formatted_table_name = Util.
|
|
142
|
+
formatted_table_name = Util.format_table_name(table_name)
|
|
141
143
|
table_alias = @table_registry.alias_for(table_name)
|
|
142
144
|
return formatted_table_name unless table_alias
|
|
143
145
|
|
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
module SqlBeautifier
|
|
4
4
|
module Clauses
|
|
5
5
|
class Limit < Base
|
|
6
|
-
|
|
6
|
+
KEYWORD = "limit"
|
|
7
7
|
|
|
8
8
|
def call
|
|
9
|
-
"#{
|
|
9
|
+
"#{keyword_prefix}#{@value.strip}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def keyword_prefix
|
|
15
|
+
"#{Util.format_keyword(KEYWORD)} "
|
|
10
16
|
end
|
|
11
17
|
end
|
|
12
18
|
end
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
module SqlBeautifier
|
|
4
4
|
module Clauses
|
|
5
5
|
class Select < Base
|
|
6
|
-
|
|
7
|
-
CONTINUATION_INDENTATION = " "
|
|
6
|
+
KEYWORD = "select"
|
|
8
7
|
DISTINCT_ON_PARENTHESIS_PATTERN = %r{distinct on\s*\(}
|
|
9
8
|
DISTINCT_ON_PATTERN = %r{distinct on }
|
|
10
9
|
LEADING_COMMA_PATTERN = %r{\A,\s*}
|
|
@@ -22,15 +21,15 @@ module SqlBeautifier
|
|
|
22
21
|
private
|
|
23
22
|
|
|
24
23
|
def keyword_line(column)
|
|
25
|
-
"#{
|
|
24
|
+
"#{keyword_prefix}#{column.strip}"
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def continuation_line(column)
|
|
29
|
-
"#{
|
|
28
|
+
"#{continuation_indent}#{column.strip}"
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def format_with_prefix(prefix, columns)
|
|
33
|
-
first_line = "#{
|
|
32
|
+
first_line = "#{keyword_prefix}#{prefix}"
|
|
34
33
|
column_lines = columns.map { |column| continuation_line(column) }
|
|
35
34
|
|
|
36
35
|
"#{first_line}\n#{column_lines.join(",\n")}"
|
|
@@ -9,7 +9,7 @@ module SqlBeautifier
|
|
|
9
9
|
return text.strip if conditions.length <= 1 && !parse_condition_group(conditions.dig(0, 1))
|
|
10
10
|
|
|
11
11
|
conditions = flatten_same_conjunction_groups(conditions)
|
|
12
|
-
indentation =
|
|
12
|
+
indentation = Util.whitespace(indent_width)
|
|
13
13
|
lines = []
|
|
14
14
|
|
|
15
15
|
conditions.each_with_index do |(conjunction, condition_text), index|
|
|
@@ -92,11 +92,11 @@ module SqlBeautifier
|
|
|
92
92
|
return condition_text unless inner_conditions
|
|
93
93
|
|
|
94
94
|
inline_version = rebuild_inline(inner_conditions)
|
|
95
|
-
return inline_version if inline_version.length <=
|
|
95
|
+
return inline_version if inline_version.length <= SqlBeautifier.config_for(:inline_group_threshold)
|
|
96
96
|
|
|
97
97
|
inner_content = Util.strip_outer_parentheses(condition_text.strip)
|
|
98
98
|
formatted_inner_content = format(inner_content, indent_width: indent_width + 4)
|
|
99
|
-
indentation =
|
|
99
|
+
indentation = Util.whitespace(indent_width)
|
|
100
100
|
|
|
101
101
|
"(\n#{formatted_inner_content}\n#{indentation})"
|
|
102
102
|
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
|
|
@@ -29,11 +29,6 @@ module SqlBeautifier
|
|
|
29
29
|
CONJUNCTIONS = %w[and or].freeze
|
|
30
30
|
BETWEEN_KEYWORD = "between"
|
|
31
31
|
|
|
32
|
-
INLINE_GROUP_THRESHOLD = 100
|
|
33
|
-
KEYWORD_COLUMN_WIDTH = 8
|
|
34
|
-
|
|
35
|
-
LEADING_KEYWORD_INDENT_PATTERN = %r{\A#{' ' * KEYWORD_COLUMN_WIDTH}}
|
|
36
|
-
|
|
37
32
|
OPEN_PARENTHESIS = "("
|
|
38
33
|
CLOSE_PARENTHESIS = ")"
|
|
39
34
|
COMMA = ","
|
|
@@ -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
|
|
@@ -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
|
|
@@ -16,6 +17,9 @@ module SqlBeautifier
|
|
|
16
17
|
@normalized_value = Normalizer.call(@value)
|
|
17
18
|
return unless @normalized_value.present?
|
|
18
19
|
|
|
20
|
+
cte_result = CteFormatter.format(@normalized_value, depth: @depth)
|
|
21
|
+
return cte_result if cte_result
|
|
22
|
+
|
|
19
23
|
first_clause_position = Tokenizer.first_clause_position(@normalized_value)
|
|
20
24
|
return "#{@normalized_value}\n" if first_clause_position.nil? || first_clause_position.positive?
|
|
21
25
|
|
|
@@ -31,9 +35,10 @@ module SqlBeautifier
|
|
|
31
35
|
append_clause!(:order_by, Clauses::OrderBy)
|
|
32
36
|
append_clause!(:limit, Clauses::Limit)
|
|
33
37
|
|
|
34
|
-
output = @parts.join(
|
|
38
|
+
output = @parts.join(clause_separator)
|
|
35
39
|
return "#{@normalized_value}\n" if output.empty?
|
|
36
40
|
|
|
41
|
+
output = SubqueryFormatter.format(output, @depth)
|
|
37
42
|
output = @table_registry.apply_aliases(output) if @table_registry
|
|
38
43
|
"#{output}\n"
|
|
39
44
|
end
|
|
@@ -53,5 +58,48 @@ module SqlBeautifier
|
|
|
53
58
|
|
|
54
59
|
@parts << Clauses::From.call(value, table_registry: @table_registry)
|
|
55
60
|
end
|
|
61
|
+
|
|
62
|
+
def clause_separator
|
|
63
|
+
return "\n\n" if SqlBeautifier.config_for(:clause_spacing_mode) == :spacious
|
|
64
|
+
return "\n\n" unless compact_query?
|
|
65
|
+
|
|
66
|
+
"\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def compact_query?
|
|
70
|
+
compact_clause_set? && single_select_column? && single_from_table? && one_or_fewer_conditions?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def compact_clause_set?
|
|
74
|
+
clause_keys = @clauses.keys
|
|
75
|
+
allowed_keys = %i[select from where order_by limit]
|
|
76
|
+
|
|
77
|
+
clause_keys.all? { |key| allowed_keys.include?(key) } && clause_keys.include?(:select) && clause_keys.include?(:from)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def single_select_column?
|
|
81
|
+
select_value = @clauses[:select]
|
|
82
|
+
return false unless select_value.present?
|
|
83
|
+
|
|
84
|
+
formatted_select = Clauses::Select.call(select_value)
|
|
85
|
+
formatted_select.lines.length == 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def single_from_table?
|
|
89
|
+
from_value = @clauses[:from]
|
|
90
|
+
return false unless from_value.present?
|
|
91
|
+
|
|
92
|
+
join_keywords = Constants::JOIN_KEYWORDS_BY_LENGTH.any? { |keyword| Tokenizer.find_top_level_keyword(from_value, keyword) }
|
|
93
|
+
return false if join_keywords
|
|
94
|
+
|
|
95
|
+
!from_value.match?(%r{,})
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def one_or_fewer_conditions?
|
|
99
|
+
where_value = @clauses[:where]
|
|
100
|
+
return true unless where_value.present?
|
|
101
|
+
|
|
102
|
+
Tokenizer.split_top_level_conditions(where_value).length <= 1
|
|
103
|
+
end
|
|
56
104
|
end
|
|
57
105
|
end
|
|
@@ -18,6 +18,11 @@ 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
|
|
|
@@ -110,5 +115,71 @@ module SqlBeautifier
|
|
|
110
115
|
def requires_quoting?(identifier)
|
|
111
116
|
identifier !~ SAFE_UNQUOTED_IDENTIFIER
|
|
112
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
|
|
113
184
|
end
|
|
114
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
|
|
@@ -7,6 +7,7 @@ module SqlBeautifier
|
|
|
7
7
|
def initialize(from_content)
|
|
8
8
|
@from_content = from_content
|
|
9
9
|
@table_map = {}
|
|
10
|
+
@alias_strategy = SqlBeautifier.config_for(:alias_strategy)
|
|
10
11
|
build!
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -15,6 +16,8 @@ module SqlBeautifier
|
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def apply_aliases(text)
|
|
19
|
+
return text if @table_map.empty?
|
|
20
|
+
|
|
18
21
|
output = +""
|
|
19
22
|
position = 0
|
|
20
23
|
|
|
@@ -48,10 +51,19 @@ module SqlBeautifier
|
|
|
48
51
|
|
|
49
52
|
def build!
|
|
50
53
|
table_entries = extract_table_entries(@from_content)
|
|
51
|
-
initials_occurrence_counts = count_initials_occurrences(table_entries)
|
|
52
|
-
used_aliases = []
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
if @alias_strategy == :none
|
|
56
|
+
table_entries.each do |table_entry|
|
|
57
|
+
next unless table_entry[:explicit_alias]
|
|
58
|
+
|
|
59
|
+
@table_map[table_entry[:table_name]] = table_entry[:explicit_alias]
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
initials_occurrence_counts = count_initials_occurrences(table_entries)
|
|
63
|
+
used_aliases = []
|
|
64
|
+
assign_aliases!(table_entries, initials_occurrence_counts, used_aliases)
|
|
65
|
+
end
|
|
66
|
+
|
|
55
67
|
@tables_by_descending_length = @table_map.keys.sort_by { |name| -name.length }.freeze
|
|
56
68
|
end
|
|
57
69
|
|
|
@@ -163,6 +175,8 @@ module SqlBeautifier
|
|
|
163
175
|
end
|
|
164
176
|
|
|
165
177
|
def table_initials(table_name)
|
|
178
|
+
return @alias_strategy.call(table_name) if @alias_strategy.respond_to?(:call)
|
|
179
|
+
|
|
166
180
|
table_name.split("_").map { |segment| segment[0] }.join
|
|
167
181
|
end
|
|
168
182
|
|
data/lib/sql_beautifier/util.rb
CHANGED
|
@@ -4,6 +4,10 @@ module SqlBeautifier
|
|
|
4
4
|
module Util
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
+
def whitespace(length)
|
|
8
|
+
" " * length
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
def upper_pascal_case(name)
|
|
8
12
|
name.split("_").map(&:capitalize).join("_")
|
|
9
13
|
end
|
|
@@ -30,5 +34,34 @@ module SqlBeautifier
|
|
|
30
34
|
|
|
31
35
|
value.gsub(Constants::DOUBLE_QUOTE, Constants::ESCAPED_DOUBLE_QUOTE)
|
|
32
36
|
end
|
|
37
|
+
|
|
38
|
+
def keyword_padding(keyword)
|
|
39
|
+
formatted_keyword = format_keyword(keyword)
|
|
40
|
+
padding_width = [SqlBeautifier.config_for(:keyword_column_width) - formatted_keyword.length, 1].max
|
|
41
|
+
|
|
42
|
+
"#{formatted_keyword}#{whitespace(padding_width)}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def continuation_padding
|
|
46
|
+
whitespace(SqlBeautifier.config_for(:keyword_column_width))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def format_keyword(keyword)
|
|
50
|
+
case SqlBeautifier.config_for(:keyword_case)
|
|
51
|
+
when :upper
|
|
52
|
+
keyword.upcase
|
|
53
|
+
else
|
|
54
|
+
keyword.downcase
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_table_name(name)
|
|
59
|
+
case SqlBeautifier.config_for(:table_name_format)
|
|
60
|
+
when :lowercase
|
|
61
|
+
name.downcase
|
|
62
|
+
else
|
|
63
|
+
upper_pascal_case(name)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
33
66
|
end
|
|
34
67
|
end
|
data/lib/sql_beautifier.rb
CHANGED
|
@@ -5,11 +5,14 @@ require "active_support/core_ext/object/blank"
|
|
|
5
5
|
require_relative "sql_beautifier/version"
|
|
6
6
|
require_relative "sql_beautifier/constants"
|
|
7
7
|
require_relative "sql_beautifier/util"
|
|
8
|
+
require_relative "sql_beautifier/configuration"
|
|
8
9
|
|
|
9
10
|
require_relative "sql_beautifier/normalizer"
|
|
10
11
|
require_relative "sql_beautifier/tokenizer"
|
|
11
12
|
require_relative "sql_beautifier/table_registry"
|
|
12
13
|
require_relative "sql_beautifier/condition_formatter"
|
|
14
|
+
require_relative "sql_beautifier/subquery_formatter"
|
|
15
|
+
require_relative "sql_beautifier/cte_formatter"
|
|
13
16
|
require_relative "sql_beautifier/clauses/base"
|
|
14
17
|
require_relative "sql_beautifier/clauses/condition_clause"
|
|
15
18
|
require_relative "sql_beautifier/clauses/select"
|
|
@@ -31,4 +34,20 @@ module SqlBeautifier
|
|
|
31
34
|
|
|
32
35
|
Formatter.call(value)
|
|
33
36
|
end
|
|
37
|
+
|
|
38
|
+
def configuration
|
|
39
|
+
@configuration ||= Configuration.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def configure
|
|
43
|
+
yield configuration
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def config_for(key)
|
|
47
|
+
configuration.public_send(key)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reset_configuration!
|
|
51
|
+
@configuration = Configuration.new
|
|
52
|
+
end
|
|
34
53
|
end
|
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
|
|
@@ -47,9 +47,12 @@ files:
|
|
|
47
47
|
- lib/sql_beautifier/clauses/select.rb
|
|
48
48
|
- lib/sql_beautifier/clauses/where.rb
|
|
49
49
|
- lib/sql_beautifier/condition_formatter.rb
|
|
50
|
+
- lib/sql_beautifier/configuration.rb
|
|
50
51
|
- lib/sql_beautifier/constants.rb
|
|
52
|
+
- lib/sql_beautifier/cte_formatter.rb
|
|
51
53
|
- lib/sql_beautifier/formatter.rb
|
|
52
54
|
- lib/sql_beautifier/normalizer.rb
|
|
55
|
+
- lib/sql_beautifier/subquery_formatter.rb
|
|
53
56
|
- lib/sql_beautifier/table_registry.rb
|
|
54
57
|
- lib/sql_beautifier/tokenizer.rb
|
|
55
58
|
- lib/sql_beautifier/util.rb
|