timescaledb 0.3.1 → 0.3.2

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: 0f5295cfa57caa17bb740b28dd70f54fb0c239f9d011233ae04cfffa2bbfadbf
4
- data.tar.gz: ec1b742c312d289124522e490fe47269d7aa6b9774ce0d82fe99bed206ce227c
3
+ metadata.gz: 33706a3c899ae608bb30b9ef62ca32875045960745b526bc0700fddec09ddc8b
4
+ data.tar.gz: 88a7cb9f1f2f2dbb68f131547fa7c9575979f6e87b0bb29f576ee95d9bb9696c
5
5
  SHA512:
6
- metadata.gz: f1a0937798a8f97bccaf8a39d61268fc040f525f04b92632e1a0fc7c8d892d73b39d7f4fd7252fe430ac3f53523b5a01570bc89257aff8cef35fa68c161204e4
7
- data.tar.gz: 460df42d48ceac9d552c47408655a9c9330b3efe9bc40d8963021e7c2f4e297d4210c54fc6fe43678f56dc156ac28836f7b06d17d11977fa82a134cb356c9127
6
+ metadata.gz: a700d4b61d11af54856fb02e19db676a720ba5ff3c2367e7eb9ba153bc14ae1c3c67d9130bfc47e1ecac786a007274df9c3595a6b10fe797fb06b13ae30ac957
7
+ data.tar.gz: 05c45d270e86c24d40984cea92e8870869441580d35426b5028ca17b346a47ad7468482337adec69eaf0d6daecdd4292d7108868677a7388f35cf2529d7e631a
data/bin/tsdb CHANGED
@@ -6,17 +6,19 @@ require "pry"
6
6
 
7
7
  Timescaledb.establish_connection(ARGV[0])
8
8
 
9
- hypertables = Timescaledb.connection.query('SELECT * FROM timescaledb_information.hypertables')
9
+ ActiveSupport.on_load(:active_record) { extend Timescaledb::ActsAsHypertable }
10
+
10
11
 
11
12
  if ARGV.index("--stats")
13
+ hypertables = Timescaledb::Hypertable.all
12
14
  if (only = ARGV.index("--only"))
13
15
  only_hypertables = ARGV[only+1].split(",")
14
16
 
15
- hypertables.select! { |hypertable| only_hypertables.includes?(hypertable.hypertable_name) }
17
+ hypertables.select! { |hypertable| only_hypertables.include?(hypertable.hypertable_name) }
16
18
  elsif (except = ARGV.index("--except"))
17
19
  except_hypertables = ARGV[except+1].split(",")
18
20
 
19
- hypertables.select! { |hypertable| except_hypertables.includes?(hypertable.hypertable_name) }
21
+ hypertables.select! { |hypertable| except_hypertables.include?(hypertable.hypertable_name) }
20
22
  end
21
23
 
22
24
  stats = Timescaledb::Stats.new(hypertables).to_h
@@ -25,25 +27,26 @@ if ARGV.index("--stats")
25
27
  end
26
28
 
27
29
  if ARGV.index("--console")
28
- ActiveRecord::Base.establish_connection(ARGV[0])
29
-
30
30
  Timescaledb::Hypertable.find_each do |hypertable|
31
31
  class_name = hypertable.hypertable_name.singularize.camelize
32
32
  model = Class.new(ActiveRecord::Base) do
33
- self.table_name = hypertable.hypertable_name
33
+ self.table_name = "#{hypertable.hypertable_schema}.#{hypertable.hypertable_name}"
34
+
34
35
  acts_as_hypertable time_column: hypertable.main_dimension.column_name
35
36
  end
36
- Timescaledb.const_set(class_name, model)
37
- end
38
37
 
