timescaledb 0.2.7 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/tsdb +30 -29
- data/lib/timescaledb/acts_as_hypertable.rb +5 -2
- data/lib/timescaledb/connection.rb +54 -0
- data/lib/timescaledb/connection_handling.rb +21 -0
- data/lib/timescaledb/database/chunk_statements.rb +21 -0
- data/lib/timescaledb/database/hypertable_statements.rb +37 -0
- data/lib/timescaledb/database/schema_statements.rb +94 -16
- data/lib/timescaledb/database/types.rb +4 -1
- data/lib/timescaledb/database.rb +4 -0
- data/lib/timescaledb/extension.rb +23 -0
- data/lib/timescaledb/migration_helpers.rb +36 -8
- data/lib/timescaledb/scenic/extension.rb +1 -2
- data/lib/timescaledb/schema_dumper.rb +58 -15
- data/lib/timescaledb/stats/chunks.rb +41 -0
- data/lib/timescaledb/stats/continuous_aggregates.rb +24 -0
- data/lib/timescaledb/stats/hypertables.rb +102 -0
- data/lib/timescaledb/stats/job_stats.rb +29 -0
- data/lib/timescaledb/stats.rb +22 -0
- data/lib/timescaledb/toolkit/time_vector.rb +11 -44
- data/lib/timescaledb/version.rb +1 -1
- data/lib/timescaledb.rb +12 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89553264709636b53e3d56174f3e244561c10f2c4801b922ba25adb8bf32aca7
|
4
|
+
data.tar.gz: f254f76133d13d6854f8f7dee865a0f3bb4adc57617c200ee276d1408541475a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b30bbba9b0da08b3cad42fbc80e73c7aed2e37763d01cfd2b93d569e4bd65768a507fc6e25a757c7b83bd4f9ad3e7e18d98090b7a2f2e8bebcb5d16fb3a63d7c
|
7
|
+
data.tar.gz: bb0ba7379e86631d71256cf66cc506eb42511145f17e343e749bc10e28b7c394a482e9d4a3380ff9ee2af511be51d44529556566fe08421aa92b924755d492c8
|
data/bin/tsdb
CHANGED
@@ -1,48 +1,49 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
|
2
3
|
require "bundler/setup"
|
3
4
|
require "timescaledb"
|
4
5
|
require "pry"
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
Timescaledb::Hypertable.find_each do |hypertable|
|
9
|
-
class_name = hypertable.hypertable_name.singularize.camelize
|
10
|
-
model = Class.new(ActiveRecord::Base) do
|
11
|
-
self.table_name = hypertable.hypertable_name
|
12
|
-
acts_as_hypertable time_column: hypertable.main_dimension.column_name
|
13
|
-
end
|
14
|
-
Timescaledb.const_set(class_name, model)
|
15
|
-
end
|
16
|
-
|
17
|
-
Timescaledb::ContinuousAggregates.find_each do |cagg|
|
18
|
-
class_name = cagg.view_name.singularize.camelize
|
19
|
-
model = Class.new(ActiveRecord::Base) do
|
20
|
-
self.table_name = cagg.view_name
|
21
|
-
acts_as_hypertable
|
22
|
-
end
|
23
|
-
Timescaledb.const_set(class_name, model)
|
24
|
-
end
|
7
|
+
Timescaledb.establish_connection(ARGV[0])
|
25
8
|
|
26
|
-
|
27
|
-
Pry::ColorPrinter.pp(obj)
|
28
|
-
end
|
9
|
+
hypertables = Timescaledb.connection.query('SELECT * FROM timescaledb_information.hypertables')
|
29
10
|
|
30
11
|
if ARGV.index("--stats")
|
31
|
-
scope = Timescaledb::Hypertable.all
|
32
|
-
|
33
12
|
if (only = ARGV.index("--only"))
|
34
13
|
only_hypertables = ARGV[only+1].split(",")
|
35
|
-
scope = scope.where({hypertable_name: only_hypertables})
|
36
|
-
end
|
37
14
|
|
38
|
-
|
15
|
+
hypertables.select! { |hypertable| only_hypertables.includes?(hypertable.hypertable_name) }
|
16
|
+
elsif (except = ARGV.index("--except"))
|
39
17
|
except_hypertables = ARGV[except+1].split(",")
|
40
|
-
|
18
|
+
|
19
|
+
hypertables.select! { |hypertable| except_hypertables.includes?(hypertable.hypertable_name) }
|
41
20
|
end
|
42
21
|
|
43
|
-
|
22
|
+
stats = Timescaledb::Stats.new(hypertables).to_h
|
23
|
+
|
24
|
+
Pry::ColorPrinter.pp(stats)
|
44
25
|
end
|
45
26
|
|
46
27
|
if ARGV.index("--console")
|
28
|
+
ActiveRecord::Base.establish_connection(ARGV[0])
|
29
|
+
|
30
|
+
Timescaledb::Hypertable.find_each do |hypertable|
|
31
|
+
class_name = hypertable.hypertable_name.singularize.camelize
|
32
|
+
model = Class.new(ActiveRecord::Base) do
|
33
|
+
self.table_name = hypertable.hypertable_name
|
34
|
+
acts_as_hypertable time_column: hypertable.main_dimension.column_name
|
35
|
+
end
|
36
|
+
Timescaledb.const_set(class_name, model)
|
37
|
+
end
|
38
|
+
|
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
|
44
|
+
end
|
45
|
+
Timescaledb.const_set(class_name, model)
|
46
|
+
end
|
47
|
+
|
47
48
|
Pry.start(Timescaledb)
|
48
49
|
end
|
@@ -42,6 +42,9 @@ module Timescaledb
|
|
42
42
|
# acts_as_hypertable time_column: :timestamp
|
43
43
|
# end
|
44
44
|
#
|
45
|
+
# @param [Hash] options The options to initialize your macro with.
|
46
|
+
# @option options [Boolean] :skip_association_scopes to avoid `.hypertable`, `.chunks` and other scopes related to metadata.
|
47
|
+
# @option options [Boolean] :skip_default_scopes to avoid the generation of default time related scopes like `last_hour`, `last_week`, `yesterday` and so on...
|
45
48
|
def acts_as_hypertable(options = {})
|
46
49
|
return if acts_as_hypertable?
|
47
50
|
|
@@ -53,8 +56,8 @@ module Timescaledb
|
|
53
56
|
hypertable_options.merge!(options)
|
54
57
|
normalize_hypertable_options
|
55
58
|
|
56
|
-
define_association_scopes
|
57
|
-
define_default_scopes
|
59
|
+
define_association_scopes unless options[:skip_association_scopes]
|
60
|
+
define_default_scopes unless options[:skip_default_scopes]
|
58
61
|
end
|
59
62
|
end
|
60
63
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
# Minimal connection setup for Timescaledb directly with the PG.
|
5
|
+
# The concept is use a singleton component that can query
|
6
|
+
# independently of the ActiveRecord::Base connections.
|
7
|
+
# This is useful for the extension and hypertable metadata.
|
8
|
+
# It can also #use_connection from active record if needed.
|
9
|
+
class Connection
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
attr_writer :config
|
13
|
+
|
14
|
+
# @param [String] query The SQL raw query.
|
15
|
+
# @param [Array] params The SQL query parameters.
|
16
|
+
# @return [Array<OpenStruct>] The SQL result.
|
17
|
+
def query(query, params = [])
|
18
|
+
query = params.empty? ? connection.exec(query) : connection.exec_params(query, params)
|
19
|
+
|
20
|
+
query.map(&OpenStruct.method(:new))
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] query The SQL raw query.
|
24
|
+
# @param [Array] params The SQL query parameters.
|
25
|
+
# @return [OpenStruct] The first SQL result.
|
26
|
+
def query_first(query, params = [])
|
27
|
+
query(query, params).first
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [String] query The SQL raw query.
|
31
|
+
# @param [Array] params The SQL query parameters.
|
32
|
+
# @return [Integr] The count value from SQL result.
|
33
|
+
def query_count(query, params = [])
|
34
|
+
query_first(query, params).count.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Boolean] True if the connection singleton was configured, otherwise returns false.
|
38
|
+
def connected?
|
39
|
+
!@config.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Override the connection with a raw PG connection.
|
43
|
+
# @param [PG::Connection] connection The raw PG connection.
|
44
|
+
def use_connection connection
|
45
|
+
@connection = connection
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def connection
|
51
|
+
@connection ||= PG.connect(@config)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class ConnectionNotEstablishedError < StandardError; end
|
3
|
+
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# @param [String] config with the postgres connection string.
|
7
|
+
def establish_connection(config)
|
8
|
+
Connection.instance.config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [PG::Connection] to use it directly from a raw connection
|
12
|
+
def use_connection conn
|
13
|
+
Connection.instance.use_connection conn
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection
|
17
|
+
raise ConnectionNotEstablishedError.new unless Connection.instance.connected?
|
18
|
+
|
19
|
+
Connection.instance
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class Database
|
3
|
+
module ChunkStatements
|
4
|
+
# @see https://docs.timescale.com/api/latest/compression/compress_chunk/
|
5
|
+
#
|
6
|
+
# @param [String] chunk_name The name of the chunk to be compressed
|
7
|
+
# @return [String] The compress_chunk SQL statement
|
8
|
+
def compress_chunk_sql(chunk_name)
|
9
|
+
"SELECT compress_chunk(#{quote(chunk_name)});"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @see https://docs.timescale.com/api/latest/compression/decompress_chunk/
|
13
|
+
#
|
14
|
+
# @param [String] chunk_name The name of the chunk to be decompressed
|
15
|
+
# @return [String] The decompress_chunk SQL statement
|
16
|
+
def decompress_chunk_sql(chunk_name)
|
17
|
+
"SELECT decompress_chunk(#{quote(chunk_name)});"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class Database
|
3
|
+
module HypertableStatements
|
4
|
+
# @see https://docs.timescale.com/api/latest/hypertable/hypertable_size/
|
5
|
+
#
|
6
|
+
# @param [String] hypertable The hypertable to show size of
|
7
|
+
# @return [String] The hypertable_size SQL statement
|
8
|
+
def hypertable_size_sql(hypertable)
|
9
|
+
"SELECT hypertable_size(#{quote(hypertable)});"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @see https://docs.timescale.com/api/latest/hypertable/hypertable_detailed_size/
|
13
|
+
#
|
14
|
+
# @param [String] hypertable The hypertable to show detailed size of
|
15
|
+
# @return [String] The hypertable_detailed_size SQL statementh
|
16
|
+
def hypertable_detailed_size_sql(hypertable)
|
17
|
+
"SELECT * FROM hypertable_detailed_size(#{quote(hypertable)});"
|
18
|
+
end
|
19
|
+
|
20
|
+
# @see https://docs.timescale.com/api/latest/hypertable/hypertable_index_size/
|
21
|
+
#
|
22
|
+
# @param [String] index_name The name of the index on a hypertable
|
23
|
+
# @return [String] The hypertable_detailed_size SQL statementh
|
24
|
+
def hypertable_index_size_sql(index_name)
|
25
|
+
"SELECT hypertable_index_size(#{quote(index_name)});"
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see https://docs.timescale.com/api/latest/hypertable/chunks_detailed_size/
|
29
|
+
#
|
30
|
+
# @param [String] hypertable The name of the hypertable
|
31
|
+
# @return [String] The chunks_detailed_size SQL statementh
|
32
|
+
def chunks_detailed_size_sql(hypertable)
|
33
|
+
"SELECT * FROM chunks_detailed_size(#{quote(hypertable)});"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -15,7 +15,7 @@ module Timescaledb
|
|
15
15
|
|
16
16
|
arguments = [quote(relation), quote(time_column_name)]
|
17
17
|
arguments += [quote(partitioning_column), number_partitions] if partitioning_column && number_partitions
|
18
|
-
arguments +=
|
18
|
+
arguments += create_hypertable_options_to_named_notation_sql(options)
|
19
19
|
|
20
20
|
"SELECT create_hypertable(#{arguments.join(', ')});"
|
21
21
|
end
|
@@ -56,7 +56,7 @@ module Timescaledb
|
|
56
56
|
options.transform_keys!(&:to_sym)
|
57
57
|
|
58
58
|
arguments = [quote(hypertable), interval_to_sql(compress_after)]
|
59
|
-
arguments +=
|
59
|
+
arguments += policy_options_to_named_notation_sql(options)
|
60
60
|
|
61
61
|
"SELECT add_compression_policy(#{arguments.join(', ')});"
|
62
62
|
end
|
@@ -70,7 +70,7 @@ module Timescaledb
|
|
70
70
|
options.transform_keys!(&:to_sym)
|
71
71
|
|
72
72
|
arguments = [quote(hypertable)]
|
73
|
-
arguments +=
|
73
|
+
arguments += policy_options_to_named_notation_sql(options)
|
74
74
|
|
75
75
|
"SELECT remove_compression_policy(#{arguments.join(', ')});"
|
76
76
|
end
|
@@ -85,7 +85,7 @@ module Timescaledb
|
|
85
85
|
options.transform_keys!(&:to_sym)
|
86
86
|
|
87
87
|
arguments = [quote(hypertable), interval_to_sql(drop_after)]
|
88
|
-
arguments +=
|
88
|
+
arguments += policy_options_to_named_notation_sql(options)
|
89
89
|
|
90
90
|
"SELECT add_retention_policy(#{arguments.join(', ')});"
|
91
91
|
end
|
@@ -99,7 +99,7 @@ module Timescaledb
|
|
99
99
|
options.transform_keys!(&:to_sym)
|
100
100
|
|
101
101
|
arguments = [quote(hypertable)]
|
102
|
-
arguments +=
|
102
|
+
arguments += policy_options_to_named_notation_sql(options)
|
103
103
|
|
104
104
|
"SELECT remove_retention_policy(#{arguments.join(', ')});"
|
105
105
|
end
|
@@ -114,7 +114,7 @@ module Timescaledb
|
|
114
114
|
options.transform_keys!(&:to_sym)
|
115
115
|
|
116
116
|
arguments = [quote(hypertable), quote(index_name)]
|
117
|
-
arguments +=
|
117
|
+
arguments += policy_options_to_named_notation_sql(options)
|
118
118
|
|
119
119
|
"SELECT add_reorder_policy(#{arguments.join(', ')});"
|
120
120
|
end
|
@@ -128,41 +128,119 @@ module Timescaledb
|
|
128
128
|
options.transform_keys!(&:to_sym)
|
129
129
|
|
130
130
|
arguments = [quote(hypertable)]
|
131
|
-
arguments +=
|
131
|
+
arguments += policy_options_to_named_notation_sql(options)
|
132
132
|
|
133
133
|
"SELECT remove_reorder_policy(#{arguments.join(', ')});"
|
134
134
|
end
|
135
135
|
|
136
|
+
# @see https://docs.timescale.com/api/latest/continuous-aggregates/create_materialized_view
|
137
|
+
#
|
138
|
+
# @param [String] continuous_aggregate The name of the continuous aggregate view to be created
|
139
|
+
# @param [Hash] options The optional arguments
|
140
|
+
# @return [String] The create materialized view SQL statement
|
141
|
+
def create_continuous_aggregate_sql(continuous_aggregate, sql, **options)
|
142
|
+
options.transform_keys!(&:to_sym)
|
143
|
+
|
144
|
+
with_data_opts = %w[WITH DATA]
|
145
|
+
with_data_opts.insert(1, 'NO') if options.key?(:with_no_data)
|
146
|
+
|
147
|
+
<<~SQL
|
148
|
+
CREATE MATERIALIZED VIEW #{continuous_aggregate}
|
149
|
+
WITH (timescaledb.continuous) AS
|
150
|
+
#{sql.strip}
|
151
|
+
#{with_data_opts.join(' ')};
|
152
|
+
SQL
|
153
|
+
end
|
154
|
+
|
155
|
+
# @see https://docs.timescale.com/api/latest/continuous-aggregates/drop_materialized_view
|
156
|
+
#
|
157
|
+
# @param [String] continuous_aggregate The name of the continuous aggregate view to be dropped
|
158
|
+
# @param [Boolean] cascade A boolean to drop objects that depend on the continuous aggregate view
|
159
|
+
# @return [String] The drop materialized view SQL statement
|
160
|
+
def drop_continuous_aggregate_sql(continuous_aggregate, cascade: false)
|
161
|
+
arguments = [continuous_aggregate]
|
162
|
+
arguments << 'CASCADE' if cascade
|
163
|
+
|
164
|
+
"DROP MATERIALIZED VIEW #{arguments.join(' ')};"
|
165
|
+
end
|
166
|
+
|
167
|
+
# @see https://docs.timescale.com/api/latest/continuous-aggregates/add_continuous_aggregate_policy
|
168
|
+
#
|
169
|
+
# @param [String] continuous_aggregate The name of the continuous aggregate to add the policy for
|
170
|
+
# @param [String] start_offset The start of the refresh window as an interval relative to the time when the policy is executed
|
171
|
+
# @param [String] end_offset The end of the refresh window as an interval relative to the time when the policy is executed
|
172
|
+
# @param [String] schedule_interval The interval between refresh executions in wall-clock time
|
173
|
+
# @param [Hash] options The optional arguments
|
174
|
+
# @return [String] The add_continuous_aggregate_policy SQL statement
|
175
|
+
def add_continuous_aggregate_policy_sql(continuous_aggregate, start_offset: nil, end_offset: nil, schedule_interval:, **options)
|
176
|
+
options.transform_keys!(&:to_sym)
|
177
|
+
|
178
|
+
arguments = [quote(continuous_aggregate)]
|
179
|
+
arguments << named_notation_sql(name: :start_offset, value: interval_to_sql(start_offset))
|
180
|
+
arguments << named_notation_sql(name: :end_offset, value: interval_to_sql(end_offset))
|
181
|
+
arguments << named_notation_sql(name: :schedule_interval, value: interval_to_sql(schedule_interval))
|
182
|
+
arguments += continuous_aggregate_policy_options_to_named_notation_sql(options)
|
183
|
+
|
184
|
+
"SELECT add_continuous_aggregate_policy(#{arguments.join(', ')});"
|
185
|
+
end
|
186
|
+
|
187
|
+
# @see https://docs.timescale.com/api/latest/continuous-aggregates/remove_continuous_aggregate_policy
|
188
|
+
#
|
189
|
+
# @param [String] continuous_aggregate The name of the continuous aggregate the policy should be removed from
|
190
|
+
# @param [Hash] options The optional arguments
|
191
|
+
# @return [String] The remove_continuous_aggregate_policy SQL statement
|
192
|
+
def remove_continuous_aggregate_policy_sql(continuous_aggregate, **options)
|
193
|
+
options.transform_keys!(&:to_sym)
|
194
|
+
|
195
|
+
arguments = [quote(continuous_aggregate)]
|
196
|
+
arguments += policy_options_to_named_notation_sql(options)
|
197
|
+
|
198
|
+
"SELECT remove_continuous_aggregate_policy(#{arguments.join(', ')});"
|
199
|
+
end
|
200
|
+
|
136
201
|
private
|
137
202
|
|
138
203
|
# @param [Array<Hash<Symbol, Object>>] options The policy optional arguments.
|
139
204
|
# @return [Array<String>]
|
140
|
-
def
|
205
|
+
def policy_options_to_named_notation_sql(options)
|
141
206
|
options.map do |option, value|
|
142
207
|
case option
|
143
|
-
when :if_not_exists, :if_exists then
|
144
|
-
when :initial_start, :timezone then
|
208
|
+
when :if_not_exists, :if_exists then named_notation_sql(name: option, value: boolean_to_sql(value))
|
209
|
+
when :initial_start, :timezone then named_notation_sql(name: option, value: quote(value))
|
145
210
|
end
|
146
211
|
end.compact
|
147
212
|
end
|
148
213
|
|
149
214
|
# @param [Array<Hash<Symbol, Object>>] options The create_hypertable optional arguments.
|
150
215
|
# @return [Array<String>]
|
151
|
-
def
|
216
|
+
def create_hypertable_options_to_named_notation_sql(options)
|
152
217
|
options.map do |option, value|
|
153
218
|
case option
|
154
219
|
when :chunk_time_interval
|
155
|
-
|
220
|
+
named_notation_sql(name: option, value: interval_to_sql(value))
|
156
221
|
when :if_not_exists, :create_default_indexes, :migrate_data, :distributed
|
157
|
-
|
222
|
+
named_notation_sql(name: option, value: boolean_to_sql(value))
|
158
223
|
when :partitioning_func, :associated_schema_name,
|
159
224
|
:associated_table_prefix, :time_partitioning_func
|
160
|
-
|
161
|
-
|
162
|
-
|
225
|
+
named_notation_sql(name: option, value: quote(value))
|
226
|
+
end
|
227
|
+
end.compact
|
228
|
+
end
|
229
|
+
|
230
|
+
# @param [Array<Hash<Symbol, Object>>] options The continuous aggregate policy arguments.
|
231
|
+
# @return [Array<String>]
|
232
|
+
def continuous_aggregate_policy_options_to_named_notation_sql(options)
|
233
|
+
options.map do |option, value|
|
234
|
+
case option
|
235
|
+
when :if_not_exists then named_notation_sql(name: option, value: boolean_to_sql(value))
|
236
|
+
when :initial_start, :timezone then named_notation_sql(name: option, value: quote(value))
|
163
237
|
end
|
164
238
|
end.compact
|
165
239
|
end
|
240
|
+
|
241
|
+
def named_notation_sql(name:, value:)
|
242
|
+
"#{name} => #{value}"
|
243
|
+
end
|
166
244
|
end
|
167
245
|
end
|
168
246
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
module Timescaledb
|
2
2
|
class Database
|
3
3
|
module Types
|
4
|
-
# @param [String] interval The interval value
|
4
|
+
# @param [String, Integer] interval The interval value
|
5
5
|
# @return [String]
|
6
6
|
def interval_to_sql(interval)
|
7
|
+
return 'NULL' if interval.nil?
|
8
|
+
return interval if interval.kind_of?(Integer)
|
9
|
+
|
7
10
|
"INTERVAL #{quote(interval)}"
|
8
11
|
end
|
9
12
|
|
data/lib/timescaledb/database.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
+
require_relative 'database/chunk_statements'
|
2
|
+
require_relative 'database/hypertable_statements'
|
1
3
|
require_relative 'database/quoting'
|
2
4
|
require_relative 'database/schema_statements'
|
3
5
|
require_relative 'database/types'
|
4
6
|
|
5
7
|
module Timescaledb
|
6
8
|
class Database
|
9
|
+
extend ChunkStatements
|
10
|
+
extend HypertableStatements
|
7
11
|
extend Quoting
|
8
12
|
extend SchemaStatements
|
9
13
|
extend Types
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
|
3
|
+
# Provides metadata around the extension in the database
|
4
|
+
module Extension
|
5
|
+
module_function
|
6
|
+
# @return String version of the timescaledb extension
|
7
|
+
def version
|
8
|
+
@version ||= Timescaledb.connection.query_first(<<~SQL)&.version
|
9
|
+
SELECT extversion as version
|
10
|
+
FROM pg_extension
|
11
|
+
WHERE extname = 'timescaledb'
|
12
|
+
SQL
|
13
|
+
end
|
14
|
+
|
15
|
+
def installed?
|
16
|
+
version.present?
|
17
|
+
end
|
18
|
+
|
19
|
+
def update!
|
20
|
+
Timescaledb.connection.execute('ALTER EXTENSION timescaledb UPDATE')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -29,6 +29,11 @@ module Timescaledb
|
|
29
29
|
create_hypertable(table_name, **options[:hypertable]) if options.key?(:hypertable)
|
30
30
|
end
|
31
31
|
|
32
|
+
# Override the valid_table_definition_options to include hypertable.
|
33
|
+
def valid_table_definition_options # :nodoc:
|
34
|
+
super + [:hypertable]
|
35
|
+
end
|
36
|
+
|
32
37
|
# Setup hypertable from options
|
33
38
|
# @see create_table with the hypertable options.
|
34
39
|
def create_hypertable(table_name,
|
@@ -41,6 +46,7 @@ module Timescaledb
|
|
41
46
|
number_partitions: nil,
|
42
47
|
**hypertable_options)
|
43
48
|
|
49
|
+
original_logger = ActiveRecord::Base.logger
|
44
50
|
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
45
51
|
|
46
52
|
options = ["chunk_time_interval => INTERVAL '#{chunk_time_interval}'"]
|
@@ -58,16 +64,18 @@ module Timescaledb
|
|
58
64
|
|
59
65
|
if compress_segmentby
|
60
66
|
execute <<~SQL
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
67
|
+
ALTER TABLE #{table_name} SET (
|
68
|
+
timescaledb.compress,
|
69
|
+
timescaledb.compress_orderby = '#{compress_orderby}',
|
70
|
+
timescaledb.compress_segmentby = '#{compress_segmentby}'
|
71
|
+
)
|
66
72
|
SQL
|
67
73
|
end
|
68
74
|
if compression_interval
|
69
75
|
execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compression_interval}')"
|
70
76
|
end
|
77
|
+
ensure
|
78
|
+
ActiveRecord::Base.logger = original_logger if original_logger
|
71
79
|
end
|
72
80
|
|
73
81
|
# Create a new continuous aggregate
|
@@ -79,7 +87,11 @@ module Timescaledb
|
|
79
87
|
# @option refresh_policies [String] start_offset: INTERVAL or integer
|
80
88
|
# @option refresh_policies [String] end_offset: INTERVAL or integer
|
81
89
|
# @option refresh_policies [String] schedule_interval: INTERVAL
|
90
|
+
# @option materialized_only [Boolean] Override the WITH clause 'timescaledb.materialized_only'
|
91
|
+
# @option create_group_indexes [Boolean] Override the WITH clause 'timescaledb.create_group_indexes'
|
92
|
+
# @option finalized [Boolean] Override the WITH clause 'timescaledb.finalized'
|
82
93
|
#
|
94
|
+
# @see https://docs.timescale.com/api/latest/continuous-aggregates/create_materialized_view/
|
83
95
|
# @see https://docs.timescale.com/api/latest/continuous-aggregates/add_continuous_aggregate_policy/
|
84
96
|
#
|
85
97
|
# @example
|
@@ -94,15 +106,19 @@ module Timescaledb
|
|
94
106
|
def create_continuous_aggregate(table_name, query, **options)
|
95
107
|
execute <<~SQL
|
96
108
|
CREATE MATERIALIZED VIEW #{table_name}
|
97
|
-
WITH (
|
109
|
+
WITH (
|
110
|
+
timescaledb.continuous
|
111
|
+
#{build_with_clause_option_string(:materialized_only, options)}
|
112
|
+
#{build_with_clause_option_string(:create_group_indexes, options)}
|
113
|
+
#{build_with_clause_option_string(:finalized, options)}
|
114
|
+
) AS
|
98
115
|
#{query.respond_to?(:to_sql) ? query.to_sql : query}
|
99
|
-
WITH #{
|
116
|
+
WITH #{'NO' unless options[:with_data]} DATA;
|
100
117
|
SQL
|
101
118
|
|
102
119
|
create_continuous_aggregate_policy(table_name, **(options[:refresh_policies] || {}))
|
103
120
|
end
|
104
121
|
|
105
|
-
|
106
122
|
# Drop a new continuous aggregate.
|
107
123
|
#
|
108
124
|
# It basically DROP MATERIALIZED VIEW for a given @name.
|
@@ -137,6 +153,18 @@ module Timescaledb
|
|
137
153
|
def remove_retention_policy(table_name)
|
138
154
|
execute "SELECT remove_retention_policy('#{table_name}')"
|
139
155
|
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# Build a string for the WITH clause of the CREATE MATERIALIZED VIEW statement.
|
160
|
+
# When the option is omitted, this method returns an empty string, which allows this gem to use the
|
161
|
+
# defaults provided by TimescaleDB.
|
162
|
+
def build_with_clause_option_string(option_key, options)
|
163
|
+
return '' unless options.key?(option_key)
|
164
|
+
|
165
|
+
value = options[option_key] ? 'true' : 'false'
|
166
|
+
",timescaledb.#{option_key}=#{value}"
|
167
|
+
end
|
140
168
|
end
|
141
169
|
end
|
142
170
|
|
@@ -25,7 +25,6 @@ module Timescaledb
|
|
25
25
|
|
26
26
|
# @override Scenic::Adapters::Postgres#create_view
|
27
27
|
# to add the `with: ` keyword that can be used for such option.
|
28
|
-
#
|
29
28
|
def create_view(name, version: nil, with: nil, sql_definition: nil, materialized: false, no_data: false)
|
30
29
|
if version.present? && sql_definition.present?
|
31
30
|
raise(
|
@@ -69,4 +68,4 @@ end
|
|
69
68
|
|
70
69
|
|
71
70
|
Scenic::Adapters::Postgres.include(Timescaledb::Scenic::Extension)
|
72
|
-
ActiveRecord::ConnectionAdapters::AbstractAdapter.
|
71
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(Timescaledb::Scenic::MigrationHelpers)
|
@@ -2,15 +2,57 @@ require 'active_record/connection_adapters/postgresql_adapter'
|
|
2
2
|
require 'active_support/core_ext/string/indent'
|
3
3
|
|
4
4
|
module Timescaledb
|
5
|
+
# Schema dumper overrides default schema dumper to include:
|
6
|
+
# * hypertables
|
7
|
+
# * retention policies
|
8
|
+
# * continuous aggregates
|
9
|
+
# * compression settings
|
10
|
+
# It also ignores Timescale related schemas when dumping the schema.
|
11
|
+
# It also ignores dumping options as extension is not installed or no hypertables are available.
|
5
12
|
module SchemaDumper
|
6
13
|
def tables(stream)
|
7
14
|
super # This will call #table for each table in the database
|
8
15
|
|
9
|
-
|
16
|
+
if exports_timescaledb_metadata?
|
17
|
+
timescale_hypertables(stream)
|
18
|
+
timescale_retention_policies(stream)
|
19
|
+
timescale_continuous_aggregates(stream) # Define these before any Scenic views that might use them
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ignore dumps in case DB is not eligible for TimescaleDB metadata.
|
24
|
+
# @return [Boolean] true if the extension is installed and hypertables are available, otherwise false.
|
25
|
+
private def exports_timescaledb_metadata?
|
26
|
+
# Note it's safe to use the raw connection here because we're only reading from the database
|
27
|
+
# and not modifying it. We're also on the same connection pool as ActiveRecord::Base.
|
28
|
+
# The dump process also runs standalone, so we don't need to worry about the connection being
|
29
|
+
# used elsewhere.
|
30
|
+
Timescaledb.use_connection @connection.raw_connection
|
10
31
|
|
11
|
-
|
12
|
-
|
13
|
-
|
32
|
+
Timescaledb.extension.installed? && Timescaledb.hypertables.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ignores Timescale related schemas when dumping the schema
|
36
|
+
IGNORE_SCHEMAS = %w[
|
37
|
+
_timescaledb_cache
|
38
|
+
_timescaledb_config
|
39
|
+
_timescaledb_catalog
|
40
|
+
_timescaledb_debug
|
41
|
+
_timescaledb_functions
|
42
|
+
_timescaledb_internal
|
43
|
+
timescaledb_experimental
|
44
|
+
timescaledb_information
|
45
|
+
toolkit_experimental
|
46
|
+
]
|
47
|
+
|
48
|
+
def schemas(stream)
|
49
|
+
schema_names = @connection.schema_names - ["public", *IGNORE_SCHEMAS]
|
50
|
+
if schema_names.any?
|
51
|
+
schema_names.sort.each do |name|
|
52
|
+
stream.puts " create_schema #{name.inspect}"
|
53
|
+
end
|
54
|
+
stream.puts
|
55
|
+
end
|
14
56
|
end
|
15
57
|
|
16
58
|
def timescale_hypertables(stream)
|
@@ -112,19 +154,20 @@ module Timescaledb
|
|
112
154
|
def timescale_continuous_aggregates(stream)
|
113
155
|
return unless Timescaledb::ContinuousAggregates.table_exists?
|
114
156
|
|
115
|
-
Timescaledb::ContinuousAggregates.all.
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
157
|
+
Timescaledb::ContinuousAggregates.all.find_each do |aggregate|
|
158
|
+
refresh_policies_opts = if (refresh_policy = aggregate.jobs.refresh_continuous_aggregate.first)
|
159
|
+
interval = timescale_interval(refresh_policy.schedule_interval)
|
160
|
+
end_offset = timescale_interval(refresh_policy.config["end_offset"])
|
161
|
+
start_offset = timescale_interval(refresh_policy.config["start_offset"])
|
162
|
+
%(refresh_policies: { start_offset: "#{start_offset}", end_offset: "#{end_offset}", schedule_interval: "#{interval}"})
|
163
|
+
else
|
164
|
+
""
|
165
|
+
end
|
124
166
|
|
167
|
+
with_clause_opts = "materialized_only: #{aggregate[:materialized_only]}, finalized: #{aggregate[:finalized]}"
|
125
168
|
stream.puts <<~AGG.indent(2)
|
126
|
-
create_continuous_aggregate("#{aggregate.view_name}", <<-SQL#{
|
127
|
-
#{aggregate.view_definition.strip.gsub(/;$/,
|
169
|
+
create_continuous_aggregate("#{aggregate.view_name}", <<-SQL, #{refresh_policies_opts}, #{with_clause_opts})
|
170
|
+
#{aggregate.view_definition.strip.gsub(/;$/, '')}
|
128
171
|
SQL
|
129
172
|
AGG
|
130
173
|
stream.puts
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class Stats
|
3
|
+
class Chunks
|
4
|
+
# @param [Array<String>] hypertables The list of hypertable names.
|
5
|
+
# @param [Timescaledb:Connection] connection The PG connection.
|
6
|
+
def initialize(hypertables = [], connection = Timescaledb.connection)
|
7
|
+
@connection = connection
|
8
|
+
@hypertables = hypertables
|
9
|
+
end
|
10
|
+
|
11
|
+
delegate :query_count, to: :@connection
|
12
|
+
|
13
|
+
# @return [Hash] The chunks stats
|
14
|
+
def to_h
|
15
|
+
{ total: total, compressed: compressed, uncompressed: uncompressed }
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def total
|
21
|
+
query_count(base_query, [@hypertables])
|
22
|
+
end
|
23
|
+
|
24
|
+
def compressed
|
25
|
+
compressed_query = [base_query, 'is_compressed'].join(' AND ')
|
26
|
+
|
27
|
+
query_count(compressed_query, [@hypertables])
|
28
|
+
end
|
29
|
+
|
30
|
+
def uncompressed
|
31
|
+
uncompressed_query = [base_query, 'NOT is_compressed'].join(' AND ')
|
32
|
+
|
33
|
+
query_count(uncompressed_query, [@hypertables])
|
34
|
+
end
|
35
|
+
|
36
|
+
def base_query
|
37
|
+
"SELECT COUNT(1) FROM timescaledb_information.chunks WHERE hypertable_name IN ($1)"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Timescaledb
|
3
|
+
class Stats
|
4
|
+
class ContinuousAggregates
|
5
|
+
# @param [Timescaledb:Connection] connection The PG connection.
|
6
|
+
def initialize(connection = Timescaledb.connection)
|
7
|
+
@connection = connection
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate :query_count, to: :@connection
|
11
|
+
|
12
|
+
# @return [Hash] The continuous_aggregates stats
|
13
|
+
def to_h
|
14
|
+
{ total: total }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def total
|
20
|
+
query_count('SELECT COUNT(1) FROM timescaledb_information.continuous_aggregates')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative './chunks'
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
class Stats
|
5
|
+
class Hypertables
|
6
|
+
# @param [Timescaledb:Connection] connection The PG connection.
|
7
|
+
# @param [Array<String>] hypertables The list of hypertable names.
|
8
|
+
def initialize(hypertables = [], connection = Timescaledb.connection)
|
9
|
+
@connection = connection
|
10
|
+
@hypertables = hypertables.map(&method('hypertable_name_with_schema'))
|
11
|
+
end
|
12
|
+
|
13
|
+
delegate :query, :query_first, :query_count, to: :@connection
|
14
|
+
|
15
|
+
# @return [Hash] The hypertables stats
|
16
|
+
def to_h
|
17
|
+
{
|
18
|
+
count: @hypertables.count,
|
19
|
+
uncompressed_count: uncompressed_count,
|
20
|
+
approximate_row_count: approximate_row_count,
|
21
|
+
chunks: Timescaledb::Stats::Chunks.new(@hypertables).to_h,
|
22
|
+
size: size
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def uncompressed_count
|
29
|
+
@hypertables.count do |hypertable|
|
30
|
+
query("SELECT * from hypertable_compression_stats('#{hypertable}')").empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def approximate_row_count
|
35
|
+
@hypertables.each_with_object(Hash.new) do |hypertable, summary|
|
36
|
+
row_count = query_first("SELECT * FROM approximate_row_count('#{hypertable}')").approximate_row_count.to_i
|
37
|
+
|
38
|
+
summary[hypertable] = row_count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def size
|
43
|
+
sum = -> (method_name) { (@hypertables.map(&method(method_name)).inject(:+) || 0) }
|
44
|
+
|
45
|
+
{
|
46
|
+
uncompressed: humanize_bytes(sum[:before_total_bytes]),
|
47
|
+
compressed: humanize_bytes(sum[:after_total_bytes])
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def before_total_bytes(hypertable)
|
52
|
+
(compression_stats[hypertable]&.before_compression_total_bytes || detailed_size[hypertable]).to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
def after_total_bytes(hypertable)
|
56
|
+
(compression_stats[hypertable]&.after_compression_total_bytes || 0).to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
def compression_stats
|
60
|
+
@compression_stats ||=
|
61
|
+
@hypertables.each_with_object(Hash.new) do |hypertable, stats|
|
62
|
+
stats[hypertable] = query_first(compression_stats_query, [hypertable])
|
63
|
+
stats
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def compression_stats_query
|
68
|
+
'SELECT * FROM hypertable_compression_stats($1)'
|
69
|
+
end
|
70
|
+
|
71
|
+
def detailed_size
|
72
|
+
@detailed_size ||=
|
73
|
+
@hypertables.each_with_object(Hash.new) do |hypertable, size|
|
74
|
+
size[hypertable] = query_first(detailed_size_query, [hypertable]).total_bytes
|
75
|
+
size
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def detailed_size_query
|
80
|
+
'SELECT * FROM hypertable_detailed_size($1)'
|
81
|
+
end
|
82
|
+
|
83
|
+
def hypertable_name_with_schema(hypertable)
|
84
|
+
[hypertable.hypertable_schema, hypertable.hypertable_name].compact.join('.')
|
85
|
+
end
|
86
|
+
|
87
|
+
def humanize_bytes(bytes)
|
88
|
+
units = %w(B KiB MiB GiB TiB PiB EiB)
|
89
|
+
|
90
|
+
return '0 B' if bytes == 0
|
91
|
+
|
92
|
+
exp = (Math.log2(bytes) / 10).floor
|
93
|
+
max_exp = units.size - 1
|
94
|
+
exp = max_exp if exp > max_exp
|
95
|
+
|
96
|
+
value = (bytes.to_f / (1 << (exp * 10))).round(1)
|
97
|
+
|
98
|
+
"#{value} #{units[exp]}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module Timescaledb
|
3
|
+
class Stats
|
4
|
+
class JobStats
|
5
|
+
# @param [Timescaledb:Connection] connection The PG connection.
|
6
|
+
def initialize(connection = Timescaledb.connection)
|
7
|
+
@connection = connection
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate :query_first, to: :@connection
|
11
|
+
|
12
|
+
# @return [Hash] The job_stats stats
|
13
|
+
def to_h
|
14
|
+
query_first(job_stats_query).to_h.transform_values(&:to_i)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def job_stats_query
|
20
|
+
<<-SQL
|
21
|
+
SELECT SUM(total_successes)::INT AS success,
|
22
|
+
SUM(total_runs)::INT AS runs,
|
23
|
+
SUM(total_failures)::INT AS failures
|
24
|
+
FROM timescaledb_information.job_stats
|
25
|
+
SQL
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative './stats/continuous_aggregates'
|
2
|
+
require_relative './stats/hypertables'
|
3
|
+
require_relative './stats/job_stats'
|
4
|
+
|
5
|
+
module Timescaledb
|
6
|
+
class Stats
|
7
|
+
# @param [Array<OpenStruct>] hypertables The list of hypertables.
|
8
|
+
# @param [Timescaledb:Connection] connection The PG connection.
|
9
|
+
def initialize(hypertables = [], connection = Timescaledb.connection)
|
10
|
+
@hypertables = hypertables
|
11
|
+
@connection = connection
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{
|
16
|
+
hypertables: Hypertables.new(@hypertables).to_h,
|
17
|
+
continuous_aggregates: ContinuousAggregates.new.to_h,
|
18
|
+
jobs_stats: JobStats.new.to_h
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -67,7 +67,7 @@ module Timescaledb
|
|
67
67
|
|
68
68
|
select( %|time_bucket('#{timeframe}', "#{time}")|,
|
69
69
|
*segment_by,
|
70
|
-
"
|
70
|
+
"candlestick_agg(#{time}, #{value}, #{volume}) as candlestick")
|
71
71
|
.order(1)
|
72
72
|
.group(*(segment_by ? [1,2] : 1))
|
73
73
|
end
|
@@ -82,49 +82,16 @@ module Timescaledb
|
|
82
82
|
unscoped
|
83
83
|
.from("(#{raw.to_sql}) AS candlestick")
|
84
84
|
.select("time_bucket",*segment_by,
|
85
|
-
"
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
scope :_ohlc, -> (timeframe: '1h',
|
98
|
-
segment_by: segment_by_column,
|
99
|
-
time: time_column,
|
100
|
-
value: value_column) do
|
101
|
-
|
102
|
-
select( *segment_by,
|
103
|
-
%|time_bucket('#{timeframe}', #{time}) as "#{time}"|,
|
104
|
-
"toolkit_experimental.ohlc(#{time}, #{value})")
|
105
|
-
.order(1)
|
106
|
-
.group(*(segment_by ? [1,2] : 1))
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
scope :ohlc, -> (timeframe: '1h',
|
112
|
-
segment_by: segment_by_column,
|
113
|
-
time: time_column,
|
114
|
-
value: value_column) do
|
115
|
-
|
116
|
-
raw = _ohlc(timeframe: timeframe, segment_by: segment_by, time: time, value: value)
|
117
|
-
unscoped
|
118
|
-
.from("(#{raw.to_sql}) AS ohlc")
|
119
|
-
.select(*segment_by, time,
|
120
|
-
"toolkit_experimental.open(ohlc),
|
121
|
-
toolkit_experimental.high(ohlc),
|
122
|
-
toolkit_experimental.low(ohlc),
|
123
|
-
toolkit_experimental.close(ohlc),
|
124
|
-
toolkit_experimental.open_time(ohlc),
|
125
|
-
toolkit_experimental.high_time(ohlc),
|
126
|
-
toolkit_experimental.low_time(ohlc),
|
127
|
-
toolkit_experimental.close_time(ohlc)")
|
85
|
+
"open(candlestick),
|
86
|
+
high(candlestick),
|
87
|
+
low(candlestick),
|
88
|
+
close(candlestick),
|
89
|
+
open_time(candlestick),
|
90
|
+
high_time(candlestick),
|
91
|
+
low_time(candlestick),
|
92
|
+
close_time(candlestick),
|
93
|
+
volume(candlestick),
|
94
|
+
vwap(candlestick)")
|
128
95
|
end
|
129
96
|
end
|
130
97
|
end
|
data/lib/timescaledb/version.rb
CHANGED
data/lib/timescaledb.rb
CHANGED
@@ -3,22 +3,34 @@ 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/connection'
|
6
7
|
require_relative 'timescaledb/toolkit'
|
7
8
|
require_relative 'timescaledb/chunk'
|
8
9
|
require_relative 'timescaledb/compression_settings'
|
10
|
+
require_relative 'timescaledb/connection_handling'
|
9
11
|
require_relative 'timescaledb/continuous_aggregates'
|
10
12
|
require_relative 'timescaledb/dimensions'
|
11
13
|
require_relative 'timescaledb/hypertable'
|
12
14
|
require_relative 'timescaledb/job'
|
13
15
|
require_relative 'timescaledb/job_stats'
|
14
16
|
require_relative 'timescaledb/schema_dumper'
|
17
|
+
require_relative 'timescaledb/stats'
|
15
18
|
require_relative 'timescaledb/stats_report'
|
16
19
|
require_relative 'timescaledb/migration_helpers'
|
20
|
+
require_relative 'timescaledb/extension'
|
17
21
|
require_relative 'timescaledb/version'
|
18
22
|
|
19
23
|
module Timescaledb
|
20
24
|
module_function
|
21
25
|
|
26
|
+
def connection
|
27
|
+
Connection.instance
|
28
|
+
end
|
29
|
+
|
30
|
+
def extension
|
31
|
+
Extension
|
32
|
+
end
|
33
|
+
|
22
34
|
def chunks
|
23
35
|
Chunk.all
|
24
36
|
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.2.
|
4
|
+
version: 0.2.9
|
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: 2024-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -153,12 +153,17 @@ files:
|
|
153
153
|
- lib/timescaledb/application_record.rb
|
154
154
|
- lib/timescaledb/chunk.rb
|
155
155
|
- lib/timescaledb/compression_settings.rb
|
156
|
+
- lib/timescaledb/connection.rb
|
157
|
+
- lib/timescaledb/connection_handling.rb
|
156
158
|
- lib/timescaledb/continuous_aggregates.rb
|
157
159
|
- lib/timescaledb/database.rb
|
160
|
+
- lib/timescaledb/database/chunk_statements.rb
|
161
|
+
- lib/timescaledb/database/hypertable_statements.rb
|
158
162
|
- lib/timescaledb/database/quoting.rb
|
159
163
|
- lib/timescaledb/database/schema_statements.rb
|
160
164
|
- lib/timescaledb/database/types.rb
|
161
165
|
- lib/timescaledb/dimensions.rb
|
166
|
+
- lib/timescaledb/extension.rb
|
162
167
|
- lib/timescaledb/hypertable.rb
|
163
168
|
- lib/timescaledb/job.rb
|
164
169
|
- lib/timescaledb/job_stats.rb
|
@@ -166,6 +171,11 @@ files:
|
|
166
171
|
- lib/timescaledb/scenic/adapter.rb
|
167
172
|
- lib/timescaledb/scenic/extension.rb
|
168
173
|
- lib/timescaledb/schema_dumper.rb
|
174
|
+
- lib/timescaledb/stats.rb
|
175
|
+
- lib/timescaledb/stats/chunks.rb
|
176
|
+
- lib/timescaledb/stats/continuous_aggregates.rb
|
177
|
+
- lib/timescaledb/stats/hypertables.rb
|
178
|
+
- lib/timescaledb/stats/job_stats.rb
|
169
179
|
- lib/timescaledb/stats_report.rb
|
170
180
|
- lib/timescaledb/toolkit.rb
|
171
181
|
- lib/timescaledb/toolkit/helpers.rb
|