timescaledb-rails 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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