timescaledb-rails 0.1.2 → 0.1.4

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: af4399bfd70056ed23cfb0994580a8c61fcd88722a72ce7839761b305c6d2029
4
- data.tar.gz: dad61e01554eee6ee23fd780a6745196d7fb5b3703398c795bbe3e00dfbe028b
3
+ metadata.gz: 12170e03389f6ac4f3bba7816dfb20dc75a47103032ec3e3799f3c78dca25a4b
4
+ data.tar.gz: f26796af01f755648e952f9e851f24d05d7adb3439e3653d793beef624ea8044
5
5
  SHA512:
6
- metadata.gz: 1679b7421eca2f5b557b83a7b338f72444d073009f532b77d75e8e233c07828750d02afc96f4b2ee0a5c61ce6c750f03832742621df1187529cddcec3e46995f
7
- data.tar.gz: 3628e056c77754629a65c2ef343c59702272bbec882b47f2e70564fbd1008080729e05017f923a0145d78e186bb272a5e13394605ccf96bcb39a34f520c3ad9a
6
+ metadata.gz: 22c4c72512895fadb2a7701222e6b890b60a15948dc9c6b5c01ded24afff9865d6ebc954673452b55b95ac68b18c933dc2bf7aec6ac135285ecfa950d346e8e3
7
+ data.tar.gz: f67c442f8d2eb837566f8cc3a450d809a769343799997f3979a222bd0413c2ce89fad70b9b8f3e96267ab61e8bcf3cc2c167c15a60db1f1144ea17daf9cfee36
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
- # TimescaleDB extension for Rails [![Actions Status](https://github.com/crunchloop/timescaledb-rails/workflows/CI/badge.svg?branch=main)](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 [![Gem Version](https://badge.fury.io/rb/timescaledb-rails.svg)](https://badge.fury.io/rb/timescaledb-rails) [![Actions Status](https://github.com/crunchloop/timescaledb-rails/workflows/CI/badge.svg?branch=main)](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
- ## Examples
19
+ ## Usage
20
+
21
+ ### Migrations
21
22
 
22
- Create a hypertable from a PostgreSQL table by doing:
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 :occured_at, null: false
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 by doing:
40
+ Create a hypertable without a PostgreSQL table
40
41
 
41
42
  ```ruby
42
43
  class CreatePayloadHypertable < ActiveRecord::Migration[7.0]
@@ -50,19 +51,127 @@ class CreatePayloadHypertable < ActiveRecord::Migration[7.0]
50
51
  end
51
52
  ```
52
53
 
53
- Enable hypertable compression by doing:
54
+ Add hypertable compression
54
55
 
55
56
  ```ruby
56
57
  class AddEventCompression < ActiveRecord::Migration[7.0]
57
- def change
58
- add_hypertable_compression :events, 20.days, segment_by: :name, order_by: 'occured_at DESC'
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
64
+ end
65
+ end
66
+ ```
67
+
68
+ Add hypertable retention policy
69
+
70
+ ```ruby
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
59
92
  end
60
93
  end
61
94
  ```
62
95
 
