timescaledb-rails 0.1.4 → 0.1.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12170e03389f6ac4f3bba7816dfb20dc75a47103032ec3e3799f3c78dca25a4b
4
- data.tar.gz: f26796af01f755648e952f9e851f24d05d7adb3439e3653d793beef624ea8044
3
+ metadata.gz: 8e350967c0553e59b57a773226c1b532f9e1e2a93a03b1908c7228816c942d39
4
+ data.tar.gz: 6636c247f01919bfd344f8ff4afb7a74eaff050f4ec3246b64f24904c3046752
5
5
  SHA512:
6
- metadata.gz: 22c4c72512895fadb2a7701222e6b890b60a15948dc9c6b5c01ded24afff9865d6ebc954673452b55b95ac68b18c933dc2bf7aec6ac135285ecfa950d346e8e3
7
- data.tar.gz: f67c442f8d2eb837566f8cc3a450d809a769343799997f3979a222bd0413c2ce89fad70b9b8f3e96267ab61e8bcf3cc2c167c15a60db1f1144ea17daf9cfee36
6
+ metadata.gz: a96ee3099632fa74997735f6bbef27971be5f65d0e54e2978c90d4df7ae0ea64a7478acd0ffec787d05bcb0c7126a0ee51937fa4d59b03fc1fbb5c80bb20189c
7
+ data.tar.gz: 6bdd3e8bfe645fea36dc66461d6c4773d560d86d9af79c0bbb5a7a6d1d11eaf424898b6f3e07591b03828a2b2a521581b6a47783bb60e8dfa3b2b2a8e3ff8721
data/README.md CHANGED
@@ -51,16 +51,20 @@ class CreatePayloadHypertable < ActiveRecord::Migration[7.0]
51
51
  end
52
52
  ```
53
53
 
54
- Add hypertable compression
54
+ Add hypertable compression policy
55
55
 
56
56
  ```ruby
57
- class AddEventCompression < ActiveRecord::Migration[7.0]
57
+ class AddEventCompressionPolicy < ActiveRecord::Migration[7.0]
58
58
  def up
59
- add_hypertable_compression :events, 20.days, segment_by: :name, order_by: 'occurred_at DESC'
59
+ enable_hypertable_compression :events, segment_by: :name, order_by: 'occurred_at DESC'
60
+
61
+ add_hypertable_compression_policy :events, 20.days
60
62
  end
61
63
 
62
64
  def down
63
- remove_hypertable_compression :events
65
+ remove_hypertable_compression_policy :events
66
+
67
+ disable_hypertable_compression :events
64
68
  end
65
69
  end
66
70
  ```
@@ -93,26 +97,69 @@ class AddEventReorderPolicy < ActiveRecord::Migration[7.0]
93
97
  end
94
98
  ```
95
99
 
100
+ Create continuous aggregate
101
+
102
+ ```ruby
103
+ class CreateTemperatureEventAggregate < ActiveRecord::Migration[7.0]
104
+ disable_ddl_transaction!
105
+
106
+ def up
107
+ create_continuous_aggregate(
108
+ :temperature_events,
109
+ Event.time_bucket(1.day).avg(:value).temperature.to_sql
110
+ )
111
+
112
+ add_continuous_aggregate_policy(:temperature_events, 1.month, 1.day, 1.hour)
113
+ end
114
+
115
+ def down
116
+ drop_continuous_aggregate(:temperature_events)
117
+
118
+ remove_continuous_aggregate_policy(:temperature_events)
119
+ end
120
+ end
121
+ ```
122
+
123
+ > **Reversible Migrations:**
124
+ >
125
+ > Above examples implement `up`/`down` methods to better document all the different APIs. Feel free to use `change` method, timescaledb-rails defines all the reverse calls for each API method so Active Record can automatically figure out how to reverse your migration.
126
+
96
127
  ### Models
97
128
 
98
129
  If one of your models need TimescaleDB support, just include `Timescaledb::Rails::Model`
130
+
99
131
  ```ruby
100
- class Event < ActiveRecord::Base
132
+ class Payload < ActiveRecord::Base
101
133
  include Timescaledb::Rails::Model
134
+
135
+ self.primary_key = 'id'
102
136
  end
103
137
  ```
104
138
 
105
- If the hypertable does not belong to the default schema, don't forget to override `table_name`
139
+ When hypertable belongs to a non default schema, don't forget to override `table_name`
106
140
 
107
141
  ```ruby
108
142
  class Event < ActiveRecord::Base
109
143
  include Timescaledb::Rails::Model
110
144
 
111
- self.table_name = 'v1.events'
145
+ self.table_name = 'tdb.events'
112
146
  end
113
147
  ```
114
148
 
115
- If you need to query data for a specific time period, `Timescaledb::Rails::Model` incluldes useful scopes
149
+ Using `.find` is not recommended, to achieve more performat results, use these other find methods
150
+
151
+ ```ruby
152
+ # When you know the exact time value
153
+ Payload.find_at_time(111, Time.new(2022, 01, 01, 10, 15, 30))
154
+
155
+ # If you know that the record occurred after a given time
156
+ Payload.find_after(222, 11.days.ago)
157
+
158
+ # Lastly, if you want to scope the search by a time range
159
+ Payload.find_between(333, 1.week.ago, 1.day.ago)
160
+ ```
161
+
162
+ If you need to query data for a specific time period, `Timescaledb::Rails::Model` includes useful scopes
116
163
 
117
164
  ```ruby
118
165
  # If you want to get all records from last year
@@ -136,6 +183,19 @@ Here the list of all available scopes
136
183
  * yesterday
137
184
  * today
138
185
 
186
+ If you still need to query data by other time periods, take a look at these other scopes
187
+
188
+ ```ruby
189
+ # If you want to get all records that occurred in the last 30 minutes
190
+ Event.after(30.minutes.ago) #=> [#<Event name...>, ...]
191
+
192
+ # If you want to get records that occurred in the last 4 days, excluding today
193
+ Event.between(4.days.ago, 1.day.ago) #=> [#<Event name...>, ...]
194
+
195
+ # If you want to get records that occurred at a specific time
196
+ Event.at_time(Time.new(2023, 01, 04, 10, 20, 30)) #=> [#<Event name...>, ...]
197
+ ```
198
+
139
199
  If you need information about your hypertable, use the following helper methods to get useful information
140
200
 
141
201
  ```ruby
