niceql 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +3 -1
- data/README.md +5 -4
- data/Rakefile +3 -1
- data/bin/console +1 -0
- data/lib/generators/niceql/install_generator.rb +4 -2
- data/lib/generators/templates/niceql_initializer.rb +3 -1
- data/lib/niceql/version.rb +3 -1
- data/lib/niceql.rb +337 -105
- data/niceql.gemspec +19 -11
- metadata +37 -11
- data/lib/benchmark/cat.rb +0 -34
- data/lib/benchmark/gsub.rb +0 -34
- data/lib/benchmark/txt +0 -748
data/lib/niceql.rb
CHANGED
@@ -1,33 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "niceql/version"
|
4
|
+
require "securerandom"
|
5
|
+
require "forwardable"
|
2
6
|
|
3
7
|
module Niceql
|
4
|
-
|
5
8
|
module StringColorize
|
6
|
-
def self.
|
9
|
+
def self.colorize_keyword(str)
|
7
10
|
# yellow ANSI color
|
8
11
|
"\e[0;33;49m#{str}\e[0m"
|
9
12
|
end
|
13
|
+
|
10
14
|
def self.colorize_str(str)
|
11
15
|
# cyan ANSI color
|
12
16
|
"\e[0;36;49m#{str}\e[0m"
|
13
17
|
end
|
18
|
+
|
14
19
|
def self.colorize_err(err)
|
15
20
|
# red ANSI color
|
16
21
|
"\e[0;31;49m#{err}\e[0m"
|
17
22
|
end
|
23
|
+
|
24
|
+
def self.colorize_comment(comment)
|
25
|
+
# bright black bold ANSI color
|
26
|
+
"\e[0;90;1;49m#{comment}\e[0m"
|
27
|
+
end
|
18
28
|
end
|
19
29
|
|
20
30
|
module Prettifier
|
21
|
-
|
22
|
-
|
31
|
+
# ?= -- should be present but without being added to MatchData
|
32
|
+
AFTER_KEYWORD_SPACE = '(?=\s{1})'
|
33
|
+
JOIN_KEYWORDS = '(RIGHT\s+|LEFT\s+){0,1}(INNER\s+|OUTER\s+){0,1}JOIN(\s+LATERAL){0,1}'
|
34
|
+
INLINE_KEYWORDS = "WITH|ASC|COALESCE|AS|WHEN|THEN|ELSE|END|AND|UNION|ALL|ON|DISTINCT|INTERSECT|EXCEPT|EXISTS|NOT|COUNT|ROUND|CAST|IN"
|
35
|
+
NEW_LINE_KEYWORDS = "SELECT|FROM|WHERE|CASE|ORDER BY|LIMIT|GROUP BY|HAVING|OFFSET|UPDATE|SET|#{JOIN_KEYWORDS}"
|
36
|
+
|
23
37
|
POSSIBLE_INLINER = /(ORDER BY|CASE)/
|
24
|
-
|
25
|
-
|
38
|
+
KEYWORDS = "(#{NEW_LINE_KEYWORDS}|#{INLINE_KEYWORDS})#{AFTER_KEYWORD_SPACE}"
|
39
|
+
# ?: -- will not match partial enclosed by (..)
|
40
|
+
MULTILINE_INDENTABLE_LITERAL = /(?:'[^']+'\s*\n+\s*)+(?:'[^']+')+/
|
41
|
+
# STRINGS matched both kind of strings the multiline solid
|
42
|
+
# and single quoted multiline strings with \s*\n+\s* separation
|
43
|
+
STRINGS = /("[^"]+")|((?:'[^']+'\s*\n+\s*)*(?:'[^']+')+)/
|
26
44
|
BRACKETS = '[\(\)]'
|
27
|
-
|
28
|
-
#
|
29
|
-
|
45
|
+
# will match all /* single line and multiline comments */ and -- based comments
|
46
|
+
# the last will be matched as single block whenever comment lines followed each other.
|
47
|
+
# For instance:
|
48
|
+
# SELECT * -- comment 1
|
49
|
+
# -- comment 2
|
50
|
+
# all comments will be matched as a single block
|
51
|
+
SQL_COMMENTS = %r{(\s*?--[^\n]+\n*)+|(\s*?/\*[^/\*]*\*/\s*)}m
|
30
52
|
COMMENT_CONTENT = /[\S]+[\s\S]*[\S]+/
|
53
|
+
NAMED_DOLLAR_QUOTED_STRINGS_REGEX = /[^\$](\$[^\$]+\$)[^\$]/
|
54
|
+
DOLLAR_QUOTED_STRINGS = /(\$\$.*\$\$)/
|
31
55
|
|
32
56
|
class << self
|
33
57
|
def config
|
@@ -35,10 +59,9 @@ module Niceql
|
|
35
59
|
end
|
36
60
|
|
37
61
|
def prettify_err(err, original_sql_query = nil)
|
38
|
-
prettify_pg_err(
|
62
|
+
prettify_pg_err(err.to_s, original_sql_query)
|
39
63
|
end
|
40
64
|
|
41
|
-
|
42
65
|
# Postgres error output:
|
43
66
|
# ERROR: VALUES in FROM must have an alias
|
44
67
|
# LINE 2: FROM ( VALUES(1), (2) );
|
@@ -54,7 +77,7 @@ module Niceql
|
|
54
77
|
# ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "usr" does not exist
|
55
78
|
# LINE 1: SELECT usr FROM users ORDER BY 1
|
56
79
|
# ^
|
57
|
-
|
80
|
+
# : SELECT usr FROM users ORDER BY 1
|
58
81
|
|
59
82
|
# prettify_pg_err parses ActiveRecord::StatementInvalid string,
|
60
83
|
# but you may use it without ActiveRecord either way:
|
@@ -62,6 +85,7 @@ module Niceql
|
|
62
85
|
# don't mess with original sql query, or prettify_pg_err will deliver incorrect results
|
63
86
|
def prettify_pg_err(err, original_sql_query = nil)
|
64
87
|
return err if err[/LINE \d+/].nil?
|
88
|
+
|
65
89
|
# LINE 2: ... -> err_line_num = 2
|
66
90
|
err_line_num = err.match(/LINE (\d+):/)[1].to_i
|
67
91
|
# LINE 1: SELECT usr FROM users ORDER BY 1
|
@@ -80,130 +104,335 @@ module Niceql
|
|
80
104
|
# and to apply a full red colorizing schema on an SQL line with error
|
81
105
|
err_line = sql_body_lines[err_line_num - 1]
|
82
106
|
|
83
|
-
#colorizing
|
84
|
-
err_body = sql_body_lines.map
|
107
|
+
# colorizing keywords, strings and error line
|
108
|
+
err_body = sql_body_lines.map do |ln|
|
109
|
+
ln == err_line ? StringColorize.colorize_err(ln) : colorize_err_line(ln)
|
110
|
+
end
|
111
|
+
|
112
|
+
err_caret_line = extract_err_caret_line(err_address_line, err_line, sql_body_lines, err)
|
113
|
+
err_body.insert(err_line_num, StringColorize.colorize_err(err_caret_line))
|
85
114
|
|
86
|
-
|
87
|
-
|
115
|
+
err.lines[0..sql_start_line_num - 1].join + err_body.join
|
116
|
+
end
|
88
117
|
|
89
|
-
|
118
|
+
def prettify_sql(sql, colorize = true)
|
119
|
+
QueryNormalizer.new(sql, colorize).prettified_sql
|
90
120
|
end
|
91
121
|
|
92
|
-
def
|
122
|
+
def prettify_multiple(sql_multi, colorize = true)
|
123
|
+
sql_multi.split(/(?>#{SQL_COMMENTS})|(\;)/).each_with_object([""]) do |pattern, queries|
|
124
|
+
queries[-1] += pattern
|
125
|
+
queries << "" if pattern == ";"
|
126
|
+
end.map! do |sql|
|
127
|
+
# we were splitting by comments and ';', so if next sql start with comment we've got a misplaced \n\n
|
128
|
+
sql.match?(/\A\s+\z/) ? nil : prettify_sql(sql, colorize)
|
129
|
+
end.compact.join("\n")
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def colorize_err_line(line)
|
135
|
+
line.gsub(/#{KEYWORDS}/) { |keyword| StringColorize.colorize_keyword(keyword) }
|
136
|
+
.gsub(STRINGS) { |str| StringColorize.colorize_str(str) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def extract_err_caret_line(err_address_line, err_line, sql_body, err)
|
140
|
+
# LINE could be quoted ( both sides and sometimes only from one ):
|
141
|
+
# "LINE 1: ...t_id\" = $13 AND \"products\".\"carrier_id\" = $14 AND \"product_t...\n",
|
142
|
+
err_quote = (err_address_line.match(/\.\.\.(.+)\.\.\./) || err_address_line.match(/\.\.\.(.+)/))&.send(:[], 1)
|
143
|
+
|
144
|
+
# line[2] is original err caret line i.e.: ' ^'
|
145
|
+
# err_address_line[/LINE \d+:/].length+1..-1 - is a position from error quote begin
|
146
|
+
err_caret_line = err.lines[2][err_address_line[/LINE \d+:/].length + 1..-1]
|
147
|
+
|
148
|
+
# when err line is too long postgres quotes it in double '...'
|
149
|
+
# so we need to reposition caret against original line
|
150
|
+
if err_quote
|
151
|
+
err_quote_caret_offset = err_caret_line.length - err_address_line.index("...").to_i + 3
|
152
|
+
err_caret_line = " " * (err_line.index(err_quote) + err_quote_caret_offset) + "^\n"
|
153
|
+
end
|
154
|
+
|
155
|
+
# older versions of ActiveRecord were adding ': ' before an original query :(
|
156
|
+
err_caret_line.prepend(" ") if sql_body[0].start_with?(": ")
|
157
|
+
# if mistake is on last string than err_line.last != \n then we need to prepend \n to caret line
|
158
|
+
err_caret_line.prepend("\n") unless err_line[-1] == "\n"
|
159
|
+
err_caret_line
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# The normalizing and formatting logic:
|
164
|
+
# 1. Split the original query onto the query part + literals + comments
|
165
|
+
# a. find all potential dollar-signed separators
|
166
|
+
# b. prepare full literal extractor regex
|
167
|
+
# 2. Find and separate all literals and comments into mutable/format-able types and immutable ( see the typing and formatting rules below )
|
168
|
+
# 3. Replace all literals and comments with uniq ids on the original query to get the parametrized query
|
169
|
+
# 4. Format parametrized query alongside with mutable/format-able comments and literals
|
170
|
+
# a. clear space characters: replace all \s+ to \s, remove all "\n" e.t.c
|
171
|
+
# b. split in lines -> indent -> colorize
|
172
|
+
# 5. Restore literals and comments with their values
|
173
|
+
class QueryNormalizer
|
174
|
+
extend Forwardable
|
175
|
+
def_delegator :Niceql, :config
|
176
|
+
|
177
|
+
# Literals content should not be indented, only string parts separated by new lines can be indented
|
178
|
+
# indentable_string:
|
179
|
+
# UPDATE docs SET body = 'First line'
|
180
|
+
# 'Second line'
|
181
|
+
# 'Third line', ...
|
182
|
+
#
|
183
|
+
# SQL standard allow such multiline separation.
|
184
|
+
|
185
|
+
# newline_end_comments:
|
186
|
+
# SELECT * -- get all column
|
187
|
+
# SELECT * /* get all column */
|
188
|
+
#
|
189
|
+
# SELECT * -- get all column
|
190
|
+
# -- we need all columns for this request
|
191
|
+
# SELECT * /* get all column
|
192
|
+
# we need all columns for this request */
|
193
|
+
#
|
194
|
+
# rare case newline_start_comments:
|
195
|
+
# SELECT *
|
196
|
+
# /* get all column
|
197
|
+
# we need all columns for this request */ FROM table
|
198
|
+
#
|
199
|
+
# newline_wrapped_comments:
|
200
|
+
# SELECT *
|
201
|
+
# /* get all column
|
202
|
+
# we need all columns for this request */
|
203
|
+
# FROM table
|
204
|
+
#
|
205
|
+
# SELECT *
|
206
|
+
# -- get all column
|
207
|
+
# -- we need all columns for this request
|
208
|
+
# FROM ...
|
209
|
+
# Potentially we could prettify different type of comments and strings a little bit differently,
|
210
|
+
# but right now there is no difference between the
|
211
|
+
# newline_wrapped_comment, newline_start_comment, newline_end_comment, they all will be wrapped in newlines
|
212
|
+
COMMENT_AND_LITERAL_TYPES = [:immutable_string, :indentable_string, :inline_comment, :newline_wrapped_comment,
|
213
|
+
:newline_start_comment, :newline_end_comment]
|
214
|
+
|
215
|
+
attr_reader :parametrized_sql, :initial_sql, :string_regex, :literals_and_comments_types, :colorize
|
216
|
+
|
217
|
+
def initialize(sql, colorize)
|
218
|
+
@initial_sql = sql
|
219
|
+
@colorize = colorize
|
220
|
+
@parametrized_sql = ""
|
221
|
+
@guids_to_content = {}
|
222
|
+
@literals_and_comments_types = {}
|
223
|
+
@counter = Hash.new(0)
|
224
|
+
|
225
|
+
init_strings_regex
|
226
|
+
prepare_parametrized_sql
|
227
|
+
prettify_parametrized_sql
|
228
|
+
end
|
229
|
+
|
230
|
+
def prettified_sql
|
231
|
+
@parametrized_sql % @guids_to_content.transform_keys(&:to_sym)
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def prettify_parametrized_sql
|
93
237
|
indent = 0
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
sql.gsub!( /(#{VERBS}|#{BRACKETS}|#{SQL_COMMENTS_CLEARED})/) do |verb|
|
112
|
-
if 'SELECT' == verb
|
113
|
-
indent += config.indentation_base if !config.open_bracket_is_newliner || parentness.last.nil? || parentness.last[:nested]
|
114
|
-
parentness.last[:nested] = true if parentness.last
|
115
|
-
add_new_line = !first_verb
|
116
|
-
elsif verb == '('
|
117
|
-
next_closing_bracket = Regexp.last_match.post_match.index(')')
|
238
|
+
brackets = []
|
239
|
+
first_keyword = true
|
240
|
+
|
241
|
+
parametrized_sql.gsub!(query_split_regex) do |matched_part|
|
242
|
+
if inline_piece?(matched_part)
|
243
|
+
first_keyword = false
|
244
|
+
next matched_part
|
245
|
+
end
|
246
|
+
post_match_str = Regexp.last_match.post_match
|
247
|
+
|
248
|
+
if ["SELECT", "UPDATE", "INSERT"].include?(matched_part)
|
249
|
+
indent += config.indentation_base if !config.open_bracket_is_newliner || brackets.last.nil? || brackets.last[:nested]
|
250
|
+
brackets.last[:nested] = true if brackets.last
|
251
|
+
add_new_line = !first_keyword
|
252
|
+
elsif matched_part == "("
|
253
|
+
next_closing_bracket = post_match_str.index(")")
|
118
254
|
# check if brackets contains SELECT statement
|
119
|
-
add_new_line = !!
|
120
|
-
|
121
|
-
elsif
|
255
|
+
add_new_line = !!post_match_str[0..next_closing_bracket][/SELECT/] && config.open_bracket_is_newliner
|
256
|
+
brackets << { nested: add_new_line }
|
257
|
+
elsif matched_part == ")"
|
122
258
|
# this also covers case when right bracket is used without corresponding left one
|
123
|
-
add_new_line =
|
124
|
-
indent -= (
|
259
|
+
add_new_line = brackets.last.nil? || brackets.last[:nested]
|
260
|
+
indent -= (brackets.last.nil? && 2 || brackets.last[:nested] && 1 || 0) * config.indentation_base
|
125
261
|
indent = 0 if indent < 0
|
126
|
-
|
127
|
-
elsif
|
262
|
+
brackets.pop
|
263
|
+
elsif matched_part[POSSIBLE_INLINER]
|
128
264
|
# in postgres ORDER BY can be used in aggregation function this will keep it
|
129
265
|
# inline with its agg function
|
130
|
-
add_new_line =
|
266
|
+
add_new_line = brackets.last.nil? || brackets.last[:nested]
|
131
267
|
else
|
132
|
-
|
268
|
+
# since we are matching KEYWORD without space on the end
|
269
|
+
# IN will be present in JOIN, DISTINCT e.t.c, so we need to exclude it explicitly
|
270
|
+
add_new_line = matched_part.match?(/(#{NEW_LINE_KEYWORDS})/)
|
133
271
|
end
|
134
272
|
|
135
|
-
#
|
136
|
-
|
137
|
-
# inliners match with a space before so we need to strip it
|
138
|
-
verb.lstrip! if !add_new_line && prev_was_comment
|
273
|
+
# do not indent first keyword in query, and indent everytime we started new line
|
274
|
+
add_indent_to_keyword = !first_keyword && add_new_line
|
139
275
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
276
|
+
if literals_and_comments_types[matched_part]
|
277
|
+
# this is a case when comment followed by ordinary SQL part not by any keyword
|
278
|
+
# this means that it will not be gsubed and no indent will be added before this part, while needed
|
279
|
+
last_comment_followed_by_keyword = post_match_str.match?(/\A\}\s{0,1}(?:#{KEYWORDS})/)
|
280
|
+
indent_parametrized_part(matched_part, indent, !last_comment_followed_by_keyword, !first_keyword)
|
281
|
+
matched_part
|
146
282
|
else
|
147
|
-
|
148
|
-
|
283
|
+
first_keyword = false
|
284
|
+
indented_sql = (add_indent_to_keyword ? indent_multiline(matched_part, indent) : matched_part)
|
285
|
+
add_new_line ? "\n" + indented_sql : indented_sql
|
149
286
|
end
|
287
|
+
end
|
288
|
+
|
289
|
+
parametrized_sql.gsub!(" \n", "\n") # moved keywords could keep space before it, we can crop it anyway
|
290
|
+
|
291
|
+
clear_extra_newline_after_comments
|
292
|
+
|
293
|
+
colorize_query if colorize
|
294
|
+
end
|
150
295
|
|
151
|
-
|
296
|
+
def add_string_or_comment(string_or_comment)
|
297
|
+
# when we splitting original SQL, it could and could not end with literal/comment
|
298
|
+
# hence we could try to add nil...
|
299
|
+
return if string_or_comment.nil?
|
300
|
+
|
301
|
+
type = get_placeholder_type(string_or_comment)
|
302
|
+
# will be formatted to comment_1_guid
|
303
|
+
typed_id = new_placeholder_name(type)
|
304
|
+
@guids_to_content[typed_id] = string_or_comment
|
305
|
+
@counter[type] += 1
|
306
|
+
@literals_and_comments_types[typed_id] = type
|
307
|
+
"%{#{typed_id}}"
|
308
|
+
end
|
309
|
+
|
310
|
+
def literal_and_comments_placeholders_regex
|
311
|
+
/(#{@literals_and_comments_types.keys.join("|")})/
|
312
|
+
end
|
313
|
+
|
314
|
+
def inline_piece?(comment_or_string)
|
315
|
+
[:immutable_string, :inline_comment].include?(literals_and_comments_types[comment_or_string])
|
316
|
+
end
|
317
|
+
|
318
|
+
def prepare_parametrized_sql
|
319
|
+
@parametrized_sql = @initial_sql.split(/#{SQL_COMMENTS}|#{string_regex}/)
|
320
|
+
.each_slice(2).map do |sql_part, comment_or_string|
|
321
|
+
# remove additional formatting for sql_parts and replace comment and strings with a guids
|
322
|
+
[sql_part.gsub(/[\s]+/, " "), add_string_or_comment(comment_or_string)]
|
323
|
+
end.flatten.compact.join("")
|
324
|
+
end
|
152
325
|
|
153
|
-
|
154
|
-
|
326
|
+
def query_split_regex(with_brackets = true)
|
327
|
+
if with_brackets
|
328
|
+
/(#{KEYWORDS}|#{BRACKETS}|#{literal_and_comments_placeholders_regex})/
|
329
|
+
else
|
330
|
+
/(#{KEYWORDS}|#{literal_and_comments_placeholders_regex})/
|
155
331
|
end
|
332
|
+
end
|
156
333
|
|
157
|
-
|
158
|
-
|
334
|
+
# when comment ending with newline followed by a keyword we should remove double newlines
|
335
|
+
def clear_extra_newline_after_comments
|
336
|
+
newlined_comments = @literals_and_comments_types.select { |k,| new_line_ending_comment?(k) }
|
337
|
+
parametrized_sql.gsub!(/(#{newlined_comments.keys.join("}\n|")}}\n)/, &:chop)
|
159
338
|
end
|
160
339
|
|
161
|
-
def
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
340
|
+
def colorize_query
|
341
|
+
parametrized_sql.gsub!(query_split_regex(false)) do |matched_part|
|
342
|
+
if literals_and_comments_types[matched_part]
|
343
|
+
colorize_comment_or_literal(matched_part)
|
344
|
+
matched_part
|
345
|
+
else
|
346
|
+
StringColorize.colorize_keyword(matched_part)
|
347
|
+
end
|
348
|
+
end
|
170
349
|
end
|
171
350
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
351
|
+
def indent_parametrized_part(matched_typed_id, indent, indent_after_comment, start_with_newline = true)
|
352
|
+
case @literals_and_comments_types[matched_typed_id]
|
353
|
+
# technically we will not get here, since this types of literals/comments are not indentable
|
354
|
+
when :inline_comment, :immutable_string
|
355
|
+
when :indentable_string
|
356
|
+
lines = @guids_to_content[matched_typed_id].lines
|
357
|
+
@guids_to_content[matched_typed_id] = lines[0] +
|
358
|
+
lines[1..-1].map! { |ln| indent_multiline(ln[/'[^']+'/], indent) }.join("\n")
|
176
359
|
else
|
177
|
-
|
360
|
+
content = @guids_to_content[matched_typed_id][COMMENT_CONTENT]
|
361
|
+
@guids_to_content[matched_typed_id] = (start_with_newline ? "\n" : "") +
|
362
|
+
"#{indent_multiline(content, indent)}\n" +
|
363
|
+
(indent_after_comment ? indent_multiline("", indent) : "")
|
178
364
|
end
|
179
365
|
end
|
180
366
|
|
181
|
-
def
|
182
|
-
|
183
|
-
|
367
|
+
def colorize_comment_or_literal(matched_typed_id)
|
368
|
+
@guids_to_content[matched_typed_id] = if comment?(@literals_and_comments_types[matched_typed_id])
|
369
|
+
StringColorize.colorize_comment(@guids_to_content[matched_typed_id])
|
370
|
+
else
|
371
|
+
StringColorize.colorize_str(@guids_to_content[matched_typed_id])
|
372
|
+
end
|
184
373
|
end
|
185
374
|
|
186
|
-
def
|
187
|
-
|
188
|
-
|
189
|
-
|
375
|
+
def get_placeholder_type(comment_or_string)
|
376
|
+
if SQL_COMMENTS.match?(comment_or_string)
|
377
|
+
get_comment_type(comment_or_string)
|
378
|
+
else
|
379
|
+
get_string_type(comment_or_string)
|
380
|
+
end
|
381
|
+
end
|
190
382
|
|
191
|
-
|
192
|
-
|
193
|
-
|
383
|
+
def get_comment_type(comment)
|
384
|
+
case comment
|
385
|
+
when /\s*\n+\s*.+\s*\n+\s*/ then :newline_wrapped_comment
|
386
|
+
when /\s*\n+\s*.+/ then :newline_start_comment
|
387
|
+
when /.+\s*\n+\s*/ then :newline_end_comment
|
388
|
+
else :inline_comment
|
389
|
+
end
|
390
|
+
end
|
194
391
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
392
|
+
def get_string_type(string)
|
393
|
+
MULTILINE_INDENTABLE_LITERAL.match?(string) ? :indentable_string : :immutable_string
|
394
|
+
end
|
395
|
+
|
396
|
+
def new_placeholder_name(placeholder_type)
|
397
|
+
"#{placeholder_type}_#{@counter[placeholder_type]}_#{SecureRandom.uuid}"
|
398
|
+
end
|
399
|
+
|
400
|
+
def get_sql_named_strs(sql)
|
401
|
+
freq = Hash.new(0)
|
402
|
+
sql.scan(NAMED_DOLLAR_QUOTED_STRINGS_REGEX).select do |str|
|
403
|
+
freq[str] += 1
|
404
|
+
freq[str] == 2
|
200
405
|
end
|
406
|
+
.flatten
|
407
|
+
.map { |str| str.gsub!("$", '\$') }
|
408
|
+
end
|
201
409
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
410
|
+
def init_strings_regex
|
411
|
+
# /($STR$.+$STR$|$$[^$]$$|'[^']'|"[^"]")/
|
412
|
+
strs = get_sql_named_strs(initial_sql).map { |dq_str| "#{dq_str}.+#{dq_str}" }
|
413
|
+
strs = ["(#{strs.join("|")})"] if strs != []
|
414
|
+
@string_regex ||= /#{[*strs, DOLLAR_QUOTED_STRINGS, STRINGS].join("|")}/m
|
415
|
+
end
|
416
|
+
|
417
|
+
def comment?(piece_type)
|
418
|
+
!literal?(piece_type)
|
419
|
+
end
|
420
|
+
|
421
|
+
def literal?(piece_type)
|
422
|
+
[:indentable_string, :immutable_string].include?(piece_type)
|
423
|
+
end
|
424
|
+
|
425
|
+
def new_line_ending_comment?(comment_or_literal)
|
426
|
+
[:newline_wrapped_comment, :newline_end_comment, :newline_start_comment]
|
427
|
+
.include?(@literals_and_comments_types[comment_or_literal])
|
428
|
+
end
|
429
|
+
|
430
|
+
def indent_multiline(keyword, indent)
|
431
|
+
if keyword.match?(/.\s*\n\s*./)
|
432
|
+
keyword.lines.map! { |ln| " " * indent + ln }.join("")
|
433
|
+
else
|
434
|
+
" " * indent + keyword
|
435
|
+
end
|
207
436
|
end
|
208
437
|
end
|
209
438
|
end
|
@@ -217,8 +446,11 @@ module Niceql
|
|
217
446
|
end
|
218
447
|
end
|
219
448
|
|
220
|
-
def self.configure
|
221
|
-
|
222
|
-
|
449
|
+
def self.configure
|
450
|
+
yield(config)
|
451
|
+
end
|
223
452
|
|
453
|
+
def self.config
|
454
|
+
@config ||= NiceQLConfig.new
|
455
|
+
end
|
224
456
|
end
|
data/niceql.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
lib = File.expand_path("../lib", __FILE__)
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
6
|
require "niceql/version"
|
@@ -9,8 +11,12 @@ Gem::Specification.new do |spec|
|
|
9
11
|
spec.authors = ["alekseyl"]
|
10
12
|
spec.email = ["leshchuk@gmail.com"]
|
11
13
|
|
12
|
-
spec.summary =
|
13
|
-
|
14
|
+
spec.summary = "This is a simple and nice gem for SQL prettifying and formatting. "\
|
15
|
+
"Niceql splits, indent and colorize SQL query and PG errors if any. "
|
16
|
+
spec.description = "This is a simple and nice gem for SQL prettifying and formatting. "\
|
17
|
+
"Niceql splits, indent and colorize SQL query and PG errors if any. "\
|
18
|
+
"Could be used as a standalone gem without any dependencies. "\
|
19
|
+
"Seamless ActiveRecord integration via rails_sql_prettifier gem. "
|
14
20
|
spec.homepage = "https://github.com/alekseyl/niceql"
|
15
21
|
spec.license = "MIT"
|
16
22
|
|
@@ -23,21 +29,23 @@ Gem::Specification.new do |spec|
|
|
23
29
|
"public gem pushes."
|
24
30
|
end
|
25
31
|
|
26
|
-
spec.files
|
32
|
+
spec.files = %x(git ls-files -z).split("\x0").reject do |f|
|
27
33
|
f.match(%r{^(test|spec|features)/})
|
28
34
|
end
|
29
35
|
spec.bindir = "exe"
|
30
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
37
|
spec.require_paths = ["lib"]
|
32
38
|
|
33
|
-
spec.required_ruby_version =
|
39
|
+
spec.required_ruby_version = ">= 2.4"
|
34
40
|
|
35
|
-
spec.add_development_dependency
|
36
|
-
spec.add_development_dependency
|
37
|
-
spec.add_development_dependency
|
41
|
+
spec.add_development_dependency("awesome_print")
|
42
|
+
spec.add_development_dependency("bundler", ">= 1")
|
43
|
+
spec.add_development_dependency("minitest", "~> 5.0")
|
44
|
+
spec.add_development_dependency("rake", ">= 12.3.3")
|
45
|
+
spec.add_development_dependency("rubocop-shopify", "~> 2.0.0")
|
38
46
|
|
39
|
-
spec.add_development_dependency
|
40
|
-
spec.add_development_dependency
|
41
|
-
spec.add_development_dependency
|
42
|
-
spec.add_development_dependency
|
47
|
+
spec.add_development_dependency("benchmark-ips")
|
48
|
+
spec.add_development_dependency("differ")
|
49
|
+
spec.add_development_dependency("pry-byebug")
|
50
|
+
spec.add_development_dependency("sqlite3")
|
43
51
|
end
|