timescaledb 0.2.9 → 0.3.1

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: 89553264709636b53e3d56174f3e244561c10f2c4801b922ba25adb8bf32aca7
4
- data.tar.gz: f254f76133d13d6854f8f7dee865a0f3bb4adc57617c200ee276d1408541475a
3
+ metadata.gz: 0f5295cfa57caa17bb740b28dd70f54fb0c239f9d011233ae04cfffa2bbfadbf
4
+ data.tar.gz: ec1b742c312d289124522e490fe47269d7aa6b9774ce0d82fe99bed206ce227c
5
5
  SHA512:
6
- metadata.gz: b30bbba9b0da08b3cad42fbc80e73c7aed2e37763d01cfd2b93d569e4bd65768a507fc6e25a757c7b83bd4f9ad3e7e18d98090b7a2f2e8bebcb5d16fb3a63d7c
7
- data.tar.gz: bb0ba7379e86631d71256cf66cc506eb42511145f17e343e749bc10e28b7c394a482e9d4a3380ff9ee2af511be51d44529556566fe08421aa92b924755d492c8
6
+ metadata.gz: f1a0937798a8f97bccaf8a39d61268fc040f525f04b92632e1a0fc7c8d892d73b39d7f4fd7252fe430ac3f53523b5a01570bc89257aff8cef35fa68c161204e4
7
+ data.tar.gz: 460df42d48ceac9d552c47408655a9c9330b3efe9bc40d8963021e7c2f4e297d4210c54fc6fe43678f56dc156ac28836f7b06d17d11977fa82a134cb356c9127
@@ -35,7 +35,7 @@ module Timescaledb
35
35
  CompressionSettings.where(hypertable_name: table_name)
36
36
  end
37
37
 
38
- scope :continuous_aggregates, -> do
38
+ scope :caggs, -> do
39
39
  ContinuousAggregates.where(hypertable_name: table_name)
40
40
  end
41
41
  end
@@ -22,7 +22,8 @@ module Timescaledb
22
22
  # for configuration options
23
23
  module ActsAsHypertable
24
24
  DEFAULT_OPTIONS = {
25
- time_column: :created_at
25
+ time_column: :created_at,
26
+ # Add any default time vector options here if needed
26
27
  }.freeze
27
28
 
28
29
  def acts_as_hypertable?
@@ -45,10 +46,12 @@ module Timescaledb
45
46
  # @param [Hash] options The options to initialize your macro with.
46
47
  # @option options [Boolean] :skip_association_scopes to avoid `.hypertable`, `.chunks` and other scopes related to metadata.
47
48
  # @option options [Boolean] :skip_default_scopes to avoid the generation of default time related scopes like `last_hour`, `last_week`, `yesterday` and so on...
49
+ # @option options [Boolean] :skip_time_vector to avoid the generation of time vector related scopes
48
50
  def acts_as_hypertable(options = {})
49
51
  return if acts_as_hypertable?
50
52
 
51
53
  include Timescaledb::ActsAsHypertable::Core
54
+ include Timescaledb::Toolkit::TimeVector
52
55
 
53
56
  class_attribute :hypertable_options, instance_writer: false
54
57
 
@@ -58,8 +61,8 @@ module Timescaledb
58
61
 
59
62
  define_association_scopes unless options[:skip_association_scopes]
60
63
  define_default_scopes unless options[:skip_default_scopes]
64
+ define_default_vector_scopes unless options[:skip_time_vector]
61
65
  end
62
66
  end
63
67
  end
64
68
 
65
- ActiveRecord::Base.extend Timescaledb::ActsAsHypertable
@@ -1,4 +1,5 @@
1
1
  require 'singleton'
2
+ require 'ostruct'
2
3
 
3
4
  module Timescaledb
4
5
  # Minimal connection setup for Timescaledb directly with the PG.
@@ -1,5 +1,5 @@
1
1
  module Timescaledb
2
- class ContinuousAggregate < ::Timescaledb::ApplicationRecord
2
+ class ContinuousAggregates < ::Timescaledb::ApplicationRecord
3
3
  self.table_name = "timescaledb_information.continuous_aggregates"
