lib/niceql.rb in niceql-0.1.18 vs lib/niceql.rb in niceql-0.1.20
- old
+ new
@@ -37,50 +37,81 @@
NEW_LINE_VERBS = 'SELECT|FROM|WHERE|CASE|ORDER BY|LIMIT|GROUP BY|(RIGHT |LEFT )*(INNER |OUTER )*JOIN|HAVING|OFFSET|UPDATE'
POSSIBLE_INLINER = /(ORDER BY|CASE)/
VERBS = "#{NEW_LINE_VERBS}|#{INLINE_VERBS}"
STRINGS = /("[^"]+")|('[^']+')/
BRACKETS = '[\(\)]'
+ SQL_COMMENTS = /(\s*?--.+\s*)|(\s*?\/\*[^\/\*]*\*\/\s*)/
+ # only newlined comments will be matched
+ SQL_COMMENTS_CLEARED = /(\s*?--.+\s{1})|(\s*$\s*\/\*[^\/\*]*\*\/\s{1})/
+ COMMENT_CONTENT = /[\S]+[\s\S]*[\S]+/
-
def self.config
Niceql.config
end
def self.prettify_err(err)
prettify_pg_err( err.to_s )
end
- def self.prettify_pg_err(err)
+ # Postgres error output:
+ # ERROR: VALUES in FROM must have an alias
+ # LINE 2: FROM ( VALUES(1), (2) );
+ # ^
+ # HINT: For example, FROM (VALUES ...) [AS] foo.
+
+ # May go without HINT or DETAIL:
+ # ERROR: column "usr" does not exist
+ # LINE 1: SELECT usr FROM users ORDER BY 1
+ # ^
+
+ # ActiveRecord::StatementInvalid will add original SQL query to the bottom like this:
+ # ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "usr" does not exist
+ # LINE 1: SELECT usr FROM users ORDER BY 1
+ # ^
+ #: SELECT usr FROM users ORDER BY 1
+
+ # prettify_pg_err parses ActiveRecord::StatementInvalid string,
+ # but you may use it without ActiveRecord either way:
+ # prettify_pg_err( err + "\n" + sql ) OR prettify_pg_err( err, sql )
+ # don't mess with original sql query, or prettify_pg_err will deliver incorrect results
+ def self.prettify_pg_err(err, original_sql_query = nil)
return err if err[/LINE \d+/].nil?
err_line_num = err[/LINE \d+/][5..-1].to_i
+ #
start_sql_line = err.lines[3][/(HINT|DETAIL)/] ? 4 : 3
- err_body = err.lines[start_sql_line..-1]
+ err_body = start_sql_line < err.lines.length ? err.lines[start_sql_line..-1] : original_sql_query&.lines
+
+
+ # this means original query is missing so it's nothing to prettify
+ return err unless err_body
+
err_quote = ( err.lines[1][/\.\.\..+\.\.\./] && err.lines[1][/\.\.\..+\.\.\./][3..-4] ) ||
( err.lines[1][/\.\.\..+/] && err.lines[1][/\.\.\..+/][3..-1] )
- # line 2 is err carret line
+ # line[2] is err carret line i.e.: ' ^'
# err.lines[1][/LINE \d+:/].length+1..-1 - is a position from error quote begin
err_carret_line = err.lines[2][err.lines[1][/LINE \d+:/].length+1..-1]
- # err line painted red completly, so we just remembering it and use
+ # err line will be painted in red completely, so we just remembering it and use
# to replace after paiting the verbs
err_line = err_body[err_line_num-1]
- # when err line is too long postgres quotes it part in doble ...
+ # when err line is too long postgres quotes it part in double '...'
if err_quote
err_quote_carret_offset = err_carret_line.length - err.lines[1].index( '...' ) + 3
err_carret_line = ' ' * ( err_line.index( err_quote ) + err_quote_carret_offset ) + "^\n"
end
+ err_carret_line = " " + err_carret_line if err_body[0].start_with?(': ')
# if mistake is on last string than err_line.last != \n so we need to prepend \n to carret line
- err_carret_line = "\n" + err_carret_line unless err_line.last == "\n"
+ err_carret_line = "\n" + err_carret_line unless err_line[-1] == "\n"
#colorizing verbs and strings
err_body = err_body.join.gsub(/#{VERBS}/ ) { |verb| StringColorize.colorize_verb(verb) }
- err_body = err_body.gsub(STRINGS){ |str| StringColorize.colorize_str(str) }
+ .gsub(STRINGS){ |str| StringColorize.colorize_str(str) }
#reassemling error message
err_body = err_body.lines
err_body[err_line_num-1]= StringColorize.colorize_err( err_line )
err_body.insert( err_line_num, StringColorize.colorize_err( err_carret_line ) )
@@ -90,20 +121,25 @@
def self.prettify_sql( sql, colorize = true )
indent = 0
parentness = []
- #it's better to remove all new lines because it will break formatting
- sql = sql.gsub("\n", ' ')
- # remove any additional formatting
- sql = sql.gsub(/[\s]+/, ' ')
+ #it's better to remove all new lines because it will break formatting + remove any additional formatting with many spaces
+ # second map! is add newlines at start and begining for all comments started with new line
+ sql = sql.split( SQL_COMMENTS ).each_slice(2).map{ | crmb, cmmnt |
+ [crmb.gsub(/[\s]+/, ' '),
+ cmmnt && ( cmmnt&.match?(/\A\s*$/) ? "\n" + cmmnt[/[\S]+[\s\S]*[\S]+/] + "\n" : cmmnt[/[\S]+[\s\S]*[\S]+/] ) ]
+ }.flatten.join(' ')
- sql = sql.gsub(STRINGS){ |str| StringColorize.colorize_str(str) } if colorize
+ sql.gsub!(/ \n/, "\n")
+
+ sql.gsub!(STRINGS){ |str| StringColorize.colorize_str(str) } if colorize
+
first_verb = true
+ previous_was_comment = false
- sql.gsub( /(#{VERBS}|#{BRACKETS})/).with_index do |verb, index|
- add_new_line = false
+ sql.gsub!( /(#{VERBS}|#{BRACKETS}|#{SQL_COMMENTS_CLEARED})/) do |verb|
if 'SELECT' == verb
indent += config.indentation_base if !config.open_bracket_is_newliner || parentness.last.nil? || parentness.last[:nested]
parentness.last[:nested] = true if parentness.last
add_new_line = !first_verb
elsif verb == '('
@@ -122,14 +158,31 @@
add_new_line = parentness.last.nil? || parentness.last[:nested]
else
add_new_line = verb[/(#{INLINE_VERBS})/].nil?
end
first_verb = false
+
+ verb = verb[COMMENT_CONTENT] if verb[SQL_COMMENTS_CLEARED]
+ # !add_new_line && previous_was_comment means we had newlined comment, and now even
+ # if verb is inline verb we will need to add new line with indentation BUT all
+ # newliners match with a space before so we need to strip it
+ verb.lstrip! if !add_new_line && previous_was_comment
verb = StringColorize.colorize_verb(verb) if !['(', ')'].include?(verb) && colorize
- add_new_line ? "\n#{' ' * indent}" + verb : verb
+ (previous_was_comment || add_new_line ? indent_multiline(verb, indent) : verb).tap{ previous_was_comment = !verb.to_s[SQL_COMMENTS_CLEARED].nil? }
end
+ sql.gsub( /\s+\n/, "\n" ).gsub(/\s+\z/, '')
end
+
+ private
+ def self.indent_multiline( verb, indent )
+ # byebug
+ if verb.match?(/.\n./)
+ verb.lines.map!{|ln| "\n#{' ' * indent}" + ln}.join
+ else
+ "\n#{' ' * indent}" + verb.to_s
+ end
+ end
end
module PostgresAdapterNiceQL
def exec_query(sql, name = "SQL", binds = [], prepare: false)
# replacing sql with prettified sql, thats all
@@ -138,37 +191,40 @@
end
module AbstractAdapterLogPrettifier
def log( sql, *args, &block )
# \n need to be placed because AR log will start with action description + time info.
- # rescue sql - just to be sure Prettifier didn't break production
+ # rescue sql - just to be sure Prettifier wouldn't break production
formatted_sql = "\n" + Prettifier.prettify_sql(sql) rescue sql
super( formatted_sql, *args, &block )
end
end
module ErrorExt
def to_s
- if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
- Prettifier.prettify_err( super )
+ if Niceql.config.prettify_pg_errors && ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
+ Prettifier.prettify_err(super)
else
super
end
end
end
class NiceQLConfig
attr_accessor :pg_adapter_with_nicesql,
:indentation_base,
:open_bracket_is_newliner,
- :prettify_active_record_log_output
+ :prettify_active_record_log_output,
+ :prettify_pg_errors
+
def initialize
self.pg_adapter_with_nicesql = false
self.indentation_base = 2
self.open_bracket_is_newliner = false
self.prettify_active_record_log_output = false
+ self.prettify_pg_errors = defined? ::ActiveRecord::Base && ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
end
end
def self.configure
@@ -177,20 +233,22 @@
if config.pg_adapter_with_nicesql
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include(PostgresAdapterNiceQL)
end
if config.prettify_active_record_log_output
- ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(AbstractAdapterLogPrettifier)
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend( AbstractAdapterLogPrettifier )
end
+
+ if config.prettify_pg_errors
+ ::ActiveRecord::StatementInvalid.include( Niceql::ErrorExt )
+ end
end
def self.config
@config ||= NiceQLConfig.new
end
-
if defined? ::ActiveRecord::Base
- ActiveRecord::StatementInvalid.include( Niceql::ErrorExt )
::ActiveRecord::Base.extend ArExtentions
[::ActiveRecord::Relation, ::ActiveRecord::Associations::CollectionProxy].each { |klass| klass.send(:include, ArExtentions) }
end
end