diff --git a/lib/whoop.rb b/lib/whoop.rb index 086efc9..accfbbd 100644 --- a/lib/whoop.rb +++ b/lib/whoop.rb @@ -87,6 +87,7 @@ def whoop( logger_method.call "" logger_method.call formatter_method.call(result) logger_method.call "" + display_invalid_format_message(format, color_method, logger_method) logger_method.call color_method.call "#{BOTTOM_LINE_CHAR}#{line}\n\n" end else @@ -98,6 +99,7 @@ def whoop( logger_method.call "" logger_method.call formatter_method.call(label) logger_method.call "" + display_invalid_format_message(format, color_method, logger_method) logger_method.call color_method.call "#{BOTTOM_LINE_CHAR}#{line}\n\n" end end @@ -150,6 +152,13 @@ def detect_formatter_method(format, colorize: false, explain: false) end end + def display_invalid_format_message(format, color_method, logger_method) + return if FORMATS.include?(format) + + invalid_format_line = [color_method.call(INDENT), "note:".colorize(:blue).underline, "Unsupported format used. Available formats: #{FORMATS.to_sentence}"].join(" ") + logger_method.call invalid_format_line + end + # Return the line with the label centered in it # @param [String] label # @param [Integer] count diff --git a/lib/whoop/formatters/sql_formatter.rb b/lib/whoop/formatters/sql_formatter.rb index b5ccdaa..6ab774f 100644 --- a/lib/whoop/formatters/sql_formatter.rb +++ b/lib/whoop/formatters/sql_formatter.rb @@ -6,6 +6,33 @@ module Whoop module Formatters module SqlFormatter + # Hash of patterns to preserve in the SQL. The key is the expected pattern, + # the value is the pattern after it has been "broken" by anbt formatting. + # Instances of the value are replaced by the key. + # Patterns are jsonb column operators from https://www.postgresql.org/docs/15/functions-json.html + PATTERNS_TO_PRESERVE = { + # jsonb operators without surrounding spaces + # IE, the first one replaces " : : " with "::" + "::" => /\s?: :\s?/, + "->>" => /\s?- > >\s?/, + "->" => /\s?- >\s?/, + "#>>" => /\s?# > >\s?/, + "#>" => /\s?# >\s?/, + + # jsonb operators with surrounding spaces + # IE, the first one replaces " @ > " with " @> " + " @> " => /\s?@ >\s?/, + " <@ " => /\s?< @\s?/, + " ?| " => /\s?\? \|\s?/, + " ?& " => /\s?\? &\s?/, + " || " => /\s?\| \|\s?/, + " #- " => /\s?# -\s?/, + + # Additional broken patterns + "[" => /\[\s?/, + "]" => /\s?\]/ + } + # Format the SQL query # @param [String] sql The SQL query # @param [Boolean] colorize - colorize the SQL query (default: false) @@ -39,8 +66,20 @@ def self.format(sql, colorize: false, explain: false) def self.generate_pretty_sql(sql) rule = AnbtSql::Rule.new rule.indent_string = " " + formatter = AnbtSql::Formatter.new(rule) - formatter.format(sql.dup) + formatted_string = formatter.format(sql.dup) + + # Anbt injects additional spaces into joined symbols. + # This removes them by generating the "broken" collection + # of symbols, and replacing them with the original. + PATTERNS_TO_PRESERVE.each do |correct_pattern, incorrect_pattern| + next unless incorrect_pattern.match?(formatted_string) + + formatted_string.gsub!(incorrect_pattern, correct_pattern) + end + + formatted_string end # Execute the `EXPLAIN` query diff --git a/sig/whoop.rbs b/sig/whoop.rbs index 66337a4..886c98e 100644 --- a/sig/whoop.rbs +++ b/sig/whoop.rbs @@ -73,6 +73,12 @@ module Whoop # _@return_ — format method def detect_formatter_method: (Symbol format, ?colorize: untyped, ?explain: untyped) -> Method + # sord omit - no YARD type given for "format", using untyped + # sord omit - no YARD type given for "color_method", using untyped + # sord omit - no YARD type given for "logger_method", using untyped + # sord omit - no YARD return type given, using untyped + def display_invalid_format_message: (untyped format, untyped color_method, untyped logger_method) -> untyped + # Return the line with the label centered in it # # _@param_ `label` @@ -85,6 +91,8 @@ module Whoop module Formatters module SqlFormatter + PATTERNS_TO_PRESERVE: untyped + # Format the SQL query # # _@param_ `sql` — The SQL query diff --git a/spec/whoop_spec.rb b/spec/whoop_spec.rb index 61337b2..ef2b11c 100644 --- a/spec/whoop_spec.rb +++ b/spec/whoop_spec.rb @@ -33,6 +33,19 @@ end end + context "when the format is invalid" do + it "adds a note listing available formats" do + io = setup_whoop + whoop("Bad format", format: :invalid, color: false) + logged_message = io.string + + puts logged_message + + expect(logged_message).to include("Bad format") + expect(logged_message).to include("Unsupported format used. Available formats: plain, json, and sql") + end + end + context "when the format is :json" do it "writes to the logger" do io = setup_whoop @@ -42,6 +55,7 @@ puts logged_message expect(logged_message).to include('"hello": "world"') + expect(logged_message).not_to include("Unsupported format used.") end end @@ -55,6 +69,7 @@ puts logged_message expect(logged_message).to include("SELECT") + expect(logged_message).not_to include("Unsupported format used.") end end @@ -88,6 +103,35 @@ expect(logged_message).not_to include(context) end end + + context "parsing PostgreSQL operators" do + it "appropriately formats jsonb column operators" do + io = setup_whoop + + # Examples from https://www.postgresql.org/docs/9.5/functions-json.html + [ + %('{"a": {"b":"foo"}}'::json->'a'), + %('[1,2,3]'::json->>2), + %('{"a":1,"b":2}'::json->>'b'), + %('{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'), + %('{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'), + %('{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb), + %('{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb), + %('{"a":1, "b":2}'::jsonb ? 'b'), + %('{"a":1, "b":2, "c":3}'::jsonb ?| array['b']), + %('["a", "b"]'::jsonb ?& array['a']), + %('["a", "b"]'::jsonb || '["c"]'::jsonb), + %('{"a": "b"}'::jsonb - 'a'), + %('["a", "b"]'::jsonb - 1), + %('["a", {"b":1}]'::jsonb #- '{1,b}') + ].each do |token| + whoop(token, format: :sql, color: false) + logged_message = io.string + + expect(logged_message.uncolorize).to include(token) + end + end + end end private