require 'shiba/parsers/shiba_string_scanner' require 'shiba/parsers/bad_parse' module Shiba module Parsers class PostgresExplainIndexConditions def initialize(string) @string = string @sc = ShibaStringScanner.new(string) @fields = nil end attr_reader :sc def parse! return if @fields @fields = {} sc.scan(LPAREN) if sc.peek(1) == "(" && !sc.match?(/\(\w+\)::/) while sc.peek(1) == "(" sc.getch extract_field(sc) sc.scan(/\s+AND\s+/) end else extract_field(sc) end end def fields parse! @fields[nil] end def join_fields parse! @fields end private LPAREN = /\(/ RPAREN = /\)/ def parse_string(sc) quote_char = sc.peek(1) v = sc.match_quoted_double_escape(quote_char) sc.scan(/::timestamp without time zone/) sc.scan(/::\w+(\[\])?/) v end def parse_value(sc) peek = sc.peek(1) if peek == "'" parse_string(sc) elsif peek == '"' parse_field(sc) elsif (v = sc.scan(/\d+\.?\d*/)) if v.include?('.') v.to_f else v.to_i end elsif sc.scan(/ANY \(/) # parse as string v = parse_value(sc) sc.scan(/\)/) v else parse_field(sc) end end def parse_ident(sc) peek = sc.peek(1) if peek == "(" sc.getch ident = sc.scan(/[^\(\)]+/) # typed column like (name)::text = 'ben' sc.scan(/\)::\S+/) elsif peek == '"' ident = parse_string(sc) else ident = sc.scan(/[^ \(\.\)\[]+/) # function, lower((name)::text) if sc.scan(/\(/) ident = nil parse_ident(sc) sc.scan(/\)/) end # field[1] for array fields, not bothering to do brace matching here yet, oy vey sc.scan(/\[.*?\]/) end ident end def parse_field(sc) first = nil second = nil first = parse_ident(sc) if sc.scan(/\./) second = parse_ident(sc) table = first field = second else table = nil field = first end @fields[table] ||= [] @fields[table] << field unless @fields[table].include?(field) end def extract_field(sc) # (type = 1) # ((type)::text = 1) # (((type)::text = ANY ('{User,AnonymousUser}'::text[])) AND ((type)::text = 'User'::text)) table = nil parse_field(sc) sc.scan(/\s+/) if !sc.scan(/IS NOT NULL/) sc.scan(/\S+\s+/) # operator parse_value(sc) end if sc.scan(RPAREN).nil? raise BadParse.new(@sc.peek(15), @string) end end end end end