timescaledb-rails 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/connection_adapters/postgresql_adapter'
4
-
5
3
  module Timescaledb
6
4
  module Rails
7
5
  module ActiveRecord
8
6
  # :nodoc:
9
7
  module SchemaStatements
8
+ # Returns an array of hypertable names defined in the database.
9
+ def hypertables
10
+ query_values('SELECT hypertable_name FROM timescaledb_information.hypertables')
11
+ end
12
+
13
+ # Checks to see if the hypertable exists on the database.
14
+ #
15
+ # hypertable_exists?(:developers)
16
+ #
17
+ def hypertable_exists?(hypertable)
18
+ query_value(
19
+ <<-SQL.squish
20
+ SELECT COUNT(*) FROM timescaledb_information.hypertables WHERE hypertable_name = #{quote(hypertable)}
21
+ SQL
22
+ ).to_i.positive?
23
+ end
24
+
10
25
  # Converts given standard PG table into a hypertable.
11
26
  #
12
27
  # create_hypertable('readings', 'created_at', chunk_time_interval: '7 days')
@@ -22,7 +37,7 @@ module Timescaledb
22
37
  create_table(table_name, id: false, primary_key: primary_key, force: force, **options, &block)
23
38
  end
24
39
 
25
- execute "SELECT create_hypertable('#{table_name}', '#{time_column_name}', #{options_as_sql})"
40
+ execute "SELECT create_hypertable('#{table_name}', '#{time_column_name}', #{options_as_sql});"
26
41
  end
27
42
 
28
43
  # Enables compression and sets compression options.
@@ -36,24 +51,103 @@ module Timescaledb
36
51
  options << "timescaledb.compress_orderby = '#{order_by}'" unless order_by.nil?
37
52
  options << "timescaledb.compress_segmentby = '#{segment_by}'" unless segment_by.nil?
38
53
 
39
- execute "ALTER TABLE #{table_name} SET (#{options.join(', ')})"
54
+ execute "ALTER TABLE #{table_name} SET (#{options.join(', ')});"
40
55
 
41
- execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compress_after}')"
56
+ execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{stringify_interval(compress_after)}');"
42
57
  end
43
58
 
44
- # Disables compression from given table.
59
+ # Removes compression policy and disables compression from given hypertable.
45
60
  #
46
61
  # remove_hypertable_compression('events')
47
62
  #
48
63
  def remove_hypertable_compression(table_name, compress_after = nil, segment_by: nil, order_by: nil) # rubocop:disable Lint/UnusedMethodArgument
49
64
  execute "SELECT remove_compression_policy('#{table_name}');"
