timescaledb-rails 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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