4
4
  self.primary_key = 'materialization_hypertable_name'
5
5
 
@@ -14,6 +14,29 @@ module Timescaledb
14
14
  total: count
15
15
  }
16
16
  end
17
+
18
+ scope :hierarchical, -> do
19
+ with_recursive = <<~SQL
20
+ WITH RECURSIVE caggs AS (
21
+ SELECT mat_hypertable_id, parent_mat_hypertable_id, user_view_name
22
+ FROM _timescaledb_catalog.continuous_agg
23
+ UNION ALL
24
+ SELECT continuous_agg.mat_hypertable_id, continuous_agg.parent_mat_hypertable_id, continuous_agg.user_view_name
25
+ FROM _timescaledb_catalog.continuous_agg
26
+ JOIN caggs ON caggs.parent_mat_hypertable_id = continuous_agg.mat_hypertable_id
27
+ )
28
+ SELECT * FROM caggs
29
+ ORDER BY mat_hypertable_id
30
+ SQL
31
+ views = unscoped
32
+ .select("distinct user_view_name")
33
+ .from("(#{with_recursive}) as caggs")
34
+ .pluck(:user_view_name)
35
+ .uniq
36
+
37
+ views.map do |view|
38
+ find_by(view_name: view)
39
+ end
40
+ end
17
41
  end
18
- ContinuousAggregates = ContinuousAggregate
19
42
  end