@@ -165,6 +225,53 @@ chunk.compress! unless chunk.is_compressed?
165
225
  chunk.decompress! if chunk.is_compressed?
166
226
  ```
167
227
 
228
+ If you need to reorder a specific chunk
229
+
230
+ ```ruby
231
+ chunk = Event.hypertable_chunks.first
232
+
233
+ # If an index is not specified, it will use the one from the reorder policy
234
+ # In case there is no reorder policy index it will raise an ArgumentError
235
+ chunk.reorder!
236
+
237
+ # If an index is specified it will use that index
238
+ chunk.reorder!(index)
239
+ ```
240
+
241
+ If you need to manually refresh a continuous aggregate
242
+
243
+ ```ruby
244
+ aggregate = Event.hypertable.continuous_aggregates.first
245
+
246
+ aggregate.refresh!(5.days.ago, 1.day.ago)
247
+ ```
248
+
249
+ ### Hyperfunctions
250
+
251
+ #### Time bucket
252
+
253
+ You can call the time bucket function with an interval (note that leaving the target column blank will use the default time column of the hypertable)
254
+
255
+ ```ruby
256
+ Event.time_bucket(1.day)
257
+
258
+ Event.time_bucket('1 day')
259
+
260
+ Event.time_bucket(1.day, :created_at)
261
+
262
+ Event.time_bucket(1.day, 'occurred_at')
263
+ ```
264
+
265
+ You may add aggregation like so:
266
+
267
+ ```ruby
268
+ Event.time_bucket(1.day).avg(:column)
269
+ Event.time_bucket(1.day).sum(:column)
270
+ Event.time_bucket(1.day).min(:column)
271
+ Event.time_bucket(1.day).max(:column)
272
+ Event.time_bucket(1.day).count
273
+ ```
274
+
168
275
  ## Contributing
169
276
 
170
277
  Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests.
@@ -5,32 +5,27 @@ module Timescaledb
5
5
  module ActiveRecord
6
6
  # :nodoc:
7
7
  module CommandRecorder
8
- def create_hypertable(*args, &block)
9
- record(:create_hypertable, args, &block)
10
- end
11
-
12
- def add_hypertable_compression(*args, &block)
13
- record(:add_hypertable_compression, args, &block)
14
- end
15
-
16
- def remove_hypertable_compression(*args, &block)
17
- record(:remove_hypertable_compression, args, &block)
18
- end
19
-
20
- def add_hypertable_reorder_policy(*args, &block)
21
- record(:add_hypertable_reorder_policy, args, &block)
22
- end
23
-
24
- def remove_hypertable_reorder_policy(*args, &block)
25
- record(:remove_hypertable_reorder_policy, args, &block)
26
- end
27
-
28
- def add_hypertable_retention_policy(*args, &block)
29
- record(:add_hypertable_retention_policy, args, &block)
30
- end
31
-
32
- def remove_hypertable_retention_policy(*args, &block)
33
- record(:remove_hypertable_retention_policy, args, &block)
8
+ %w[
9
+ create_hypertable
10
+ enable_hypertable_compression
11
+ disable_hypertable_compression
12
+ add_hypertable_compression_policy
13
+ remove_hypertable_compression_policy
14
+ add_hypertable_reorder_policy
15
+ remove_hypertable_reorder_policy
16
+ add_hypertable_retention_policy
17
+ remove_hypertable_retention_policy
18
+ create_continuous_aggregate
19
+ drop_continuous_aggregate
20
+ add_continuous_aggregate_policy
21
+ remove_continuous_aggregate_policy
22
+ ].each do |method|
23
+ module_eval <<-METHOD, __FILE__, __LINE__ + 1
24
+ def #{method}(*args, &block) # def create_table(*args, &block)
25
+ record(:"#{method}", args, &block) # record(:create_table, args, &block)
26
+ end # end
27
+ METHOD
28
+ ruby2_keywords(method) if respond_to?(:ruby2_keywords)
34
29
  end
35
30
 
36
31
  def invert_create_hypertable(args, &block)
@@ -41,16 +36,24 @@ module Timescaledb
41
36
  [:drop_table, args.first, block]
42
37
  end
43
38
 
44
- def invert_add_hypertable_compression(args, &block)
45
- [:remove_hypertable_compression, args, block]
39
+ def invert_enable_hypertable_compression(args, &block)
40
+ [:disable_hypertable_compression, args, block]
46
41
  end
47
42
 
48
- def invert_remove_hypertable_compression(args, &block)
43
+ def invert_disable_hypertable_compression(args, &block)
44
+ [:enable_hypertable_compression, args, block]
45
+ end
46
+
47
+ def invert_add_hypertable_compression_policy(args, &block)
48
+ [:remove_hypertable_compression_policy, args, block]
49
+ end
50
+
51
+ def invert_remove_hypertable_compression_policy(args, &block)
49
52
  if args.size < 2
50
- raise ::ActiveRecord::IrreversibleMigration, 'remove_hypertable_compression is only reversible if given table name and compress period.' # rubocop:disable Layout/LineLength
53
+ raise ::ActiveRecord::IrreversibleMigration, 'remove_hypertable_compression_policy is only reversible if given table name and compress period.' # rubocop:disable Layout/LineLength
51
54
  end
52
55
 
53
- [:add_hypertable_compression, args, block]
56
+ [:add_hypertable_compression_policy, args, block]
54
57
  end
55
58
 
56
59
  def invert_add_hypertable_retention_policy(args, &block)
@@ -76,6 +79,30 @@ module Timescaledb
76
79
 
77
80
  [:add_hypertable_reorder_policy, args, block]
78
81
  end
82
+
83
+ def invert_create_continuous_aggregate(args, &block)
84
+ [:drop_continuous_aggregate, args, block]
85
+ end
86
+
87
+ def invert_drop_continuous_aggregate(args, &block)
88
+ if args.size < 2
89
+ raise ::ActiveRecord::IrreversibleMigration, 'drop_continuous_aggregate is only reversible if given view name and view query.' # rubocop:disable Layout/LineLength
90
+ end
91
+
92
+ [:create_continuous_aggregate, args, block]
93
+ end
94
+
95
+ def invert_add_continuous_aggregate_policy(args, &block)
96
+ [:remove_continuous_aggregate_policy, args, block]
97
+ end
98
+
99
+ def invert_remove_continuous_aggregate_policy(args, &block)
100
+ if args.size < 4
101
+ raise ::ActiveRecord::IrreversibleMigration, 'remove_continuous_aggregate_policy is only reversible if given view name, start offset, end offset and schedule interval.' # rubocop:disable Layout/LineLength
102
+ end
103
+
104
+ [:add_continuous_aggregate_policy, args, block]
105
+ end
79
106
  end
80
107
  end
81
108
  end
@@ -7,59 +7,95 @@ module Timescaledb
7
7
  module Rails
8
8
  module ActiveRecord
9
9
  # :nodoc:
10
+ # rubocop:disable Layout/LineLength
10
11
  module PostgreSQLDatabaseTasks
11
12
  # @override
12
- def structure_dump(filename, extra_flags) # rubocop:disable Metrics/MethodLength
13
+ def structure_dump(filename, extra_flags)
13
14
  extra_flags = Array(extra_flags)
14
- extra_flags << timescale_structure_dump_default_flags if timescale_enabled?
15
+ extra_flags |= timescale_structure_dump_default_flags if timescale_enabled?
15
16
 
16
17
  super(filename, extra_flags)
17
18
 
18
19
  return unless timescale_enabled?
19
20
 
21
+ hypertables(filename)
22
+ continuous_aggregates(filename)
23
+ end
24
+
25
+ def hypertables(filename)
20
26
  File.open(filename, 'a') do |file|
21
27
  Timescaledb::Rails::Hypertable.all.each do |hypertable|
22
28
  drop_ts_insert_trigger_statment(hypertable, file)
23
29
  create_hypertable_statement(hypertable, file)
24
- add_hypertable_compression_statement(hypertable, file)
30
+ enable_hypertable_compression_statement(hypertable, file)
31
+ add_hypertable_compression_policy_statement(hypertable, file)
25
32
  add_hypertable_reorder_policy_statement(hypertable, file)
26
33
  add_hypertable_retention_policy_statement(hypertable, file)
27
34
  end
28
35
  end
29
36
  end
30
37
 
38
+ def continuous_aggregates(filename)
39
+ File.open(filename, 'a') do |file|
40
+ Timescaledb::Rails::ContinuousAggregate.dependency_ordered.each do |continuous_aggregate|
41
+ create_continuous_aggregate_statement(continuous_aggregate, file)
42
+ add_continuous_aggregate_policy_statement(continuous_aggregate, file)
43
+ end
44
+ end
45
+ end
46
+
31
47
  def drop_ts_insert_trigger_statment(hypertable, file)
32
48
  file << "---\n"
33
- file << "--- Drop ts_insert_blocker previously created by pg_dump to avoid pg errors, create_hypertable will re-create it again.\n" # rubocop:disable Layout/LineLength
49
+ file << "--- Drop ts_insert_blocker previously created by pg_dump to avoid pg errors, create_hypertable will re-create it again.\n"
34
50
  file << "---\n\n"
35
- file << "DROP TRIGGER IF EXISTS ts_insert_blocker ON #{hypertable.hypertable_name};\n"
51
+ file << "DROP TRIGGER IF EXISTS ts_insert_blocker ON #{hypertable.hypertable_schema}.#{hypertable.hypertable_name};\n"
36
52
  end
37
53
 
38
54
  def create_hypertable_statement(hypertable, file)
39
55
  options = hypertable_options(hypertable)
40
56
 
41
- file << "SELECT create_hypertable('#{hypertable.hypertable_name}', '#{hypertable.time_column_name}', #{options});\n\n" # rubocop:disable Layout/LineLength
57
+ file << "SELECT create_hypertable('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', '#{hypertable.time_column_name}', #{options});\n\n"
42
58
  end
43
59
 
44
- def add_hypertable_compression_statement(hypertable, file)
60
+ def enable_hypertable_compression_statement(hypertable, file)
45
61
  return unless hypertable.compression?
46
62
 
47
63
  options = hypertable_compression_options(hypertable)
48
64
 
49
- file << "ALTER TABLE #{hypertable.hypertable_name} SET (#{options});\n\n"
50
- file << "SELECT add_compression_policy('#{hypertable.hypertable_name}', INTERVAL '#{hypertable.compression_policy_interval}');\n\n" # rubocop:disable Layout/LineLength
65
+ file << "ALTER TABLE #{hypertable.hypertable_schema}.#{hypertable.hypertable_name} SET (#{options});\n\n"
66
+ end
67
+
68
+ def add_hypertable_compression_policy_statement(hypertable, file)
69
+ return unless hypertable.compression_policy?
70
+
71
+ file << "SELECT add_compression_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', INTERVAL '#{hypertable.compression_policy_interval}');\n\n"
51
72
  end
52
73
 
53
74
  def add_hypertable_reorder_policy_statement(hypertable, file)
54
75
  return unless hypertable.reorder?
55
76
 
56
- file << "SELECT add_reorder_policy('#{hypertable.hypertable_name}', '#{hypertable.reorder_policy_index_name}');\n\n" # rubocop:disable Layout/LineLength
77
+ file << "SELECT add_reorder_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', '#{hypertable.reorder_policy_index_name}');\n\n"
57
78
  end
58
79
 
59
80
  def add_hypertable_retention_policy_statement(hypertable, file)
60
81
  return unless hypertable.retention?
61
82
 
62
- file << "SELECT add_retention_policy('#{hypertable.hypertable_name}', INTERVAL '#{hypertable.retention_policy_interval}');\n\n" # rubocop:disable Layout/LineLength
83
+ file << "SELECT add_retention_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', INTERVAL '#{hypertable.retention_policy_interval}');\n\n"
84
+ end
85
+
86
+ def create_continuous_aggregate_statement(continuous_aggregate, file)
87
+ file << "CREATE MATERIALIZED VIEW #{continuous_aggregate.view_schema}.#{continuous_aggregate.view_name} WITH (timescaledb.continuous) AS\n"
88
+ file << "#{continuous_aggregate.view_definition.strip.indent(2)}\n\n"
89
+ end
90
+
91
+ def add_continuous_aggregate_policy_statement(continuous_aggregate, file)
92
+ return unless continuous_aggregate.refresh?
93
+
94
+ start_offset = continuous_aggregate.refresh_start_offset
95
+ end_offset = continuous_aggregate.refresh_end_offset
96
+ schedule_interval = continuous_aggregate.refresh_schedule_interval
97
+
98
+ file << "SELECT add_continuous_aggregate_policy('#{continuous_aggregate.view_schema}.#{continuous_aggregate.view_name}', start_offset => INTERVAL '#{start_offset}', end_offset => INTERVAL '#{end_offset}', schedule_interval => INTERVAL '#{schedule_interval}');\n\n"
63
99
  end
64
100
 
65
101
  def hypertable_options(hypertable)
@@ -93,18 +129,26 @@ module Timescaledb
93
129
  hypertable.compression_segment_settings.map(&:attname)
94
130
  end
95
131
 
96
- # Returns `pg_dump` flag to exclude `_timescaledb_internal` schema tables.
132
+ # Returns `pg_dump` flags to exclude `_timescaledb_internal` schema tables and
133
+ # exclude the corresponding continuous aggregate views.
97
134
  #
98
- # @return [String]
135
+ # @return [Array<String>]
99
136
  def timescale_structure_dump_default_flags
100
- '--exclude-schema=_timescaledb_internal'
137
+ flags = ['--exclude-schema=_timescaledb_internal']
138
+
139
+ Timescaledb::Rails::ContinuousAggregate.pluck(:view_schema, :view_name).each do |view_schema, view_name|
140
+ flags << "--exclude-table=#{view_schema}.#{view_name}"
141
+ end
142
+
143
+ flags
101
144
  end
102
145
 
103
146
  # @return [Boolean]
104
147
  def timescale_enabled?
105
- Timescaledb::Rails::Hypertable.table_exists?
148
+ ApplicationRecord.timescale_connection?(connection) && Hypertable.table_exists?
106
149
  end
107
150
  end
151
+ # rubocop:enable Layout/LineLength
108
152
  end
109
153
  end
110
154
  end
@@ -7,7 +7,46 @@ module Timescaledb
7
7
  module Rails
8
8
  module ActiveRecord
9
9
  # :nodoc:
10
- module SchemaDumper
10
+ module SchemaDumper # rubocop:disable Metrics/ModuleLength
11
+ # @override
12
+ def tables(stream)
13
+ super
14
+
15
+ continuous_aggregates(stream)
16
+ stream
17
+ end
18
+
19
+ def continuous_aggregates(stream)
20
+ return unless timescale_enabled?
21
+
22
+ Timescaledb::Rails::ContinuousAggregate.dependency_ordered.each do |ca|
23
+ continuous_aggregate(ca, stream)
24
+ continuous_aggregate_policy(ca, stream)
25
+ end
26
+ end
27
+
28
+ def continuous_aggregate(continuous_aggregate, stream)
29
+ stream.puts " create_continuous_aggregate #{continuous_aggregate.view_name.inspect}, <<-SQL"
30
+ stream.puts " #{continuous_aggregate.view_definition.strip.indent(2)}"
31
+ stream.puts ' SQL'
32
+ stream.puts
33
+ end
34
+
35
+ def continuous_aggregate_policy(continuous_aggregate, stream)
36
+ return unless continuous_aggregate.refresh?
37
+
38
+ options = [
39
+ continuous_aggregate.view_name.inspect,
40
+ continuous_aggregate.refresh_start_offset.inspect,
41
+ continuous_aggregate.refresh_end_offset.inspect,
42
+ continuous_aggregate.refresh_schedule_interval.inspect
43
+ ]
44
+
45
+ stream.puts " add_continuous_aggregate_policy #{options.join(', ')}"
46
+ stream.puts
47
+ end
48
+
49
+ # @override
11
50
  def table(table, stream)
12
51
  super(table, stream)
13
52
 
@@ -16,8 +55,9 @@ module Timescaledb
16
55
 
17
56
  hypertable(hypertable, stream)
18
57
  hypertable_compression(hypertable, stream)
19
- hypertable_reorder(hypertable, stream)
20
- hypertable_retention(hypertable, stream)
58
+ hypertable_compression_policy(hypertable, stream)
59
+ hypertable_reorder_policy(hypertable, stream)
60
+ hypertable_retention_policy(hypertable, stream)
21
61
  end
22
62
 
23
63
  private
@@ -33,14 +73,22 @@ module Timescaledb
33
73
  def hypertable_compression(hypertable, stream)
34
74
  return unless hypertable.compression?
35
75
 
76
+ options = [hypertable.hypertable_name.inspect, hypertable_compression_options(hypertable)]
77
+
78
+ stream.puts " enable_hypertable_compression #{options.join(', ')}"
79
+ stream.puts
80
+ end
81
+
82
+ def hypertable_compression_policy(hypertable, stream)
83
+ return unless hypertable.compression_policy?
84
+
36
85
  options = [hypertable.hypertable_name.inspect, hypertable.compression_policy_interval.inspect]
37
- options |= hypertable_compression_options(hypertable)
38
86
 
39
- stream.puts " add_hypertable_compression #{options.join(', ')}"
87
+ stream.puts " add_hypertable_compression_policy #{options.join(', ')}"
40
88
  stream.puts
41
89
  end
42
90
 
43
- def hypertable_reorder(hypertable, stream)
91
+ def hypertable_reorder_policy(hypertable, stream)
44
92
  return unless hypertable.reorder?
45
93
 
46
94
  options = [hypertable.hypertable_name.inspect, hypertable.reorder_policy_index_name.inspect]
@@ -49,7 +97,7 @@ module Timescaledb
49
97
  stream.puts
50
98
  end
51
99
 
52
- def hypertable_retention(hypertable, stream)
100
+ def hypertable_retention_policy(hypertable, stream)
53
101
  return unless hypertable.retention?
54
102
 
55
103
  options = [hypertable.hypertable_name.inspect, hypertable.retention_policy_interval.inspect]
@@ -101,7 +149,7 @@ module Timescaledb
101
149
  end
102
150
 
103
151
  def timescale_enabled?
104
- Timescaledb::Rails::Hypertable.table_exists?
152
+ ApplicationRecord.timescale_connection?(@connection) && Hypertable.table_exists?
105
153
  end
106
154
  end
107
155
  end
@@ -37,31 +37,45 @@ module Timescaledb
37
37
  create_table(table_name, id: false, primary_key: primary_key, force: force, **options, &block)
38
38
  end
39
39
 
40
- 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});"
41
41
  end
42
42
 
43
- # Enables compression and sets compression options.
43
+ # Enables compression on given hypertable.
44
44
  #
45
- # add_hypertable_compression('events', 7.days, segment_by: :created_at, order_by: :name)
45
+ # enable_hypertable_compression('events', segment_by: :created_at, order_by: :name)
46
46
  #
47
- def add_hypertable_compression(table_name, compress_after, segment_by: nil, order_by: nil)
48
- compress_after = compress_after.inspect if compress_after.is_a?(ActiveSupport::Duration)
49
-
47
+ def enable_hypertable_compression(table_name, segment_by: nil, order_by: nil)
50
48
  options = ['timescaledb.compress']
51
49
  options << "timescaledb.compress_orderby = '#{order_by}'" unless order_by.nil?
52
50
  options << "timescaledb.compress_segmentby = '#{segment_by}'" unless segment_by.nil?
53
51
 
54
- execute "ALTER TABLE #{table_name} SET (#{options.join(', ')})"
52
+ execute "ALTER TABLE #{table_name} SET (#{options.join(', ')});"
53
+ end
55
54
 
56
- execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compress_after.inspect}')"
55
+ # Disables compression on given hypertable.
56
+ #
57
+ # disable_hypertable_compression('events')
58
+ #
59
+ def disable_hypertable_compression(table_name, segment_by: nil, order_by: nil) # rubocop:disable Lint/UnusedMethodArgument
60
+ execute "ALTER TABLE #{table_name} SET (timescaledb.compress = false);"
57
61
  end
58
62
 
59
- # Disables compression from given table.
63
+ # Adds compression policy to given hypertable.
60
64
  #
61
- # remove_hypertable_compression('events')
65
+ # add_hypertable_compression_policy('events', 7.days)
62
66
  #
63
- def remove_hypertable_compression(table_name, compress_after = nil, segment_by: nil, order_by: nil) # rubocop:disable Lint/UnusedMethodArgument
64
- execute "SELECT remove_compression_policy('#{table_name.inspect}');"
67
+ def add_hypertable_compression_policy(table_name, compress_after)
68
+ compress_after = compress_after.inspect if compress_after.is_a?(ActiveSupport::Duration)
69
+
70
+ execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{stringify_interval(compress_after)}');"
71
+ end
72
+
73
+ # Removes compression policy from the given hypertable.
74
+ #
75
+ # remove_hypertable_compression_policy('events')
76
+ #
77
+ def remove_hypertable_compression_policy(table_name, _compress_after = nil)
78
+ execute "SELECT remove_compression_policy('#{table_name}');"
65
79
  end
66
80
 
67
81
  # Add a data retention policy to given hypertable.
@@ -69,7 +83,7 @@ module Timescaledb
69
83
  # add_hypertable_retention_policy('events', 7.days)
70
84
  #
71
85
  def add_hypertable_retention_policy(table_name, drop_after)
72
- execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{drop_after.inspect}')"
86
+ execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{stringify_interval(drop_after)}');"
73
87
  end
74
88
 
75
89
  # Removes data retention policy from given hypertable.
@@ -77,7 +91,7 @@ module Timescaledb
77
91
  # remove_hypertable_retention_policy('events')
78
92
  #
79
93
  def remove_hypertable_retention_policy(table_name, _drop_after = nil)
80
- execute "SELECT remove_retention_policy('#{table_name}')"
94
+ execute "SELECT remove_retention_policy('#{table_name}');"
81
95
  end
82
96
 
83
97
  # Adds a policy to reorder chunks on a given hypertable index in the background.
@@ -85,7 +99,7 @@ module Timescaledb
85
99
  # add_hypertable_reorder_policy('events', 'index_events_on_created_at_and_name')
86
100
  #
87
101
  def add_hypertable_reorder_policy(table_name, index_name)
88
- execute "SELECT add_reorder_policy('#{table_name}', '#{index_name}')"
102
+ execute "SELECT add_reorder_policy('#{table_name}', '#{index_name}');"
89
103
  end
90
104
 
91
105
  # Removes a policy to reorder a particular hypertable.
@@ -93,14 +107,67 @@ module Timescaledb
93
107
  # remove_hypertable_reorder_policy('events')
94
108
  #
95
109
  def remove_hypertable_reorder_policy(table_name, _index_name = nil)
96
- execute "SELECT remove_reorder_policy('#{table_name}')"
110
+ execute "SELECT remove_reorder_policy('#{table_name}');"
111
+ end
112
+
113
+ # Creates a continuous aggregate
114
+ #
115
+ # create_continuous_aggregate(
116
+ # 'temperature_events', "SELECT * FROM events where event_type = 'temperature'"
117
+ # )
118
+ #
119
+ def create_continuous_aggregate(view_name, view_query, force: false)
120
+ if force
121
+ execute "DROP MATERIALIZED VIEW #{quote_table_name(view_name)} CASCADE;" if view_exists? view_name
122
+ else
123
+ schema_cache.clear_data_source_cache!(view_name.to_s)
124
+ end
125
+
126
+ execute "CREATE MATERIALIZED VIEW #{quote_table_name(view_name)} " \
127
+ "WITH (timescaledb.continuous) AS #{view_query};"
128
+ end
129
+
130
+ # Drops a continuous aggregate
131
+ #
132
+ # drop_continuous_aggregate('temperature_events')
133
+ #
134
+ def drop_continuous_aggregate(view_name, _view_query = nil, force: false) # rubocop:disable Lint/UnusedMethodArgument
135
+ execute "DROP MATERIALIZED VIEW #{view_name};"
136
+ end
137
+
138
+ # Adds refresh continuous aggregate policy
139
+ #
140
+ # add_continuous_aggregate_policy('temperature_events', 1.month, 1.day, 1.hour)
141
+ #
142
+ def add_continuous_aggregate_policy(view_name, start_offset, end_offset, schedule_interval)
143
+ start_offset = start_offset.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(start_offset)}'"
144
+ end_offset = end_offset.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(end_offset)}'"
145
+ schedule_interval = schedule_interval.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(schedule_interval)}'"
146
+
147
+ execute "SELECT add_continuous_aggregate_policy('#{view_name}', start_offset => #{start_offset}, end_offset => #{end_offset}, schedule_interval => #{schedule_interval});" # rubocop:disable Layout/LineLength
148
+ end
149
+
150
+ # Removes refresh continuous aggregate policy
151
+ #
152
+ # remove_continuous_aggregate_policy('temperature_events')
153
+ #
154
+ def remove_continuous_aggregate_policy(view_name, _start_offset = nil,
155
+ _end_offset = nil, _schedule_interval = nil)
156
+ execute "SELECT remove_continuous_aggregate_policy('#{view_name}');"
157
+ end
158
+
159
+ private
160
+
161
+ # @param [ActiveSupport::Duration|String] interval
162
+ def stringify_interval(interval)
163
+ interval.is_a?(ActiveSupport::Duration) ? interval.inspect : interval
97
164
  end
98
165
 
99
166
  # @return [String]
100
167
  def hypertable_options_to_sql(options)
101
168
  sql_statements = options.map do |option, value|
102
169
  case option
103
- when :chunk_time_interval then "chunk_time_interval => INTERVAL '#{value}'"
170
+ when :chunk_time_interval then "chunk_time_interval => INTERVAL '#{stringify_interval(value)}'"
104
171
  when :if_not_exists then "if_not_exists => #{value ? 'TRUE' : 'FALSE'}"
105
172
  end
106
173
  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,36 @@
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, select_alias: TIME_BUCKET_ALIAS)
14
+ target_column &&= Arel.sql(target_column.to_s)
15
+ target_column ||= arel_table[hypertable_time_column_name]
16
+
17
+ time_bucket = Arel::Nodes::NamedFunction.new(
18
+ 'time_bucket',
19
+ [Arel::Nodes.build_quoted(format_interval_value(interval)), target_column]
20
+ )
21
+
22
+ select(time_bucket.dup.as(select_alias))
23
+ .group(time_bucket)
24
+ .order(time_bucket)
25
+ .extending(AggregateFunctions)
26
+ end
27
+
28
+ private
29
+
30
+ def format_interval_value(value)
31
+ value.is_a?(ActiveSupport::Duration) ? value.inspect : value
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -12,19 +12,19 @@ module Timescaledb
12
12
  scope :last_year, lambda {
13
13
  date = Date.current - 1.year
14
14
 
15
- between_time_column(date.beginning_of_year, date.end_of_year)
15
+ between(date.beginning_of_year, date.end_of_year)
16
16
  }
17
17
 
18
18
  scope :last_month, lambda {
19
19
  date = Date.current - 1.month
20
20
 
21
- between_time_column(date.beginning_of_month, date.end_of_month)
21
+ between(date.beginning_of_month, date.end_of_month)
22
22
  }
23
23
 
24
24
  scope :last_week, lambda {
25
25
  date = Date.current - 1.week
26
26
 
27
- between_time_column(date.beginning_of_week, date.end_of_week)
27
+ between(date.beginning_of_week, date.end_of_week)
28
28
  }
29
29
 
30
30
  scope :yesterday, lambda {
@@ -32,24 +32,31 @@ module Timescaledb
32
32
  }
33
33
 
34
34
  scope :this_year, lambda {
35
- between_time_column(Date.current.beginning_of_year, Date.current.end_of_year)
35
+ between(Date.current.beginning_of_year, Date.current.end_of_year)
36
36
  }
37
37
 
38
38
  scope :this_month, lambda {
39
- between_time_column(Date.current.beginning_of_month, Date.current.end_of_month)
39
+ between(Date.current.beginning_of_month, Date.current.end_of_month)
40
40
  }
41
41
 
42
42
  scope :this_week, lambda {
43
- between_time_column(Date.current.beginning_of_week, Date.current.end_of_week)
43
+ between(Date.current.beginning_of_week, Date.current.end_of_week)
44
44
  }
45
45
 
46
46
  scope :today, lambda {
47
47
  where("DATE(#{hypertable_time_column_name}) = ?", Date.current)
48
48
  }
49
49
 
50
- # @!visibility private
51
- scope :between_time_column, lambda { |from, to|
52
- where("DATE(#{hypertable_time_column_name}) BETWEEN ? AND ?", from, to)
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)
53
60
  }
54
61
  end
55
62
  # rubocop:enable Metrics/BlockLength
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timescaledb/rails/model/finder_methods'
4
+ require 'timescaledb/rails/model/hyperfunctions'
3
5
  require 'timescaledb/rails/model/scopes'
4
6
 
5
7
  module Timescaledb
@@ -14,25 +16,32 @@ module Timescaledb
14
16
 
15
17
  # :nodoc:
16
18
  module ClassMethods
17
- delegate :time_column_name, to: :hypertable, prefix: true
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
18
26
 
19
27
  # Returns only the name of the hypertable, table_name could include
20
28
  # the schema path, we need to remove it.
21
29
  #
22
30
  # @return [String]
23
31
  def hypertable_name
24
- table_name.split('.').last
32
+ @hypertable_name ||= table_name.split('.').last
25
33
  end
26
34
 
27
35
  # Returns the schema where hypertable is stored.
28
36
  #
29
37
  # @return [String]
30
38
  def hypertable_schema
31
- if table_name.split('.').size > 1
32
- table_name.split('.')[0..-2].join('.')
33
- else
34
- PUBLIC_SCHEMA_NAME
35
- end
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
36
45
  end
37
46
 
38
47
  # @return [Timescaledb::Rails::Hypertable]
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ # :nodoc:
6
+ class ApplicationRecord < ::ActiveRecord::Base
7
+ self.abstract_class = true
8
+
9
+ def self.timescale_connection?(connection)
10
+ pool_name = lambda do |pool|
11
+ if pool.respond_to?(:db_config)
12
+ pool.db_config.name
13
+ elsif pool.respond_to?(:spec)
14
+ pool.spec.name
15
+ else
16
+ raise "Don't know how to get pool name from #{pool.inspect}"
17
+ end
18
+ end
19
+
20
+ pool_name[connection.pool] == pool_name[self.connection.pool]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,10 +3,12 @@
3
3
  module Timescaledb
4
4
  module Rails
5
5
  # :nodoc:
6
- class Chunk < ::ActiveRecord::Base
6
+ class Chunk < ApplicationRecord
7
7
  self.table_name = 'timescaledb_information.chunks'
8
8
  self.primary_key = 'hypertable_name'
9
9
 
10
+ belongs_to :hypertable, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Hypertable'
11
+
10
12
  scope :compressed, -> { where(is_compressed: true) }
11
13
  scope :decompressed, -> { where(is_compressed: false) }
12
14
 
@@ -15,16 +17,33 @@ module Timescaledb
15
17
  end
16
18
 
17
19
  def compress!
18
- ::ActiveRecord::Base.connection.execute(
20
+ self.class.connection.execute(
19
21
  "SELECT compress_chunk('#{chunk_full_name}')"
20
22
  )
21
23
  end
22
24
 
23
25
  def decompress!
24
- ::ActiveRecord::Base.connection.execute(
26
+ self.class.connection.execute(
25
27
  "SELECT decompress_chunk('#{chunk_full_name}')"
26
28
  )
27
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
+ self.class.connection.execute(
44
+ "SELECT reorder_chunk(#{options.join(', ')})"
45
+ )
46
+ end
28
47
  end
29
48
  end
30
49
  end
@@ -3,7 +3,7 @@
3
3
  module Timescaledb
4
4
  module Rails
5
5
  # :nodoc:
6
- class CompressionSetting < ::ActiveRecord::Base
6
+ class CompressionSetting < ApplicationRecord
7
7
  self.table_name = 'timescaledb_information.compression_settings'
8
8
  self.primary_key = 'hypertable_name'
9
9
 
@@ -0,0 +1,78 @@
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
+ return if duration.nil?
15
+
16
+ duration_in_seconds = duration_in_seconds(duration)
17
+
18
+ duration_to_interval(
19
+ ActiveSupport::Duration.build(duration_in_seconds)
20
+ )
21
+ rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
22
+ duration
23
+ end
24
+
25
+ private
26
+
27
+ # Converts different interval formats into seconds.
28
+ #
29
+ # duration_in_seconds('P1D') #=> 86400
30
+ # duration_in_seconds('24:00:00') #=> 86400
31
+ # duration_in_seconds(1.day) #=> 86400
32
+ #
33
+ # @param [ActiveSupport::Duration|String] duration
34
+ # @return [Integer]
35
+ def duration_in_seconds(duration)
36
+ return duration.to_i if duration.is_a?(ActiveSupport::Duration)
37
+
38
+ if (duration =~ HOUR_MINUTE_SECOND_REGEX).present?
39
+ hours, minutes, seconds = duration.split(':').map(&:to_i)
40
+
41
+ (hours.hour + minutes.minute + seconds.second).to_i
42
+ else
43
+ ActiveSupport::Duration.parse(duration).to_i
44
+ end
45
+ end
46
+
47
+ # Converts given duration into a human interval readable format.
48
+ #
49
+ # duration_to_interval(1.day) #=> '1 day'
50
+ # duration_to_interval(2.weeks + 6.days) #=> '20 days'
51
+ # duration_to_interval(1.years + 3.months) #=> '1 year 3 months'
52
+ #
53
+ # @param [ActiveSupport::Duration] duration
54
+ # @return [String]
55
+ def duration_to_interval(duration)
56
+ parts = duration.parts
57
+
58
+ # Combine days and weeks if both present
59
+ #
60
+ # "1 week 2 days" => "9 days"
61
+ parts[:days] += parts.delete(:weeks) * 7 if parts.key?(:weeks) && parts.key?(:days)
62
+
63
+ parts.map do |(unit, quantity)|
64
+ "#{quantity} #{humanize_duration_unit(unit.to_s, quantity)}"
65
+ end.join(' ')
66
+ end
67
+
68
+ # Pluralize or singularize given duration unit based on given count.
69
+ #
70
+ # @param [String] duration_unit
71
+ # @param [Integer] count
72
+ def humanize_duration_unit(duration_unit, count)
73
+ count > 1 ? duration_unit.pluralize : duration_unit.singularize
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,66 @@
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 < ApplicationRecord
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
+ def self.dependency_ordered
17
+ deps = find_each.index_by(&:materialization_hypertable_name)
18
+
19
+ TSort.tsort_each(
20
+ ->(&b) { deps.each_value.sort_by(&:hypertable_name).each(&b) },
21
+ ->(n, &b) { Array.wrap(deps[n.hypertable_name]).each(&b) }
22
+ )
23
+ end
24
+
25
+ # Manually refresh a continuous aggregate.
26
+ #
27
+ # @param [DateTime] start_time
28
+ # @param [DateTime] end_time
29
+ #
30
+ def refresh!(start_time = 'NULL', end_time = 'NULL')
31
+ self.class.connection.execute(
32
+ "CALL refresh_continuous_aggregate('#{view_name}', #{start_time}, #{end_time});"
33
+ )
34
+ end
35
+
36
+ # @return [String]
37
+ def refresh_start_offset
38
+ parse_duration(refresh_job.config['start_offset'])
39
+ end
40
+
41
+ # @return [String]
42
+ def refresh_end_offset
43
+ parse_duration(refresh_job.config['end_offset'])
44
+ end
45
+
46
+ # @return [String]
47
+ def refresh_schedule_interval
48
+ interval = refresh_job.schedule_interval
49
+
50
+ interval.is_a?(String) ? parse_duration(interval) : interval.inspect
51
+ end
52
+
53
+ # @return [Boolean]
54
+ def refresh?
55
+ refresh_job.present?
56
+ end
57
+
58
+ private
59
+
60
+ # @return [Job]
61
+ def refresh_job
62
+ @refresh_job ||= jobs.policy_refresh_continuous_aggregate.first
63
+ end
64
+ end
65
+ end
66
+ end
@@ -3,7 +3,7 @@
3
3
  module Timescaledb
4
4
  module Rails
5
5
  # :nodoc:
6
- class Dimension < ::ActiveRecord::Base
6
+ class Dimension < ApplicationRecord
7
7
  TIME_TYPE = 'Time'
8
8
 
9
9
  self.table_name = 'timescaledb_information.dimensions'
@@ -1,12 +1,18 @@
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
- class Hypertable < ::ActiveRecord::Base
8
+ class Hypertable < ApplicationRecord
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'
@@ -51,10 +57,15 @@ module Timescaledb
51
57
  end
52
58
 
53
59
  # @return [Boolean]
54
- def compression?
60
+ def compression_policy?
55
61
  compression_job.present?
56
62
  end
57
63
 
64
+ # @return [Boolean]
65
+ def compression?
66
+ compression_settings.any?
67
+ end
68
+
58
69
  # @return [Boolean]
59
70
  def reorder?
60
71
  reorder_job.present?
@@ -86,13 +97,6 @@ module Timescaledb
86
97
  def time_dimension
87
98
  @time_dimension ||= dimensions.time.first
88
99
  end
89
-
90
- # @return [String]
91
- def parse_duration(duration)
92
- ActiveSupport::Duration.parse(duration).inspect
93
- rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
94
- duration
95
- end
96
100
  end
97
101
  end
98
102
  end
@@ -3,17 +3,19 @@
3
3
  module Timescaledb
4
4
  module Rails
5
5
  # :nodoc:
6
- class Job < ::ActiveRecord::Base
6
+ class Job < ApplicationRecord
7
7
  self.table_name = 'timescaledb_information.jobs'
8
8
  self.primary_key = 'hypertable_name'
9
9
 
10
10
  POLICY_COMPRESSION = 'policy_compression'
11
11
  POLICY_REORDER = 'policy_reorder'
12
12
  POLICY_RETENTION = 'policy_retention'
13
+ POLICY_REFRESH_CONTINUOUS_AGGREGATE = 'policy_refresh_continuous_aggregate'
13
14
 
14
15
  scope :policy_compression, -> { where(proc_name: POLICY_COMPRESSION) }
15
16
  scope :policy_reorder, -> { where(proc_name: POLICY_REORDER) }
16
17
  scope :policy_retention, -> { where(proc_name: POLICY_RETENTION) }
18
+ scope :policy_refresh_continuous_aggregate, -> { where(proc_name: POLICY_REFRESH_CONTINUOUS_AGGREGATE) }
17
19
  end
18
20
  end
19
21
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './models/application_record'
4
+
3
5
  require_relative './models/chunk'
4
6
  require_relative './models/compression_setting'
7
+ require_relative './models/continuous_aggregate'
5
8
  require_relative './models/dimension'
6
9
  require_relative './models/hypertable'
7
10
  require_relative './models/job'
@@ -3,6 +3,6 @@
3
3
  module Timescaledb
4
4
  # :nodoc:
5
5
  module Rails
6
- VERSION = '0.1.4'
6
+ VERSION = '0.1.6'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timescaledb-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Iván Etchart
8
8
  - Santiago Doldán
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-12-30 00:00:00.000000000 Z
12
+ date: 2023-04-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -123,7 +123,7 @@ dependencies:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
125
  version: '2.15'
126
- description:
126
+ description:
127
127
  email: oss@crunchloop.io
128
128
  executables: []
129
129
  extensions: []
@@ -139,10 +139,16 @@ files:
139
139
  - lib/timescaledb/rails/extensions/active_record/schema_dumper.rb
140
140
  - lib/timescaledb/rails/extensions/active_record/schema_statements.rb
141
141
  - lib/timescaledb/rails/model.rb
142
+ - lib/timescaledb/rails/model/aggregate_functions.rb
143
+ - lib/timescaledb/rails/model/finder_methods.rb
144
+ - lib/timescaledb/rails/model/hyperfunctions.rb
142
145
  - lib/timescaledb/rails/model/scopes.rb
143
146
  - lib/timescaledb/rails/models.rb
147
+ - lib/timescaledb/rails/models/application_record.rb
144
148
  - lib/timescaledb/rails/models/chunk.rb
145
149
  - lib/timescaledb/rails/models/compression_setting.rb
150
+ - lib/timescaledb/rails/models/concerns/durationable.rb
151
+ - lib/timescaledb/rails/models/continuous_aggregate.rb
146
152
  - lib/timescaledb/rails/models/dimension.rb
147
153
  - lib/timescaledb/rails/models/hypertable.rb
148
154
  - lib/timescaledb/rails/models/job.rb
@@ -154,7 +160,7 @@ licenses:
154
160
  - MIT
155
161
  metadata:
156
162
  rubygems_mfa_required: 'true'
157
- post_install_message:
163
+ post_install_message:
158
164
  rdoc_options: []
159
165
  require_paths:
160
166
  - lib
@@ -169,8 +175,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
175
  - !ruby/object:Gem::Version
170
176
  version: '0'
171
177
  requirements: []
172
- rubygems_version: 3.3.26
173
- signing_key:
178
+ rubygems_version: 3.3.7
179
+ signing_key:
174
180
  specification_version: 4
175
181
  summary: TimescaleDB Rails integration
176
182
  test_files: []