timescaledb-rails 0.1.4 → 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 +105 -4
- data/lib/timescaledb/rails/extensions/active_record/command_recorder.rb +40 -0
- data/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb +50 -12
- data/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +40 -1
- data/lib/timescaledb/rails/extensions/active_record/schema_statements.rb +57 -10
- 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 +16 -9
- data/lib/timescaledb/rails/model.rb +16 -7
- data/lib/timescaledb/rails/models/chunk.rb +19 -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 +6 -7
- data/lib/timescaledb/rails/models/job.rb +2 -0
- data/lib/timescaledb/rails/models.rb +1 -0
- data/lib/timescaledb/rails/version.rb +1 -1
- metadata +8 -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
@@ -93,26 +93,67 @@ class AddEventReorderPolicy < ActiveRecord::Migration[7.0]
|
|
93
93
|
end
|
94
94
|
```
|
95
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
|
+
|
96
121
|
### Models
|
97
122
|
|
98
123
|
If one of your models need TimescaleDB support, just include `Timescaledb::Rails::Model`
|
124
|
+
|
99
125
|
```ruby
|
100
|
-
class
|
126
|
+
class Payload < ActiveRecord::Base
|
101
127
|
include Timescaledb::Rails::Model
|
128
|
+
|
129
|
+
self.primary_key = 'id'
|
102
130
|
end
|
103
131
|
```
|
104
132
|
|
105
|
-
|
133
|
+
When hypertable belongs to a non default schema, don't forget to override `table_name`
|
106
134
|
|
107
135
|
```ruby
|
108
136
|
class Event < ActiveRecord::Base
|
109
137
|
include Timescaledb::Rails::Model
|
110
138
|
|
111
|
-
self.table_name = '
|
139
|
+
self.table_name = 'tdb.events'
|
112
140
|
end
|
113
141
|
```
|
114
142
|
|
115
|
-
|
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
|
116
157
|
|
117
158
|
```ruby
|
118
159
|
# If you want to get all records from last year
|
@@ -136,6 +177,19 @@ Here the list of all available scopes
|
|
136
177
|
* yesterday
|
137
178
|
* today
|
138
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
|
+
|
139
193
|
If you need information about your hypertable, use the following helper methods to get useful information
|
140
194
|
|
141
195
|
```ruby
|
@@ -165,6 +219,53 @@ chunk.compress! unless chunk.is_compressed?
|
|
165
219
|
chunk.decompress! if chunk.is_compressed?
|
166
220
|
```
|
167
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
|
+
|
168
269
|
## Contributing
|
169
270
|
|
170
271
|
Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests.
|
@@ -33,6 +33,22 @@ module Timescaledb
|
|
33
33
|
record(:remove_hypertable_retention_policy, args, &block)
|
34
34
|
end
|
35
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
|
+
|
36
52
|
def invert_create_hypertable(args, &block)
|
37
53
|
if block.nil?
|
38
54
|
raise ::ActiveRecord::IrreversibleMigration, 'create_hypertable is only reversible if given a block (can be empty).' # rubocop:disable Layout/LineLength
|
@@ -76,6 +92,30 @@ module Timescaledb
|
|
76
92
|
|
77
93
|
[:add_hypertable_reorder_policy, args, block]
|
78
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
|
79
119
|
end
|
80
120
|
end
|
81
121
|
end
|
@@ -7,16 +7,22 @@ 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)
|
@@ -28,17 +34,26 @@ module Timescaledb
|
|
28
34
|
end
|
29
35
|
end
|
30
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)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
31
46
|
def drop_ts_insert_trigger_statment(hypertable, file)
|
32
47
|
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"
|
48
|
+
file << "--- Drop ts_insert_blocker previously created by pg_dump to avoid pg errors, create_hypertable will re-create it again.\n"
|
34
49
|
file << "---\n\n"
|
35
|
-
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"
|
36
51
|
end
|
37
52
|
|
38
53
|
def create_hypertable_statement(hypertable, file)
|
39
54
|
options = hypertable_options(hypertable)
|
40
55
|
|
41
|
-
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"
|
42
57
|
end
|
43
58
|
|
44
59
|
def add_hypertable_compression_statement(hypertable, file)
|
@@ -46,20 +61,35 @@ module Timescaledb
|
|
46
61
|
|
47
62
|
options = hypertable_compression_options(hypertable)
|
48
63
|
|
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"
|
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"
|
51
66
|
end
|
52
67
|
|
53
68
|
def add_hypertable_reorder_policy_statement(hypertable, file)
|
54
69
|
return unless hypertable.reorder?
|
55
70
|
|
56
|
-
file << "SELECT add_reorder_policy('#{hypertable.hypertable_name}', '#{hypertable.reorder_policy_index_name}');\n\n"
|
71
|
+
file << "SELECT add_reorder_policy('#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}', '#{hypertable.reorder_policy_index_name}');\n\n"
|
57
72
|
end
|
58
73
|
|
59
74
|
def add_hypertable_retention_policy_statement(hypertable, file)
|
60
75
|
return unless hypertable.retention?
|
61
76
|
|
62
|
-
file << "SELECT add_retention_policy('#{hypertable.hypertable_name}', INTERVAL '#{hypertable.retention_policy_interval}');\n\n"
|
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"
|
63
93
|
end
|
64
94
|
|
65
95
|
def hypertable_options(hypertable)
|
@@ -93,11 +123,18 @@ module Timescaledb
|
|
93
123
|
hypertable.compression_segment_settings.map(&:attname)
|
94
124
|
end
|
95
125
|
|
96
|
-
# Returns `pg_dump`
|
126
|
+
# Returns `pg_dump` flags to exclude `_timescaledb_internal` schema tables and
|
127
|
+
# exclude the corresponding continuous aggregate views.
|
97
128
|
#
|
98
|
-
# @return [String]
|
129
|
+
# @return [Array<String>]
|
99
130
|
def timescale_structure_dump_default_flags
|
100
|
-
'--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
|
101
138
|
end
|
102
139
|
|
103
140
|
# @return [Boolean]
|
@@ -105,6 +142,7 @@ module Timescaledb
|
|
105
142
|
Timescaledb::Rails::Hypertable.table_exists?
|
106
143
|
end
|
107
144
|
end
|
145
|
+
# rubocop:enable Layout/LineLength
|
108
146
|
end
|
109
147
|
end
|
110
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
|
|
@@ -37,7 +37,7 @@ 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
43
|
# Enables compression and sets compression options.
|
@@ -51,17 +51,18 @@ module Timescaledb
|
|
51
51
|
options << "timescaledb.compress_orderby = '#{order_by}'" unless order_by.nil?
|
52
52
|
options << "timescaledb.compress_segmentby = '#{segment_by}'" unless segment_by.nil?
|
53
53
|
|
54
|
-
execute "ALTER TABLE #{table_name} SET (#{options.join(', ')})"
|
54
|
+
execute "ALTER TABLE #{table_name} SET (#{options.join(', ')});"
|
55
55
|
|
56
|
-
execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compress_after
|
56
|
+
execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{stringify_interval(compress_after)}');"
|
57
57
|
end
|
58
58
|
|
59
|
-
#
|
59
|
+
# Removes compression policy and disables compression from given hypertable.
|
60
60
|
#
|
61
61
|
# remove_hypertable_compression('events')
|
62
62
|
#
|
63
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
|
64
|
+
execute "SELECT remove_compression_policy('#{table_name}');"
|
65
|
+
execute "ALTER TABLE #{table_name.inspect} SET (timescaledb.compress = false);"
|
65
66
|
end
|
66
67
|
|
67
68
|
# Add a data retention policy to given hypertable.
|
@@ -69,7 +70,7 @@ module Timescaledb
|
|
69
70
|
# add_hypertable_retention_policy('events', 7.days)
|
70
71
|
#
|
71
72
|
def add_hypertable_retention_policy(table_name, drop_after)
|
72
|
-
execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{drop_after
|
73
|
+
execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{stringify_interval(drop_after)}');"
|
73
74
|
end
|
74
75
|
|
75
76
|
# Removes data retention policy from given hypertable.
|
@@ -77,7 +78,7 @@ module Timescaledb
|
|
77
78
|
# remove_hypertable_retention_policy('events')
|
78
79
|
#
|
79
80
|
def remove_hypertable_retention_policy(table_name, _drop_after = nil)
|
80
|
-
execute "SELECT remove_retention_policy('#{table_name}')"
|
81
|
+
execute "SELECT remove_retention_policy('#{table_name}');"
|
81
82
|
end
|
82
83
|
|
83
84
|
# Adds a policy to reorder chunks on a given hypertable index in the background.
|
@@ -85,7 +86,7 @@ module Timescaledb
|
|
85
86
|
# add_hypertable_reorder_policy('events', 'index_events_on_created_at_and_name')
|
86
87
|
#
|
87
88
|
def add_hypertable_reorder_policy(table_name, index_name)
|
88
|
-
execute "SELECT add_reorder_policy('#{table_name}', '#{index_name}')"
|
89
|
+
execute "SELECT add_reorder_policy('#{table_name}', '#{index_name}');"
|
89
90
|
end
|
90
91
|
|
91
92
|
# Removes a policy to reorder a particular hypertable.
|
@@ -93,14 +94,60 @@ module Timescaledb
|
|
93
94
|
# remove_hypertable_reorder_policy('events')
|
94
95
|
#
|
95
96
|
def remove_hypertable_reorder_policy(table_name, _index_name = nil)
|
96
|
-
execute "SELECT remove_reorder_policy('#{table_name}')"
|
97
|
+
execute "SELECT remove_reorder_policy('#{table_name}');"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Creates a continuous aggregate
|
101
|
+
#
|
102
|
+
# create_continuous_aggregate(
|
103
|
+
# 'temperature_events', "SELECT * FROM events where event_type = 'temperature'"
|
104
|
+
# )
|
105
|
+
#
|
106
|
+
def create_continuous_aggregate(view_name, view_query)
|
107
|
+
execute "CREATE MATERIALIZED VIEW #{view_name} WITH (timescaledb.continuous) AS #{view_query};"
|
108
|
+
end
|
109
|
+
|
110
|
+
# Drops a continuous aggregate
|
111
|
+
#
|
112
|
+
# drop_continuous_aggregate('temperature_events')
|
113
|
+
#
|
114
|
+
def drop_continuous_aggregate(view_name, _view_query = nil)
|
115
|
+
execute "DROP MATERIALIZED VIEW #{view_name};"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Adds refresh continuous aggregate policy
|
119
|
+
#
|
120
|
+
# add_continuous_aggregate_policy('temperature_events', 1.month, 1.day, 1.hour)
|
121
|
+
#
|
122
|
+
def add_continuous_aggregate_policy(view_name, start_offset, end_offset, schedule_interval)
|
123
|
+
start_offset = start_offset.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(start_offset)}'"
|
124
|
+
end_offset = end_offset.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(end_offset)}'"
|
125
|
+
schedule_interval = schedule_interval.nil? ? 'NULL' : "INTERVAL '#{stringify_interval(schedule_interval)}'"
|
126
|
+
|
127
|
+
execute "SELECT add_continuous_aggregate_policy('#{view_name}', start_offset => #{start_offset}, end_offset => #{end_offset}, schedule_interval => #{schedule_interval});" # rubocop:disable Layout/LineLength
|
128
|
+
end
|
129
|
+
|
130
|
+
# Removes refresh continuous aggregate policy
|
131
|
+
#
|
132
|
+
# remove_continuous_aggregate_policy('temperature_events')
|
133
|
+
#
|
134
|
+
def remove_continuous_aggregate_policy(view_name, _start_offset = nil,
|
135
|
+
_end_offset = nil, _schedule_interval = nil)
|
136
|
+
execute "SELECT remove_continuous_aggregate_policy('#{view_name}');"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# @param [ActiveSupport::Duration|String] interval
|
142
|
+
def stringify_interval(interval)
|
143
|
+
interval.is_a?(ActiveSupport::Duration) ? interval.inspect : interval
|
97
144
|
end
|
98
145
|
|
99
146
|
# @return [String]
|
100
147
|
def hypertable_options_to_sql(options)
|
101
148
|
sql_statements = options.map do |option, value|
|
102
149
|
case option
|
103
|
-
when :chunk_time_interval then "chunk_time_interval => INTERVAL '#{value}'"
|
150
|
+
when :chunk_time_interval then "chunk_time_interval => INTERVAL '#{stringify_interval(value)}'"
|
104
151
|
when :if_not_exists then "if_not_exists => #{value ? 'TRUE' : 'FALSE'}"
|
105
152
|
end
|
106
153
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
module Rails
|
5
|
+
module Model
|
6
|
+
# :nodoc:
|
7
|
+
module AggregateFunctions
|
8
|
+
def count(alias_name = 'count')
|
9
|
+
select("COUNT(1) AS #{alias_name}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def avg(column_name, alias_name = 'avg')
|
13
|
+
select("AVG(#{column_name}) AS #{alias_name}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def sum(column_name, alias_name = 'sum')
|
17
|
+
select("SUM(#{column_name}) AS #{alias_name}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def min(column_name, alias_name = 'min')
|
21
|
+
select("MIN(#{column_name}) AS #{alias_name}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def max(column_name, alias_name = 'max')
|
25
|
+
select("MAX(#{column_name}) AS #{alias_name}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
module Rails
|
5
|
+
module Model
|
6
|
+
# :nodoc:
|
7
|
+
module FinderMethods
|
8
|
+
# Adds a warning message to avoid calling find without filtering by time.
|
9
|
+
#
|
10
|
+
# @override
|
11
|
+
def find(*args)
|
12
|
+
warn "WARNING: Calling `.find` without filtering by `#{hypertable_time_column_name}` could cause performance issues, use built-in find_(at_time|between|after) methods for more performant results." # rubocop:disable Layout/LineLength
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# Finds records by primary key and chunk time.
|
18
|
+
#
|
19
|
+
# @param [Array<Integer>, Integer] id The primary key values.
|
20
|
+
# @param [Time, Date, Integer] time The chunk time value.
|
21
|
+
def find_at_time(id, time)
|
22
|
+
at_time(time).find(id)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Finds records by primary key and chunk time occurring between given time range.
|
26
|
+
#
|
27
|
+
# @param [Array<Integer>, Integer] id The primary key values.
|
28
|
+
# @param [Time, Date, Integer] from The chunk from time value.
|
29
|
+
# @param [Time, Date, Integer] to The chunk to time value.
|
30
|
+
def find_between(id, from, to)
|
31
|
+
between(from, to).find(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Finds records by primary key and chunk time occurring after given time.
|
35
|
+
#
|
36
|
+
# @param [Array<Integer>, Integer] id The primary key values.
|
37
|
+
# @param [Time, Date, Integer] from The chunk from time value.
|
38
|
+
def find_after(id, from)
|
39
|
+
after(from).find(id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timescaledb/rails/model/aggregate_functions'
|
4
|
+
|
5
|
+
module Timescaledb
|
6
|
+
module Rails
|
7
|
+
module Model
|
8
|
+
# :nodoc:
|
9
|
+
module Hyperfunctions
|
10
|
+
TIME_BUCKET_ALIAS = 'time_bucket'
|
11
|
+
|
12
|
+
# @return [ActiveRecord::Relation<ActiveRecord::Base>]
|
13
|
+
def time_bucket(interval, target_column = nil)
|
14
|
+
target_column ||= hypertable_time_column_name
|
15
|
+
|
16
|
+
select("time_bucket('#{format_interval_value(interval)}', #{target_column}) as #{TIME_BUCKET_ALIAS}")
|
17
|
+
.group(TIME_BUCKET_ALIAS)
|
18
|
+
.order(TIME_BUCKET_ALIAS)
|
19
|
+
.extending(AggregateFunctions)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def format_interval_value(value)
|
25
|
+
value.is_a?(ActiveSupport::Duration) ? value.inspect : value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -12,19 +12,19 @@ module Timescaledb
|
|
12
12
|
scope :last_year, lambda {
|
13
13
|
date = Date.current - 1.year
|
14
14
|
|
15
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
35
|
+
between(Date.current.beginning_of_year, Date.current.end_of_year)
|
36
36
|
}
|
37
37
|
|
38
38
|
scope :this_month, lambda {
|
39
|
-
|
39
|
+
between(Date.current.beginning_of_month, Date.current.end_of_month)
|
40
40
|
}
|
41
41
|
|
42
42
|
scope :this_week, lambda {
|
43
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
32
|
-
table_name.split('.')
|
33
|
-
|
34
|
-
|
35
|
-
|
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]
|
@@ -7,6 +7,8 @@ module Timescaledb
|
|
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
|
|
@@ -25,6 +27,23 @@ module Timescaledb
|
|
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
|
+
::ActiveRecord::Base.connection.execute(
|
44
|
+
"SELECT reorder_chunk(#{options.join(', ')})"
|
45
|
+
)
|
46
|
+
end
|
28
47
|
end
|
29
48
|
end
|
30
49
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
module Rails
|
5
|
+
module Models
|
6
|
+
# :nodoc:
|
7
|
+
module Durationable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
HOUR_MINUTE_SECOND_REGEX = /^\d+:\d+:\d+$/.freeze
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
def parse_duration(duration)
|
14
|
+
duration_in_seconds = duration_in_seconds(duration)
|
15
|
+
|
16
|
+
duration_to_interval(
|
17
|
+
ActiveSupport::Duration.build(duration_in_seconds)
|
18
|
+
)
|
19
|
+
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
|
20
|
+
duration
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Converts different interval formats into seconds.
|
26
|
+
#
|
27
|
+
# duration_in_seconds('P1D') #=> 86400
|
28
|
+
# duration_in_seconds('24:00:00') #=> 86400
|
29
|
+
# duration_in_seconds(1.day) #=> 86400
|
30
|
+
#
|
31
|
+
# @param [ActiveSupport::Duration|String] duration
|
32
|
+
# @return [Integer]
|
33
|
+
def duration_in_seconds(duration)
|
34
|
+
return duration.to_i if duration.is_a?(ActiveSupport::Duration)
|
35
|
+
|
36
|
+
if (duration =~ HOUR_MINUTE_SECOND_REGEX).present?
|
37
|
+
hours, minutes, seconds = duration.split(':').map(&:to_i)
|
38
|
+
|
39
|
+
(hours.hour + minutes.minute + seconds.second).to_i
|
40
|
+
else
|
41
|
+
ActiveSupport::Duration.parse(duration).to_i
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Converts given duration into a human interval readable format.
|
46
|
+
#
|
47
|
+
# duration_to_interval(1.day) #=> '1 day'
|
48
|
+
# duration_to_interval(2.weeks + 6.days) #=> '20 days'
|
49
|
+
# duration_to_interval(1.years + 3.months) #=> '1 year 3 months'
|
50
|
+
#
|
51
|
+
# @param [ActiveSupport::Duration] duration
|
52
|
+
# @return [String]
|
53
|
+
def duration_to_interval(duration)
|
54
|
+
parts = duration.parts
|
55
|
+
|
56
|
+
# Combine days and weeks if both present
|
57
|
+
#
|
58
|
+
# "1 week 2 days" => "9 days"
|
59
|
+
parts[:days] += parts.delete(:weeks) * 7 if parts.key?(:weeks) && parts.key?(:days)
|
60
|
+
|
61
|
+
parts.map do |(unit, quantity)|
|
62
|
+
"#{quantity} #{humanize_duration_unit(unit.to_s, quantity)}"
|
63
|
+
end.join(' ')
|
64
|
+
end
|
65
|
+
|
66
|
+
# Pluralize or singularize given duration unit based on given count.
|
67
|
+
#
|
68
|
+
# @param [String] duration_unit
|
69
|
+
# @param [Integer] count
|
70
|
+
def humanize_duration_unit(duration_unit, count)
|
71
|
+
count > 1 ? duration_unit.pluralize : duration_unit.singularize
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timescaledb/rails/models/concerns/durationable'
|
4
|
+
|
5
|
+
module Timescaledb
|
6
|
+
module Rails
|
7
|
+
# :nodoc:
|
8
|
+
class ContinuousAggregate < ::ActiveRecord::Base
|
9
|
+
include Timescaledb::Rails::Models::Durationable
|
10
|
+
|
11
|
+
self.table_name = 'timescaledb_information.continuous_aggregates'
|
12
|
+
self.primary_key = 'materialization_hypertable_name'
|
13
|
+
|
14
|
+
has_many :jobs, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Job'
|
15
|
+
|
16
|
+
# Manually refresh a continuous aggregate.
|
17
|
+
#
|
18
|
+
# @param [DateTime] start_time
|
19
|
+
# @param [DateTime] end_time
|
20
|
+
#
|
21
|
+
def refresh!(start_time = 'NULL', end_time = 'NULL')
|
22
|
+
::ActiveRecord::Base.connection.execute(
|
23
|
+
"CALL refresh_continuous_aggregate('#{view_name}', #{start_time}, #{end_time});"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def refresh_start_offset
|
29
|
+
parse_duration(refresh_job.config['start_offset'])
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String]
|
33
|
+
def refresh_end_offset
|
34
|
+
parse_duration(refresh_job.config['end_offset'])
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String]
|
38
|
+
def refresh_schedule_interval
|
39
|
+
interval = refresh_job.schedule_interval
|
40
|
+
|
41
|
+
interval.is_a?(String) ? parse_duration(interval) : interval.inspect
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Boolean]
|
45
|
+
def refresh?
|
46
|
+
refresh_job.present?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# @return [Job]
|
52
|
+
def refresh_job
|
53
|
+
@refresh_job ||= jobs.policy_refresh_continuous_aggregate.first
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,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
8
|
class Hypertable < ::ActiveRecord::Base
|
9
|
+
include Timescaledb::Rails::Models::Durationable
|
10
|
+
|
7
11
|
self.table_name = 'timescaledb_information.hypertables'
|
8
12
|
self.primary_key = 'hypertable_name'
|
9
13
|
|
14
|
+
has_many :continuous_aggregates, foreign_key: 'hypertable_name',
|
15
|
+
class_name: 'Timescaledb::Rails::ContinuousAggregate'
|
10
16
|
has_many :compression_settings, foreign_key: 'hypertable_name',
|
11
17
|
class_name: 'Timescaledb::Rails::CompressionSetting'
|
12
18
|
has_many :dimensions, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Dimension'
|
@@ -86,13 +92,6 @@ module Timescaledb
|
|
86
92
|
def time_dimension
|
87
93
|
@time_dimension ||= dimensions.time.first
|
88
94
|
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
95
|
end
|
97
96
|
end
|
98
97
|
end
|
@@ -10,10 +10,12 @@ module Timescaledb
|
|
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
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timescaledb-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Iván Etchart
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-01-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -139,10 +139,15 @@ 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
|
144
147
|
- lib/timescaledb/rails/models/chunk.rb
|
145
148
|
- lib/timescaledb/rails/models/compression_setting.rb
|
149
|
+
- lib/timescaledb/rails/models/concerns/durationable.rb
|
150
|
+
- lib/timescaledb/rails/models/continuous_aggregate.rb
|
146
151
|
- lib/timescaledb/rails/models/dimension.rb
|
147
152
|
- lib/timescaledb/rails/models/hypertable.rb
|
148
153
|
- lib/timescaledb/rails/models/job.rb
|
@@ -169,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
174
|
- !ruby/object:Gem::Version
|
170
175
|
version: '0'
|
171
176
|
requirements: []
|
172
|
-
rubygems_version: 3.3.
|
177
|
+
rubygems_version: 3.0.3.1
|
173
178
|
signing_key:
|
174
179
|
specification_version: 4
|
175
180
|
summary: TimescaleDB Rails integration
|