module Squint

Squint json, jsonb, hstore queries

Constants

HASH_DATA_COLUMNS
VERSION

Public Class Methods

squint_hash_field_reln(*args) click to toggle source

squint_hash_field_reln return an Arel object with the appropriate query Strings want to be a SQL Literal, other things can be passed in bare to the eq or in operator

# File lib/squint.rb, line 91
def self.squint_hash_field_reln(*args)
  temp_attr = args[0]
  contains_nil = false
  column_type = self::HASH_DATA_COLUMNS[args[0].keys.first]
  column_name_segments = []
  quote_char = '"'.freeze
  while  temp_attr.is_a?(Hash)
    attribute_sym = temp_attr.keys.first.to_sym
    column_name_segments << (quote_char + temp_attr.keys.first.to_s + quote_char)
    quote_char = '\''.freeze
    temp_attr = temp_attr[temp_attr.keys.first]
  end

  check_attr_missing = squint_storext_default?(temp_attr, attribute_sym)

  # Check for nil in array
  if temp_attr.is_a? Array
    contains_nil = temp_attr.include?(nil)
    # remove the nil from the array - we'll handle that later
    temp_attr.compact!
    # if the Array is now just 1 element, it doesn't need to be
    # an Array any longer
    temp_attr = temp_attr[0] if temp_attr.size == 1
  end

  if temp_attr.is_a? Array
    temp_attr = temp_attr.map(&:to_s)
  elsif ![FalseClass, TrueClass, NilClass].include?(temp_attr.class)
    temp_attr = temp_attr.to_s
  end

  query_value = if [Array, NilClass].include?(temp_attr.class)
                  temp_attr
                else  # strings or string-like things
                  Arel::Nodes::Quoted.new(temp_attr.to_s)
                end
  # column_name_segments[0] = column_name_segments[0]
  attribute_selector = column_name_segments.join('->'.freeze)

  # JSON(B) data needs to have the last accessor be ->> instead of
  # -> .   The ->> returns the data as text instead of jsonb.
  # hstore columns generally don't have nested keys / hashes
  # Possibly need to raise an error if the hash for an hstore
  # column references nested arrays?
  unless column_type == 'hstore'.freeze
    attribute_selector[attribute_selector.rindex('>'.freeze)] = '>>'.freeze
  end

  reln = if query_value.is_a?(Array)
           arel_table[Arel::Nodes::SqlLiteral.new(attribute_selector)].in(query_value)
         else
           arel_table[Arel::Nodes::SqlLiteral.new(attribute_selector)].eq(query_value)
         end

  # If a nil is present in an Array, need add a specific IS NULL comparison
  if contains_nil
    reln = Arel::Nodes::Grouping.new(
      reln.or(arel_table[Arel::Nodes::SqlLiteral.new(attribute_selector)].eq(nil))
    )
  end

  # check_attr_missing for StoreXT attributes where the default is
  # specified as a query value
  if check_attr_missing
    reln = if column_type == 'hstore'.freeze
             squint_hstore_element_missing(column_name_segments, reln)
           else
             squint_jsonb_element_missing(column_name_segments, reln)
           end
  end
  reln
end
squint_hstore_element_exists(element, attribute_hash_column, value) click to toggle source
# File lib/squint.rb, line 176
def self.squint_hstore_element_exists(element, attribute_hash_column, value)
  Arel::Nodes::Equality.new(
    Arel::Nodes::NamedFunction.new(
      "exist",
      [arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
       Arel::Nodes::SqlLiteral.new(element)]
    ), value
  )
end
squint_hstore_element_missing(column_name_segments, reln) click to toggle source
# File lib/squint.rb, line 186
def self.squint_hstore_element_missing(column_name_segments, reln)
  element = column_name_segments.pop
  attribute_hash_column = column_name_segments.join('->'.freeze)
  # Query generated is equals default or attribute present is null or equals false
  #    * Is null happens the the column is null
  #    * equals false is when the column has jsonb data, but the key doesn't exist
  # ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
  #   (exists("posts"."storext_attributes", 'is_awesome') IS NULL OR
  #    exists("posts"."storext_attributes", 'is_awesome') = FALSE)
  # )
  Arel::Nodes::Grouping.new(
    reln.or(
      Arel::Nodes::Grouping.new(
        squint_hstore_element_exists(element, attribute_hash_column, Arel::Nodes::False.new)
      ).or(
        squint_hstore_element_exists(element, attribute_hash_column, nil)
      )
    )
  )
end
squint_jsonb_element_equality(element, attribute_hash_column, value) click to toggle source
# File lib/squint.rb, line 207
def self.squint_jsonb_element_equality(element, attribute_hash_column, value)
  Arel::Nodes::Equality.new(
    Arel::Nodes::Grouping.new(
      Arel::Nodes::InfixOperation.new(
        Arel::Nodes::SqlLiteral.new('?'),
        arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
        Arel::Nodes::SqlLiteral.new(element)
      )
    ), value
  )
end
squint_jsonb_element_missing(column_name_segments, reln) click to toggle source
# File lib/squint.rb, line 219
def self.squint_jsonb_element_missing(column_name_segments, reln)
  element = column_name_segments.pop
  attribute_hash_column = column_name_segments.join('->'.freeze)
  # Query generated is equals default or attribute present is null or equals false
  #    * Is null happens when the the whole column is null
  #    * equals false is when the column has jsonb data, but the key doesn't exist
  # ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
  #   (("posts"."storext_attributes" ? 'is_awesome') IS NULL OR
  #    ("posts"."storext_attributes" ? 'is_awesome') = FALSE)
  # )
  Arel::Nodes::Grouping.new(
    reln.or(
      Arel::Nodes::Grouping.new(
        squint_jsonb_element_equality(element, attribute_hash_column, nil).or(
          squint_jsonb_element_equality(element, attribute_hash_column, Arel::Nodes::False.new)
        )
      )
    )
  )
end
squint_storext_default?(temp_attr, attribute_sym) click to toggle source
# File lib/squint.rb, line 164
def self.squint_storext_default?(temp_attr, attribute_sym)
  return false unless respond_to?(:storext_definitions)
  if storext_definitions.keys.include?(attribute_sym) &&
     !(storext_definitions[attribute_sym][:opts] &&
       storext_definitions[attribute_sym][:opts][:default]).nil? &&
     [temp_attr].compact.map(&:to_s).
       flatten.
       include?(storext_definitions[attribute_sym][:opts][:default].to_s)
    true
  end
end