timescaledb 0.2.7 → 0.2.9
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 +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
|