timescaledb-rails 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +215 -15
- data/lib/timescaledb/rails/extensions/active_record/base.rb +20 -0
- data/lib/timescaledb/rails/extensions/active_record/command_recorder.rb +80 -0
- data/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb +77 -18
- data/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +75 -10
- data/lib/timescaledb/rails/extensions/active_record/schema_statements.rb +101 -7
- data/lib/timescaledb/rails/model/aggregate_functions.rb +30 -0
- data/lib/timescaledb/rails/model/finder_methods.rb +44 -0
- data/lib/timescaledb/rails/model/hyperfunctions.rb +30 -0
- data/lib/timescaledb/rails/model/scopes.rb +66 -0
- data/lib/timescaledb/rails/model.rb +83 -0
- data/lib/timescaledb/rails/models/chunk.rb +49 -0
- data/lib/timescaledb/rails/models/concerns/durationable.rb +76 -0
- data/lib/timescaledb/rails/models/continuous_aggregate.rb +57 -0
- data/lib/timescaledb/rails/models/hypertable.rb +48 -3
- data/lib/timescaledb/rails/models/job.rb +6 -0
- data/lib/timescaledb/rails/models.rb +3 -1
- data/lib/timescaledb/rails/railtie.rb +1 -20
- data/lib/timescaledb/rails/version.rb +1 -1
- data/lib/timescaledb/rails.rb +16 -0
- metadata +26 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fc0e42b6db3e42267d014923180abf3c64a643937942b8b3f17ad9b2a3bf223
|
4
|
+
data.tar.gz: 96c3105df56f184894cdd7e3ca8658b083a87e8a050711c17e52eeae6c836c3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9988f806f0512c73456e531dd42dfe3f3c5765d9ee426bcf6f4b45e3098568fcac3518884abde8fb473a42cba66d73aa4558631b2806256ea4726496a212bdb9
|
7
|
+
data.tar.gz: e85fa03def618ed5d114275a6f2b929871ca0c0ea52e95ada85cca332baa2f1252f8a6bc3fc29be544aaa8dc8225dbcda52df83903bedd530a63b3683be30e8b
|
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
# TimescaleDB extension for Rails [](https://github.com/crunchloop/timescaledb-rails/actions?query=workflow%3ACI)
|
2
|
-
|
3
|
-
`timescaledb-rails` extends ActiveRecord PostgreSQL adapter and provides features from [`TimescaleDB`](https://www.timescale.com). It provides support for hypertables and other features added by TimescaleDB PostgreSQL extension.
|
1
|
+
# TimescaleDB extension for Rails [](https://badge.fury.io/rb/timescaledb-rails) [](https://github.com/crunchloop/timescaledb-rails/actions?query=workflow%3ACI)
|
4
2
|
|
3
|
+
`timescaledb-rails` extends ActiveRecord PostgreSQL adapter and provides features from [TimescaleDB](https://www.timescale.com). It provides support for hypertables and other features added by TimescaleDB PostgreSQL extension.
|
5
4
|
|
6
5
|
## Installation
|
7
6
|
|
@@ -17,16 +16,18 @@ Or include it in your project's `Gemfile` with Bundler:
|
|
17
16
|
gem 'timescaledb-rails', '~> 0.1'
|
18
17
|
```
|
19
18
|
|
20
|
-
##
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Migrations
|
21
22
|
|
22
|
-
Create a hypertable from a PostgreSQL table
|
23
|
+
Create a hypertable from a PostgreSQL table
|
23
24
|
|
24
25
|
```ruby
|
25
26
|
class CreateEvent < ActiveRecord::Migration[7.0]
|
26
27
|
def change
|
27
28
|
create_table :events, id: false do |t|
|
28
29
|
t.string :name, null: false
|
29
|
-
t.time :
|
30
|
+
t.time :occurred_at, null: false
|
30
31
|
|
31
32
|
t.timestamps
|
32
33
|
end
|
@@ -36,7 +37,7 @@ class CreateEvent < ActiveRecord::Migration[7.0]
|
|
36
37
|
end
|
37
38
|
```
|
38
39
|
|
39
|
-
Create a hypertable without a PostgreSQL table
|
40
|
+
Create a hypertable without a PostgreSQL table
|
40
41
|
|
41
42
|
```ruby
|
42
43
|
class CreatePayloadHypertable < ActiveRecord::Migration[7.0]
|
@@ -50,29 +51,228 @@ class CreatePayloadHypertable < ActiveRecord::Migration[7.0]
|
|
50
51
|
end
|
51
52
|
```
|
52
53
|
|
53
|
-
|
54
|
+
Add hypertable compression
|
54
55
|
|
55
56
|
```ruby
|
56
57
|
class AddEventCompression < ActiveRecord::Migration[7.0]
|
57
|
-
def
|
58
|
-
add_hypertable_compression :events, 20.days, segment_by: :name, order_by: '
|
58
|
+
def up
|
59
|
+
add_hypertable_compression :events, 20.days, segment_by: :name, order_by: 'occurred_at DESC'
|
60
|
+
end
|
61
|
+
|
62
|
+
def down
|
63
|
+
remove_hypertable_compression :events
|
59
64
|
end
|
60
65
|
end
|
61
66
|
```
|
62
67
|
|
63
|
-
|
68
|
+
Add hypertable retention policy
|
64
69
|
|
65
70
|
```ruby
|
66
|
-
class
|
67
|
-
def
|
68
|
-
|
71
|
+
class AddEventRetentionPolicy < ActiveRecord::Migration[7.0]
|
72
|
+
def up
|
73
|
+
add_hypertable_retention_policy :events, 1.year
|
74
|
+
end
|
75
|
+
|
76
|
+
def down
|
77
|
+
remove_hypertable_retention_policy :events
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Add hypertable reorder policy
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
class AddEventReorderPolicy < ActiveRecord::Migration[7.0]
|
86
|
+
def up
|
87
|
+
add_hypertable_reorder_policy :events, :index_events_on_created_at_and_name
|
88
|
+
end
|
89
|
+
|
90
|
+
def down
|
91
|
+
remove_hypertable_reorder_policy :events
|
69
92
|
end
|
70
93
|
end
|
71
94
|
```
|
72
95
|
|
96
|
+
Create continuous aggregate
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class CreateTemperatureEventAggregate < ActiveRecord::Migration[7.0]
|
100
|
+
def up
|
101
|
+
create_continuous_aggregate(
|
102
|
+
:temperature_events,
|
103
|
+
Event.time_bucket(1.day).avg(:value).temperature.to_sql
|
104
|
+
)
|
105
|
+
|
106
|
+
add_continuous_aggregate_policy(:temperature_events, 1.month, 1.day, 1.hour)
|
107
|
+
end
|
108
|
+
|
109
|
+
def down
|
110
|
+
drop_continuous_aggregate(:temperature_events)
|
111
|
+
|
112
|
+
remove_continuous_aggregate_policy(:temperature_events)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
> **Reversible Migrations:**
|
118
|
+
>
|
119
|
+
> 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.
|
120
|
+
|
121
|
+
### Models
|
122
|
+
|
123
|
+
If one of your models need TimescaleDB support, just include `Timescaledb::Rails::Model`
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
class Payload < ActiveRecord::Base
|
127
|
+
include Timescaledb::Rails::Model
|
128
|
+
|
129
|
+
self.primary_key = 'id'
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
When hypertable belongs to a non default schema, don't forget to override `table_name`
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class Event < ActiveRecord::Base
|
137
|
+
include Timescaledb::Rails::Model
|
138
|
+
|
139
|
+
self.table_name = 'tdb.events'
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
Using `.find` is not recommended, to achieve more performat results, use these other find methods
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
# When you know the exact time value
|
147
|
+
Payload.find_at_time(111, Time.new(2022, 01, 01, 10, 15, 30))
|
148
|
+
|
149
|
+
# If you know that the record occurred after a given time
|
150
|
+
Payload.find_after(222, 11.days.ago)
|
151
|
+
|
152
|
+
# Lastly, if you want to scope the search by a time range
|
153
|
+
Payload.find_between(333, 1.week.ago, 1.day.ago)
|
154
|
+
```
|
155
|
+
|
156
|
+
If you need to query data for a specific time period, `Timescaledb::Rails::Model` includes useful scopes
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# If you want to get all records from last year
|
160
|
+
Event.last_year #=> [#<Event name...>, ...]
|
161
|
+
|
162
|
+
# Or if you want to get records from this year
|
163
|
+
Event.this_year #=> [#<Event name...>, ...]
|
164
|
+
|
165
|
+
# Or even getting records from today
|
166
|
+
Event.today #=> [#<Event name...>, ...]
|
167
|
+
```
|
168
|
+
|
169
|
+
Here the list of all available scopes
|
170
|
+
|
171
|
+
* last_year
|
172
|
+
* last_month
|
173
|
+
* last_week
|
174
|
+
* this_year
|
175
|
+
* this_month
|
176
|
+
* this_week
|
177
|
+
* yesterday
|
178
|
+
* today
|
179
|
+
|
180
|
+
If you still need to query data by other time periods, take a look at these other scopes
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
# If you want to get all records that occurred in the last 30 minutes
|
184
|
+
Event.after(30.minutes.ago) #=> [#<Event name...>, ...]
|
185
|
+
|
186
|
+
# If you want to get records that occurred in the last 4 days, excluding today
|
187
|
+
Event.between(4.days.ago, 1.day.ago) #=> [#<Event name...>, ...]
|
188
|
+
|
189
|
+
# If you want to get records that occurred at a specific time
|
190
|
+
Event.at_time(Time.new(2023, 01, 04, 10, 20, 30)) #=> [#<Event name...>, ...]
|
191
|
+
```
|
192
|
+
|
193
|
+
If you need information about your hypertable, use the following helper methods to get useful information
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# Hypertable metadata
|
197
|
+
Event.hypertable #=> #<Timescaledb::Rails::Hypertable ...>
|
198
|
+
|
199
|
+
# Hypertable chunks metadata
|
200
|
+
Event.hypertable_chunks #=> [#<Timescaledb::Rails::Chunk ...>, ...]
|
201
|
+
|
202
|
+
# Hypertable jobs, it includes jobs like compression, retention or reorder policies, etc.
|
203
|
+
Event.hypertable_jobs #=> [#<Timescaledb::Rails::Job ...>, ...]
|
204
|
+
|
205
|
+
# Hypertable dimensions, like time or space dimensions
|
206
|
+
Event.hypertable_dimensions #=> [#<Timescaledb::Rails::Dimension ...>, ...]
|
207
|
+
|
208
|
+
# Hypertable compression settings
|
209
|
+
Event.hypertable_compression_settings #=> [#<Timescaledb::Rails::CompressionSetting ...>, ...]
|
210
|
+
```
|
211
|
+
|
212
|
+
If you need to compress or decompress a specific chunk
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
chunk = Event.hypertable_chunks.first
|
216
|
+
|
217
|
+
chunk.compress! unless chunk.is_compressed?
|
218
|
+
|
219
|
+
chunk.decompress! if chunk.is_compressed?
|
220
|
+
```
|
221
|
+
|
222
|
+
If you need to reorder a specific chunk
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
chunk = Event.hypertable_chunks.first
|
226
|
+
|
227
|
+
# If an index is not specified, it will use the one from the reorder policy
|
228
|
+
# In case there is no reorder policy index it will raise an ArgumentError
|
229
|
+
chunk.reorder!
|
230
|
+
|
231
|
+
# If an index is specified it will use that index
|
232
|
+
chunk.reorder!(index)
|
233
|
+
```
|
234
|
+
|
235
|
+
If you need to manually refresh a continuous aggregate
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
aggregate = Event.hypertable.continuous_aggregates.first
|
239
|
+
|
240
|
+
aggregate.refresh!(5.days.ago, 1.day.ago)
|
241
|
+
```
|
242
|
+
|
243
|
+
### Hyperfunctions
|
244
|
+
|
245
|
+
#### Time bucket
|
246
|
+
|
247
|
+
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)
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
Event.time_bucket(1.day)
|
251
|
+
|
252
|
+
Event.time_bucket('1 day')
|
253
|
+
|
254
|
+
Event.time_bucket(1.day, :created_at)
|
255
|
+
|
256
|
+
Event.time_bucket(1.day, 'occurred_at')
|
257
|
+
```
|
258
|
+
|
259
|
+
You may add aggregation like so:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
Event.time_bucket(1.day).avg(:column)
|
263
|
+
Event.time_bucket(1.day).sum(:column)
|
264
|
+
Event.time_bucket(1.day).min(:column)
|
265
|
+
Event.time_bucket(1.day).max(:column)
|
266
|
+
Event.time_bucket(1.day).count
|
267
|
+
```
|
268
|
+
|
269
|
+
## Contributing
|
270
|
+
|
271
|
+
Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests.
|
272
|
+
|
73
273
|
## Supported Ruby/Rails versions
|
74
274
|
|
75
|
-
Supported Ruby/Rails versions are listed in [`.github/workflows/ci.yaml`](
|
275
|
+
Supported Ruby/Rails versions are listed in [`.github/workflows/ci.yaml`](./.github/workflows/ci.yaml)
|
76
276
|
|
77
277
|
## License
|
78
278
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
module Rails
|
5
|
+
module ActiveRecord
|
6
|
+
# :nodoc:
|
7
|
+
module Base
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
# :nodoc:
|
11
|
+
module ClassMethods
|
12
|
+
# Returns if the current active record model is a hypertable.
|
13
|
+
def hypertable?
|
14
|
+
connection.hypertable_exists?(table_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -17,6 +17,38 @@ module Timescaledb
|
|
17
17
|
record(:remove_hypertable_compression, args, &block)
|
18
18
|
end
|
19
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)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_continuous_aggregate(*args, &block)
|
37
|
+
record(:create_continuous_aggregate, args, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def drop_continuous_aggregate(*args, &block)
|
41
|
+
record(:drop_continuous_aggregate, args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_continuous_aggregate_policy(*args, &block)
|
45
|
+
record(:add_continuous_aggregate_policy, args, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_continuous_aggregate_policy(*args, &block)
|
49
|
+
record(:remove_continuous_aggregate_policy, args, &block)
|
50
|
+
end
|
51
|
+
|
20
52
|
def invert_create_hypertable(args, &block)
|
21
53
|
if block.nil?
|
22
54
|
raise ::ActiveRecord::IrreversibleMigration, 'create_hypertable is only reversible if given a block (can be empty).' # rubocop:disable Layout/LineLength
|
@@ -36,6 +68,54 @@ module Timescaledb
|
|
36
68
|
|
37
69
|
[:add_hypertable_compression, args, block]
|
38
70
|
end
|
71
|
+
|
72
|
+
def invert_add_hypertable_retention_policy(args, &block)
|
73
|
+
[:remove_hypertable_retention_policy, args, block]
|
74
|
+
end
|
75
|
+
|
76
|
+
def invert_remove_hypertable_retention_policy(args, &block)
|
77
|
+
if args.size < 2
|
78
|
+
raise ::ActiveRecord::IrreversibleMigration, 'remove_hypertable_retention_policy is only reversible if given table name and drop after period.' # rubocop:disable Layout/LineLength
|
79
|
+
end
|
80
|
+
|
81
|
+
[:add_hypertable_retention_policy, args, block]
|
82
|
+
end
|
83
|
+
|
84
|
+
def invert_add_hypertable_reorder_policy(args, &block)
|
85
|
+
[:remove_hypertable_reorder_policy, args, block]
|
86
|
+
end
|
87
|
+
|
88
|
+
def invert_remove_hypertable_reorder_policy(args, &block)
|
89
|
+
if args.size < 2
|
90
|
+
raise ::ActiveRecord::IrreversibleMigration, 'remove_hypertable_reorder_policy is only reversible if given table name and index name.' # rubocop:disable Layout/LineLength
|
91
|
+
end
|
92
|
+
|
93
|
+
[:add_hypertable_reorder_policy, args, block]
|
94
|
+
end
|
95
|
+
|
96
|
+
def invert_create_continuous_aggregate(args, &block)
|
97
|
+
[:drop_continuous_aggregate, args, block]
|
98
|
+
end
|
99
|
+
|
100
|
+
def invert_drop_continuous_aggregate(args, &block)
|
101
|
+
if args.size < 2
|
102
|
+
raise ::ActiveRecord::IrreversibleMigration, 'drop_continuous_aggregate is only reversible if given view name and view query.' # rubocop:disable Layout/LineLength
|
103
|
+
end
|
104
|
+
|
105
|
+
[:create_continuous_aggregate, args, block]
|
106
|
+
end
|
107
|
+
|
108
|
+
def invert_add_continuous_aggregate_policy(args, &block)
|
109
|
+
[:remove_continuous_aggregate_policy, args, block]
|
110
|
+
end
|
111
|
+
|
112
|
+
def invert_remove_continuous_aggregate_policy(args, &block)
|
113
|
+
if args.size < 4
|
114
|
+
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
|
115
|
+
end
|
116
|
+
|
117
|
+
[:add_continuous_aggregate_policy, args, block]
|
118
|
+
end
|
39
119
|
end
|
40
120
|
end
|
41
121
|
end
|
@@ -7,36 +7,53 @@ 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)
|
13
|
+
def structure_dump(filename, extra_flags)
|
13
14
|
extra_flags = Array(extra_flags)
|
14
|
-
extra_flags
|
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
30
|
add_hypertable_compression_statement(hypertable, file)
|
31
|
+
add_hypertable_reorder_policy_statement(hypertable, file)
|
32
|
+
add_hypertable_retention_policy_statement(hypertable, file)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def continuous_aggregates(filename)
|
38
|
+
File.open(filename, 'a') do |file|
|
39
|
+
Timescaledb::Rails::ContinuousAggregate.all.each do |continuous_aggregate|
|
40
|
+
create_continuous_aggregate_statement(continuous_aggregate, file)
|
41
|
+
add_continuous_aggregate_policy_statement(continuous_aggregate, file)
|
25
42
|
end
|
26
43
|
end
|
27
44
|
end
|
28
45
|
|
29
46
|
def drop_ts_insert_trigger_statment(hypertable, file)
|
30
47
|
file << "---\n"
|
31
|
-
file << "--- Drop ts_insert_blocker previously created by pg_dump to avoid pg errors, create_hypertable will re-create it again.\n"
|
48
|
+
file << "--- Drop ts_insert_blocker previously created by pg_dump to avoid pg errors, create_hypertable will re-create it again.\n"
|
32
49
|
file << "---\n\n"
|
33
|
-
file << "DROP TRIGGER IF EXISTS ts_insert_blocker ON #{hypertable.hypertable_name};\n"
|
50
|
+
file << "DROP TRIGGER IF EXISTS ts_insert_blocker ON #{hypertable.hypertable_schema}.#{hypertable.hypertable_name};\n"
|
34
51
|
end
|
35
52
|
|
36
53
|
def create_hypertable_statement(hypertable, file)
|
37
54
|
options = hypertable_options(hypertable)
|
38
55
|
|
39
|
-
file << "SELECT create_hypertable('#{hypertable.hypertable_name}', '#{hypertable.time_column_name}', #{options});\n\n"
|
56
|
+
file << "SELECT create_hypertable('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', '#{hypertable.time_column_name}', #{options});\n\n"
|
40
57
|
end
|
41
58
|
|
42
59
|
def add_hypertable_compression_statement(hypertable, file)
|
@@ -44,8 +61,35 @@ module Timescaledb
|
|
44
61
|
|
45
62
|
options = hypertable_compression_options(hypertable)
|
46
63
|
|
47
|
-
file << "ALTER TABLE #{hypertable.hypertable_name} SET (#{options});\n\n"
|
48
|
-
file << "SELECT add_compression_policy('#{hypertable.hypertable_name}', INTERVAL '#{hypertable.compression_policy_interval}');\n\n"
|
64
|
+
file << "ALTER TABLE #{hypertable.hypertable_schema}.#{hypertable.hypertable_name} SET (#{options});\n\n"
|
65
|
+
file << "SELECT add_compression_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', INTERVAL '#{hypertable.compression_policy_interval}');\n\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_hypertable_reorder_policy_statement(hypertable, file)
|
69
|
+
return unless hypertable.reorder?
|
70
|
+
|
71
|
+
file << "SELECT add_reorder_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', '#{hypertable.reorder_policy_index_name}');\n\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_hypertable_retention_policy_statement(hypertable, file)
|
75
|
+
return unless hypertable.retention?
|
76
|
+
|
77
|
+
file << "SELECT add_retention_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', INTERVAL '#{hypertable.retention_policy_interval}');\n\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_continuous_aggregate_statement(continuous_aggregate, file)
|
81
|
+
file << "CREATE MATERIALIZED VIEW #{continuous_aggregate.view_schema}.#{continuous_aggregate.view_name} WITH (timescaledb.continuous) AS\n"
|
82
|
+
file << "#{continuous_aggregate.view_definition.strip.indent(2)}\n\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_continuous_aggregate_policy_statement(continuous_aggregate, file)
|
86
|
+
return unless continuous_aggregate.refresh?
|
87
|
+
|
88
|
+
start_offset = continuous_aggregate.refresh_start_offset
|
89
|
+
end_offset = continuous_aggregate.refresh_end_offset
|
90
|
+
schedule_interval = continuous_aggregate.refresh_schedule_interval
|
91
|
+
|
92
|
+
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"
|
49
93
|
end
|
50
94
|
|
51
95
|
def hypertable_options(hypertable)
|
@@ -56,27 +100,41 @@ module Timescaledb
|
|
56
100
|
end
|
57
101
|
|
58
102
|
def hypertable_compression_options(hypertable)
|
59
|
-
segmentby_setting = hypertable.compression_settings.segmentby_setting.first
|
60
|
-
orderby_setting = hypertable.compression_settings.orderby_setting.first
|
61
|
-
|
62
103
|
sql_statements = ['timescaledb.compress']
|
63
|
-
sql_statements << "timescaledb.compress_segmentby = '#{segmentby_setting.attname}'" if segmentby_setting
|
64
104
|
|
65
|
-
if
|
66
|
-
|
67
|
-
|
105
|
+
if (segments = compression_segment_settings(hypertable)).present?
|
106
|
+
sql_statements << "timescaledb.compress_segmentby = '#{segments.join(', ')}'"
|
107
|
+
end
|
68
108
|
|
69
|
-
|
109
|
+
if (orders = compression_order_settings(hypertable)).present?
|
110
|
+
sql_statements << "timescaledb.compress_orderby = '#{orders.join(', ')}'"
|
70
111
|
end
|
71
112
|
|
72
113
|
sql_statements.join(', ')
|
73
114
|
end
|
74
115
|
|
75
|
-
|
116
|
+
def compression_order_settings(hypertable)
|
117
|
+
hypertable.compression_order_settings.map do |os|
|
118
|
+
Timescaledb::Rails::OrderbyCompression.new(os.attname, os.orderby_asc).to_s
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def compression_segment_settings(hypertable)
|
123
|
+
hypertable.compression_segment_settings.map(&:attname)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns `pg_dump` flags to exclude `_timescaledb_internal` schema tables and
|
127
|
+
# exclude the corresponding continuous aggregate views.
|
76
128
|
#
|
77
|
-
# @return [String]
|
129
|
+
# @return [Array<String>]
|
78
130
|
def timescale_structure_dump_default_flags
|
79
|
-
'--exclude-schema=_timescaledb_internal'
|
131
|
+
flags = ['--exclude-schema=_timescaledb_internal']
|
132
|
+
|
133
|
+
Timescaledb::Rails::ContinuousAggregate.pluck(:view_schema, :view_name).each do |view_schema, view_name|
|
134
|
+
flags << "--exclude-table=#{view_schema}.#{view_name}"
|
135
|
+
end
|
136
|
+
|
137
|
+
flags
|
80
138
|
end
|
81
139
|
|
82
140
|
# @return [Boolean]
|
@@ -84,6 +142,7 @@ module Timescaledb
|
|
84
142
|
Timescaledb::Rails::Hypertable.table_exists?
|
85
143
|
end
|
86
144
|
end
|
145
|
+
# rubocop:enable Layout/LineLength
|
87
146
|
end
|
88
147
|
end
|
89
148
|
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.all.each do |continuous_aggregate|
|
23
|
+
continuous_aggregate(continuous_aggregate, stream)
|
24
|
+
continuous_aggregate_policy(continuous_aggregate, 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,6 +55,8 @@ module Timescaledb
|
|
16
55
|
|
17
56
|
hypertable(hypertable, stream)
|
18
57
|
hypertable_compression(hypertable, stream)
|
58
|
+
hypertable_reorder(hypertable, stream)
|
59
|
+
hypertable_retention(hypertable, stream)
|
19
60
|
end
|
20
61
|
|
21
62
|
private
|
@@ -38,6 +79,24 @@ module Timescaledb
|
|
38
79
|
stream.puts
|
39
80
|
end
|
40
81
|
|
82
|
+
def hypertable_reorder(hypertable, stream)
|
83
|
+
return unless hypertable.reorder?
|
84
|
+
|
85
|
+
options = [hypertable.hypertable_name.inspect, hypertable.reorder_policy_index_name.inspect]
|
86
|
+
|
87
|
+
stream.puts " add_hypertable_reorder_policy #{options.join(', ')}"
|
88
|
+
stream.puts
|
89
|
+
end
|
90
|
+
|
91
|
+
def hypertable_retention(hypertable, stream)
|
92
|
+
return unless hypertable.retention?
|
93
|
+
|
94
|
+
options = [hypertable.hypertable_name.inspect, hypertable.retention_policy_interval.inspect]
|
95
|
+
|
96
|
+
stream.puts " add_hypertable_retention_policy #{options.join(', ')}"
|
97
|
+
stream.puts
|
98
|
+
end
|
99
|
+
|
41
100
|
def hypertable_options(hypertable)
|
42
101
|
options = {
|
43
102
|
chunk_time_interval: hypertable.chunk_time_interval
|
@@ -50,21 +109,27 @@ module Timescaledb
|
|
50
109
|
end
|
51
110
|
|
52
111
|
def hypertable_compression_options(hypertable)
|
53
|
-
segmentby_setting = hypertable.compression_settings.segmentby_setting.first
|
54
|
-
orderby_setting = hypertable.compression_settings.orderby_setting.first
|
55
|
-
|
56
112
|
[].tap do |result|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
orderby = Timescaledb::Rails::OrderbyCompression.new(orderby_setting.attname,
|
61
|
-
orderby_setting.orderby_asc).to_s
|
113
|
+
if (segments = compression_segment_settings(hypertable)).present?
|
114
|
+
result << "segment_by: #{segments.join(', ').inspect}"
|
115
|
+
end
|
62
116
|
|
63
|
-
|
117
|
+
if (orders = compression_order_settings(hypertable)).present?
|
118
|
+
result << "order_by: #{orders.join(', ').inspect}"
|
64
119
|
end
|
65
120
|
end
|
66
121
|
end
|
67
122
|
|
123
|
+
def compression_order_settings(hypertable)
|
124
|
+
hypertable.compression_order_settings.map do |os|
|
125
|
+
Timescaledb::Rails::OrderbyCompression.new(os.attname, os.orderby_asc).to_s
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def compression_segment_settings(hypertable)
|
130
|
+
hypertable.compression_segment_settings.map(&:attname)
|
131
|
+
end
|
132
|
+
|
68
133
|
def format_hypertable_option_value(value)
|
69
134
|
case value
|
70
135
|
when String then value.inspect
|