timescaledb-rails 0.1.4 → 0.1.6

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