39
- Timescaledb::ContinuousAggregates.find_each do |cagg|
40
- class_name = cagg.view_name.singularize.camelize
41
- model = Class.new(ActiveRecord::Base) do
42
- self.table_name = cagg.view_name
43
- acts_as_hypertable
38
+ Timescaledb::ContinuousAggregate.where( hypertable_schema: hypertable.hypertable_schema, hypertable_name: hypertable.hypertable_name).hierarchical.find_each do |cagg|
39
+ cagg_model_name = cagg.view_name.singularize.camelize
40
+ cagg_model = Class.new(ActiveRecord::Base) do
41
+ self.table_name = cagg.view_name
42
+ self.schema_name = cagg.view_schema
43
+ acts_as_hypertable
44
+ end
45
+ model.const_set(cagg_model_name, cagg_model)
44
46
  end
45
47
  Timescaledb.const_set(class_name, model)
46
48
  end
47
49
 
50
+
48
51
  Pry.start(Timescaledb)
49
52
  end
@@ -41,41 +41,41 @@ module Timescaledb
41
41
  end
42
42
 
43
43
  def define_default_scopes
44
+ scope :between, ->(start_time, end_time) do
45
+ where("#{time_column} BETWEEN ? AND ?", start_time, end_time)
46
+ end
47
+
44
48
  scope :previous_month, -> do
45
- where(
46
- "DATE(#{time_column}) >= :start_time AND DATE(#{time_column}) <= :end_time",
47
- start_time: Date.today.last_month.in_time_zone.beginning_of_month.to_date,
48
- end_time: Date.today.last_month.in_time_zone.end_of_month.to_date
49
- )
49
+ ref = 1.month.ago.in_time_zone
50
+ between(ref.beginning_of_month, ref.end_of_month)
50
51
  end
51
52
 
52
53
  scope :previous_week, -> do
53
- where(
54
- "DATE(#{time_column}) >= :start_time AND DATE(#{time_column}) <= :end_time",
55
- start_time: Date.today.last_week.in_time_zone.beginning_of_week.to_date,
56
- end_time: Date.today.last_week.in_time_zone.end_of_week.to_date
57
- )
54
+ ref = 1.week.ago.in_time_zone
55
+ between(ref.beginning_of_week, ref.end_of_week)
58
56
  end
59
57
 
60
58
  scope :this_month, -> do
61
- where(
62
- "DATE(#{time_column}) >= :start_time AND DATE(#{time_column}) <= :end_time",
63
- start_time: Date.today.in_time_zone.beginning_of_month.to_date,
64
- end_time: Date.today.in_time_zone.end_of_month.to_date
65
- )
59
+ ref = Time.now.in_time_zone
60
+ between(ref.beginning_of_month, ref.end_of_month)
66
61
  end
67
62
 
68
63
  scope :this_week, -> do
69
- where(
70
- "DATE(#{time_column}) >= :start_time AND DATE(#{time_column}) <= :end_time",
71
- start_time: Date.today.in_time_zone.beginning_of_week.to_date,
72
- end_time: Date.today.in_time_zone.end_of_week.to_date
73
- )
64
+ ref = Time.now.in_time_zone
65
+ between(ref.beginning_of_week, ref.end_of_week)
66
+ end
67
+
68
+ scope :yesterday, -> do
69
+ ref = 1.day.ago.in_time_zone
70
+ between(ref.yesterday, ref.yesterday)
71
+ end
72
+
73
+ scope :today, -> do
74
+ ref = Time.now.in_time_zone
75
+ between(ref.beginning_of_day, ref.end_of_day)
74
76
  end
75
77
 
76
- scope :yesterday, -> { where("DATE(#{time_column}) = ?", Date.yesterday.in_time_zone.to_date) }
77
- scope :today, -> { where("DATE(#{time_column}) = ?", Date.today.in_time_zone.to_date) }
78
- scope :last_hour, -> { where("#{time_column} between ? and ?", 1.hour.ago.in_time_zone, Time.now.end_of_hour.in_time_zone) }
78
+ scope :last_hour, -> { where("#{time_column} > ?", 1.hour.ago.in_time_zone) }
79
79
  end
80
80
 
81
81
  def normalize_hypertable_options
@@ -23,7 +23,6 @@ module Timescaledb
23
23
  module ActsAsHypertable
24
24
  DEFAULT_OPTIONS = {
25
25
  time_column: :created_at,
26
- # Add any default time vector options here if needed
27
26
  }.freeze
28
27
 
29
28
  def acts_as_hypertable?
