module Relationizer::BigQuery

Constants

DEFAULT_TYPES
KNOWN_TYPES

Public Instance Methods

create_relation_literal(schema, tuples) click to toggle source
# File lib/relationizer/big_query.rb, line 28
def create_relation_literal(schema, tuples)
  types = fixed_types(schema.values, tuples)

  _types_exp = types_exp(schema.keys, types)

  tuples_exp = tuples.map { |tuple|
    tuple.zip(types).
      map { |(col, type)| to_literal(col, type) }.
      join(", ").
      tap { |t| break "(#{t}#{', NULL' if tuple.length == 1})" }
  }.join(", ").tap { |t| break "[#{t}]"}

  select_exp = if schema.one?
                  "#{schema.keys.first}"
                else
                  '*'
                end

  "SELECT #{select_exp} FROM UNNEST(#{_types_exp}#{tuples_exp})"
end

Private Instance Methods

array_type(array) click to toggle source
# File lib/relationizer/big_query.rb, line 51
def array_type(array)
  classes = array.compact.map(&:class).uniq

  unless classes.length == 1
    raise ReasonlessTypeError.new("Ambiguous type of element in array: #{classes}")
  end

  DEFAULT_TYPES[array.first] || :STRING
end
empty_candidate_check(types) click to toggle source
# File lib/relationizer/big_query.rb, line 74
def empty_candidate_check(types)
  raise ReasonlessTypeError.new("Candidate nothing") if types.empty?
end
fixed_types(schema, tuples) click to toggle source
# File lib/relationizer/big_query.rb, line 78
def fixed_types(schema, tuples)
  if tuples.empty?
    raise TypeNotFoundError unless schema.all?
    return schema
  end

  tuples.transpose.zip(schema.to_a).map { |values, type|
    next type if type

    if values.map { |o| o.is_a?(Array) }.all?
      types = values.
                map(&method(:array_type)).uniq.
                tap(&method(:empty_candidate_check)).
                tap(&method(:many_candidate_check))

      next "ARRAY<#{types.first}>".to_sym
    end

    values.
      map(&DEFAULT_TYPES).compact.uniq.
      tap(&method(:empty_candidate_check)).
      tap(&method(:many_candidate_check)).
      first || :STRING
  }
end
many_candidate_check(types) click to toggle source
# File lib/relationizer/big_query.rb, line 70
def many_candidate_check(types)
  raise ReasonlessTypeError.new("Many candidate: #{types.join(', ')}") unless types.one?
end
to_literal(obj, type) click to toggle source
# File lib/relationizer/big_query.rb, line 104
def to_literal(obj, type)
  return "NULL" if obj.nil?

  case type
  when :ARRAY
    t = array_type(obj)
    obj.map { |e| to_literal(e, t) }.join(', ').tap { |s| break "[#{s}]"}
  when /^ARRAY\<.+\>$/
    t = /^ARRAY\<(.+)\>$/.match(type).to_a&.dig(1).to_sym
    raise "Unknown type: #{t}" unless KNOWN_TYPES.include?(t)
    obj.map { |e| to_literal(e, t) }.join(', ').tap { |s| break "[#{s}]"}
  when :TIMESTAMP
    %Q{'#{obj.strftime('%Y-%m-%d %H:%M:%S')}'}
  when :STRING, :DATE
    obj.to_s.gsub(/'/, "\'").tap do |s|
      break "'#{s}'"
    end
  when :BOOL
    case obj
    when TrueClass, FalseClass
      obj
    else
      !!obj
    end
  when :FLOAT64
    case obj
    when Float::INFINITY
      "CAST('inf' AS FLOAT64)"
    when -Float::INFINITY
      "CAST('-inf' AS FLOAT64)"
    else
      obj
    end
  when :INT64
    obj.to_s
  else
    raise "Unknown type: #{type}"
  end
end
types_exp(names, types) click to toggle source
# File lib/relationizer/big_query.rb, line 61
def types_exp(names, types)
  case names.length
  when 1
    %Q{ARRAY<STRUCT<`#{names.first}` #{types.first}, `___dummy` STRING>>}
  else
    %Q{ARRAY<STRUCT<#{names.zip(types).map { |(name, type)| "`#{name}` #{type}" }.join(", ")}>>}
  end
end