96
+ ### Models
97
+
98
+ If one of your models need TimescaleDB support, just include `Timescaledb::Rails::Model`
99
+ ```ruby
100
+ class Event < ActiveRecord::Base
101
+ include Timescaledb::Rails::Model
102
+ end
103
+ ```
104
+
105
+ If the hypertable does not belong to the default schema, don't forget to override `table_name`
106
+
107
+ ```ruby
108
+ class Event < ActiveRecord::Base
109
+ include Timescaledb::Rails::Model
110
+
111
+ self.table_name = 'v1.events'
112
+ end
113
+ ```
114
+
115
+ If you need to query data for a specific time period, `Timescaledb::Rails::Model` incluldes useful scopes
116
+
117
+ ```ruby
118
+ # If you want to get all records from last year
119
+ Event.last_year #=> [#<Event name...>, ...]
120
+
121
+ # Or if you want to get records from this year
122
+ Event.this_year #=> [#<Event name...>, ...]
123
+
124
+ # Or even getting records from today
125
+ Event.today #=> [#<Event name...>, ...]
126
+ ```
127
+
128
+ Here the list of all available scopes
129
+
130
+ * last_year
131
+ * last_month
132
+ * last_week
133
+ * this_year
134
+ * this_month
135
+ * this_week
136
+ * yesterday
137
+ * today
138
+
139
+ If you need information about your hypertable, use the following helper methods to get useful information
140
+
141
+ ```ruby
142
+ # Hypertable metadata
143
+ Event.hypertable #=> #<Timescaledb::Rails::Hypertable ...>
144
+
145
+ # Hypertable chunks metadata
146
+ Event.hypertable_chunks #=> [#<Timescaledb::Rails::Chunk ...>, ...]
147
+
148
+ # Hypertable jobs, it includes jobs like compression, retention or reorder policies, etc.
149
+ Event.hypertable_jobs #=> [#<Timescaledb::Rails::Job ...>, ...]
150
+
151
+ # Hypertable dimensions, like time or space dimensions
152
+ Event.hypertable_dimensions #=> [#<Timescaledb::Rails::Dimension ...>, ...]
153
+
154
+ # Hypertable compression settings
155
+ Event.hypertable_compression_settings #=> [#<Timescaledb::Rails::CompressionSetting ...>, ...]
156
+ ```
157
+
158
+ If you need to compress or decompress a specific chunk
159
+
160
+ ```ruby
161
+ chunk = Event.hypertable_chunks.first
162
+
163
+ chunk.compress! unless chunk.is_compressed?
164
+
165
+ chunk.decompress! if chunk.is_compressed?
166
+ ```
167
+
168
+ ## Contributing
169
+
170
+ Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests.
171
+
63
172
  ## Supported Ruby/Rails versions
64
173
 
65
- Supported Ruby/Rails versions are listed in [`.github/workflows/ci.yaml`](https://github.com/crunchloop/timescaledb-rails/blob/main/.github/workflows/ci.yaml)
174
+ Supported Ruby/Rails versions are listed in [`.github/workflows/ci.yaml`](./.github/workflows/ci.yaml)
66
175
 
67
176
  ## License
68
177
 
@@ -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
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ module ActiveRecord
6
+ # :nodoc:
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)
34
+ end
35
+
36
+ def invert_create_hypertable(args, &block)
37
+ if block.nil?
38
+ raise ::ActiveRecord::IrreversibleMigration, 'create_hypertable is only reversible if given a block (can be empty).' # rubocop:disable Layout/LineLength
39
+ end
40
+
41
+ [:drop_table, args.first, block]
42
+ end
43
+
44
+ def invert_add_hypertable_compression(args, &block)
45
+ [:remove_hypertable_compression, args, block]
46
+ end
47
+
48
+ def invert_remove_hypertable_compression(args, &block)
49
+ 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
51
+ end
52
+
53
+ [:add_hypertable_compression, args, block]
54
+ end
55
+
56
+ def invert_add_hypertable_retention_policy(args, &block)
57
+ [:remove_hypertable_retention_policy, args, block]
58
+ end
59
+
60
+ def invert_remove_hypertable_retention_policy(args, &block)
61
+ if args.size < 2
62
+ raise ::ActiveRecord::IrreversibleMigration, 'remove_hypertable_retention_policy is only reversible if given table name and drop after period.' # rubocop:disable Layout/LineLength
63
+ end
64
+
65
+ [:add_hypertable_retention_policy, args, block]
66
+ end
67
+
68
+ def invert_add_hypertable_reorder_policy(args, &block)
69
+ [:remove_hypertable_reorder_policy, args, block]
70
+ end
71
+
72
+ def invert_remove_hypertable_reorder_policy(args, &block)
73
+ if args.size < 2
74
+ raise ::ActiveRecord::IrreversibleMigration, 'remove_hypertable_reorder_policy is only reversible if given table name and index name.' # rubocop:disable Layout/LineLength
75
+ end
76
+
77
+ [:add_hypertable_reorder_policy, args, block]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -9,8 +9,11 @@ module Timescaledb
9
9
  # :nodoc:
