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