65
+ execute "ALTER TABLE #{table_name.inspect} SET (timescaledb.compress = false);"
66
+ end
67
+
68
+ # Add a data retention policy to given hypertable.
69
+ #
70
+ # add_hypertable_retention_policy('events', 7.days)
71
+ #
72
+ def add_hypertable_retention_policy(table_name, drop_after)
73
+ execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{stringify_interval(drop_after)}');"
74
+ end
75
+
76
+ # Removes data retention policy from given hypertable.
77
+ #
78
+ # remove_hypertable_retention_policy('events')
79
+ #
80
+ def remove_hypertable_retention_policy(table_name, _drop_after = nil)
81
+ execute "SELECT remove_retention_policy('#{table_name}');"
82
+ end
83
+
84
+ # Adds a policy to reorder chunks on a given hypertable index in the background.
85
+ #
86
+ # add_hypertable_reorder_policy('events', 'index_events_on_created_at_and_name')
87
+ #
88
+ def add_hypertable_reorder_policy(table_name, index_name)
89
+ execute "SELECT add_reorder_policy('#{table_name}', '#{index_name}');"
90
+ end
91
+
92
+ # Removes a policy to reorder a particular hypertable.
93
+ #
94
+ # remove_hypertable_reorder_policy('events')
95
+ #
96
+ def remove_hypertable_reorder_policy(table_name, _index_name = nil)
97
+ execute "SELECT remove_reorder_policy('#{table_name}');"
98
+ end
99
+
100
+ # Creates a continuous aggregate
101
+ #
102
+ # create_continuous_aggregate(
103
+ # 'temperature_events', "SELECT * FROM events where event_type = 'temperature'"
104
+ # )
105
+ #
106
+ def create_continuous_aggregate(view_name, view_query)
107
+ execute "CREATE MATERIALIZED VIEW #{view_name} WITH (timescaledb.continuous) AS #{view_query};"
108
+ end
109
+
110
+ # Drops a continuous aggregate
111
+ #
112
+ # drop_continuous_aggregate('temperature_events')
113
+ #
114
+ def drop_continuous_aggregate(view_name, _view_query = nil)
115
+ execute "DROP MATERIALIZED VIEW #{view_name};"
116
+ end
117
+
118
+ # Adds refresh continuous aggregate policy
119
+ #
120
+ # add_continuous_aggregate_policy('temperature_events', 1.month, 1.day, 1.hour)
121
+ #
122
+ def add_continuous_aggregate_policy(view_name, start_offset, end_offset, schedule_interval)
123
+ start_offset = start_offset.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(start_offset)}'"
124
+ end_offset = end_offset.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(end_offset)}'"
125
+ schedule_interval = schedule_interval.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(schedule_interval)}'"
126
+
127
+ execute "SELECT add_continuous_aggregate_policy('#{view_name}', start_offset => #{start_offset}, end_offset => #{end_offset}, schedule_interval => #{schedule_interval});" # rubocop:disable Layout/LineLength
128
+ end
129
+
130
+ # Removes refresh continuous aggregate policy
131
+ #
132
+ # remove_continuous_aggregate_policy('temperature_events')
133
+ #
134
+ def remove_continuous_aggregate_policy(view_name, _start_offset = nil,
135
+ _end_offset = nil, _schedule_interval = nil)
136
+ execute "SELECT remove_continuous_aggregate_policy('#{view_name}');"
137
+ end
138
+
139
+ private
140
+
141
+ # @param [ActiveSupport::Duration|String] interval
142
+ def stringify_interval(interval)
143
+ interval.is_a?(ActiveSupport::Duration) ? interval.inspect : interval
50
144
  end
51
145
 
52
146
  # @return [String]
53
147
  def hypertable_options_to_sql(options)
54
148
  sql_statements = options.map do |option, value|
55
149
  case option
56
- when :chunk_time_interval then "chunk_time_interval => INTERVAL '#{value}'"
150
+ when :chunk_time_interval then "chunk_time_interval => INTERVAL '#{stringify_interval(value)}'"
57
151
  when :if_not_exists then "if_not_exists => #{value ? 'TRUE' : 'FALSE'}"
58
152
  end