10
10
  module PostgreSQLDatabaseTasks
11
11
  # @override
12
- def structure_dump(filename, extra_flags)
13
- super
12
+ def structure_dump(filename, extra_flags) # rubocop:disable Metrics/MethodLength
13
+ extra_flags = Array(extra_flags)
14
+ extra_flags << timescale_structure_dump_default_flags if timescale_enabled?
15
+
16
+ super(filename, extra_flags)
14
17
 
15
18
  return unless timescale_enabled?
16
19
 
@@ -19,6 +22,8 @@ module Timescaledb
19
22
  drop_ts_insert_trigger_statment(hypertable, file)
20
23
  create_hypertable_statement(hypertable, file)
21
24
  add_hypertable_compression_statement(hypertable, file)
25
+ add_hypertable_reorder_policy_statement(hypertable, file)
26
+ add_hypertable_retention_policy_statement(hypertable, file)
22
27
  end
23
28
  end
24
29
  end
@@ -45,30 +50,57 @@ module Timescaledb
45
50
  file << "SELECT add_compression_policy('#{hypertable.hypertable_name}', INTERVAL '#{hypertable.compression_policy_interval}');\n\n" # rubocop:disable Layout/LineLength
46
51
  end
47
52
 
53
+ def add_hypertable_reorder_policy_statement(hypertable, file)
54
+ return unless hypertable.reorder?
55
+
56
+ file << "SELECT add_reorder_policy('#{hypertable.hypertable_name}', '#{hypertable.reorder_policy_index_name}');\n\n" # rubocop:disable Layout/LineLength
57
+ end
58
+
59
+ def add_hypertable_retention_policy_statement(hypertable, file)
60
+ return unless hypertable.retention?
61
+
62
+ file << "SELECT add_retention_policy('#{hypertable.hypertable_name}', INTERVAL '#{hypertable.retention_policy_interval}');\n\n" # rubocop:disable Layout/LineLength
63
+ end
64
+
48
65
  def hypertable_options(hypertable)
49
66
  sql_statements = ["if_not_exists => 'TRUE'"]
50
- sql_statements << "chunk_time_interval => INTERVAL '#{hypertable.chunk_time_interval.inspect}'"
67
+ sql_statements << "chunk_time_interval => INTERVAL '#{hypertable.chunk_time_interval}'"
51
68
 
52
69
  sql_statements.compact.join(', ')
53
70
  end
54
71
 
55
72
  def hypertable_compression_options(hypertable)
56
- segmentby_setting = hypertable.compression_settings.segmentby_setting.first
57
- orderby_setting = hypertable.compression_settings.orderby_setting.first
58
-
59
73
  sql_statements = ['timescaledb.compress']
60
- sql_statements << "timescaledb.compress_segmentby = '#{segmentby_setting.attname}'" if segmentby_setting
61
74
 
62
- if orderby_setting
63
- orderby = Timescaledb::Rails::OrderbyCompression.new(orderby_setting.attname,
64
- orderby_setting.orderby_asc).to_s
75
+ if (segments = compression_segment_settings(hypertable)).present?
76
+ sql_statements << "timescaledb.compress_segmentby = '#{segments.join(', ')}'"
77
+ end
65
78
 
66
- sql_statements << "timescaledb.compress_orderby = '#{orderby}'"
79
+ if (orders = compression_order_settings(hypertable)).present?
80
+ sql_statements << "timescaledb.compress_orderby = '#{orders.join(', ')}'"
67
81
  end
68
82
 
69
83
  sql_statements.join(', ')
70
84
  end
71
85
 
