Class: PgEventstore::Web::Paginator::EventsCollection

Inherits:
BaseCollection
  • Object
show all
Defined in:
lib/pg_eventstore/web/paginator/events_collection.rb,
sig/pg_eventstore/web/paginator/events_collection.rbs

Constant Summary collapse

SQL_DIRECTIONS =

Returns SQL directions, string-to-symbol mapping.

Returns:

  • (Hash<String => Symbol>)

    SQL directions, string-to-symbol mapping

{
  'asc' => :asc,
  'desc' => :desc,
}.tap do |directions|
  directions.default = :desc
end.freeze
PER_PAGE =

Returns per page limits, string to Integer mapping.

Returns:

  • (Hash<String => Integer>)

    per page limits, string to Integer mapping

%w[10 20 50 100 1000].to_h { [_1, _1.to_i] }.tap do |per_page|
  per_page.default = 10
end.freeze
MAX_NUMBER_TO_COUNT =

Returns max number of events after which we don't perform the exact count and keep the estimate count instead because of the potential performance degradation.

Returns:

  • (Integer)

    max number of events after which we don't perform the exact count and keep the estimate count instead because of the potential performance degradation.

10_000

Constants inherited from BaseCollection

BaseCollection::MIN_QUERY_SIZE_FOR_ADVANCE_SEARCH

Instance Attribute Summary

Attributes inherited from BaseCollection

#config_name, #options, #order, #per_page, #starting_id

Instance Method Summary collapse

Methods inherited from BaseCollection

#connection, #count, #initialize

Constructor Details

This class inherits a constructor from PgEventstore::Web::Paginator::BaseCollection

Instance Method Details

#_collectionArray[Event]

Returns:



71
72
73
74
75
76
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 71

def _collection
  @_collection ||= PgEventstore.client(config_name).read(
    PgEventstore::Stream.all_stream,
    options: options.merge(from_position: starting_id, max_count: per_page + 1, direction: order)
  )
end

#collectionArray<PgEventstore::Event>

Returns:



23
24
25
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 23

def collection
  _collection[0...per_page]
end

#estimate_count(sql_builder) ⇒ Integer

Parameters:

  • sql_builder (PgEventstore::SQLBuilder)

Returns:

  • (Integer)


86
87
88
89
90
91
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 86

def estimate_count(sql_builder)
  sql, params = sql_builder.to_exec_params
  connection.with do |conn|
    conn.exec_params("EXPLAIN #{sql}", params)
  end.to_a.first['QUERY PLAN'].match(/rows=(\d+)/)[1].to_i
end

#event_global_position(event) ⇒ Integer?

Parameters:

Returns:

  • (Integer, nil)


80
81
82
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 80

def event_global_position(event)
  event&.link&.global_position || event&.global_position
end

#global_position(sql_builder) ⇒ Integer?

Parameters:

  • sql_builder (PgEventstore::SQLBuilder)

Returns:

  • (Integer, nil)


105
106
107
108
109
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 105

def global_position(sql_builder)
  connection.with do |conn|
    conn.exec_params(*sql_builder.to_exec_params)
  end.to_a.dig(0, 'global_position')
end

#next_page_starting_idInteger?

Returns:

  • (Integer, nil)


28
29
30
31
32
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 28

def next_page_starting_id
  return unless collection.size == per_page

  event_global_position(_collection[per_page])
end

#prev_page_starting_idInteger?

Returns:

  • (Integer, nil)


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 35

def prev_page_starting_id
  starting_position = event_global_position(_collection.first) || starting_id
  return unless starting_position

  order = self.order == :asc ? :desc : :asc
  starting_position = self.order == :asc ? starting_position - 1 : starting_position + 1

  # Not ideal from the perspective of the amount of records we load here, but this is the only way to read from
  # the db in the correct way. Direct usage of query builders may result in heavy, unoptimized queries.
  options = self.options.merge(
    from_position: starting_position, max_count: per_page, direction: order, resolve_link_tos: false
  )
  PgEventstore.client(config_name).read(
    PgEventstore::Stream.all_stream,
    options:,
    middlewares: []
  ).last&.global_position
end

#regular_count(sql_builder) ⇒ Integer

Parameters:

  • sql_builder (PgEventstore::SQLBuilder)

Returns:

  • (Integer)


95
96
97
98
99
100
101
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 95

def regular_count(sql_builder)
  sql_builder.unselect.select('count(*) as count_all')

  connection.with do |conn|
    conn.exec_params(*sql_builder.to_exec_params)
  end.to_a.first['count_all']
end

#total_countInteger

Returns:

  • (Integer)


55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/pg_eventstore/web/paginator/events_collection.rb', line 55

def total_count
  @total_count ||=
    begin
      filters_collection = QueryBuilders::Filters::Collection.from_options(options)
      sql_builder = QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_estimate_count(
        filters_collection.collection
      )
      count = estimate_count(sql_builder)
      return count if count > MAX_NUMBER_TO_COUNT

      regular_count(sql_builder)
    end
end