tsql_parser 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0473885bdca58523ffe440e748c72c6918e0c549e33a65d9287d3c48bf63e6b9'
4
- data.tar.gz: fe2f61520f1ee60df0ed980af63f187273a8d275d245d5aeeb688ce52e3c8760
3
+ metadata.gz: d67275fb1f5b1ff478b0e8a79d99af69a2d33b53059822e882b6a0c94e68d0a1
4
+ data.tar.gz: 50b27357c58d8c0ca89b069ddef3a03e4565b1e354a7e322d79c9ba64440895d
5
5
  SHA512:
6
- metadata.gz: c0286ddb36e63aa05587d22ebc8bd26578de851779934fe590c77f43d7ff646379922d6eae4b5f265dbbf6a609b3e12de3bb829f865ef8850fc2314076c19d11
7
- data.tar.gz: 48797d07c9d3dc5615ea692692717c0ac5312af185a930bf12e089eab2a4f2e80ca347790514e62dfbcf6238617ffc61b0ef248769ad3de4bb411c8dbe22399f
6
+ metadata.gz: dd4314846a33bd5b4c0e99dd41ecb67c65494faf015fc5ec81ffcf74cd5f98c52215bc28fd1a8729e0a1694ad7a055b09f51b724b0d7f27ae945a3ed2f778d13
7
+ data.tar.gz: 664dce679b765bf73e229f5cafc4eb0059f540c85674d4b6d41f1e3508e7536d512c457b64075d6c0f12b70bf7d3bfabdf4887466894beceec4182f444a5b986
@@ -0,0 +1,56 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/config/defaults.rb
14
+ # object:
15
+ # TSqlParser::Parsing::Defaults
16
+
17
+ module TSqlParser::Parsing
18
+ class Defaults
19
+ @@default_single_char_tokens = ["(", ",", ")", "=", "+", "-", "%", "/", "*", "<", "!", ">", "'", "[", "]", ";"]
20
+ @@default_delimiters = [" ", "\n", "\t"]
21
+ @@default_tab_count = 0
22
+ @@default_tab = " "
23
+
24
+ def self.set_default_tab_count(tab_count = 0)
25
+ @@default_tab_count = tab_count
26
+ end
27
+
28
+ def self.set_default_tab(tab = " ")
29
+ @@default_tab = tab
30
+ end
31
+
32
+ def self.set_default_single_char_tokens(single_char_tokens = ["(", ",", ")", "=", "+", "-", "%", "/", "*", "<", "!", ">", "'", "[", "]", ";"])
33
+ @@default_single_char_tokens = single_char_tokens
34
+ end
35
+
36
+ def self.set_default_delimiters(delimiters = [" ", "\n", "\t"])
37
+ @@default_delimiters = delimiters
38
+ end
39
+
40
+ def self.get_default_single_char_tokens
41
+ @@default_single_char_tokens
42
+ end
43
+
44
+ def self.get_default_delimiters
45
+ @@default_delimiters
46
+ end
47
+
48
+ def self.get_default_tab_count
49
+ @@default_tab_count
50
+ end
51
+
52
+ def self.get_default_tab
53
+ @@default_tab
54
+ end
55
+ end
56
+ end
@@ -15,26 +15,25 @@
15
15
  # TSqlParser::Parsing::Formatter
16
16
 
17
17
  module TSqlParser::Parsing
18
- require_relative "iterator"
18
+ require_relative "config/defaults"
19
19
  require_relative "parser"
20
20
  require_relative "formatters/text_formatter"
21
- require_relative "model/sql_container"
22
- require_relative "model/flat_sql_container"
23
-
21
+ require_relative "transformers/token_transformer"
22
+
24
23
  class Formatter