@@ -0,0 +1,180 @@
1
+ module Timescaledb
2
+ module ContinuousAggregatesHelper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :rollup_rules, default: {
7
+ /count\(\*\)\s+as\s+(\w+)/ => 'sum(\1) as \1',
8
+ /sum\((\w+)\)\s+as\s+(\w+)/ => 'sum(\2) as \2',
9
+ /min\((\w+)\)\s+as\s+(\w+)/ => 'min(\2) as \2',
10
+ /max\((\w+)\)\s+as\s+(\w+)/ => 'max(\2) as \2',
11
+ /first\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'first(\3, \2) as \3',
12
+ /high\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'max(\1) as \1',
13
+ /low\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'min(\1) as \1',
14
+ /last\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'last(\3, \2) as \3',
15
+ /candlestick_agg\((\w+),\s*(\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'rollup(\4) as \4',
16
+ /stats_agg\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'rollup(\3) as \3',
17
+ /stats_agg\((\w+)\)\s+as\s+(\w+)/ => 'rollup(\2) as \2',
18
+ /state_agg\((\w+)\)\s+as\s+(\w+)/ => 'rollup(\2) as \2',
19
+ /percentile_agg\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'rollup(\3) as \3',
20
+ /heartbeat_agg\((\w+)\)\s+as\s+(\w+)/ => 'rollup(\2) as \2',
21
+ }
22
+
23
+ scope :rollup, ->(interval) do
24
+ select_values = (self.select_values - ["time"]).select{|e|!e.downcase.start_with?("time_bucket")}
25
+ if self.select_values.any?{|e|e.downcase.start_with?('time_bucket(')} || self.select_values.include?('time')
26
+ select_values = apply_rollup_rules(select_values)
27
+ select_values.gsub!(/time_bucket\((.+), (.+)\)/, "time_bucket(#{interval}, \2)")
28
+ select_values.gsub!(/\btime\b/, "time_bucket(#{interval}, time) as time")
29
+ end
30
+ group_values = self.group_values.dup
31
+
32
+ if self.segment_by_column
33
+ if !group_values.include?(self.segment_by_column)
34
+ group_values << self.segment_by_column
35
+ end
36
+ if !select_values.include?(self.segment_by_column.to_s)
37
+ select_values.insert(0, self.segment_by_column.to_s)
38
+ end
39
+ end
40
+ where_values = self.where_values_hash
41
+ tb = "time_bucket(#{interval}, #{time_column})"
42
+ self.unscoped.select("#{tb} as #{time_column}, #{select_values.join(', ')}")
43
+ .where(where_values)
44
+ .group(tb, *group_values)
45
+ end
46
+ end
47
+
48
+ class_methods do
49
+ def continuous_aggregates(options = {})
50
+ @time_column = options[:time_column] || self.time_column
51
+ @timeframes = options[:timeframes] || [:minute, :hour, :day, :week, :month, :year]
52
+
53
+ scopes = options[:scopes] || []
54
+ @aggregates = {}
55
+
56
+ scopes.each do |scope_name|
57
+ @aggregates[scope_name] = {
58
+ scope_name: scope_name,
59
+ select: nil,
60
+ group_by: nil,
61
+ refresh_policy: options[:refresh_policy] || {}
62
+ }
63
+ end
64
+
65
+ # Allow for custom aggregate definitions to override or add to scope-based ones
66
+ @aggregates.merge!(options[:aggregates] || {})
67
+
68
+ # Add custom rollup rules if provided
69
+ self.rollup_rules.merge!(options[:custom_rollup_rules] || {})
70
+
71
+ define_continuous_aggregate_classes unless options[:skip_definition]
72
+ end
73
+
74
+ def refresh_aggregates(timeframes = nil)
75
+ timeframes ||= @timeframes
76
+ @aggregates.each do |aggregate_name, _|
77
+ timeframes.each do |timeframe|
78
+ klass = const_get("#{aggregate_name}_per_#{timeframe}".classify)
79
+ klass.refresh!
80
+ end
81
+ end
82
+ end
83
+
84
+ def create_continuous_aggregates(with_data: false)
85
+ @aggregates.each do |aggregate_name, config|
86
+ @timeframes.each do |timeframe|
87
+ klass = const_get("#{aggregate_name}_per_#{timeframe}".classify)
88
+ connection.execute <<~SQL
89
+ CREATE MATERIALIZED VIEW IF NOT EXISTS #{klass.table_name}
90
+ WITH (timescaledb.continuous) AS
91
+ #{klass.base_query}
92
+ #{with_data ? 'WITH DATA' : 'WITH NO DATA'};
93
+ SQL
94
+
95
+ if (policy = klass.refresh_policy)
96
+ connection.execute <<~SQL
97
+ SELECT add_continuous_aggregate_policy('#{klass.table_name}',
98
+ start_offset => INTERVAL '#{policy[:start_offset]}',
99
+ end_offset => INTERVAL '#{policy[:end_offset]}',
100
+ schedule_interval => INTERVAL '#{policy[:schedule_interval]}');
101
+ SQL
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def apply_rollup_rules(select_values)
108
+ result = select_values.dup
109
+ rollup_rules.each do |pattern, replacement|
110
+ result.gsub!(pattern, replacement)
111
+ end
112
+ # Remove any remaining time_bucket
113
+ result.gsub!(/time_bucket\(.+?\)( as \w+)?/, '')
114
+ result
115
+ end
116
+
117
+ def drop_continuous_aggregates
118
+ @aggregates.each do |aggregate_name, _|
119
+ @timeframes.reverse_each do |timeframe|
120
+ view_name = "#{aggregate_name}_per_#{timeframe}"
121
+ connection.execute("DROP MATERIALIZED VIEW IF EXISTS #{view_name} CASCADE")
122
+ end
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ def define_continuous_aggregate_classes
129
+ base_model = self
130
+ @aggregates.each do |aggregate_name, config|
131
+ previous_timeframe = nil
132
+ @timeframes.each do |timeframe|
133
+ _table_name = "#{aggregate_name}_per_#{timeframe}"
134
+ class_name = "#{aggregate_name}_per_#{timeframe}".classify
135
+ const_set(class_name, Class.new(base_model) do
136
+ class << self
137
+ attr_accessor :config, :timeframe, :base_query, :base_model
138
+ end
139
+
140
+ self.table_name = _table_name
141
+ self.config = config
142
+ self.timeframe = timeframe
143
+
144
+ interval = "'1 #{timeframe.to_s}'"
145
+ self.base_model = base_model
146
+ tb = "time_bucket(#{interval}, #{time_column})"
147
+ if previous_timeframe
148
+ prev_klass = base_model.const_get("#{aggregate_name}_per_#{previous_timeframe}".classify)
149
+ select_clause = base_model.apply_rollup_rules("#{config[:select]}")
150
+ self.base_query = "SELECT #{tb} as #{time_column}, #{select_clause} FROM \"#{prev_klass.table_name}\" GROUP BY #{[tb, *config[:group_by]].join(', ')}"
151
+ else
152
+ scope = base_model.public_send(config[:scope_name])
153
+ config[:select] = scope.select_values.select{|e|!e.downcase.start_with?("time_bucket")}.join(', ')
154
+ config[:group_by] = scope.group_values
155
+ self.base_query = "SELECT #{tb} as #{time_column}, #{config[:select]} FROM \"#{scope.table_name}\" GROUP BY #{[tb, *config[:group_by]].join(', ')}"
156
+ end
157
+
158
+ def self.refresh!(start_time = nil, end_time = nil)
159
+ if start_time && end_time
160
+ connection.execute("CALL refresh_continuous_aggregate('#{table_name}', '#{start_time}', '#{end_time}')")
161
+ else
162
+ connection.execute("CALL refresh_continuous_aggregate('#{table_name}', null, null)")
163
+ end
164
+ end
165
+
166
+ def readonly?
167
+ true
168
+ end
169
+
170
+ def self.refresh_policy
171
+ config[:refresh_policy]&.dig(timeframe)
172
+ end
173
+ end)
174
+ previous_timeframe = timeframe
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -1,3 +1,5 @@
1
+ require 'ostruct'
2
+
1
3
  module Timescaledb
2
4
  class Hypertable < ::Timescaledb::ApplicationRecord
3
5
  self.table_name = "timescaledb_information.hypertables"
@@ -16,7 +16,7 @@ module Timescaledb
16
16
  # chunk_time_interval: '1 min',
17
17
  # compress_segmentby: 'identifier',
18
18
  # compress_orderby: 'created_at',
19
- # compression_interval: '7 days'
19
+ # compress_after: '7 days'
20
20
  # }
21
21
  #
22
22
  # create_table(:events, id: false, hypertable: options) do |t|
@@ -41,7 +41,8 @@ module Timescaledb
41
41
  chunk_time_interval: '1 week',
42
42
  compress_segmentby: nil,
43
43
  compress_orderby: 'created_at',
44
- compression_interval: nil,
44
+ compress_after: nil,
45
+ drop_after: nil,
45
46
  partition_column: nil,
46
47
  number_partitions: nil,
47
48
  **hypertable_options)
@@ -49,30 +50,24 @@ module Timescaledb
49
50
  original_logger = ActiveRecord::Base.logger
50
51
  ActiveRecord::Base.logger = Logger.new(STDOUT)
51
52
 
52
- options = ["chunk_time_interval => INTERVAL '#{chunk_time_interval}'"]
53
- options += hypertable_options.map { |k, v| "#{k} => #{quote(v)}" }
53
+ dimension = "by_range(#{quote(time_column)}, #{parse_interval(chunk_time_interval)})"
54
54
 
55
- arguments = [
56
- quote(table_name),
57
- quote(time_column),
58
- (quote(partition_column) if partition_column),
59
- (number_partitions if partition_column),
60
- *options
55
+ arguments = [ quote(table_name), dimension,
56
+ *hypertable_options.map { |k, v| "#{k} => #{quote(v)}" }
61
57
  ]
62
58
 
63
59
  execute "SELECT create_hypertable(#{arguments.compact.join(', ')})"
64
60
 
65
- if compress_segmentby
66
- execute <<~SQL
67
- ALTER TABLE #{table_name} SET (
68
- timescaledb.compress,
69
- timescaledb.compress_orderby = '#{compress_orderby}',
70
- timescaledb.compress_segmentby = '#{compress_segmentby}'
71
- )
72
- SQL
61
+ if partition_column && number_partitions
62
+ execute "SELECT add_dimension('#{table_name}', by_hash(#{quote(partition_column)}, #{number_partitions}))"
73
63
  end
74
- if compression_interval
75
- execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compression_interval}')"
64
+
65
+ if compress_segmentby || compress_after
66
+ add_compression_policy(table_name, orderby: compress_orderby, segmentby: compress_segmentby, compress_after: compress_after)
67
+ end
68
+
69
+ if drop_after
70
+ add_retention_policy(table_name, drop_after: drop_after)
76
71
  end
