timescaledb 0.2.6 → 0.2.8
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/core.rb +1 -1
- data/lib/timescaledb/acts_as_hypertable.rb +5 -2
- data/lib/timescaledb/connection.rb +43 -0
- data/lib/timescaledb/connection_handling.rb +16 -0
- data/lib/timescaledb/database/chunk_statements.rb +21 -0
- data/lib/timescaledb/database/hypertable_statements.rb +37 -0
- data/lib/timescaledb/database/quoting.rb +12 -0
- data/lib/timescaledb/database/schema_statements.rb +246 -0
- data/lib/timescaledb/database/types.rb +20 -0
- data/lib/timescaledb/database.rb +15 -0
- data/lib/timescaledb/migration_helpers.rb +36 -8
- data/lib/timescaledb/schema_dumper.rb +40 -12
- 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 +21 -17
- data/lib/timescaledb/version.rb +1 -1
- data/lib/timescaledb.rb +3 -0
- metadata +15 -95
- data/.github/workflows/ci.yml +0 -72
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/.tool-versions +0 -1
- data/.travis.yml +0 -9
- data/CODE_OF_CONDUCT.md +0 -74
- data/Fastfile +0 -17
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -75
- data/Gemfile.scenic +0 -7
- data/Gemfile.scenic.lock +0 -119
- data/README.md +0 -490
- data/Rakefile +0 -21
- data/bin/console +0 -28
- data/bin/setup +0 -13
- data/docs/command_line.md +0 -178
- data/docs/img/lttb_example.png +0 -0
- data/docs/img/lttb_sql_vs_ruby.gif +0 -0
- data/docs/img/lttb_zoom.gif +0 -0
- data/docs/index.md +0 -72
- data/docs/migrations.md +0 -76
- data/docs/models.md +0 -78
- data/docs/toolkit.md +0 -507
- data/docs/toolkit_lttb_tutorial.md +0 -557
- data/docs/toolkit_lttb_zoom.md +0 -357
- data/docs/toolkit_ohlc.md +0 -315
- data/docs/videos.md +0 -16
- data/examples/all_in_one/all_in_one.rb +0 -94
- data/examples/all_in_one/benchmark_comparison.rb +0 -108
- data/examples/all_in_one/caggs.rb +0 -93
- data/examples/all_in_one/query_data.rb +0 -78
- data/examples/ranking/.gitattributes +0 -7
- data/examples/ranking/.gitignore +0 -29
- data/examples/ranking/.ruby-version +0 -1
- data/examples/ranking/Gemfile +0 -33
- data/examples/ranking/Gemfile.lock +0 -189
- data/examples/ranking/README.md +0 -166
- data/examples/ranking/Rakefile +0 -6
- data/examples/ranking/app/controllers/application_controller.rb +0 -2
- data/examples/ranking/app/controllers/concerns/.keep +0 -0
- data/examples/ranking/app/jobs/application_job.rb +0 -7
- data/examples/ranking/app/models/application_record.rb +0 -3
- data/examples/ranking/app/models/concerns/.keep +0 -0
- data/examples/ranking/app/models/game.rb +0 -2
- data/examples/ranking/app/models/play.rb +0 -7
- data/examples/ranking/bin/bundle +0 -114
- data/examples/ranking/bin/rails +0 -4
- data/examples/ranking/bin/rake +0 -4
- data/examples/ranking/bin/setup +0 -33
- data/examples/ranking/config/application.rb +0 -39
- data/examples/ranking/config/boot.rb +0 -4
- data/examples/ranking/config/credentials.yml.enc +0 -1
- data/examples/ranking/config/database.yml +0 -86
- data/examples/ranking/config/environment.rb +0 -5
- data/examples/ranking/config/environments/development.rb +0 -60
- data/examples/ranking/config/environments/production.rb +0 -75
- data/examples/ranking/config/environments/test.rb +0 -53
- data/examples/ranking/config/initializers/cors.rb +0 -16
- data/examples/ranking/config/initializers/filter_parameter_logging.rb +0 -8
- data/examples/ranking/config/initializers/inflections.rb +0 -16
- data/examples/ranking/config/initializers/timescale.rb +0 -2
- data/examples/ranking/config/locales/en.yml +0 -33
- data/examples/ranking/config/puma.rb +0 -43
- data/examples/ranking/config/routes.rb +0 -6
- data/examples/ranking/config/storage.yml +0 -34
- data/examples/ranking/config.ru +0 -6
- data/examples/ranking/db/migrate/20220209120747_create_games.rb +0 -10
- data/examples/ranking/db/migrate/20220209120910_create_plays.rb +0 -19
- data/examples/ranking/db/migrate/20220209143347_create_score_per_hours.rb +0 -5
- data/examples/ranking/db/schema.rb +0 -47
- data/examples/ranking/db/seeds.rb +0 -7
- data/examples/ranking/db/views/score_per_hours_v01.sql +0 -7
- data/examples/ranking/lib/tasks/.keep +0 -0
- data/examples/ranking/log/.keep +0 -0
- data/examples/ranking/public/robots.txt +0 -1
- data/examples/ranking/storage/.keep +0 -0
- data/examples/ranking/tmp/.keep +0 -0
- data/examples/ranking/tmp/pids/.keep +0 -0
- data/examples/ranking/tmp/storage/.keep +0 -0
- data/examples/ranking/vendor/.keep +0 -0
- data/examples/toolkit-demo/compare_volatility.rb +0 -104
- data/examples/toolkit-demo/lttb/README.md +0 -15
- data/examples/toolkit-demo/lttb/lttb.rb +0 -92
- data/examples/toolkit-demo/lttb/lttb_sinatra.rb +0 -139
- data/examples/toolkit-demo/lttb/lttb_test.rb +0 -21
- data/examples/toolkit-demo/lttb/views/index.erb +0 -27
- data/examples/toolkit-demo/lttb-zoom/README.md +0 -13
- data/examples/toolkit-demo/lttb-zoom/lttb_zoomable.rb +0 -90
- data/examples/toolkit-demo/lttb-zoom/views/index.erb +0 -33
- data/examples/toolkit-demo/ohlc.rb +0 -175
- data/mkdocs.yml +0 -34
- data/timescaledb.gemspec +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 240bc1a55d3955c734d79946868b5e878cacfa354f7b0a843b72f78c26a85c83
|
4
|
+
data.tar.gz: 23ccc7e91c0da1ea522e5c8a71cd4522478aa1c73ff0e9ee6f61e42b26f9955c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ae2cda69cc099ad24a47aa4125ee10db96eb8186e261125f1e56ad6f4f2d805888d02d9eee92d48d18eab49d628918fef4e33234edc48ddb4ecb402d4518207
|
7
|
+
data.tar.gz: 65ee70a4b17944880979e71d1abee756a5f398f48cb048b575afbe4a0412f91d993ec97723c2b10d472ceafd405d7d013480824702c2a5db11e23f6eacb7f69a
|
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
|
@@ -75,7 +75,7 @@ module Timescaledb
|
|
75
75
|
|
76
76
|
scope :yesterday, -> { where("DATE(#{time_column}) = ?", Date.yesterday.in_time_zone.to_date) }
|
77
77
|
scope :today, -> { where("DATE(#{time_column}) = ?", Date.today.in_time_zone.to_date) }
|
78
|
-
scope :last_hour, -> { where("#{time_column}
|
78
|
+
scope :last_hour, -> { where("#{time_column} between ? and ?", 1.hour.ago.in_time_zone, Time.now.end_of_hour.in_time_zone) }
|
79
79
|
end
|
80
80
|
|
81
81
|
def normalize_hypertable_options
|
@@ -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,43 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Timescaledb
|
4
|
+
class Connection
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_writer :config
|
8
|
+
|
9
|
+
# @param [String] query The SQL raw query.
|
10
|
+
# @param [Array] params The SQL query parameters.
|
11
|
+
# @return [Array<OpenStruct>] The SQL result.
|
12
|
+
def query(query, params = [])
|
13
|
+
query = params.empty? ? connection.exec(query) : connection.exec_params(query, params)
|
14
|
+
|
15
|
+
query.map(&OpenStruct.method(:new))
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String] query The SQL raw query.
|
19
|
+
# @param [Array] params The SQL query parameters.
|
20
|
+
# @return [OpenStruct] The first SQL result.
|
21
|
+
def query_first(query, params = [])
|
22
|
+
query(query, params).first
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [String] query The SQL raw query.
|
26
|
+
# @param [Array] params The SQL query parameters.
|
27
|
+
# @return [Integr] The count value from SQL result.
|
28
|
+
def query_count(query, params = [])
|
29
|
+
query_first(query, params).count.to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [Boolean] True if the connection singleton was configured, otherwise returns false.
|
33
|
+
def connected?
|
34
|
+
!@config.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def connection
|
40
|
+
@connection ||= PG.connect(@config)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class ConnectionNotEstablishedError < StandardError; end
|
3
|
+
|
4
|
+
# @param [String] config The postgres connection string.
|
5
|
+
def establish_connection(config)
|
6
|
+
Connection.instance.config = config
|
7
|
+
end
|
8
|
+
module_function :establish_connection
|
9
|
+
|
10
|
+
def connection
|
11
|
+
raise ConnectionNotEstablishedError.new unless Connection.instance.connected?
|
12
|
+
|
13
|
+
Connection.instance
|
14
|
+
end
|
15
|
+
module_function :connection
|
16
|
+
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class Database
|
3
|
+
module Quoting
|
4
|
+
# Quotes given value and escapes single quote and backslash characters.
|
5
|
+
#
|
6
|
+
# @return [String] The given value between quotes
|
7
|
+
def quote(value)
|
8
|
+
"'#{value.gsub("\\", '\&\&').gsub("'", "''")}'"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class Database
|
3
|
+
module SchemaStatements
|
4
|
+
# @see https://docs.timescale.com/api/latest/hypertable/create_hypertable/#create_hypertable
|
5
|
+
#
|
6
|
+
# @param [String] relation The identifier of the table to convert to hypertable
|
7
|
+
# @param [String] time_column_name The name of the column containing time values as well as the primary column to partition by
|
8
|
+
# @param [Hash] options The optional arguments
|
9
|
+
# @return [String] The create_hypertable SQL statement
|
10
|
+
def create_hypertable_sql(relation, time_column_name, **options)
|
11
|
+
options.transform_keys!(&:to_sym)
|
12
|
+
|
13
|
+
partitioning_column = options.delete(:partitioning_column)
|
14
|
+
number_partitions = options.delete(:number_partitions)
|
15
|
+
|
16
|
+
arguments = [quote(relation), quote(time_column_name)]
|
17
|
+
arguments += [quote(partitioning_column), number_partitions] if partitioning_column && number_partitions
|
18
|
+
arguments += create_hypertable_options_to_named_notation_sql(options)
|
19
|
+
|
20
|
+
"SELECT create_hypertable(#{arguments.join(', ')});"
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see https://docs.timescale.com/api/latest/compression/alter_table_compression/#alter-table-compression
|
24
|
+
#
|
25
|
+
# @param [String] hypertable The name of the hypertable to enable compression
|
26
|
+
# @param [Hash] options The optional arguments
|
27
|
+
# @return [String] The ALTER TABLE SQL to enable compression
|
28
|
+
def enable_hypertable_compression_sql(hypertable, **options)
|
29
|
+
options.transform_keys!(&:to_sym)
|
30
|
+
|
31
|
+
compress_orderby = options.delete(:compress_orderby)
|
32
|
+
compress_segmentby = options.delete(:compress_segmentby)
|
33
|
+
|
34
|
+
arguments = ['timescaledb.compress']
|
35
|
+
arguments << "timescaledb.compress_orderby = #{quote(compress_orderby)}" if compress_orderby
|
36
|
+
arguments << "timescaledb.compress_segmentby = #{quote(compress_segmentby)}" if compress_segmentby
|
37
|
+
|
38
|
+
"ALTER TABLE #{hypertable} SET (#{arguments.join(', ')});"
|
39
|
+
end
|
40
|
+
|
41
|
+
# @see https://docs.timescale.com/api/latest/compression/alter_table_compression/#alter-table-compression
|
42
|
+
#
|
43
|
+
# @param [String] hypertable The name of the hypertable to disable compression
|
44
|
+
# @return [String] The ALTER TABLE SQL to disable compression
|
45
|
+
def disable_hypertable_compression_sql(hypertable)
|
46
|
+
"ALTER TABLE #{hypertable} SET (timescaledb.compress = FALSE);"
|
47
|
+
end
|
48
|
+
|
49
|
+
# @see https://docs.timescale.com/api/latest/compression/add_compression_policy/#add_compression_policy
|
50
|
+
#
|
51
|
+
# @param [String] hypertable The name of the hypertable or continuous aggregate to create the policy for
|
52
|
+
# @param [String] compress_after The age after which the policy job compresses chunks
|
53
|
+
# @param [Hash] options The optional arguments
|
54
|
+
# @return [String] The add_compression_policy SQL statement
|
55
|
+
def add_compression_policy_sql(hypertable, compress_after, **options)
|
56
|
+
options.transform_keys!(&:to_sym)
|
57
|
+
|
58
|
+
arguments = [quote(hypertable), interval_to_sql(compress_after)]
|
59
|
+
arguments += policy_options_to_named_notation_sql(options)
|
60
|
+
|
61
|
+
"SELECT add_compression_policy(#{arguments.join(', ')});"
|
62
|
+
end
|
63
|
+
|
64
|
+
# @see https://docs.timescale.com/api/latest/compression/remove_compression_policy/#remove_compression_policy
|
65
|
+
#
|
66
|
+
# @param [String] hypertable The name of the hypertable to remove the policy from
|
67
|
+
# @param [Hash] options The optional arguments
|
68
|
+
# @return [String] The remove_compression_policy SQL statement
|
69
|
+
def remove_compression_policy_sql(hypertable, **options)
|
70
|
+
options.transform_keys!(&:to_sym)
|
71
|
+
|
72
|
+
arguments = [quote(hypertable)]
|
73
|
+
arguments += policy_options_to_named_notation_sql(options)
|
74
|
+
|
75
|
+
"SELECT remove_compression_policy(#{arguments.join(', ')});"
|
76
|
+
end
|
77
|
+
|
78
|
+
# @see https://docs.timescale.com/api/latest/data-retention/add_retention_policy/#add_retention_policy
|
79
|
+
#
|
80
|
+
# @param [String] hypertable The name of the hypertable to create the policy for
|
81
|
+
# @param [String] drop_after The age after which the policy job drops chunks
|
82
|
+
# @param [Hash] options The optional arguments
|
83
|
+
# @return [String] The add_retention_policy SQL statement
|
84
|
+
def add_retention_policy_sql(hypertable, drop_after, **options)
|
85
|
+
options.transform_keys!(&:to_sym)
|
86
|
+
|
87
|
+
arguments = [quote(hypertable), interval_to_sql(drop_after)]
|
88
|
+
arguments += policy_options_to_named_notation_sql(options)
|
89
|
+
|
90
|
+
"SELECT add_retention_policy(#{arguments.join(', ')});"
|
91
|
+
end
|
92
|
+
|
93
|
+
# @see https://docs.timescale.com/api/latest/data-retention/remove_retention_policy/#remove_retention_policy
|
94
|
+
#
|
95
|
+
# @param [String] hypertable The name of the hypertable to remove the policy from
|
96
|
+
# @param [Hash] options The optional arguments
|
97
|
+
# @return [String] The remove_retention_policy SQL statement
|
98
|
+
def remove_retention_policy_sql(hypertable, **options)
|
99
|
+
options.transform_keys!(&:to_sym)
|
100
|
+
|
101
|
+
arguments = [quote(hypertable)]
|
102
|
+
arguments += policy_options_to_named_notation_sql(options)
|
103
|
+
|
104
|
+
"SELECT remove_retention_policy(#{arguments.join(', ')});"
|
105
|
+
end
|
106
|
+
|
107
|
+
# @see https://docs.timescale.com/api/latest/hypertable/add_reorder_policy/#add_reorder_policy
|
108
|
+
#
|
109
|
+
# @param [String] hypertable The name of the hypertable to create the policy for
|
110
|
+
# @param [String] index_name The existing index by which to order rows on disk
|
111
|
+
# @param [Hash] options The optional arguments
|
112
|
+
# @return [String] The add_reorder_policy SQL statement
|
113
|
+
def add_reorder_policy_sql(hypertable, index_name, **options)
|
114
|
+
options.transform_keys!(&:to_sym)
|
115
|
+
|
116
|
+
arguments = [quote(hypertable), quote(index_name)]
|
117
|
+
arguments += policy_options_to_named_notation_sql(options)
|
118
|
+
|
119
|
+
"SELECT add_reorder_policy(#{arguments.join(', ')});"
|
120
|
+
end
|
121
|
+
|
122
|
+
# @see https://docs.timescale.com/api/latest/hypertable/remove_reorder_policy/#remove_reorder_policy
|
123
|
+
#
|
124
|
+
# @param [String] hypertable The name of the hypertable to remove the policy from
|
125
|
+
# @param [Hash] options The optional arguments
|
126
|
+
# @return [String] The remove_retention_policy SQL statement
|
127
|
+
def remove_reorder_policy_sql(hypertable, **options)
|
128
|
+
options.transform_keys!(&:to_sym)
|
129
|
+
|
130
|
+
arguments = [quote(hypertable)]
|
131
|
+
arguments += policy_options_to_named_notation_sql(options)
|
132
|
+
|
133
|
+
"SELECT remove_reorder_policy(#{arguments.join(', ')});"
|
134
|
+
end
|
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
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
# @param [Array<Hash<Symbol, Object>>] options The policy optional arguments.
|
204
|
+
# @return [Array<String>]
|
205
|
+
def policy_options_to_named_notation_sql(options)
|
206
|
+
options.map do |option, value|
|
207
|
+
case option
|
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))
|
210
|
+
end
|
211
|
+
end.compact
|
212
|
+
end
|
213
|
+
|
214
|
+
# @param [Array<Hash<Symbol, Object>>] options The create_hypertable optional arguments.
|
215
|
+
# @return [Array<String>]
|
216
|
+
def create_hypertable_options_to_named_notation_sql(options)
|
217
|
+
options.map do |option, value|
|
218
|
+
case option
|
219
|
+
when :chunk_time_interval
|
220
|
+
named_notation_sql(name: option, value: interval_to_sql(value))
|
221
|
+
when :if_not_exists, :create_default_indexes, :migrate_data, :distributed
|
222
|
+
named_notation_sql(name: option, value: boolean_to_sql(value))
|
223
|
+
when :partitioning_func, :associated_schema_name,
|
224
|
+
:associated_table_prefix, :time_partitioning_func
|
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))
|
237
|
+
end
|
238
|
+
end.compact
|
239
|
+
end
|
240
|
+
|
241
|
+
def named_notation_sql(name:, value:)
|
242
|
+
"#{name} => #{value}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Timescaledb
|
2
|
+
class Database
|
3
|
+
module Types
|
4
|
+
# @param [String, Integer] interval The interval value
|
5
|
+
# @return [String]
|
6
|
+
def interval_to_sql(interval)
|
7
|
+
return 'NULL' if interval.nil?
|
8
|
+
return interval if interval.kind_of?(Integer)
|
9
|
+
|
10
|
+
"INTERVAL #{quote(interval)}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [String] boolean The boolean value
|
14
|
+
# @return [String]
|
15
|
+
def boolean_to_sql(boolean)
|
16
|
+
quote(boolean ? 'TRUE' : 'FALSE')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'database/chunk_statements'
|
2
|
+
require_relative 'database/hypertable_statements'
|
3
|
+
require_relative 'database/quoting'
|
4
|
+
require_relative 'database/schema_statements'
|
5
|
+
require_relative 'database/types'
|
6
|
+
|
7
|
+
module Timescaledb
|
8
|
+
class Database
|
9
|
+
extend ChunkStatements
|
10
|
+
extend HypertableStatements
|
11
|
+
extend Quoting
|
12
|
+
extend SchemaStatements
|
13
|
+
extend Types
|
14
|
+
end
|
15
|
+
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
|
|