86
+ def compression_order_settings(hypertable)
87
+ hypertable.compression_order_settings.map do |os|
88
+ Timescaledb::Rails::OrderbyCompression.new(os.attname, os.orderby_asc).to_s
89
+ end
90
+ end
91
+
92
+ def compression_segment_settings(hypertable)
93
+ hypertable.compression_segment_settings.map(&:attname)
94
+ end
95
+
96
+ # Returns `pg_dump` flag to exclude `_timescaledb_internal` schema tables.
97
+ #
98
+ # @return [String]
99
+ def timescale_structure_dump_default_flags
100
+ '--exclude-schema=_timescaledb_internal'
101
+ end
102
+
103
+ # @return [Boolean]
72
104
  def timescale_enabled?
73
105
  Timescaledb::Rails::Hypertable.table_exists?
74
106
  end
@@ -16,6 +16,8 @@ module Timescaledb
16
16
 
17
17
  hypertable(hypertable, stream)
18
18
  hypertable_compression(hypertable, stream)
19
+ hypertable_reorder(hypertable, stream)
20
+ hypertable_retention(hypertable, stream)
19
21
  end
20
22
 
21
23
  private
@@ -38,6 +40,24 @@ module Timescaledb
38
40
  stream.puts
39
41
  end
40
42
 
43
+ def hypertable_reorder(hypertable, stream)
44
+ return unless hypertable.reorder?
45
+
46
+ options = [hypertable.hypertable_name.inspect, hypertable.reorder_policy_index_name.inspect]
47
+
48
+ stream.puts " add_hypertable_reorder_policy #{options.join(', ')}"
49
+ stream.puts
50
+ end
51
+
52
+ def hypertable_retention(hypertable, stream)
53
+ return unless hypertable.retention?
54
+
55
+ options = [hypertable.hypertable_name.inspect, hypertable.retention_policy_interval.inspect]
56
+
57
+ stream.puts " add_hypertable_retention_policy #{options.join(', ')}"
58
+ stream.puts
59
+ end
60
+
41
61
  def hypertable_options(hypertable)
