timescaledb 0.2.6 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tsdb +30 -29
  3. data/lib/timescaledb/acts_as_hypertable/core.rb +1 -1
  4. data/lib/timescaledb/acts_as_hypertable.rb +5 -2
  5. data/lib/timescaledb/connection.rb +43 -0
  6. data/lib/timescaledb/connection_handling.rb +16 -0
  7. data/lib/timescaledb/database/chunk_statements.rb +21 -0
  8. data/lib/timescaledb/database/hypertable_statements.rb +37 -0
  9. data/lib/timescaledb/database/quoting.rb +12 -0
  10. data/lib/timescaledb/database/schema_statements.rb +246 -0
  11. data/lib/timescaledb/database/types.rb +20 -0
  12. data/lib/timescaledb/database.rb +15 -0
  13. data/lib/timescaledb/migration_helpers.rb +36 -8
  14. data/lib/timescaledb/schema_dumper.rb +40 -12
  15. data/lib/timescaledb/stats/chunks.rb +41 -0
  16. data/lib/timescaledb/stats/continuous_aggregates.rb +24 -0
  17. data/lib/timescaledb/stats/hypertables.rb +102 -0
  18. data/lib/timescaledb/stats/job_stats.rb +29 -0
  19. data/lib/timescaledb/stats.rb +22 -0
  20. data/lib/timescaledb/toolkit/time_vector.rb +21 -17
  21. data/lib/timescaledb/version.rb +1 -1
  22. data/lib/timescaledb.rb +3 -0
  23. metadata +15 -95
  24. data/.github/workflows/ci.yml +0 -72
  25. data/.gitignore +0 -12
  26. data/.rspec +0 -3
  27. data/.ruby-version +0 -1
  28. data/.tool-versions +0 -1
  29. data/.travis.yml +0 -9
  30. data/CODE_OF_CONDUCT.md +0 -74
  31. data/Fastfile +0 -17
  32. data/Gemfile +0 -8
  33. data/Gemfile.lock +0 -75
  34. data/Gemfile.scenic +0 -7
  35. data/Gemfile.scenic.lock +0 -119
  36. data/README.md +0 -490
  37. data/Rakefile +0 -21
  38. data/bin/console +0 -28
  39. data/bin/setup +0 -13
  40. data/docs/command_line.md +0 -178
  41. data/docs/img/lttb_example.png +0 -0
  42. data/docs/img/lttb_sql_vs_ruby.gif +0 -0
  43. data/docs/img/lttb_zoom.gif +0 -0
  44. data/docs/index.md +0 -72
  45. data/docs/migrations.md +0 -76
  46. data/docs/models.md +0 -78
  47. data/docs/toolkit.md +0 -507
  48. data/docs/toolkit_lttb_tutorial.md +0 -557
  49. data/docs/toolkit_lttb_zoom.md +0 -357
  50. data/docs/toolkit_ohlc.md +0 -315
  51. data/docs/videos.md +0 -16
  52. data/examples/all_in_one/all_in_one.rb +0 -94
  53. data/examples/all_in_one/benchmark_comparison.rb +0 -108
  54. data/examples/all_in_one/caggs.rb +0 -93
  55. data/examples/all_in_one/query_data.rb +0 -78
  56. data/examples/ranking/.gitattributes +0 -7
  57. data/examples/ranking/.gitignore +0 -29
  58. data/examples/ranking/.ruby-version +0 -1
  59. data/examples/ranking/Gemfile +0 -33
  60. data/examples/ranking/Gemfile.lock +0 -189
  61. data/examples/ranking/README.md +0 -166
  62. data/examples/ranking/Rakefile +0 -6
  63. data/examples/ranking/app/controllers/application_controller.rb +0 -2
  64. data/examples/ranking/app/controllers/concerns/.keep +0 -0
  65. data/examples/ranking/app/jobs/application_job.rb +0 -7
  66. data/examples/ranking/app/models/application_record.rb +0 -3
  67. data/examples/ranking/app/models/concerns/.keep +0 -0
  68. data/examples/ranking/app/models/game.rb +0 -2
  69. data/examples/ranking/app/models/play.rb +0 -7
  70. data/examples/ranking/bin/bundle +0 -114
  71. data/examples/ranking/bin/rails +0 -4
  72. data/examples/ranking/bin/rake +0 -4
  73. data/examples/ranking/bin/setup +0 -33
  74. data/examples/ranking/config/application.rb +0 -39
  75. data/examples/ranking/config/boot.rb +0 -4
  76. data/examples/ranking/config/credentials.yml.enc +0 -1
  77. data/examples/ranking/config/database.yml +0 -86
  78. data/examples/ranking/config/environment.rb +0 -5
  79. data/examples/ranking/config/environments/development.rb +0 -60
  80. data/examples/ranking/config/environments/production.rb +0 -75
  81. data/examples/ranking/config/environments/test.rb +0 -53
  82. data/examples/ranking/config/initializers/cors.rb +0 -16
  83. data/examples/ranking/config/initializers/filter_parameter_logging.rb +0 -8
  84. data/examples/ranking/config/initializers/inflections.rb +0 -16
  85. data/examples/ranking/config/initializers/timescale.rb +0 -2
  86. data/examples/ranking/config/locales/en.yml +0 -33
  87. data/examples/ranking/config/puma.rb +0 -43
  88. data/examples/ranking/config/routes.rb +0 -6
  89. data/examples/ranking/config/storage.yml +0 -34
  90. data/examples/ranking/config.ru +0 -6
  91. data/examples/ranking/db/migrate/20220209120747_create_games.rb +0 -10
  92. data/examples/ranking/db/migrate/20220209120910_create_plays.rb +0 -19
  93. data/examples/ranking/db/migrate/20220209143347_create_score_per_hours.rb +0 -5
  94. data/examples/ranking/db/schema.rb +0 -47
  95. data/examples/ranking/db/seeds.rb +0 -7
  96. data/examples/ranking/db/views/score_per_hours_v01.sql +0 -7
  97. data/examples/ranking/lib/tasks/.keep +0 -0
  98. data/examples/ranking/log/.keep +0 -0
  99. data/examples/ranking/public/robots.txt +0 -1
  100. data/examples/ranking/storage/.keep +0 -0
  101. data/examples/ranking/tmp/.keep +0 -0
  102. data/examples/ranking/tmp/pids/.keep +0 -0
  103. data/examples/ranking/tmp/storage/.keep +0 -0
  104. data/examples/ranking/vendor/.keep +0 -0
  105. data/examples/toolkit-demo/compare_volatility.rb +0 -104
  106. data/examples/toolkit-demo/lttb/README.md +0 -15
  107. data/examples/toolkit-demo/lttb/lttb.rb +0 -92
  108. data/examples/toolkit-demo/lttb/lttb_sinatra.rb +0 -139
  109. data/examples/toolkit-demo/lttb/lttb_test.rb +0 -21
  110. data/examples/toolkit-demo/lttb/views/index.erb +0 -27
  111. data/examples/toolkit-demo/lttb-zoom/README.md +0 -13
  112. data/examples/toolkit-demo/lttb-zoom/lttb_zoomable.rb +0 -90
  113. data/examples/toolkit-demo/lttb-zoom/views/index.erb +0 -33
  114. data/examples/toolkit-demo/ohlc.rb +0 -175
  115. data/mkdocs.yml +0 -34
  116. data/timescaledb.gemspec +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06f324ce8793264ad6acb6a0f0f8f107511be08ecf2952e2fab285aeac366d35
