sql_beautifier 0.10.2 → 0.10.3
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 +8 -0
- data/README.md +30 -0
- data/lib/sql_beautifier/constants.rb +7 -0
- data/lib/sql_beautifier/create_table.rb +65 -0
- data/lib/sql_beautifier/create_table_as.rb +1 -27
- data/lib/sql_beautifier/create_table_parsing.rb +32 -0
- data/lib/sql_beautifier/drop_table.rb +56 -0
- data/lib/sql_beautifier/formatter.rb +6 -0
- data/lib/sql_beautifier/insert_query.rb +2 -1
- data/lib/sql_beautifier/version.rb +1 -1
- data/lib/sql_beautifier.rb +3 -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: 35c33bf6ef1f1c6095643a9e6661aa16a93a29ff44534aab07fddd0befec5b43
|
|
4
|
+
data.tar.gz: aab36afe287416dc61b4633f3d4c07975736d6111a427118bf975be2742325a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58f7b763cc122816a919b2caa1ba0281efb5fa8dc1bfbd4e97f27c6838728e8a8db2a6018d819d1f2314359851f1cd138f28bb0f0fb21986defa4a1e11ad724a
|
|
7
|
+
data.tar.gz: db2e7de085a689a9331e831432f5ed31f8a58304c9f20f4cca2376acf68681da9ba84741c18bf0fb9b9002c85fb4f1d87c3e8bf92fdf31a384455caba9cccb23
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [X.X.X] - YYYY-MM-DD
|
|
4
4
|
|
|
5
|
+
## [0.10.3] - 2026-03-30
|
|
6
|
+
|
|
7
|
+
- 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
|
|
8
|
+
- Add `CREATE TABLE` (DDL) formatting — `CREATE [TEMP|TEMPORARY|UNLOGGED|LOCAL] TABLE [IF NOT EXISTS] table_name (column_defs)` statements with column definitions are now recognized and rendered with proper keyword casing and PascalCase table names
|
|
9
|
+
- Fix `INSERT INTO` with a single column rendering the column list on multiple lines — single-column lists now render inline (e.g. `insert into Table (id)` instead of expanding to three lines)
|
|
10
|
+
- Reject `DROP TABLE` statements with trailing text (e.g. `CASCADE`, `RESTRICT`, multiple table names) — these now return `nil` instead of silently dropping the trailing text
|
|
11
|
+
- Reject `CREATE TABLE` (DDL) statements with trailing clauses after column definitions (e.g. `WITH (...)`, `TABLESPACE ...`) — these now return `nil` instead of silently dropping the trailing text
|
|
12
|
+
|
|
5
13
|
## [0.10.2] - 2026-03-30
|
|
6
14
|
|
|
7
15
|
- Fix `INSERT INTO ... (columns) (SELECT ...)` not being recognized — `InsertQuery.parse_body` now unwraps parenthesized SELECT subqueries, supporting PostgreSQL's valid `INSERT INTO table (cols) (SELECT ...)` syntax
|
data/README.md
CHANGED
|
@@ -411,6 +411,36 @@ where users.account_id = accounts.id
|
|
|
411
411
|
returning users.id;
|
|
412
412
|
```
|
|
413
413
|
|
|
414
|
+
### DROP TABLE
|
|
415
|
+
|
|
416
|
+
`DROP TABLE` statements are recognized and formatted with proper keyword casing and table name formatting:
|
|
417
|
+
|
|
418
|
+
```ruby
|
|
419
|
+
SqlBeautifier.call("DROP TABLE IF EXISTS persons")
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Produces:
|
|
423
|
+
|
|
424
|
+
```sql
|
|
425
|
+
drop table if exists Persons;
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### CREATE TABLE (DDL)
|
|
429
|
+
|
|
430
|
+
`CREATE TABLE` statements with column definitions are recognized and formatted with proper keyword casing and table name formatting. Column definitions are preserved as-is:
|
|
431
|
+
|
|
432
|
+
```ruby
|
|
433
|
+
SqlBeautifier.call("CREATE TEMPORARY TABLE persons (id bigint)")
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Produces:
|
|
437
|
+
|
|
438
|
+
```sql
|
|
439
|
+
create temporary table Persons (id bigint);
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Modifiers (`TEMP`, `TEMPORARY`, `UNLOGGED`, `LOCAL`) and `IF NOT EXISTS` are supported.
|
|
443
|
+
|
|
414
444
|
### Set Operators (UNION, INTERSECT, EXCEPT)
|
|
415
445
|
|
|
416
446
|
Compound queries joined by set operators are detected and each segment is formatted independently. The operator keyword appears on its own line with blank-line separation:
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
class CreateTable < Base
|
|
5
|
+
extend CreateTableParsing
|
|
6
|
+
|
|
7
|
+
option :modifier, default: -> {}
|
|
8
|
+
option :if_not_exists, type: Types::Bool
|
|
9
|
+
option :table_name
|
|
10
|
+
option :column_definitions
|
|
11
|
+
|
|
12
|
+
def self.parse(normalized_sql, **)
|
|
13
|
+
scanner = Scanner.new(normalized_sql)
|
|
14
|
+
return nil unless scanner.keyword_at?("create")
|
|
15
|
+
|
|
16
|
+
scanner.skip_past_keyword!("create")
|
|
17
|
+
modifier = detect_modifier(scanner)
|
|
18
|
+
scanner.skip_past_keyword!(modifier) if modifier
|
|
19
|
+
|
|
20
|
+
return nil unless scanner.keyword_at?("table")
|
|
21
|
+
|
|
22
|
+
scanner.skip_past_keyword!("table")
|
|
23
|
+
|
|
24
|
+
if_not_exists = detect_if_not_exists?(scanner)
|
|
25
|
+
skip_past_if_not_exists!(scanner) if if_not_exists
|
|
26
|
+
|
|
27
|
+
table_name = scanner.read_identifier!
|
|
28
|
+
return nil unless table_name
|
|
29
|
+
|
|
30
|
+
scanner.skip_whitespace!
|
|
31
|
+
return nil if scanner.finished?
|
|
32
|
+
return nil unless scanner.current_char == Constants::OPEN_PARENTHESIS
|
|
33
|
+
|
|
34
|
+
column_definitions = extract_column_definitions(normalized_sql, scanner)
|
|
35
|
+
return nil unless column_definitions
|
|
36
|
+
|
|
37
|
+
new(modifier: modifier, if_not_exists: if_not_exists, table_name: table_name, column_definitions: column_definitions)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.extract_column_definitions(normalized_sql, scanner)
|
|
41
|
+
closing = scanner.find_matching_parenthesis(scanner.position)
|
|
42
|
+
return nil unless closing
|
|
43
|
+
|
|
44
|
+
inner_text = normalized_sql[(scanner.position + 1)...closing].strip
|
|
45
|
+
return nil if inner_text.empty?
|
|
46
|
+
|
|
47
|
+
trailing_text = normalized_sql[(closing + 1)..].strip
|
|
48
|
+
return nil unless trailing_text.empty?
|
|
49
|
+
|
|
50
|
+
inner_text
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private_class_method :extract_column_definitions
|
|
54
|
+
|
|
55
|
+
def render
|
|
56
|
+
parts = [Util.format_keyword("create")]
|
|
57
|
+
parts << Util.format_keyword(@modifier) if @modifier
|
|
58
|
+
parts << Util.format_keyword("table")
|
|
59
|
+
parts << "#{Util.format_keyword('if')} #{Util.format_keyword('not')} #{Util.format_keyword('exists')}" if @if_not_exists
|
|
60
|
+
parts << Util.format_table_name(@table_name)
|
|
61
|
+
|
|
62
|
+
"#{parts.join(' ')} (#{@column_definitions})\n"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module SqlBeautifier
|
|
4
4
|
class CreateTableAs < Base
|
|
5
|
-
|
|
6
|
-
temp
|
|
7
|
-
temporary
|
|
8
|
-
unlogged
|
|
9
|
-
local
|
|
10
|
-
].freeze
|
|
5
|
+
extend CreateTableParsing
|
|
11
6
|
|
|
12
7
|
WITH_DATA_SUFFIX_REGEX = %r{\s+(with\s+(?:no\s+)?data)\s*\z}i
|
|
13
8
|
|
|
@@ -47,27 +42,6 @@ module SqlBeautifier
|
|
|
47
42
|
new(modifier: modifier, if_not_exists: if_not_exists, table_name: table_name, body_sql: body_sql, suffix: suffix, depth: depth)
|
|
48
43
|
end
|
|
49
44
|
|
|
50
|
-
def self.detect_modifier(scanner)
|
|
51
|
-
MODIFIERS.detect { |modifier| scanner.keyword_at?(modifier) }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def self.detect_if_not_exists?(scanner)
|
|
55
|
-
return false unless scanner.keyword_at?("if")
|
|
56
|
-
|
|
57
|
-
probe = Scanner.new(scanner.source, position: scanner.position)
|
|
58
|
-
probe.skip_past_keyword!("if")
|
|
59
|
-
return false unless probe.keyword_at?("not")
|
|
60
|
-
|
|
61
|
-
probe.skip_past_keyword!("not")
|
|
62
|
-
probe.keyword_at?("exists")
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def self.skip_past_if_not_exists!(scanner)
|
|
66
|
-
scanner.skip_past_keyword!("if")
|
|
67
|
-
scanner.skip_past_keyword!("not")
|
|
68
|
-
scanner.skip_past_keyword!("exists")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
45
|
def self.extract_body(sql, position)
|
|
72
46
|
scanner = Scanner.new(sql, position: position)
|
|
73
47
|
scanner.skip_whitespace!
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
module CreateTableParsing
|
|
5
|
+
def self.extended(base)
|
|
6
|
+
base.private_class_method :detect_modifier
|
|
7
|
+
base.private_class_method :detect_if_not_exists?
|
|
8
|
+
base.private_class_method :skip_past_if_not_exists!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def detect_modifier(scanner)
|
|
12
|
+
Constants::TABLE_MODIFIERS.detect { |modifier| scanner.keyword_at?(modifier) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def detect_if_not_exists?(scanner)
|
|
16
|
+
return false unless scanner.keyword_at?("if")
|
|
17
|
+
|
|
18
|
+
probe = Scanner.new(scanner.source, position: scanner.position)
|
|
19
|
+
probe.skip_past_keyword!("if")
|
|
20
|
+
return false unless probe.keyword_at?("not")
|
|
21
|
+
|
|
22
|
+
probe.skip_past_keyword!("not")
|
|
23
|
+
probe.keyword_at?("exists")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def skip_past_if_not_exists!(scanner)
|
|
27
|
+
scanner.skip_past_keyword!("if")
|
|
28
|
+
scanner.skip_past_keyword!("not")
|
|
29
|
+
scanner.skip_past_keyword!("exists")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqlBeautifier
|
|
4
|
+
class DropTable < Base
|
|
5
|
+
option :table_name
|
|
6
|
+
option :if_exists, type: Types::Bool
|
|
7
|
+
|
|
8
|
+
def self.parse(normalized_sql, **)
|
|
9
|
+
scanner = Scanner.new(normalized_sql)
|
|
10
|
+
return nil unless scanner.keyword_at?("drop")
|
|
11
|
+
|
|
12
|
+
scanner.skip_past_keyword!("drop")
|
|
13
|
+
return nil unless scanner.keyword_at?("table")
|
|
14
|
+
|
|
15
|
+
scanner.skip_past_keyword!("table")
|
|
16
|
+
|
|
17
|
+
if_exists = false
|
|
18
|
+
|
|
19
|
+
if scanner.keyword_at?("if")
|
|
20
|
+
return nil unless detect_if_exists?(scanner)
|
|
21
|
+
|
|
22
|
+
skip_past_if_exists!(scanner)
|
|
23
|
+
if_exists = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
table_name = scanner.read_identifier!
|
|
27
|
+
return nil unless table_name
|
|
28
|
+
|
|
29
|
+
scanner.skip_whitespace!
|
|
30
|
+
return nil unless scanner.finished?
|
|
31
|
+
|
|
32
|
+
new(table_name: table_name, if_exists: if_exists)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.detect_if_exists?(scanner)
|
|
36
|
+
probe = Scanner.new(scanner.source, position: scanner.position)
|
|
37
|
+
probe.skip_past_keyword!("if")
|
|
38
|
+
probe.keyword_at?("exists")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.skip_past_if_exists!(scanner)
|
|
42
|
+
scanner.skip_past_keyword!("if")
|
|
43
|
+
scanner.skip_past_keyword!("exists")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private_class_method :detect_if_exists?, :skip_past_if_exists!
|
|
47
|
+
|
|
48
|
+
def render
|
|
49
|
+
parts = [Util.format_keyword("drop"), Util.format_keyword("table")]
|
|
50
|
+
parts << "#{Util.format_keyword('if')} #{Util.format_keyword('exists')}" if @if_exists
|
|
51
|
+
parts << Util.format_table_name(@table_name)
|
|
52
|
+
|
|
53
|
+
"#{parts.join(' ')}\n"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -23,12 +23,18 @@ module SqlBeautifier
|
|
|
23
23
|
@leading_sentinels = extract_leading_sentinels!
|
|
24
24
|
return unless @normalized_value.present?
|
|
25
25
|
|
|
26
|
+
drop_table_result = DropTable.parse(@normalized_value)&.render
|
|
27
|
+
return prepend_sentinels(drop_table_result) if drop_table_result
|
|
28
|
+
|
|
26
29
|
cte_result = CteQuery.parse(@normalized_value, depth: @depth)&.render
|
|
27
30
|
return prepend_sentinels(cte_result) if cte_result
|
|
28
31
|
|
|
29
32
|
create_table_as_result = CreateTableAs.parse(@normalized_value, depth: @depth)&.render
|
|
30
33
|
return prepend_sentinels(create_table_as_result) if create_table_as_result
|
|
31
34
|
|
|
35
|
+
create_table_result = CreateTable.parse(@normalized_value)&.render
|
|
36
|
+
return prepend_sentinels(create_table_result) if create_table_result
|
|
37
|
+
|
|
32
38
|
compound_result = CompoundQuery.parse(@normalized_value, depth: @depth)&.render
|
|
33
39
|
return prepend_sentinels(compound_result) if compound_result
|
|
34
40
|
|
|
@@ -199,8 +199,9 @@ module SqlBeautifier
|
|
|
199
199
|
|
|
200
200
|
def render_column_list
|
|
201
201
|
columns = Tokenizer.split_by_top_level_commas(@column_list)
|
|
202
|
-
|
|
202
|
+
return " (#{columns.first.strip})" if columns.length == 1
|
|
203
203
|
|
|
204
|
+
indent = Util.whitespace(SqlBeautifier.config_for(:indent_spaces) || 4)
|
|
204
205
|
formatted_columns = columns.map { |column| "#{indent}#{column.strip}" }.join(",\n")
|
|
205
206
|
|
|
206
207
|
" (\n#{formatted_columns}\n)"
|
data/lib/sql_beautifier.rb
CHANGED
|
@@ -24,6 +24,9 @@ require_relative "sql_beautifier/sort_expression"
|
|
|
24
24
|
require_relative "sql_beautifier/condition"
|
|
25
25
|
require_relative "sql_beautifier/cte_definition"
|
|
26
26
|
require_relative "sql_beautifier/cte_query"
|
|
27
|
+
require_relative "sql_beautifier/drop_table"
|
|
28
|
+
require_relative "sql_beautifier/create_table_parsing"
|
|
29
|
+
require_relative "sql_beautifier/create_table"
|
|
27
30
|
require_relative "sql_beautifier/create_table_as"
|
|
28
31
|
require_relative "sql_beautifier/compound_query"
|
|
29
32
|
require_relative "sql_beautifier/dml_rendering"
|
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.
|
|
4
|
+
version: 0.10.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kinnell Shah
|
|
@@ -82,11 +82,14 @@ files:
|
|
|
82
82
|
- lib/sql_beautifier/condition.rb
|
|
83
83
|
- lib/sql_beautifier/configuration.rb
|
|
84
84
|
- lib/sql_beautifier/constants.rb
|
|
85
|
+
- lib/sql_beautifier/create_table.rb
|
|
85
86
|
- lib/sql_beautifier/create_table_as.rb
|
|
87
|
+
- lib/sql_beautifier/create_table_parsing.rb
|
|
86
88
|
- lib/sql_beautifier/cte_definition.rb
|
|
87
89
|
- lib/sql_beautifier/cte_query.rb
|
|
88
90
|
- lib/sql_beautifier/delete_query.rb
|
|
89
91
|
- lib/sql_beautifier/dml_rendering.rb
|
|
92
|
+
- lib/sql_beautifier/drop_table.rb
|
|
90
93
|
- lib/sql_beautifier/expression.rb
|
|
91
94
|
- lib/sql_beautifier/formatter.rb
|
|
92
95
|
- lib/sql_beautifier/insert_query.rb
|