42
62
  options = {
43
63
  chunk_time_interval: hypertable.chunk_time_interval
@@ -50,21 +70,27 @@ module Timescaledb
50
70
  end
51
71
 
52
72
  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
73
  [].tap do |result|
57
- result << "segment_by: #{segmentby_setting.attname.inspect}" if segmentby_setting
58
-
59
- if orderby_setting
60
- orderby = Timescaledb::Rails::OrderbyCompression.new(orderby_setting.attname,
61
- orderby_setting.orderby_asc).to_s
74
+ if (segments = compression_segment_settings(hypertable)).present?
75
+ result << "segment_by: #{segments.join(', ').inspect}"
76
+ end
62
77
 
63
- result << "order_by: #{orderby.inspect}"
78
+ if (orders = compression_order_settings(hypertable)).present?
79
+ result << "order_by: #{orders.join(', ').inspect}"
64
80
  end
65
81
  end
66
82
  end
67
83
 
84
+ def compression_order_settings(hypertable)
85
+ hypertable.compression_order_settings.map do |os|
86
+ Timescaledb::Rails::OrderbyCompression.new(os.attname, os.orderby_asc).to_s
87
+ end
88
+ end
89
+
90
+ def compression_segment_settings(hypertable)
91
+ hypertable.compression_segment_settings.map(&:attname)
92
+ end
93
+
68
94
  def format_hypertable_option_value(value)
69
95
  case value
70
96
  when String then value.inspect
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/connection_adapters/postgresql_adapter'
4
-
5
3
  module Timescaledb
6
4
  module Rails
7
5
  module ActiveRecord
8
6
  # :nodoc:
9
7
  module SchemaStatements
8
+ # Returns an array of hypertable names defined in the database.
9
+ def hypertables
10
+ query_values('SELECT hypertable_name FROM timescaledb_information.hypertables')
11
+ end
12
+
13
+ # Checks to see if the hypertable exists on the database.
14
+ #
15
+ # hypertable_exists?(:developers)
16
+ #
17
+ def hypertable_exists?(hypertable)
18
+ query_value(
19
+ <<-SQL.squish
20
+ SELECT COUNT(*) FROM timescaledb_information.hypertables WHERE hypertable_name = #{quote(hypertable)}
21
+ SQL
22
+ ).to_i.positive?
23
+ end
24
+
10
25
  # Converts given standard PG table into a hypertable.
11
26
  #
12
27
  # create_hypertable('readings', 'created_at', chunk_time_interval: '7 days')
@@ -38,9 +53,50 @@ module Timescaledb
38
53
 
39
54
  execute "ALTER TABLE #{table_name} SET (#{options.join(', ')})"
40
55
 
41
- execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compress_after}')"
56
+ execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compress_after.inspect}')"
57
+ end
58
+
59
+ # Disables compression from given table.
60
+ #
61
+ # remove_hypertable_compression('events')
62
+ #
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}');"
65
+ end
66
+
67
+ # Add a data retention policy to given hypertable.
68
+ #
69
+ # add_hypertable_retention_policy('events', 7.days)
70
+ #
71
+ def add_hypertable_retention_policy(table_name, drop_after)
72
+ execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{drop_after.inspect}')"
73
+ end
74
+
75
+ # Removes data retention policy from given hypertable.
76
+ #
77
+ # remove_hypertable_retention_policy('events')
78
+ #
79
+ def remove_hypertable_retention_policy(table_name, _drop_after = nil)
80
+ execute "SELECT remove_retention_policy('#{table_name}')"
81
+ end
82
+
83
+ # Adds a policy to reorder chunks on a given hypertable index in the background.
84
+ #
85
+ # add_hypertable_reorder_policy('events', 'index_events_on_created_at_and_name')
86
+ #
87
+ def add_hypertable_reorder_policy(table_name, index_name)
88
+ execute "SELECT add_reorder_policy('#{table_name}', '#{index_name}')"
89
+ end
90
+
91
+ # Removes a policy to reorder a particular hypertable.
92
+ #
93
+ # remove_hypertable_reorder_policy('events')
94
+ #
95
+ def remove_hypertable_reorder_policy(table_name, _index_name = nil)
96
+ execute "SELECT remove_reorder_policy('#{table_name}')"
42
97
  end
43
98
 
99
+ # @return [String]
44
100
  def hypertable_options_to_sql(options)
45
101
  sql_statements = options.map do |option, value|
