class WCC::Contentful::Store::PostgresStore::Query

Public Instance Methods

count() click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 150
def count
  return @count if @count

  statement, params = finalize_statement('SELECT count(*)')
  result = store.exec_query(statement, params)
  @count = result.getvalue(0, 0).to_i
end
first() click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 158
def first
  return @first if @first

  statement, params = finalize_statement('SELECT t.*', ' LIMIT 1', depth: @options[:include])
  result = store.exec_query(statement, params)
  return if result.num_tuples == 0

  row = result.first
  entry = JSON.parse(row['data'])

  if @options[:include] && @options[:include] > 0
    includes = decode_includes(row['includes'])
    entry = resolve_includes([entry, includes], @options[:include])
  end
  entry
end
result_set() click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 175
def result_set
  return @result_set if @result_set

  statement, params = finalize_statement('SELECT t.*', depth: @options[:include])
  @result_set =
    store.exec_query(statement, params)
      .lazy.map do |row|
      entry = JSON.parse(row['data'])
      includes =
        (decode_includes(row['includes']) if @options[:include] && @options[:include] > 0)

      [entry, includes]
    end
rescue PG::ConnectionBad
  []
end

Private Instance Methods

_eq(path, expected, params) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 243
def _eq(path, expected, params)
  return " AND t.id = $#{push_param(expected, params)}" if path == %w[sys id]

  if path[3] == 'sys'
    # the path can be either an array or a singular json obj, and we have to dig
    # into it to detect whether it contains `{ "sys": { "id" => expected } }`
    expected = { 'sys' => { path[4] => expected } }.to_json
    return ' AND fn_contentful_jsonb_any_to_jsonb_array(t.data->' \
      "#{quote_parameter_path(path.take(3))}) @> " \
      "jsonb_build_array($#{push_param(expected, params)}::jsonb)"
  end

  " AND t.data->#{quote_parameter_path(path)}" \
    " ? $#{push_param(expected, params)}::text"
end
_join(join_path, expectation_path, expected, params, joins) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 268
      def _join(join_path, expectation_path, expected, params, joins)
        # join back to the table using the links column (join_table_alias becomes s0, s1, s2)
        # this is faster because of the index
        join_table_alias = push_join(join_path, joins)

        # then apply the where clauses:
        #  1. that the joined entry has the data at the appropriate path
        #  2. that the entry joining to the other entry actually links at this path and not another
        <<~WHERE_CLAUSE
           AND #{join_table_alias}.data->#{quote_parameter_path(expectation_path)} ? $#{push_param(expected, params)}::text
          AND exists (select 1 from jsonb_array_elements(fn_contentful_jsonb_any_to_jsonb_array(t.data->#{quote_parameter_path(join_path)})) as link where link->'sys'->'id' ? #{join_table_alias}.id)
        WHERE_CLAUSE
      end
decode_includes(includes) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 194
def decode_includes(includes)
  return {} unless includes

  decoder = PG::TextDecoder::Array.new
  decoder.decode(includes)
    .map { |e| JSON.parse(e) }
    .each_with_object({}) do |entry, h|
      h[entry.dig('sys', 'id')] = entry
    end
end
finalize_statement(select_statement, limit_statement = nil, depth: nil) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 205
def finalize_statement(select_statement, limit_statement = nil, depth: nil)
  statement =
    if content_type == 'Asset'
      "WHERE t.data->'sys'->>'type' = $1"
    else
      "WHERE t.data->'sys'->'contentType'->'sys'->>'id' = $1"
    end
  params = [content_type]
  joins = []

  statement =
    conditions.reduce(statement) do |memo, condition|
      raise ArgumentError, "Operator #{condition.op} not supported" unless condition.op == :eq

      if condition.path_tuples.length == 1
        memo + _eq(condition.path, condition.expected, params)
      else
        join_path, expectation_path = condition.path_tuples
        memo + _join(join_path, expectation_path, condition.expected, params, joins)
      end
    end

  table = 'contentful_raw'
  if depth && depth > 0
    table = 'contentful_raw_includes'
    select_statement += ', t.includes'
  end

  statement =
    select_statement +
    " FROM #{table} AS t \n" +
    joins.join("\n") + "\n" +
    statement +
    (limit_statement || '')

  [statement, params]
end
push_join(_path, joins) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 282
def push_join(_path, joins)
  table_alias = "s#{joins.length}"
  joins << "JOIN contentful_raw AS #{table_alias} ON " \
    "#{table_alias}.id=ANY(t.links)"
  table_alias
end
push_param(param, params) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 259
def push_param(param, params)
  params << param
  params.length
end
quote_parameter_path(path) click to toggle source
# File lib/wcc/contentful/store/postgres_store.rb, line 264
def quote_parameter_path(path)
  path.map { |p| "'#{p}'" }.join('->')
end