timescaledb 0.3.1 → 0.3.2
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 +4 -4
- data/bin/tsdb +16 -13
- data/lib/timescaledb/acts_as_hypertable/core.rb +23 -23
- data/lib/timescaledb/acts_as_hypertable.rb +0 -1
- data/lib/timescaledb/connection_handling.rb +19 -2
- data/lib/timescaledb/continuous_aggregates_helper.rb +12 -1
- data/lib/timescaledb/counter_cache.rb +88 -0
- data/lib/timescaledb/schema_dumper.rb +2 -2
- data/lib/timescaledb/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33706a3c899ae608bb30b9ef62ca32875045960745b526bc0700fddec09ddc8b
|
4
|
+
data.tar.gz: 88a7cb9f1f2f2dbb68f131547fa7c9575979f6e87b0bb29f576ee95d9bb9696c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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.
|
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.
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
62
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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 :
|
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
|
@@ -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
|
13
|
-
Connection.instance.use_connection
|
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
|
-
|
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}",
|
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[:
|
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.
|
data/lib/timescaledb/version.rb
CHANGED
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.
|
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:
|
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
|