46
102
  case option
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ module Model
6
+ # :nodoc:
7
+ module Scopes
8
+ extend ActiveSupport::Concern
9
+
10
+ # rubocop:disable Metrics/BlockLength
11
+ included do
12
+ scope :last_year, lambda {
13
+ date = Date.current - 1.year
14
+
15
+ between_time_column(date.beginning_of_year, date.end_of_year)
16
+ }
17
+
18
+ scope :last_month, lambda {
19
+ date = Date.current - 1.month
20
+
21
+ between_time_column(date.beginning_of_month, date.end_of_month)
22
+ }
23
+
24
+ scope :last_week, lambda {
25
+ date = Date.current - 1.week
26
+
27
+ between_time_column(date.beginning_of_week, date.end_of_week)
28
+ }
29
+
30
+ scope :yesterday, lambda {
31
+ where("DATE(#{hypertable_time_column_name}) = ?", Date.current - 1.day)
32
+ }
33
+
34
+ scope :this_year, lambda {
35
+ between_time_column(Date.current.beginning_of_year, Date.current.end_of_year)
36
+ }
37
+
38
+ scope :this_month, lambda {
39
+ between_time_column(Date.current.beginning_of_month, Date.current.end_of_month)
40
+ }
41
+
42
+ scope :this_week, lambda {
43
+ between_time_column(Date.current.beginning_of_week, Date.current.end_of_week)
44
+ }
45
+
46
+ scope :today, lambda {
47
+ where("DATE(#{hypertable_time_column_name}) = ?", Date.current)
48
+ }
49
+
50
+ # @!visibility private
51
+ scope :between_time_column, lambda { |from, to|
52
+ where("DATE(#{hypertable_time_column_name}) BETWEEN ? AND ?", from, to)
53
+ }
54
+ end
55
+ # rubocop:enable Metrics/BlockLength
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timescaledb/rails/model/scopes'
4
+
5
+ module Timescaledb
6
+ module Rails
7
+ # :nodoc:
8
+ module Model
9
+ PUBLIC_SCHEMA_NAME = 'public'
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ include Scopes
14
+
15
+ # :nodoc:
16
+ module ClassMethods
17
+ delegate :time_column_name, to: :hypertable, prefix: true
18
+
19
+ # Returns only the name of the hypertable, table_name could include
20
+ # the schema path, we need to remove it.
21
+ #
22
+ # @return [String]
23
+ def hypertable_name
24
+ table_name.split('.').last
25
+ end
26
+
27
+ # Returns the schema where hypertable is stored.
28
+ #
29
+ # @return [String]
30
+ 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
36
+ end
37
+
38
+ # @return [Timescaledb::Rails::Hypertable]
39
+ def hypertable
40
+ Timescaledb::Rails::Hypertable.find_by(hypertable_where_options)
41
+ end
42
+
43
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::Chunk>]
44
+ def hypertable_chunks
45
+ Timescaledb::Rails::Chunk.where(hypertable_where_options)
46
+ end
47
+
48
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::Job>]
49
+ def hypertable_jobs
50
+ Timescaledb::Rails::Job.where(hypertable_where_options)
51
+ end
52
+
53
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::Dimension>]
54
+ def hypertable_dimensions
55
+ Timescaledb::Rails::Dimension.where(hypertable_where_options)
56
+ end
57
+
58
+ # @return [ActiveRecord::Relation<Timescaledb::Rails::CompressionSetting>]
59
+ def hypertable_compression_settings
60
+ Timescaledb::Rails::CompressionSetting.where(hypertable_where_options)
61
+ end
62
+
63
+ private
64
+
65
+ # Returns hypertable name and schema.
66
+ #
67
+ # @return [Hash]
68
+ def hypertable_where_options
69
+ { hypertable_name: hypertable_name, hypertable_schema: hypertable_schema }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timescaledb
4
+ module Rails
5
+ # :nodoc:
6
+ class Chunk < ::ActiveRecord::Base
7
+ self.table_name = 'timescaledb_information.chunks'
8
+ self.primary_key = 'hypertable_name'
9
+
10
+ scope :compressed, -> { where(is_compressed: true) }
11
+ scope :decompressed, -> { where(is_compressed: false) }
12
+
13
+ def chunk_full_name
14
+ "#{chunk_schema}.#{chunk_name}"
15
+ end
16
+
17
+ def compress!
18
+ ::ActiveRecord::Base.connection.execute(
19
+ "SELECT compress_chunk('#{chunk_full_name}')"
20
+ )
21
+ end
22
+
23
+ def decompress!
24
+ ::ActiveRecord::Base.connection.execute(
25
+ "SELECT decompress_chunk('#{chunk_full_name}')"
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -11,6 +11,7 @@ module Timescaledb
11
11
  class_name: 'Timescaledb::Rails::CompressionSetting'
12
12
  has_many :dimensions, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Dimension'
13
13
  has_many :jobs, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Job'
14
+ has_many :chunks, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Chunk'
14
15
 
15
16
  # @return [String]
16
17
  def time_column_name
@@ -19,14 +20,34 @@ module Timescaledb
19
20
 
20
21
  # @return [String]
21
22
  def chunk_time_interval
22
- time_dimension.time_interval
23
+ interval = time_dimension.time_interval
24
+
25
+ interval.is_a?(String) ? interval : interval.inspect
26
+ end
27
+
28
+ # @return [ActiveRecord::Relation<CompressionSetting>]
29
+ def compression_segment_settings
30
+ compression_settings.segmentby_setting
31
+ end
32
+
33
+ # @return [ActiveRecord::Relation<CompressionSetting>]
34
+ def compression_order_settings
35
+ compression_settings.orderby_setting.where.not(attname: time_column_name)
23
36
  end
24
37
 
25
38
  # @return [String]
26
39
  def compression_policy_interval
27
- ActiveSupport::Duration.parse(compression_job.config['compress_after']).inspect
28
- rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
29
- compression_job.config['compress_after']
40
+ parse_duration(compression_job.config['compress_after'])
41
+ end
42
+
43
+ # @return [String]
44
+ def reorder_policy_index_name
45
+ reorder_job.config['index_name']
46
+ end
47
+
48
+ # @return [String]
49
+ def retention_policy_interval
50
+ parse_duration(retention_job.config['drop_after'])
30
51
  end
31
52
 
32
53
  # @return [Boolean]
@@ -34,8 +55,28 @@ module Timescaledb
34
55
  compression_job.present?
35
56
  end
36
57
 
58
+ # @return [Boolean]
59
+ def reorder?
60
+ reorder_job.present?
61
+ end
62
+
63
+ # @return [Boolean]
64
+ def retention?
65
+ retention_job.present?
66
+ end
67
+
37
68
  private
38
69
 
70
+ # @return [Job]
71
+ def reorder_job
72
+ @reorder_job ||= jobs.policy_reorder.first
73
+ end
74
+
75
+ # @return [Job]
76
+ def retention_job
77
+ @retention_job ||= jobs.policy_retention.first
78
+ end
79
+
39
80
  # @return [Job]
40
81
  def compression_job
41
82
  @compression_job ||= jobs.policy_compression.first
@@ -45,6 +86,13 @@ module Timescaledb
45
86
  def time_dimension
46
87
  @time_dimension ||= dimensions.time.first
47
88
  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
48
96
  end
49
97
  end
50
98
  end
@@ -8,8 +8,12 @@ module Timescaledb
8
8
  self.primary_key = 'hypertable_name'
9
9
 
10
10
  POLICY_COMPRESSION = 'policy_compression'
11
+ POLICY_REORDER = 'policy_reorder'
12
+ POLICY_RETENTION = 'policy_retention'
11
13
 
12
14
  scope :policy_compression, -> { where(proc_name: POLICY_COMPRESSION) }
15
+ scope :policy_reorder, -> { where(proc_name: POLICY_REORDER) }
16
+ scope :policy_retention, -> { where(proc_name: POLICY_RETENTION) }
13
17
  end
14
18
  end
15
19
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './models/chunk'
3
4
  require_relative './models/compression_setting'
4
5
  require_relative './models/dimension'
5
- require_relative './models/job'
6
6
  require_relative './models/hypertable'
7
+ require_relative './models/job'
@@ -2,11 +2,6 @@
2
2
 
3
3
  require 'rails'
4
4
 
5
- require_relative 'extensions/active_record/database_tasks'
6
- require_relative 'extensions/active_record/postgresql_database_tasks'
7
- require_relative 'extensions/active_record/schema_dumper'
8
- require_relative 'extensions/active_record/schema_statements'
9
-
10
5
  module Timescaledb
11
6
  module Rails
12
7
  # :nodoc:
@@ -19,21 +14,7 @@ module Timescaledb
19
14
 
20
15
  initializer 'timescaledb-rails.add_timescale_support_to_active_record' do
21
16
  ActiveSupport.on_load(:active_record) do
22
- ::ActiveRecord::Tasks::DatabaseTasks.extend(
23
- Timescaledb::Rails::ActiveRecord::DatabaseTasks
24
- )
25
-
26
- ::ActiveRecord::Tasks::PostgreSQLDatabaseTasks.prepend(
27
- Timescaledb::Rails::ActiveRecord::PostgreSQLDatabaseTasks
28
- )
29
-
30
- ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(
31
- Timescaledb::Rails::ActiveRecord::SchemaStatements
32
- )
33
-
34
- ::ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend(
35
- Timescaledb::Rails::ActiveRecord::SchemaDumper
36
- )
17
+ Timescaledb::Rails.load
37
18
  end
38
19
  end
39
20
  end
@@ -3,6 +3,6 @@
3
3
  module Timescaledb
4
4
  # :nodoc:
5
5
  module Rails
6
- VERSION = '0.1.2'
6
+ VERSION = '0.1.4'
7
7
  end
8
8
  end
@@ -1,7 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './rails/model'
4
+
5
+ require_relative './rails/extensions/active_record/base'
6
+ require_relative './rails/extensions/active_record/command_recorder'
7
+ require_relative './rails/extensions/active_record/postgresql_database_tasks'
8
+ require_relative './rails/extensions/active_record/schema_dumper'
9
+ require_relative './rails/extensions/active_record/schema_statements'
10
+
3
11
  module Timescaledb
4
12
  # :nodoc:
5
13
  module Rails
14
+ # Adds TimescaleDB support to ActiveRecord.
15
+ def self.load
16
+ ::ActiveRecord::Migration::CommandRecorder.prepend(ActiveRecord::CommandRecorder)
17
+ ::ActiveRecord::Tasks::PostgreSQLDatabaseTasks.prepend(ActiveRecord::PostgreSQLDatabaseTasks)
18
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::SchemaStatements)
19
+ ::ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend(ActiveRecord::SchemaDumper)
20
+ ::ActiveRecord::Base.include(ActiveRecord::Base) # rubocop:disable Rails/ActiveSupportOnLoad
21
+ end
6
22
  end