77
72
  ensure
78
73
  ActiveRecord::Base.logger = original_logger if original_logger
@@ -146,14 +141,40 @@ module Timescaledb
146
141
  execute "SELECT remove_continuous_aggregate_policy('#{table_name}')"
147
142
  end
148
143
 
149
- def create_retention_policy(table_name, interval:)
150
- execute "SELECT add_retention_policy('#{table_name}', INTERVAL '#{interval}')"
144
+ def create_retention_policy(table_name, drop_after:)
145
+ execute "SELECT add_retention_policy('#{table_name}', drop_after => #{parse_interval(drop_after)})"
151
146
  end
152
147
 
148
+ alias_method :add_retention_policy, :create_retention_policy
149
+
153
150
  def remove_retention_policy(table_name)
154
151
  execute "SELECT remove_retention_policy('#{table_name}')"
155
152
  end
156
153
 
154
+
155
+ # Enable compression policy.
156
+ #
157
+ # @param table_name [String] The name of the table.
158
+ # @param orderby [String] The column to order by.
159
+ # @param segmentby [String] The column to segment by.
160
+ # @param compress_after [String] The interval to compress after.
161
+ # @param compression_chunk_time_interval [String] In case to merge chunks.
162
+ #
163
+ # @see https://docs.timescale.com/api/latest/compression/add_compression_policy/
164
+ def add_compression_policy(table_name, orderby:, segmentby:, compress_after: nil, compression_chunk_time_interval: nil)
165
+ options = []
166
+ options << 'timescaledb.compress'
167
+ options << "timescaledb.compress_orderby = '#{orderby}'" if orderby
168
+ options << "timescaledb.compress_segmentby = '#{segmentby}'" if segmentby
169
+ options << "timescaledb.compression_chunk_time_interval = INTERVAL '#{compression_chunk_time_interval}'" if compression_chunk_time_interval
170
+ execute <<~SQL
171
+ ALTER TABLE #{table_name} SET (
172
+ #{options.join(',')}
173
+ )
174
+ SQL
175
+ execute "SELECT add_compression_policy('#{table_name}', compress_after => INTERVAL '#{compress_after}')" if compress_after
176
+ end
177
+
157
178
  private