@@ -5,12 +5,29 @@ module Timescaledb
5
5
 
6
6
  # @param [String] config with the postgres connection string.
7
7
  def establish_connection(config)
8
+ # Establish connection for Timescaledb
8
9
  Connection.instance.config = config
10
+
11
+ # Also establish connection for ActiveRecord if it's defined
12
+ if defined?(ActiveRecord::Base)
13
+ ActiveRecord::Base.establish_connection(config)
14
+ end
9
15
  end
10
16
 
11
17
  # @param [PG::Connection] to use it directly from a raw connection
12
- def use_connection conn
13
- Connection.instance.use_connection conn
18
+ def use_connection(conn)
19
+ Connection.instance.use_connection(conn)
20
+
21
+ # Also set ActiveRecord connection if it's defined
22
+ if defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
23
+ ar_conn = ActiveRecord::Base.connection
24
+ current_conn = ar_conn.raw_connection
25
+
26
+ # Only set if it's different to avoid redundant assignment
27
+ if current_conn != conn
28
+ ar_conn.instance_variable_set(:@raw_connection, conn)
29
+ end
30
+ end
14
31
  end
15
32
 
16
33
  def connection
@@ -18,6 +18,8 @@ module Timescaledb
18
18
  /state_agg\((\w+)\)\s+as\s+(\w+)/ => 'rollup(\2) as \2',
19
19
  /percentile_agg\((\w+),\s*(\w+)\)\s+as\s+(\w+)/ => 'rollup(\3) as \3',
20
20
  /heartbeat_agg\((\w+)\)\s+as\s+(\w+)/ => 'rollup(\2) as \2',
21
+ /stats_agg\(([^)]+)\)\s+(as\s+(\w+))/ => 'rollup(\3) \2',
22
+ /stats_agg\((.*)\)\s+(as\s+(\w+))/ => 'rollup(\3) \2'
21
23
  }
22
24
 
23
25
  scope :rollup, ->(interval) do
@@ -147,12 +149,21 @@ module Timescaledb
147
149
  if previous_timeframe
148
150
  prev_klass = base_model.const_get("#{aggregate_name}_per_#{previous_timeframe}".classify)
149
151
  select_clause = base_model.apply_rollup_rules("#{config[:select]}")
152
+ # Note there's no where clause here, because we're using the previous timeframe's data
150
153
  self.base_query = "SELECT #{tb} as #{time_column}, #{select_clause} FROM \"#{prev_klass.table_name}\" GROUP BY #{[tb, *config[:group_by]].join(', ')}"
151
154
  else
152
155
  scope = base_model.public_send(config[:scope_name])
153
156
  config[:select] = scope.select_values.select{|e|!e.downcase.start_with?("time_bucket")}.join(', ')
154
157
  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(', ')}"
158
+ config[:where] = if scope.where_values_hash.present?
159
+ scope.where_values_hash.to_sql
160
+ elsif scope.where_clause.ast.present? && scope.where_clause.ast.to_sql.present?
161
+ scope.where_clause.ast.to_sql
162
+ end
163
+ self.base_query = "SELECT #{tb} as #{time_column}, #{config[:select]}"
164
+ self.base_query += " FROM \"#{base_model.table_name}\""
165
+ self.base_query += " WHERE #{config[:where]}" if config[:where]
166
+ self.base_query += " GROUP BY #{[tb, *config[:group_by]].join(', ')}"
156
167
  end
157
168
 
158
169
  def self.refresh!(start_time = nil, end_time = nil)