7
23
  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.2
4
+ version: 0.1.4
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: 2022-11-24 00:00:00.000000000 Z
12
+ date: 2022-12-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '6.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: debug
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: pg
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -119,11 +133,15 @@ files:
119
133
  - README.md
120
134
  - lib/timescaledb-rails.rb
121
135
  - lib/timescaledb/rails.rb
122
- - lib/timescaledb/rails/extensions/active_record/database_tasks.rb
136
+ - lib/timescaledb/rails/extensions/active_record/base.rb
137
+ - lib/timescaledb/rails/extensions/active_record/command_recorder.rb
123
138
  - lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb
124
139
  - lib/timescaledb/rails/extensions/active_record/schema_dumper.rb
125
140
  - lib/timescaledb/rails/extensions/active_record/schema_statements.rb
141
+ - lib/timescaledb/rails/model.rb
142
+ - lib/timescaledb/rails/model/scopes.rb
126
143
  - lib/timescaledb/rails/models.rb
144
+ - lib/timescaledb/rails/models/chunk.rb
127
145
  - lib/timescaledb/rails/models/compression_setting.rb
128
146
  - lib/timescaledb/rails/models/dimension.rb
129
147
  - lib/timescaledb/rails/models/hypertable.rb
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Timescaledb
4
- module Rails
5
- module ActiveRecord
6
- # :nodoc:
7
- module DatabaseTasks
8
- # @override
9
- def structure_dump_flags_for(adapter)
10
- return (flags = super) if adapter != 'postgresql'
11
-
12
- if flags.nil?
13
- timescaledb_structure_dump_default_flags
14
- elsif flags.is_a?(Array)
15
- flags << timescaledb_structure_dump_default_flags
16
- elsif flags.is_a?(String)
17
- "#{flags} #{timescaledb_structure_dump_default_flags}"
18
- else
19
- flags
20
- end
21
- end
22
-
23
- # Returns `pg_dump` flag to exclude `_timescaledb_internal` schema tables.
24
- #
25
- # @return [String]
26
- def timescaledb_structure_dump_default_flags
27
- '--exclude-schema=_timescaledb_internal'
28
- end
29
- end
30
- end
31
- end
32
- end