158
179
 
159
180
  # Build a string for the WITH clause of the CREATE MATERIALIZED VIEW statement.
@@ -165,6 +186,14 @@ module Timescaledb
165
186
  value = options[option_key] ? 'true' : 'false'
166
187
  ",timescaledb.#{option_key}=#{value}"
167
188
  end
189
+
190
+ def parse_interval(interval)
191
+ if interval.is_a?(Numeric)
192
+ interval
193
+ else
194
+ "INTERVAL '#{interval}'"
195
+ end
196
+ end
168
197
  end
169
198
  end
170
199
 
@@ -78,7 +78,7 @@ module Timescaledb
78
78
 
79
79
  options = {
80
80
  time_column: time.column_name,
81
- chunk_time_interval: time.time_interval.inspect,
81
+ chunk_time_interval: time.time_interval ? time.time_interval.inspect : time.integer_interval,
82
82
  **timescale_compression_settings_for(hypertable),
83
83
  **timescale_space_partition_for(hypertable),
84
84
  **timescale_index_options_for(hypertable)
@@ -106,14 +106,12 @@ module Timescaledb
106
106
  if setting.orderby_column_index
107
107
  if setting.orderby_asc
108
108
  direction = "ASC"
109
- if setting.orderby_nullsfirst
110
- direction += " NULLS FIRST"
111
- end
109
+ # For ASC, default is NULLS LAST, so only add if explicitly set to FIRST
110
+ direction += " NULLS FIRST" if setting.orderby_nullsfirst == true
112
111
  else
113
112
  direction = "DESC"
114
- if !setting.orderby_nullsfirst
115
- direction += " NULLS LAST"
116
- end
113
+ # For DESC, default is NULLS FIRST, so only add if explicitly set to LAST
114
+ direction += " NULLS LAST" if setting.orderby_nullsfirst == false
117
115
  end
118
116
 
119
117
  compression_settings[:compress_orderby] << "#{setting.attname} #{direction}"
@@ -154,19 +152,19 @@ module Timescaledb
154
152
  def timescale_continuous_aggregates(stream)
155
153
  return unless Timescaledb::ContinuousAggregates.table_exists?
156
154
 
157
- Timescaledb::ContinuousAggregates.all.find_each do |aggregate|
155
+ Timescaledb::ContinuousAggregates.hierarchical.each do |aggregate|
158
156
  refresh_policies_opts = if (refresh_policy = aggregate.jobs.refresh_continuous_aggregate.first)
159
157
  interval = timescale_interval(refresh_policy.schedule_interval)
160
158
  end_offset = timescale_interval(refresh_policy.config["end_offset"])
161
159
  start_offset = timescale_interval(refresh_policy.config["start_offset"])
162
- %(refresh_policies: { start_offset: "#{start_offset}", end_offset: "#{end_offset}", schedule_interval: "#{interval}"})
160
+ %(refresh_policies: { start_offset: "#{start_offset}", end_offset: "#{end_offset}", schedule_interval: "#{interval}"}, )
163
161
  else
164
162
  ""
165
163
  end
166
164
 
167
165
  with_clause_opts = "materialized_only: #{aggregate[:materialized_only]}, finalized: #{aggregate[:finalized]}"
168
166
  stream.puts <<~AGG.indent(2)
169
- create_continuous_aggregate("#{aggregate.view_name}", <<-SQL, #{refresh_policies_opts}, #{with_clause_opts})
167
+ create_continuous_aggregate("#{aggregate.view_name}", <<-SQL, #{refresh_policies_opts}#{with_clause_opts})
170
168
  #{aggregate.view_definition.strip.gsub(/;$/, '')}
171
169
  SQL
172
170
  AGG
@@ -9,23 +9,23 @@ module Timescaledb
9
9
 
10
10
  module ClassMethods
11
11
  def value_column
12
- @value_column ||= time_vector_options[:value_column] || :val
12
+ @value_column ||= hypertable_options[:value_column] || :val
13
13
  end
14
14
 
15
15
  def time_column
16
- respond_to?(:time_column) && super || time_vector_options[:time_column]
16
+ respond_to?(:time_column) && super || hypertable_options[:time_column]
17
17
  end
18
18
 
19
19
  def segment_by_column
20
- time_vector_options[:segment_by]
20
+ hypertable_options[:segment_by] || hypertable_options[:compress_segment_by]
21
21
  end
22
22
 
23
23
  protected
24
24
 
25
- def define_default_scopes
26
- scope :volatility, -> (segment_by: segment_by_column) do
25
+ def define_default_vector_scopes
26
+ scope :volatility, -> (segment_by: segment_by_column, value: value_column) do
27
27
  select([*segment_by,
28
- "timevector(#{time_column}, #{value_column}) -> sort() -> delta() -> abs() -> sum() as volatility"
28
+ "timevector(#{time_column}, #{value}) -> sort() -> delta() -> abs() -> sum() as volatility"
29
29
  ].join(", ")).group(segment_by)
30
30
  end
31
31
 
@@ -36,11 +36,15 @@ module Timescaledb
36
36
  .group(segment_by)
37
37
  end
38
38
 
39
- scope :lttb, -> (threshold:, segment_by: segment_by_column, time: time_column, value: value_column) do
39
+ scope :lttb, -> (threshold:, segment_by: segment_by_column, time: time_column, value: value_column, value_exp: value_column) do
40
+ if value =~ /(.*)\bas\b(.*)/
41
+ value_exp = $1
42
+ value = $2
43
+ end
40
44
  lttb_query = <<~SQL
41
- WITH x AS ( #{select(*segment_by, time_column, value_column).to_sql})
45
+ WITH x AS ( #{select(*segment_by, time_column, value_exp || value).to_sql})
42
46
  SELECT #{"x.#{segment_by}," if segment_by}
43
- (lttb( x.#{time_column}, x.#{value_column}, #{threshold}) -> unnest()).*
47
+ (lttb( x.#{time_column}, x.#{value}, #{threshold}) -> unnest()).*
44
48
  FROM x
45
49
  #{"GROUP BY #{segment_by}" if segment_by}
46
50
  SQL
@@ -1,3 +1,2 @@
1
1
  require_relative "toolkit/helpers"
2
- require_relative "acts_as_time_vector"
3
2
  require_relative "toolkit/time_vector"
@@ -1,3 +1,3 @@
1
1
  module Timescaledb
2
- VERSION = '0.2.9'
2
+ VERSION = '0.3.1'
3
3
  end
data/lib/timescaledb.rb CHANGED
@@ -3,6 +3,7 @@ require 'active_record'
3
3
  require_relative 'timescaledb/application_record'
4
4
  require_relative 'timescaledb/acts_as_hypertable'
5
5
  require_relative 'timescaledb/acts_as_hypertable/core'
6
+ require_relative 'timescaledb/continuous_aggregates_helper'
6
7
  require_relative 'timescaledb/connection'
7
8
  require_relative 'timescaledb/toolkit'
8
9
  require_relative 'timescaledb/chunk'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timescaledb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jônatas Davi Paganini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-05 00:00:00.000000000 Z
11
+ date: 2024-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ostruct
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: pry
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -138,7 +152,7 @@ dependencies:
138
152
  version: '0'
139
153
  description: Functions from timescaledb available in the ActiveRecord models.
140
154
  email:
141
- - jonatasdp@gmail.com
155
+ - jonatas@timescale.com
142
156
  executables:
143
157
  - tsdb
144
158
  extensions: []
@@ -149,13 +163,13 @@ files:
149
163
  - lib/timescaledb.rb
150
164
  - lib/timescaledb/acts_as_hypertable.rb
151
165
  - lib/timescaledb/acts_as_hypertable/core.rb
152
- - lib/timescaledb/acts_as_time_vector.rb
153
166
  - lib/timescaledb/application_record.rb
154
167
  - lib/timescaledb/chunk.rb
155
168
  - lib/timescaledb/compression_settings.rb
156
169
  - lib/timescaledb/connection.rb
157
170
  - lib/timescaledb/connection_handling.rb
158
171
  - lib/timescaledb/continuous_aggregates.rb
172
+ - lib/timescaledb/continuous_aggregates_helper.rb
159
173
  - lib/timescaledb/database.rb
160
174
  - lib/timescaledb/database/chunk_statements.rb
161
175
  - lib/timescaledb/database/hypertable_statements.rb
@@ -181,12 +195,13 @@ files:
181
195
  - lib/timescaledb/toolkit/helpers.rb
182
196
  - lib/timescaledb/toolkit/time_vector.rb
183
197
  - lib/timescaledb/version.rb
184
- homepage: https://github.com/jonatas/timescaledb
198
+ homepage: https://github.com/timescale/timescaledb-ruby
185
199
  licenses:
186
200
  - MIT
187
201
  metadata:
188
202
  allowed_push_host: https://rubygems.org
189
- homepage_uri: https://github.com/jonatas/timescaledb
203
+ homepage_uri: https://timescale.github.io/timescaledb-ruby/
204
+ source_code_uri: https://github.com/timescale/timescaledb-ruby
190
205
  post_install_message:
191
206
  rdoc_options: []
192
207
  require_paths:
@@ -202,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
217
  - !ruby/object:Gem::Version
203
218
  version: '0'
204
219
  requirements: []
205
- rubygems_version: 3.3.7
220
+ rubygems_version: 3.5.23
206
221
  signing_key:
207
222
  specification_version: 4
208
223
  summary: TimescaleDB helpers for Ruby ecosystem.
@@ -1,18 +0,0 @@
1
- module Timescaledb
2
- module ActsAsTimeVector
3
- def acts_as_time_vector(options = {})
4
- return if acts_as_time_vector?
5
-
6
- include Timescaledb::Toolkit::TimeVector
7
-
8
- class_attribute :time_vector_options, instance_writer: false
9
- define_default_scopes
10
- self.time_vector_options = options
11
- end
12
-
13
- def acts_as_time_vector?
14
- included_modules.include?(Timescaledb::ActsAsTimeVector)
15
- end
16
- end
17
- end
18
- ActiveRecord::Base.extend Timescaledb::ActsAsTimeVector