@@ -0,0 +1,88 @@
1
+ module Timescaledb
2
+ module CounterCache
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :counter_cache_options, default: {}
7
+ end
8
+
9
+ module ClassMethods
10
+ def belongs_to_with_counter_cache(name, scope = nil, **options)
11
+ if options[:counter_cache] == :timescaledb || options[:counter_cache].is_a?(Array)
12
+ setup_timescaledb_counter_cache(name, options)
13
+ options.delete(:counter_cache)
14
+ end
15
+
16
+ belongs_to(name, scope, **options)
17
+ end
18
+
19
+ private
20
+
21
+ def setup_timescaledb_counter_cache(association_name, options)
22
+ timeframes = if options[:counter_cache] == :timescaledb
23
+ [:hour, :day] # Default timeframes
24
+ else
25
+ options[:counter_cache]
26
+ end
27
+
28
+ # Store counter cache configuration
29
+ self.counter_cache_options[association_name] = {
30
+ timeframes: timeframes,
31
+ foreign_key: options[:foreign_key] || "#{association_name}_id"
32
+ }
33
+
34
+ # Setup continuous aggregate for counting
35
+ setup_counter_aggregate(association_name, timeframes)
36
+
37
+ # Setup associations on the target class
38
+ setup_target_associations(association_name, timeframes)
39
+ end
40
+
41
+ def setup_counter_aggregate(association_name, timeframes)
42
+ scope_name = "#{association_name}_count"
43
+
44
+ # Define the base counting scope
45
+ scope scope_name, -> { select(counter_cache_options[association_name][:foreign_key], "count(*) as count").group(1) }
46
+
47
+ # Create continuous aggregates for each timeframe
48
+ continuous_aggregates(
49
+ scopes: [scope_name],
50
+ timeframes: timeframes,
51
+ refresh_policy: {
52
+ start_offset: "1 day",
53
+ end_offset: "1 hour",
54
+ schedule_interval: "1 hour"
55
+ }
56
+ )
57
+ end
58
+
59
+ def setup_target_associations(association_name, timeframes)
60
+ target_class = reflect_on_association(association_name).klass
61
+
62
+ timeframes.each do |timeframe|
63
+ view_name = "#{table_name}_#{association_name}_count_per_#{timeframe}"
64
+
65
+ target_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
66
+ has_many :#{view_name.pluralize},
67
+ class_name: "#{self}::#{association_name.to_s.classify}CountPer#{timeframe.to_s.classify}",
68
+ foreign_key: :#{counter_cache_options[association_name][:foreign_key]}
69
+
70
+ def #{view_name}_total
71
+ #{view_name.pluralize}.sum(:count)
72
+ end
73
+ RUBY
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # Extend ActiveRecord with counter cache functionality
81
+ ActiveSupport.on_load(:active_record) do
82
+ extend Timescaledb::CounterCache
83
+
84
+ class << self
85
+ alias_method :belongs_to_without_counter_cache, :belongs_to
86
+ alias_method :belongs_to, :belongs_to_with_counter_cache
87
+ end
88
+ end
@@ -90,7 +90,7 @@ module Timescaledb
90
90
 
91
91
  def timescale_retention_policy(hypertable, stream)
92
92
  hypertable.jobs.where(proc_name: "policy_retention").each do |job|
93
- stream.puts %Q[ create_retention_policy "#{job.hypertable_name}", interval: "#{job.config["drop_after"]}"]
93
+ stream.puts %Q[ create_retention_policy "#{job.hypertable_name}", drop_after: "#{job.config["drop_after"]}"]
94
94
  end
95
95
  end
96
96
 
@@ -119,7 +119,7 @@ module Timescaledb
119
119
  end
120
120
 
121
121
  hypertable.jobs.compression.each do |job|
122
- compression_settings[:compression_interval] = job.config["compress_after"]
122
+ compression_settings[:compress_after] = job.config["compress_after"]
123
123
  end
124
124
 
125
125
  # Pack the compression setting arrays into a comma-separated string instead.
@@ -1,3 +1,3 @@
1
1
  module Timescaledb
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
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.3.1
4
+ version: 0.3.2
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-12-20 00:00:00.000000000 Z
11
+ date: 2025-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -170,6 +170,7 @@ files:
170
170
  - lib/timescaledb/connection_handling.rb
171
171
  - lib/timescaledb/continuous_aggregates.rb
172
172
  - lib/timescaledb/continuous_aggregates_helper.rb
173
+ - lib/timescaledb/counter_cache.rb
173
174
  - lib/timescaledb/database.rb
174
175
  - lib/timescaledb/database/chunk_statements.rb
175
176
  - lib/timescaledb/database/hypertable_statements.rb