59
153
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ module Model
6
+ # :nodoc:
7
+ module AggregateFunctions
8
+ def count(alias_name = 'count')
9
+ select("COUNT(1) AS #{alias_name}")
10
+ end
11
+
12
+ def avg(column_name, alias_name = 'avg')
13
+ select("AVG(#{column_name}) AS #{alias_name}")
14
+ end
15
+
16
+ def sum(column_name, alias_name = 'sum')
17
+ select("SUM(#{column_name}) AS #{alias_name}")
18
+ end
19
+
20
+ def min(column_name, alias_name = 'min')
21
+ select("MIN(#{column_name}) AS #{alias_name}")
22
+ end
23
+
24
+ def max(column_name, alias_name = 'max')
25
+ select("MAX(#{column_name}) AS #{alias_name}")
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ module Model
6
+ # :nodoc:
7
+ module FinderMethods
8
+ # Adds a warning message to avoid calling find without filtering by time.
9
+ #
10
+ # @override
11
+ def find(*args)
12
+ warn "WARNING: Calling `.find` without filtering by `#{hypertable_time_column_name}` could cause performance issues, use built-in find_(at_time|between|after) methods for more performant results." # rubocop:disable Layout/LineLength
13
+
14
+ super
15
+ end
16
+
17
+ # Finds records by primary key and chunk time.
18
+ #
19
+ # @param [Array<Integer>, Integer] id The primary key values.
20
+ # @param [Time, Date, Integer] time The chunk time value.
21
+ def find_at_time(id, time)
22
+ at_time(time).find(id)
23
+ end
24
+
25
+ # Finds records by primary key and chunk time occurring between given time range.
26
+ #
27
+ # @param [Array<Integer>, Integer] id The primary key values.
28
+ # @param [Time, Date, Integer] from The chunk from time value.
29
+ # @param [Time, Date, Integer] to The chunk to time value.
30
+ def find_between(id, from, to)
31
+ between(from, to).find(id)
32
+ end
33
+
34
+ # Finds records by primary key and chunk time occurring after given time.
35
+ #
36
+ # @param [Array<Integer>, Integer] id The primary key values.
37
+ # @param [Time, Date, Integer] from The chunk from time value.
38
+ def find_after(id, from)
39
+ after(from).find(id)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timescaledb/rails/model/aggregate_functions'
4
+
5
+ module Timescaledb
6
+ module Rails
7
+ module Model
8
+ # :nodoc:
9
+ module Hyperfunctions
10
+ TIME_BUCKET_ALIAS = 'time_bucket'
11
+
12
+ # @return [ActiveRecord::Relation<ActiveRecord::Base>]
13
+ def time_bucket(interval, target_column = nil)
14
+ target_column ||= hypertable_time_column_name
15
+
16
+ select("time_bucket('#{format_interval_value(interval)}', #{target_column}) as #{TIME_BUCKET_ALIAS}")
17
+ .group(TIME_BUCKET_ALIAS)
18
+ .order(TIME_BUCKET_ALIAS)
19
+ .extending(AggregateFunctions)
20
+ end
21
+
22
+ private
23
+
24
+ def format_interval_value(value)
25
+ value.is_a?(ActiveSupport::Duration) ? value.inspect : value
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ module Model
6
+ # :nodoc:
7
+ module Scopes
8
+ extend ActiveSupport::Concern
9
+
10
+ # rubocop:disable Metrics/BlockLength
11
+ included do
12
+ scope :last_year, lambda {
13
+ date = Date.current - 1.year
14
+
15
+ between(date.beginning_of_year, date.end_of_year)
16
+ }
17
+
18
+ scope :last_month, lambda {
19
+ date = Date.current - 1.month
20
+
21
+ between(date.beginning_of_month, date.end_of_month)
22
+ }
23
+
24
+ scope :last_week, lambda {
25
+ date = Date.current - 1.week
26
+
27
+ between(date.beginning_of_week, date.end_of_week)
28
+ }
29
+
30
+ scope :yesterday, lambda {
31
+ where("DATE(#{hypertable_time_column_name}) = ?", Date.current - 1.day)
32
+ }
33
+
34
+ scope :this_year, lambda {
35
+ between(Date.current.beginning_of_year, Date.current.end_of_year)
36
+ }
37
+
38
+ scope :this_month, lambda {
39
+ between(Date.current.beginning_of_month, Date.current.end_of_month)
40
+ }
41
+
42
+ scope :this_week, lambda {
43
+ between(Date.current.beginning_of_week, Date.current.end_of_week)
44
+ }
45
+
46
+ scope :today, lambda {
47
+ where("DATE(#{hypertable_time_column_name}) = ?", Date.current)
48
+ }
49
+
50
+ scope :after, lambda { |time|
51
+ where("#{hypertable_time_column_name} > ?", time)
52
+ }
53
+
54
+ scope :at_time, lambda { |time|
55
+ where(hypertable_time_column_name => time)
56
+ }
57
+
58
+ scope :between, lambda { |from, to|
59
+ where(hypertable_time_column_name => from..to)
60
+ }
61
+ end
62
+ # rubocop:enable Metrics/BlockLength
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timescaledb/rails/model/finder_methods'
4
+ require 'timescaledb/rails/model/hyperfunctions'
5
+ require 'timescaledb/rails/model/scopes'
6
+
7
+ module Timescaledb
8
+ module Rails
9
+ # :nodoc:
10
+ module Model
11
+ PUBLIC_SCHEMA_NAME = 'public'
12
+
13
+ extend ActiveSupport::Concern
14
+
15
+ include Scopes
16
+
17
+ # :nodoc:
18
+ module ClassMethods
19
+ include FinderMethods
20
+ include Hyperfunctions
21
+
22
+ # @return [String]
23
+ def hypertable_time_column_name
24
+ @hypertable_time_column_name ||= hypertable&.time_column_name
25
+ end
26
+
27
+ # Returns only the name of the hypertable, table_name could include
28
+ # the schema path, we need to remove it.
29
+ #
30
+ # @return [String]
31
+ def hypertable_name
32
+ @hypertable_name ||= table_name.split('.').last
33
+ end
34
+
35
+ # Returns the schema where hypertable is stored.
36
+ #
37
+ # @return [String]
38
+ def hypertable_schema
39
+ @hypertable_schema ||=
40
+ if table_name.split('.').size > 1
41
+ table_name.split('.')[0..-2].join('.')
42
+ else
43
+ PUBLIC_SCHEMA_NAME
44
+ end
45
+ end
46
+
47
+ # @return [Timescaledb::Rails::Hypertable]
48
+ def hypertable
49
+ Timescaledb::Rails::Hypertable.find_by(hypertable_where_options)
50
+ end
51
+
52
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::Chunk>]
53
+ def hypertable_chunks
54
+ Timescaledb::Rails::Chunk.where(hypertable_where_options)
55
+ end
56
+
57
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::Job>]
58
+ def hypertable_jobs
59
+ Timescaledb::Rails::Job.where(hypertable_where_options)
60
+ end
61
+
62
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::Dimension>]
63
+ def hypertable_dimensions
64
+ Timescaledb::Rails::Dimension.where(hypertable_where_options)
65
+ end
66
+
67
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::CompressionSetting>]
68
+ def hypertable_compression_settings
69
+ Timescaledb::Rails::CompressionSetting.where(hypertable_where_options)
70
+ end
71
+
72
+ private
73
+
74
+ # Returns hypertable name and schema.
75
+ #
76
+ # @return [Hash]
77
+ def hypertable_where_options
78
+ { hypertable_name: hypertable_name, hypertable_schema: hypertable_schema }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ # :nodoc:
6
+ class Chunk < ::ActiveRecord::Base
7
+ self.table_name = 'timescaledb_information.chunks'
8
+ self.primary_key = 'hypertable_name'
9
+
10
+ belongs_to :hypertable, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Hypertable'
11
+
12
+ scope :compressed, -> { where(is_compressed: true) }
13
+ scope :decompressed, -> { where(is_compressed: false) }
14
+
15
+ def chunk_full_name
16
+ "#{chunk_schema}.#{chunk_name}"
17
+ end
18
+
19
+ def compress!
20
+ ::ActiveRecord::Base.connection.execute(
21
+ "SELECT compress_chunk('#{chunk_full_name}')"
22
+ )
23
+ end
24
+
25
+ def decompress!
26
+ ::ActiveRecord::Base.connection.execute(
27
+ "SELECT decompress_chunk('#{chunk_full_name}')"
28
+ )
29
+ end
30
+
31
+ # @param index [String] The name of the index to order by
32
+ #
33
+ def reorder!(index = nil)
34
+ if index.blank? && !hypertable.reorder?
35
+ raise ArgumentError, 'Index name is required if reorder policy is not set'
36
+ end
37
+
38
+ index ||= hypertable.reorder_policy_index_name
39
+
40
+ options = ["'#{chunk_full_name}'"]
41
+ options << "'#{index}'" if index.present?
42
+
43
+ ::ActiveRecord::Base.connection.execute(
44
+ "SELECT reorder_chunk(#{options.join(', ')})"
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ module Models
6
+ # :nodoc:
7
+ module Durationable
8
+ extend ActiveSupport::Concern
9
+
10
+ HOUR_MINUTE_SECOND_REGEX = /^\d+:\d+:\d+$/.freeze
11
+
12
+ # @return [String]
13
+ def parse_duration(duration)
14
+ duration_in_seconds = duration_in_seconds(duration)
15
+
16
+ duration_to_interval(
17
+ ActiveSupport::Duration.build(duration_in_seconds)
18
+ )
19
+ rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
20
+ duration
21
+ end
22
+
23
+ private
24
+
25
+ # Converts different interval formats into seconds.
26
+ #
27
+ # duration_in_seconds('P1D') #=> 86400
28
+ # duration_in_seconds('24:00:00') #=> 86400
29
+ # duration_in_seconds(1.day) #=> 86400
30
+ #
31
+ # @param [ActiveSupport::Duration|String] duration
32
+ # @return [Integer]
33
+ def duration_in_seconds(duration)
34
+ return duration.to_i if duration.is_a?(ActiveSupport::Duration)
35
+
36
+ if (duration =~ HOUR_MINUTE_SECOND_REGEX).present?
37
+ hours, minutes, seconds = duration.split(':').map(&:to_i)
38
+
39
+ (hours.hour + minutes.minute + seconds.second).to_i
40
+ else
41
+ ActiveSupport::Duration.parse(duration).to_i
42
+ end
43
+ end
44
+
45
+ # Converts given duration into a human interval readable format.
46
+ #
47
+ # duration_to_interval(1.day) #=> '1 day'
48
+ # duration_to_interval(2.weeks + 6.days) #=> '20 days'
49
+ # duration_to_interval(1.years + 3.months) #=> '1 year 3 months'
50
+ #
51
+ # @param [ActiveSupport::Duration] duration
52
+ # @return [String]
53
+ def duration_to_interval(duration)
54
+ parts = duration.parts
55
+
56
+ # Combine days and weeks if both present
57
+ #
58
+ # "1 week 2 days" => "9 days"
59
+ parts[:days] += parts.delete(:weeks) * 7 if parts.key?(:weeks) && parts.key?(:days)
60
+
61
+ parts.map do |(unit, quantity)|
62
+ "#{quantity} #{humanize_duration_unit(unit.to_s, quantity)}"
63
+ end.join(' ')
64
+ end
65
+
66
+ # Pluralize or singularize given duration unit based on given count.
67
+ #
68
+ # @param [String] duration_unit
69
+ # @param [Integer] count
70
+ def humanize_duration_unit(duration_unit, count)
71
+ count > 1 ? duration_unit.pluralize : duration_unit.singularize
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timescaledb/rails/models/concerns/durationable'
4
+
5
+ module Timescaledb
6
+ module Rails
7
+ # :nodoc:
8
+ class ContinuousAggregate < ::ActiveRecord::Base
9
+ include Timescaledb::Rails::Models::Durationable
10
+
11
+ self.table_name = 'timescaledb_information.continuous_aggregates'
12
+ self.primary_key = 'materialization_hypertable_name'
13
+
14
+ has_many :jobs, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Job'
15
+
16
+ # Manually refresh a continuous aggregate.
17
+ #
18
+ # @param [DateTime] start_time
19
+ # @param [DateTime] end_time
20
+ #
21
+ def refresh!(start_time = 'NULL', end_time = 'NULL')
22
+ ::ActiveRecord::Base.connection.execute(
23
+ "CALL refresh_continuous_aggregate('#{view_name}', #{start_time}, #{end_time});"
24
+ )
25
+ end
26
+
27
+ # @return [String]
28
+ def refresh_start_offset
29
+ parse_duration(refresh_job.config['start_offset'])
30
+ end
31
+
32
+ # @return [String]
33
+ def refresh_end_offset
34
+ parse_duration(refresh_job.config['end_offset'])
35
+ end
36
+
37
+ # @return [String]
38
+ def refresh_schedule_interval
39
+ interval = refresh_job.schedule_interval
40
+
41
+ interval.is_a?(String) ? parse_duration(interval) : interval.inspect
42
+ end
43
+
44
+ # @return [Boolean]
45
+ def refresh?
46
+ refresh_job.present?
47
+ end
48
+
49
+ private
50
+
51
+ # @return [Job]
52
+ def refresh_job
53
+ @refresh_job ||= jobs.policy_refresh_continuous_aggregate.first
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,16 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timescaledb/rails/models/concerns/durationable'
4
+
3
5
  module Timescaledb
4
6
  module Rails
5
7
  # :nodoc:
6
8
  class Hypertable < ::ActiveRecord::Base
9
+ include Timescaledb::Rails::Models::Durationable
10
+
7
11
  self.table_name = 'timescaledb_information.hypertables'
8
12
  self.primary_key = 'hypertable_name'
9
13
 
14
+ has_many :continuous_aggregates, foreign_key: 'hypertable_name',
15
+ class_name: 'Timescaledb::Rails::ContinuousAggregate'
10
16
  has_many :compression_settings, foreign_key: 'hypertable_name',
11
17
  class_name: 'Timescaledb::Rails::CompressionSetting'
12
18
  has_many :dimensions, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Dimension'
13
19
  has_many :jobs, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Job'
20
+ has_many :chunks, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Chunk'
14
21
 
15
22
  # @return [String]
16
23
  def time_column_name
@@ -24,11 +31,29 @@ module Timescaledb
24
31
  interval.is_a?(String) ? interval : interval.inspect
25
32
  end
26
33
 
34
+ # @return [ActiveRecord::Relation<CompressionSetting>]
35
+ def compression_segment_settings
36
+ compression_settings.segmentby_setting
37
+ end
38
+
39
+ # @return [ActiveRecord::Relation<CompressionSetting>]
40
+ def compression_order_settings
41
+ compression_settings.orderby_setting.where.not(attname: time_column_name)
42
+ end
43
+
27
44
  # @return [String]
28
45
  def compression_policy_interval
29
- ActiveSupport::Duration.parse(compression_job.config['compress_after']).inspect
30
- rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
31
- compression_job.config['compress_after']
46
+ parse_duration(compression_job.config['compress_after'])
47
+ end
48
+
49
+ # @return [String]
50
+ def reorder_policy_index_name
51
+ reorder_job.config['index_name']
52
+ end
53
+
54
+ # @return [String]
55
+ def retention_policy_interval
56
+ parse_duration(retention_job.config['drop_after'])
32
57
  end
33
58
 
34
59
  # @return [Boolean]
@@ -36,8 +61,28 @@ module Timescaledb
36
61
  compression_job.present?
37
62
  end
38
63
 
64
+ # @return [Boolean]
65
+ def reorder?
66
+ reorder_job.present?
67
+ end
68
+
69
+ # @return [Boolean]
70
+ def retention?
71
+ retention_job.present?
72
+ end
73
+
39
74
  private
40
75
 
76
+ # @return [Job]
77
+ def reorder_job
78
+ @reorder_job ||= jobs.policy_reorder.first
79
+ end
80
+
81
+ # @return [Job]
82
+ def retention_job
83
+ @retention_job ||= jobs.policy_retention.first
84
+ end
85
+
41
86
  # @return [Job]
42
87
  def compression_job
43
88
  @compression_job ||= jobs.policy_compression.first
@@ -8,8 +8,14 @@ module Timescaledb
8
8
  self.primary_key = 'hypertable_name'
9
9
 
10
10
  POLICY_COMPRESSION = 'policy_compression'
11
+ POLICY_REORDER = 'policy_reorder'
12
+ POLICY_RETENTION = 'policy_retention'
13
+ POLICY_REFRESH_CONTINUOUS_AGGREGATE = 'policy_refresh_continuous_aggregate'
11
14
 
12
15
  scope :policy_compression, -> { where(proc_name: POLICY_COMPRESSION) }
16
+ scope :policy_reorder, -> { where(proc_name: POLICY_REORDER) }
17
+ scope :policy_retention, -> { where(proc_name: POLICY_RETENTION) }
18
+ scope :policy_refresh_continuous_aggregate, -> { where(proc_name: POLICY_REFRESH_CONTINUOUS_AGGREGATE) }
13
19
  end
14
20
  end
15
21
  end