4
- data.tar.gz: d95da2805b53dafd174bb83fce7cf29cc2f13b5e8543bfa144d424e679aba701
3
+ metadata.gz: 240bc1a55d3955c734d79946868b5e878cacfa354f7b0a843b72f78c26a85c83
4
+ data.tar.gz: 23ccc7e91c0da1ea522e5c8a71cd4522478aa1c73ff0e9ee6f61e42b26f9955c
5
5
  SHA512:
6
- metadata.gz: 14ce31badc6ae1eae1f51161a806354fcaea6b2d39197498766b4cefa2022565410f7fc745adfbaf0d51b68d67454460f3aabab2a94444dd377a93193d0465b2
7
- data.tar.gz: 573dd6b5ae3bcfa0948176a86561c080a860d72634e1800b750554a694e17a17890a31492e2702b38ced8444b5725586bfcca6cb23109c968748edeb509bcf12
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
- ActiveRecord::Base.establish_connection(ARGV[0])
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
- def show(obj)
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
- if (except = ARGV.index("--except"))
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
- scope = scope.where.not(hypertable_name: except_hypertables)
18
+
19
+ hypertables.select! { |hypertable| except_hypertables.includes?(hypertable.hypertable_name) }
41
20
  end
42
21
 
43
- show(Timescaledb.stats(scope))
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} > ?", 1.hour.ago.in_time_zone) }
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
- ALTER TABLE #{table_name} SET (
62
- timescaledb.compress,
63
- timescaledb.compress_orderby = '#{compress_orderby}',
64
- timescaledb.compress_segmentby = '#{compress_segmentby}'
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 (timescaledb.continuous) AS
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 #{"NO" unless options[:with_data]} DATA;
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