timescaledb 0.2.9 → 0.3.1

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: 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