25
- def self.format(tokens, tab_count = 0, tab = " ")
26
- containers = self.as_containers(tokens)
27
- lines = self.combine_containers(containers)
24
+ def self.format(tokens, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
25
+ lines = TokenTransformer.transform(tokens)
28
26
  lines = self.cleanup_whitespace(lines)
29
27
  lines = self.insert_indentation(lines, tab_count, tab)
30
- lines = self.insert_newlines(lines)
28
+ #lines = self.insert_newlines(lines)
31
29
  text = lines.join("\n")
32
- text = TextFormatter.format_inserts(text, tab)
33
- text = TextFormatter.format_updates(text, tab)
34
- text = TextFormatter.format_joins(text, tab)
35
- text = TextFormatter.format_wheres(text, tab)
36
- text = TextFormatter.format_selects(text, tab)
37
- text = TextFormatter.format_sets(text, tab)
30
+ text = TextFormatter.new(JOIN, text, tab).format
31
+ text = TextFormatter.new(INSERT, text, tab).format
32
+ text = TextFormatter.new(UPDATE, text, tab).format
33
+ text = TextFormatter.new(WHERE, text, tab).format
34
+ text = TextFormatter.new(SELECT, text, tab).format
35
+ text = TextFormatter.new(SET, text, tab).format
36
+ puts text
38
37
  text
39
38
  end
40
39
 
@@ -63,7 +62,7 @@ module TSqlParser::Parsing
63
62
  new_lines
64
63
  end
65
64
 
66
- def self.insert_indentation(lines, tab_count = 0, tab = " ")
65
+ def self.insert_indentation(lines, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
67
66
  indented_lines = []
68
67
  work_lines = []
69
68
  lines.each do |line|
@@ -116,66 +115,6 @@ module TSqlParser::Parsing
116
115
  lines
117
116
  end
118
117
 
119
- def self.combine_containers(containers)
120
- lines = []
121
- containers.each do |c|
122
- ct = c.get_token
123
-
124
- builder = []
125
- builder << ct[:value]
126
-
127
- if c.has_siblings?
128
- c.get_siblings.each do |sibling|
129
- st = sibling.get_token
130
-
131
- if st[:comment]
132
- builder << "\n#{st[:value]}"
133
- next
134
- end
135
-
136
- builder << st[:value]
137
- end
138
- end
139
-
140
- lines << builder.join(" ")
141
- end
142
-
143
- lines
144
- end
145
-
146
- def self.as_containers(tokens)
147
- containers = []
148
- container = nil
149
- skip_count = 0
150
- tokens.each_with_index do |t, index|
151
- if skip_count > 0
152
- skip_count -= 1
153
- next
154
- end
155
-
156
- next_token = tokens[index + 1]
157
- if Parser.is_new_node_keyword? t[:value]
158
- if not next_token.nil? and Parser.is_new_node_keyword? next_token[:value]
159
- if Parser.is_new_node_composite?(t[:value], next_token[:value])
160
- containers << container unless container.nil?
161
- container = SqlContainer.combine(t, next_token)
162
- skip_count = 1
163
- next
164
- end
165
- end
166
- containers << container unless container.nil?
167
- container = SqlContainer.new(t)
168
- elsif t[:label]
169
- containers << container unless container.nil?
170
- container = SqlContainer.new(t)
171
- else
172
- container.add t unless container.nil?
173
- end
174
- end
175
- containers << container unless container.nil?
176
- FlatSqlContainer.flatten_containers(containers)
177
- end
178
-
179
118
  def self.safe_ws_cleanup(line)
180
119
  parts = []
181
120
  builder = ""
@@ -0,0 +1,32 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/formatters/format_factory.rb
14
+ # object:
15
+ # TSqlParser::Parsing::FormatFactory
16
+
17
+ module TSqlParser::Parsing
18
+ require_relative "strategy/__formatters"
19
+ class FormatFactory
20
+ def self.get(type)
21
+ case type
22
+ when CTE then Formatters::CommonTableExpressionFormatter.new
23
+ when INSERT then Formatters::InsertFormatter.new
24
+ when JOIN then Formatters::JoinFormatter.new
25
+ when SELECT then Formatters::SelectFormatter.new
26
+ when SET then Formatters::SetFormatter.new
27
+ when UPDATE then Formatters::UpdateFormatter.new
28
+ when WHERE then Formatters::WhereFormatter.new
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/formatters/strategy/__defaults.rb
14
+
15
+ require_relative "../../config/defaults"
16
+ Defaults = TSqlParser::Parsing::Defaults
@@ -0,0 +1,31 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/formatters/strategy/__formatters.rb
14
+
15
+ module TSqlParser::Parsing
16
+ require_relative "cte_formatter"
17
+ require_relative "set_formatter"
18
+ require_relative "join_formatter"
19
+ require_relative "insert_formatter"
20
+ require_relative "select_formatter"
21
+ require_relative "update_formatter"
22
+ require_relative "where_formatter"
23
+
24
+ CTE = 0
25
+ INSERT = 1
26
+ JOIN = 2
27
+ SELECT = 3
28
+ SET = 4
29
+ UPDATE = 5
30
+ WHERE = 6
31
+ end
@@ -15,14 +15,24 @@
15
15
  # TSqlParser::Parsing::Formatters::SetFormatter
16
16
 
17
17
  module TSqlParser::Parsing::Formatters
18
+ require_relative "__defaults"
19
+
18
20
  class BaseFormatter
19
- def self.get_tab_count(line, tab = " ")
20
- tab_count = 0
21
+ def get_tab_count(line, tab = Defaults.get_default_tab)
22
+ tab_count = Defaults.get_default_tab_count
21
23
  while line.start_with? tab
22
24
  tab_count += 1
23
25
  line = line.sub(tab, "")
24
26
  end
25
27
  tab_count
26
28
  end
29
+
30
+ # @abstract
31
+ #
32
+ # @param [String] text
33
+ # @param [String] tab
34
+ def format(text, tab = Defaults.get_default_tab)
35
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
36
+ end
27
37
  end
28
38
  end
@@ -0,0 +1,25 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/formatters/cte_formatter.rb
14
+ # object:
15
+ # TSqlParser::Parsing::Formatters::CommonTableExpressionFormatter
16
+
17
+ module TSqlParser::Parsing::Formatters
18
+ require_relative "base_formatter"
19
+
20
+ class CommonTableExpressionFormatter < BaseFormatter
21
+ def format(text, tab = Defaults.get_default_tab)
22
+ end
23
+ end
24
+ end
25
+
@@ -18,7 +18,7 @@ module TSqlParser::Parsing::Formatters
18
18
  require_relative "base_formatter"
19
19
 
20
20
  class InsertFormatter < BaseFormatter
21
- def self.format(text, tab = " ")
21
+ def format(text, tab = Defaults.get_default_tab)
22
22
  formatted = []
23
23
  lines = text.split("\n")
24
24
  search = "INSERT INTO"
@@ -43,7 +43,7 @@ module TSqlParser::Parsing::Formatters
43
43
 
44
44
  private
45
45
 
46
- def self.format_insert(s, tab_count = 0, tab = " ")
46
+ def format_insert(s, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
47
47
  return s if s.nil?
48
48
  formatted = []
49
49
  if s.include? ") VALUES ("
@@ -18,15 +18,17 @@ module TSqlParser::Parsing::Formatters
18
18
  require_relative "base_formatter"
19
19
 
20
20
  class JoinFormatter < BaseFormatter
21
- def self.format(text, tab = " ")
21
+ def format(text, tab = Defaults.get_default_tab)
22
22
  text = text.gsub(/INNER\s+JOIN/, "INNER JOIN")
23
23
  .gsub(/LEFT\s+JOIN/, "LEFT JOIN")
24
+ .gsub(/RIGHT\s+JOIN/, "RIGHT JOIN")
25
+ .gsub(/CROSS\s+JOIN/, "CROSS JOIN")
24
26
  lines = text.split("\n")
25
27
  new_text = []
26
28
 
27
29
  lines.each do |line|
28
30
  first = line.strip.split(" ").first
29
- if line.include? " WHERE " and first != "WHERE" and not first.start_with? "--" and not first.start_with? "/*"
31
+ if line.include? " WHERE " and first != "WHERE" and not first.start_with? "--" and not first.start_with? "/*" and not line.strip.end_with? "'"
30
32
  tab_count = self.get_tab_count(line, tab)
31
33
  where_parts = line.strip.split(" WHERE ")
32
34
  where_text = []
@@ -18,7 +18,7 @@ module TSqlParser::Parsing::Formatters
18
18
  require_relative "base_formatter"
19
19
 
20
20
  class SelectFormatter < BaseFormatter
21
- def self.format(text, tab = " ")
21
+ def format(text, tab = Defaults.get_default_tab)
22
22
  formatted = []
23
23
  lines = text.split("\n")
24
24
  lines.each do |line|
@@ -42,7 +42,7 @@ module TSqlParser::Parsing::Formatters
42
42
 
43
43
  private
44
44
 
45
- def self.format_select(s, tab_count = 0, tab = " ")
45
+ def format_select(s, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
46
46
  return s if s.nil?
47
47
 
48
48
  tokens = s.split(", ")
@@ -15,10 +15,10 @@
15
15
  # TSqlParser::Parsing::Formatters::SetFormatter
16
16
 
17
17
  module TSqlParser::Parsing::Formatters
18
- require_relative 'base_formatter'
18
+ require_relative "base_formatter"
19
19
 
20
20
  class SetFormatter < BaseFormatter
21
- def self.format(text, tab = " ")
21
+ def format(text, tab = Defaults.get_default_tab)
22
22
  formatted = []
23
23
  lines = text.split("\n")
24
24
  wait = false
@@ -71,7 +71,7 @@ module TSqlParser::Parsing::Formatters
71
71
 
72
72
  private
73
73
 
74
- def self.format_set(s, tab_count = 0, tab = " ")
74
+ def format_set(s, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
75
75
  return s if s.nil?
76
76
  parts = []
77
77
  builder = ""
@@ -18,7 +18,7 @@ module TSqlParser::Parsing::Formatters
18
18
  require_relative "base_formatter"
19
19
 
20
20
  class UpdateFormatter < BaseFormatter
21
- def self.format(text, tab = " ")
21
+ def format(text, tab = Defaults.get_default_tab)
22
22
  formatted = []
23
23
  lines = text.split("\n")
24
24
  lines.each do |line|
@@ -42,7 +42,7 @@ module TSqlParser::Parsing::Formatters
42
42
 
43
43
  private
44
44
 
45
- def self.format_update(s, tab_count = 0, tab = " ")
45
+ def format_update(s, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
46
46
  return s if s.nil?
47
47
  "\n#{tab * (tab_count + 1)}#{s}"
48
48
  end
@@ -18,7 +18,7 @@ module TSqlParser::Parsing::Formatters
18
18
  require_relative "base_formatter"
19
19
 
20
20
  class WhereFormatter < BaseFormatter
21
- def self.format(text, tab = " ")
21
+ def format(text, tab = Defaults.get_default_tab)
22
22
  formatted = []
23
23
  text.split("\n").each do |line|
24
24
  first = line.strip.split(" ").first
@@ -42,7 +42,7 @@ module TSqlParser::Parsing::Formatters
42
42
 
43
43
  private
44
44
 
45
- def self.format_predicate(s, tab_count = 0, tab = " ")
45
+ def format_predicate(s, tab_count = Defaults.get_default_tab_count, tab = Defaults.get_default_tab)
46
46
  return s if s.nil?
47
47
  indented = []
48
48
  formatted = []
@@ -15,36 +15,20 @@
15
15
  # TSqlParser::Parsing::Formatter
16
16
 
17
17
  module TSqlParser::Parsing
18
- require_relative 'strategy/set_formatter'
19
- require_relative 'strategy/join_formatter'
20
- require_relative 'strategy/insert_formatter'
21
- require_relative 'strategy/select_formatter'
22
- require_relative 'strategy/update_formatter'
23
- require_relative 'strategy/where_formatter'
18
+ require_relative "strategy/__defaults"
19
+ require_relative "format_factory"
24
20
 
25
21
  class TextFormatter
26
- def self.format_sets(text, tab = " ")
27
- Formatters::SetFormatter.format(text, tab)
28
- end
29
-
30
- def self.format_joins(text, tab = " ")
31
- Formatters::JoinFormatter.format(text, tab)
32
- end
33
-
34
- def self.format_updates(text, tab = " ")
35
- Formatters::UpdateFormatter.format(text, tab)
36
- end
37
-
38
- def self.format_inserts(text, tab = " ")
39
- Formatters::InsertFormatter.format(text, tab)
40
- end
22
+ attr_writer :strategy
41
23
 
42
- def self.format_selects(text, tab = " ")
43
- Formatters::SelectFormatter.format(text, tab)
24
+ def initialize(strategy, text, tab = Defaults.get_default_tab)
25
+ @strategy = FormatFactory.get(strategy)
26
+ @text = text
27
+ @tab = tab
44
28
  end
45
29
 
46
- def self.format_wheres(text, tab = " ")
47
- Formatters::WhereFormatter.format(text, tab)
30
+ def format
31
+ @strategy.format(@text, @tab)
48
32
  end
49
33
  end
50
34
  end
@@ -24,7 +24,7 @@ module TSqlParser::Parsing
24
24
  end
25
25
 
26
26
  def self.get_new_node_keywords
27
- %w[CREATE ALTER DROP RENAME SELECT INSERT UPDATE DELETE WHILE IF ELSE DECLARE SET WITH BEGIN FROM WHERE INNER LEFT JOIN END GO GROUP ORDER CASE PRINT RETURN GOTO OPEN CLOSE DEALLOCATE FETCH] \
27
+ %w[CREATE ALTER DROP RENAME SELECT INSERT UPDATE DELETE WHILE IF ELSE DECLARE SET WITH BEGIN FROM WHERE INNER LEFT JOIN END GO GROUP ORDER CASE PRINT RETURN GOTO OPEN CLOSE DEALLOCATE FETCH USE EXEC] \
28
28
  - %w[WITH LEFT RIGHT]
29
29
  end
30
30
 
@@ -86,8 +86,8 @@ module TSqlParser::Parsing
86
86
  s == "]"
87
87
  end
88
88
 
89
- def self.is_string_mark?(s)
90
- s == "'"
89
+ def self.is_string?(s)
90
+ s.start_with? "'" and s.end_with? "'"
91
91
  end
92
92
 
93
93
  def self.is_comma?(s)
@@ -15,137 +15,166 @@
15
15
  # TSqlParser::Parsing::Tokenizer
16
16
 
17
17
  module TSqlParser::Parsing
18
- require_relative "parser"
18
+ require_relative "config/defaults"
19
+ require_relative "transformers/token_categorizer"
19
20
 
20
21
  class Tokenizer
21
22
  def self.tokenize(tsql_string)
22
- tokens = basic_tokenize(
23
- tsql_string,
24
- ["(", ",", ")", "=", "+", "-", "%", "/", "*", "<", "!", ">", "'", "[", "]", ";"],
25
- [" ", "\n", "\t"]
26
- )
27
- tokens.map do |t|
28
- categorize(t)
29
- end
23
+ Tokenizer.new.basic_tokenize(tsql_string).map { |t| TokenCategorizer.categorize(t) }
30
24
  end
31
25
 
32
- def self.categorize(s)
33
- data = {}
34
- data[:value] = s
35
- data[:keyword] = true if Parser.is_keyword? s
36
- data[:operator] = true if Parser.is_operator? s
37
- data[:function] = true if Parser.is_function? s
38
- data[:type] = true if Parser.is_type? s
39
- data[:comment] = true if Parser.is_comment? s
40
- data[:numeric] = true if Parser.is_numeric? s
41
- data[:special_variable] = true if Parser.is_special_variable? s
42
- data[:variable] = true if Parser.is_variable? s
43
- data[:temporary_table] = true if Parser.is_temp_table? s
44
- data[:label] = true if Parser.is_label? s
45
- data[:parenthesis] = true if Parser.is_parenthesis? s
46
- data[:open_parenthesis] = true if Parser.is_open_parenthesis? s
47
- data[:close_parenthesis] = true if Parser.is_close_parenthesis? s
48
- data[:bracket] = true if Parser.is_bracket? s
49
- data[:open_bracket] = true if Parser.is_open_bracket? s
50
- data[:close_bracket] = true if Parser.is_close_bracket? s
51
- data[:string_mark] = true if Parser.is_string_mark? s
52
- data[:comma] = true if Parser.is_comma? s
53
- data[:join] = true if Parser.is_join? s
54
- data[:join_type] = true if Parser.is_join_type? s
55
- data[:begin] = true if Parser.is_begin? s
56
- data[:end] = true if Parser.is_end? s
57
- data[:terminator] = true if Parser.is_terminator? s
58
- data[:value] = data[:value].upcase if data[:keyword] or data[:function] or data[:type]
59
- data[:needs_newline] = true if data[:keyword] and Parser.is_newline_required? s
60
- data
61
- end
62
-
63
- def self.basic_tokenize(tsql_string, char_delimiters, skip_delimiters)
64
- specific_tokens = []
65
- delimiters = ([] << char_delimiters << skip_delimiters).flatten
66
- builder = ""
26
+ def basic_tokenize(tsql_string)
27
+ self.reset
67
28
  tsql_chars = tsql_string.split("")
68
- multiline_comment = false
69
- comment = false
70
- string = false
71
- string_count = 0
72
- skip_count = 0
29
+
73
30
  tsql_chars.each_with_index do |c, i|
74
- if skip_count > 0
75
- skip_count -= 1
31
+ if @skip_count > 0
32
+ @skip_count -= 1
76
33
  next
77
34
  end
78
35
 
79
- next_c = tsql_chars[i + 1] unless i + 1 > tsql_chars.size
36
+ # get last and next char
37
+ @c = c
38
+ @last_c = tsql_chars[i - 1] unless i - 1 < 0
39
+ @next_c = tsql_chars[i + 1] unless i + 1 > tsql_chars.size
80
40
 
81
- if Parser.is_multiline_comment_start?(c, next_c)
82
- multiline_comment = true
83
- specific_tokens << builder unless builder.empty?
84
- builder = c
85
- next
41
+ # if we aren't in a string.
42
+ unless @string
43
+ next if self.handle_multicomment_start
44
+ next if self.handle_multicomment_end
45
+ next if self.handle_singlecomment_start
46
+ next if self.build_comment
86
47
  end
87
48
 
88
- if Parser.is_multiline_comment_end?(c, next_c)
89
- skip_count = 1
90
- multiline_comment = false
91
- builder << c << next_c
92
- specific_tokens << builder unless builder.empty?
93
- builder = ""
94
- next
49
+ unless @multiline_comment or @comment
50
+ next if self.handle_string_char
51
+ next if self.handle_two_char_op
52
+ next if self.handle_delimiter
95
53
  end
96
54
 
97
- if Parser.is_comment_start?(c, next_c) and not comment
98
- comment = true
99
- skip_count = 1
100
- specific_tokens << builder unless builder.empty?
101
- builder = "--"
102
- next
103
- end
55
+ self.build
56
+ end
104
57
 
105
- if comment and c != "\n"
106
- builder << c
107
- next
108
- elsif comment and c == "\n"
109
- specific_tokens << builder unless builder.empty?
110
- builder = ""
111
- comment = false
112
- next
113
- end
58
+ self.flush_builder
59
+ @tokens
60
+ end
114
61
 
115
- if c == "'" and not multiline_comment and not comment
116
- if not string
117
- string = true
118
- specific_tokens << builder unless builder.empty?
119
- builder = c
120
- next
121
- else
122
- string = false
123
- builder << c
124
- specific_tokens << builder unless builder.empty?
125
- builder = ""
126
- next
127
- end
128
- end
62
+ private
129
63
 
130
- if Parser.is_two_char_op?(c, next_c)
131
- skip_count = 1
132
- specific_tokens << builder unless builder.empty?
133
- specific_tokens << "#{c}#{next_c}"
134
- builder = ""
135
- next
136
- end
64
+ def handle_multicomment_start
65
+ if Parser.is_multiline_comment_start?(@c, @next_c)
66
+ @multiline_comment = true
67
+ self.flush_builder(c)
68
+ return true
69
+ end
70
+ end
137
71
 
138
- if delimiters.include? c and !multiline_comment and !string and !comment
139
- specific_tokens << builder unless builder.empty?
140
- specific_tokens << c unless skip_delimiters.include? c
141
- builder = ""
142
- next
143
- end
72
+ def handle_multicomment_end
73
+ if Parser.is_multiline_comment_end?(@c, @next_c)
74
+ @skip_count = 1
75
+ @multiline_comment = false
76
+ self.build
77
+ self.build(@next_c)
78
+ self.flush_builder("")
79
+ return true
80
+ end
81
+ end
82
+
83
+ def handle_singlecomment_start
84
+ if Parser.is_comment_start?(@c, @next_c) and not @comment
85
+ @comment = true
86
+ @skip_count = 1
87
+ self.flush_builder("--")
88
+ return true
89
+ end
90
+ end
91
+
92
+ def handle_singlecomment_end
93
+ if @c == "\n"
94
+ self.flush_builder("")
95
+ @comment = false
96
+ return true
97
+ end
98
+ end
99
+
100
+ def handle_string_char
101
+ if @c == "'"
102
+ @string_count += 1
103
+ return true if self.handle_string_start
104
+ @string_count += 1 if @next_c == "'"
105
+ return true if self.handle_string_end
106
+ end
107
+ end
108
+
109
+ def handle_string_start
110
+ if not @string
111
+ @string = true
112
+ self.flush_builder(@c)
113
+ return true
114
+ end
115
+ end
116
+
117
+ def handle_string_end
118
+ if @string_count % 2 == 0
119
+ @string = false
120
+ @string_count = 0
121
+ self.build
122
+ self.flush_builder("")
123
+ return true
124
+ end
125
+ end
126
+
127
+ def handle_two_char_op
128
+ if Parser.is_two_char_op?(@c, @next_c)
129
+ @skip_count = 1
130
+ self.flush_builder("")
131
+ @tokens << "#{@c}#{@next_c}"
132
+ return true
133
+ end
134
+ end
144
135
 
145
- builder << c
136
+ def handle_delimiter
137
+ if @delimiters.include? @c and !@multiline_comment and !@comment and !@string
138
+ self.flush_builder("")
139
+ @tokens << @c unless @skip_delimiters.include? @c
140
+ return true
146
141
  end
147
- specific_tokens << builder unless builder.empty?
148
- specific_tokens
142
+ end
143
+
144
+ def build(value = nil)
145
+ @builder << @c if value.nil?
146
+ @builder << value unless value.nil?
147
+ end
148
+
149
+ def build_comment
150
+ if @comment
151
+ return true if self.handle_singlecomment_end
152
+
153
+ self.build
154
+ return true
155
+ end
156
+ end
157
+
158
+ def flush_builder(value = nil)
159
+ @tokens << @builder unless @builder.empty?
160
+ @builder = value
161
+ end
162
+
163
+ def initialize
164
+ self.reset
165
+ end
166
+
167
+ def reset
168
+ @tokens = []
169
+ @multiline_comment = false
170
+ @comment = false
171
+ @string = false
172
+ @string_count = 0
173
+ @skip_count = 0
174
+ @char_delimiters = Defaults.get_default_single_char_tokens
175
+ @skip_delimiters = Defaults.get_default_delimiters
176
+ @delimiters = ([] << @char_delimiters << @skip_delimiters).flatten.uniq
177
+ @builder = ""
149
178
  end
150
179
  end
151
180
  end
@@ -0,0 +1,52 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/transformers/token_categorizer.rb
14
+ # object:
15
+ # TSqlParser::Parsing::TokenCategorizer
16
+
17
+ module TSqlParser::Parsing
18
+ require_relative "../parser"
19
+
20
+ class TokenCategorizer
21
+ def self.categorize(s)
22
+ data = {}
23
+ data[:value] = s
24
+ data[:keyword] = true if Parser.is_keyword? s
25
+ data[:operator] = true if Parser.is_operator? s
26
+ data[:function] = true if Parser.is_function? s
27
+ data[:type] = true if Parser.is_type? s
28
+ data[:comment] = true if Parser.is_comment? s
29
+ data[:numeric] = true if Parser.is_numeric? s
30
+ data[:special_variable] = true if Parser.is_special_variable? s
31
+ data[:variable] = true if Parser.is_variable? s
32
+ data[:temporary_table] = true if Parser.is_temp_table? s
33
+ data[:label] = true if Parser.is_label? s
34
+ data[:parenthesis] = true if Parser.is_parenthesis? s
35
+ data[:open_parenthesis] = true if Parser.is_open_parenthesis? s
36
+ data[:close_parenthesis] = true if Parser.is_close_parenthesis? s
37
+ data[:bracket] = true if Parser.is_bracket? s
38
+ data[:open_bracket] = true if Parser.is_open_bracket? s
39
+ data[:close_bracket] = true if Parser.is_close_bracket? s
40
+ data[:string] = true if Parser.is_string? s
41
+ data[:comma] = true if Parser.is_comma? s
42
+ data[:join] = true if Parser.is_join? s
43
+ data[:join_type] = true if Parser.is_join_type? s
44
+ data[:begin] = true if Parser.is_begin? s
45
+ data[:end] = true if Parser.is_end? s
46
+ data[:terminator] = true if Parser.is_terminator? s
47
+ data[:value] = data[:value].upcase if data[:keyword] or data[:function] or data[:type]
48
+ data[:needs_newline] = true if data[:keyword] and Parser.is_newline_required? s
49
+ data
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,153 @@
1
+ # __ .__
2
+ # _/ |_ ___________| | ___________ _______ ______ ___________
3
+ # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
+ # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
+ # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
+ # \/ |__| |__| \/ \/ \/
7
+ #
8
+ # A very light-weight and opinionated T-SQL parser and formatter.
9
+ #
10
+ # github.com/scstauf
11
+ #
12
+ # path:
13
+ # parsing/model/token_transformer.rb
14
+ # object:
15
+ # TSqlParser::Parsing::TokenTransformer
16
+
17
+ module TSqlParser::Parsing
18
+ require_relative "../models/sql_container"
19
+ require_relative "../models/flat_sql_container"
20
+
21
+ class TokenTransformer
22
+ def initialize(tokens = [])
23
+ @containers = []
24
+ @container = nil
25
+ @skip_count = 0
26
+ @cte = ""
27
+ @parenthesis = 0
28
+ @tokens = tokens
29
+ end
30
+
31
+ def self.transform(tokens)
32
+ TokenTransformer.new(tokens).transform
33
+ end
34
+
35
+ def transform
36
+ @tokens.each_with_index do |t, index|
37
+ @parenthesis += 1 if t[:open_parenthesis]
38
+ @parenthesis -= 1 if t[:close_parenthesis]
39
+
40
+ next if self.handle_skip
41
+
42
+ @t = t
43
+ @last_token = @tokens[index - 1] unless index - 1 < 0
44
+ @next_token = @tokens[index + 1] unless index + 1 > @tokens.size
45
+
46
+ next if self.handle_cte_end
47
+
48
+ if t[:string]
49
+ self.add_to_container
50
+ elsif Parser.is_new_node_keyword? t[:value]
51
+ if not @next_token.nil? and Parser.is_new_node_keyword? @next_token[:value]
52
+ if Parser.is_new_node_composite?(@t[:value], @next_token[:value])
53
+ self.add_container
54
+ @container = SqlContainer.combine(@t, @next_token)
55
+ @skip_count = 1
56
+ next
57
+ end
58
+ end
59
+ self.add_container
60
+ self.new_container
61
+ elsif t[:value] == "WITH"
62
+ if not @next_token.nil? and not @next_token[:keyword] and not @next_token[:parenthesis]
63
+ self.add_container
64
+ self.new_container
65
+ @cte = @next_token[:value]
66
+ else
67
+ self.add_to_container
68
+ end
69
+ elsif @t[:label]
70
+ self.add_container
71
+ self.new_container
72
+ else
73
+ self.add_to_container
74
+ end
75
+ end
76
+ self.add_container
77
+ self.get_lines_from_containers
78
+ end
79
+
80
+ private
81
+
82
+ def handle_skip
83
+ if @skip_count > 0
84
+ @skip_count -= 1
85
+ return true
86
+ end
87
+ end
88
+
89
+ def add_to_container
90
+ @container.add @t unless @container.nil?
91
+ end
92
+
93
+ def add_container
94
+ @containers << @container unless @container.nil?
95
+ end
96
+
97
+ def new_container
98
+ @container = SqlContainer.new(@t)
99
+ end
100
+
101
+ def handle_cte_end
102
+ if not @cte.empty?
103
+ if not @last_token.nil? and @last_token[:close_parenthesis] and @parenthesis == 0
104
+ if @t[:keyword]
105
+ self.add_container
106
+ self.new_container
107
+ @cte = ""
108
+ else
109
+ self.add_to_container
110
+ end
111
+ elsif not @last_token.nil? and not @last_token[:keyword] and @t[:value] == "AS"
112
+ self.add_to_container
113
+ elsif not @last_token.nil? and @last_token[:comma] and not @t[:keyword] and not @next_token.nil? and @next_token[:value] == "AS"
114
+ self.add_container
115
+ self.new_container
116
+ @cte = @t[:value]
117
+ else
118
+ self.add_to_container
119
+ end
120
+ return true
121
+ end
122
+ end
123
+
124
+ def get_lines_from_containers
125
+ @containers = FlatSqlContainer.flatten_containers(@containers)
126
+
127
+ lines = []
128
+ @containers.each do |c|
129
+ ct = c.get_token
130
+
131
+ builder = []
132
+ builder << ct[:value]
133
+
134
+ if c.has_siblings?
135
+ c.get_siblings.each do |sibling|
136
+ st = sibling.get_token
137
+
138
+ if st[:comment]
139
+ builder << "\n#{st[:value]}"
140
+ next
141
+ end
142
+
143
+ builder << st[:value]
144
+ end
145
+ end
146
+
147
+ lines << builder.join(" ")
148
+ end
149
+
150
+ lines
151
+ end
152
+ end
153
+ end
data/lib/tsql_parser.rb CHANGED
@@ -15,13 +15,15 @@
15
15
  # TSqlParser
16
16
 
17
17
  module TSqlParser
18
+ require_relative "parsing/config/defaults"
19
+
18
20
  # Formats a SQL string.
19
21
  #
20
22
  # @param sql [String] the SQL string to format.
21
23
  # @param tab_count [Integer] the number of tabs to start with.
22
24
  # @param tab [String] the tab string.
23
25
  # @return [String] the formatted SQL string.
24
- def self.format(sql, tab_count = 0, tab = " ")
26
+ def self.format(sql, tab_count = Parsing::Defaults.get_default_tab_count, tab = Parsing::Defaults.get_default_tab)
25
27
  require_relative "parsing/formatter"
26
28
  tokens = self.parse(sql)
27
29
  Parsing::Formatter.format(tokens, tab_count, tab)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tsql_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Stauffer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-28 00:00:00.000000000 Z
11
+ date: 2023-04-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A very light-weight and opinionated T-SQL parser and formatter.
14
14
  email: scott@fuseraft.com
@@ -16,8 +16,13 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - lib/parsing/config/defaults.rb
19
20
  - lib/parsing/formatter.rb
21
+ - lib/parsing/formatters/format_factory.rb
22
+ - lib/parsing/formatters/strategy/__defaults.rb
23
+ - lib/parsing/formatters/strategy/__formatters.rb
20
24
  - lib/parsing/formatters/strategy/base_formatter.rb
25
+ - lib/parsing/formatters/strategy/cte_formatter.rb
21
26
  - lib/parsing/formatters/strategy/insert_formatter.rb
22
27
  - lib/parsing/formatters/strategy/join_formatter.rb
23
28
  - lib/parsing/formatters/strategy/select_formatter.rb
@@ -25,12 +30,13 @@ files:
25
30
  - lib/parsing/formatters/strategy/update_formatter.rb
26
31
  - lib/parsing/formatters/strategy/where_formatter.rb
27
32
  - lib/parsing/formatters/text_formatter.rb
28
- - lib/parsing/iterator.rb
29
33
  - lib/parsing/keyword.rb
30
- - lib/parsing/model/flat_sql_container.rb
31
- - lib/parsing/model/sql_container.rb
34
+ - lib/parsing/models/flat_sql_container.rb
35
+ - lib/parsing/models/sql_container.rb
32
36
  - lib/parsing/parser.rb
33
37
  - lib/parsing/tokenizer.rb
38
+ - lib/parsing/transformers/token_categorizer.rb
39
+ - lib/parsing/transformers/token_transformer.rb
34
40
  - lib/tsql_parser.rb
35
41
  homepage: https://rubygems.org/gems/tsql_parser
36
42
  licenses:
@@ -1,54 +0,0 @@
1
- # __ .__
2
- # _/ |_ ___________| | ___________ _______ ______ ___________
3
- # \ __\/ ___/ ____/ | ______ \____ \__ \\_ __ \/ ___// __ \_ __ \
4
- # | | \___ < <_| | |__ /_____/ | |_> > __ \| | \/\___ \\ ___/| | \/
5
- # |__| /____ >__ |____/ | __(____ /__| /____ >\___ >__|
6
- # \/ |__| |__| \/ \/ \/
7
- #
8
- # A very light-weight and opinionated T-SQL parser and formatter.
9
- #
10
- # github.com/scstauf
11
- #
12
- # path:
13
- # parsing/iterator.rb
14
- # object:
15
- # TSqlParser::Parsing::TokenIterator
16
-
17
- module TSqlParser::Parsing
18
- class TokenIterator
19
- def initialize(tokens)
20
- @tokens = tokens
21
- @size = tokens.size
22
- @iter = -1
23
- end
24
-
25
- def has_next?
26
- @iter < @size - 1
27
- end
28
-
29
- def get!
30
- @tokens[@iter]
31
- end
32
-
33
- def peek!
34
- @tokens[@iter + 1]
35
- end
36
-
37
- def peek_ahead!(length)
38
- @tokens[@iter + length]
39
- end
40
-
41
- def peek_value!
42
- self.peek![:value]
43
- end
44
-
45
- def peek_ahead_value!(length)
46
- self.peek_ahead!(length)[:value]
47
- end
48
-
49
- def next!
50
- @iter += 1
51
- self.get!
52
- end
53
- end
54
- end
File without changes