timescaledb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d2088f631818f19cc92ea997dea0fe5a929263c5100abb3e4c640a52de0a1c94
4
+ data.tar.gz: 111d3b028355623ff1b4954491c3e4a9d92dd3d491c243fd7a9d6615596ac822
5
+ SHA512:
6
+ metadata.gz: c2461c306c4812a37b740ff5d0be84bb971aba512d5e7ba523f5b36a16811b3d85708a82646b5f2f4659cf82a0310723fe3fb84579aa2d8bca809942a759b125
7
+ data.tar.gz: 78629e727c3125ec055d17806c476c2ca7908a35386e89b52b67de3f8c07a26460d783df69425b7ac6c869c2f70e77986d672460f1c3a8b554f2b55f9aa64443
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .env
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at jonatasdp@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in timescale.gemspec
4
+ gemspec
5
+
6
+ gem 'dotenv'
7
+ gem "rake", "~> 12.0"
8
+ gem "rspec", "~> 3.0"
9
+ gem 'rspec-its'
10
+ gem 'pry'
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ timescale (0.1.0)
5
+ activerecord
6
+ pg (~> 1.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (6.1.4.1)
12
+ activesupport (= 6.1.4.1)
13
+ activerecord (6.1.4.1)
14
+ activemodel (= 6.1.4.1)
15
+ activesupport (= 6.1.4.1)
16
+ activesupport (6.1.4.1)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ zeitwerk (~> 2.3)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.1.9)
24
+ diff-lcs (1.4.4)
25
+ dotenv (2.7.6)
26
+ i18n (1.8.10)
27
+ concurrent-ruby (~> 1.0)
28
+ method_source (1.0.0)
29
+ minitest (5.14.4)
30
+ pg (1.2.3)
31
+ pry (0.14.1)
32
+ coderay (~> 1.1)
33
+ method_source (~> 1.0)
34
+ rake (12.3.3)
35
+ rspec (3.10.0)
36
+ rspec-core (~> 3.10.0)
37
+ rspec-expectations (~> 3.10.0)
38
+ rspec-mocks (~> 3.10.0)
39
+ rspec-core (3.10.1)
40
+ rspec-support (~> 3.10.0)
41
+ rspec-expectations (3.10.1)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.10.0)
44
+ rspec-its (1.3.0)
45
+ rspec-core (>= 3.0.0)
46
+ rspec-expectations (>= 3.0.0)
47
+ rspec-mocks (3.10.2)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.10.0)
50
+ rspec-support (3.10.2)
51
+ tzinfo (2.0.4)
52
+ concurrent-ruby (~> 1.0)
53
+ zeitwerk (2.4.2)
54
+
55
+ PLATFORMS
56
+ ruby
57
+
58
+ DEPENDENCIES
59
+ dotenv
60
+ pry
61
+ rake (~> 12.0)
62
+ rspec (~> 3.0)
63
+ rspec-its
64
+ timescale!
65
+
66
+ BUNDLED WITH
67
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Jônatas Davi Paganini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # Timescale
2
+
3
+ Welcome to the Timescale gem! To experiment with the code, start cloning the
4
+ repository:
5
+
6
+ ```bash
7
+ git clone https://github.com/jonatas/timescale.git
8
+ cd timescale
9
+ bundle install
10
+ ```
11
+
12
+ Then you can run `bin/console` for an interactive prompt.
13
+
14
+ ```bash
15
+ bin/console
16
+ ```
17
+
18
+ You can create a `.env` file locally to run tests locally. Make sure to put your
19
+ own credentials there!
20
+
21
+ ```bash
22
+ PG_URI_TEST="postgres://<user>@localhost:5432/<dbname>"
23
+ ```
24
+
25
+ You can also use `bin/console` without any parameters and it will use the
26
+ `PG_URI_TEST` from your `.env` file.
27
+
28
+ Alternatively, you can also put some postgres URI directly as a parameter of
29
+ `bin/console`. Here is an example from my console:
30
+
31
+ ```bash
32
+ bin/console "postgres://jonatasdp@localhost:5432/timescale_test"
33
+ ```
34
+
35
+ The console will dynamically create models for all hypertables that it finds
36
+ in the database.
37
+
38
+ It will allow you to visit any database and have all models mapped as ActiveRecord
39
+ with the [HypertableHelpers](lib/timescale/hypertable_helpers.rb).
40
+
41
+ This library was started on [twitch.tv/timescaledb](https://twitch.tv/timescaledb).
42
+ You can watch all episodes here:
43
+
44
+ 1. [Wrapping Functions to Ruby Helpers](https://www.youtube.com/watch?v=hGPsUxLFAYk).
45
+ 2. [Extending ActiveRecord with Timescale Helpers](https://www.youtube.com/watch?v=IEyJIHk1Clk).
46
+ 3. [Setup Hypertables for Rails testing environment](https://www.youtube.com/watch?v=wM6hVrZe7xA).
47
+ 4. [Packing the code to this repository](https://www.youtube.com/watch?v=CMdGAl_XlL4).
48
+
49
+ ## Installation
50
+
51
+ Add this line to your application's Gemfile:
52
+
53
+ ```ruby
54
+ gem 'timescaledb'
55
+ ```
56
+
57
+ And then execute:
58
+
59
+ $ bundle install
60
+
61
+ Or install it yourself as:
62
+
63
+ $ gem install timescaledb
64
+
65
+ ## Usage
66
+
67
+ You can check the [all_in_one.rb](examples/all_in_one.rb) that will:
68
+
69
+ 1. Create hypertable with compression settings
70
+ 2. Insert data
71
+ 3. Run some queries from HypertableHelpers
72
+ 4. Check chunk size per model
73
+ 5. Compress a chunk
74
+ 6. Check chunk status
75
+ 7. Decompress a chunk
76
+
77
+ ### Migrations
78
+
79
+ Create table is now with the `hypertable` keyword allowing to pass a few options
80
+ to the function call while also using `create_table` method:
81
+
82
+ #### create_table with `:hypertable`
83
+
84
+ ```ruby
85
+ hypertable_options = {
86
+ time_column: 'created_at',
87
+ chunk_time_interval: '1 min',
88
+ compress_segmentby: 'identifier',
89
+ compression_interval: '7 days'
90
+ }
91
+
92
+ create_table(:events, id: false, hypertable: hypertable_options) do |t|
93
+ t.string :identifier, null: false
94
+ t.jsonb :payload
95
+ t.timestamps
96
+ end
97
+ ```
98
+
99
+ #### create_continuous_aggregates
100
+
101
+ This example shows a ticks table grouping ticks as OHLCV histograms for every
102
+ minute.
103
+
104
+ ```ruby
105
+ hypertable_options = {
106
+ time_column: 'created_at',
107
+ chunk_time_interval: '1 min',
108
+ compress_segmentby: 'symbol',
109
+ compress_orderby: 'created_at',
110
+ compression_interval: '7 days'
111
+ }
112
+ create_table :ticks, hypertable: hypertable_options, id: false do |t|
113
+ t.string :symbol
114
+ t.decimal :price
115
+ t.integer :volume
116
+ t.timestamps
117
+ end
118
+ Tick = Class.new(ActiveRecord::Base) do
119
+ self.table_name = 'ticks'
120
+ self.primary_key = 'symbol'
121
+ include Timescale::HypertableHelpers
122
+ end
123
+
124
+ query = Tick.select(<<~QUERY)
125
+ time_bucket('1m', created_at) as time,
126
+ symbol,
127
+ FIRST(price, created_at) as open,
128
+ MAX(price) as high,
129
+ MIN(price) as low,
130
+ LAST(price, created_at) as close,
131
+ SUM(volume) as volume").group("1,2")
132
+ QUERY
133
+
134
+ options = {
135
+ with_data: false,
136
+ refresh_policies: {
137
+ start_offset: "INTERVAL '1 month'",
138
+ end_offset: "INTERVAL '1 minute'",
139
+ schedule_interval: "INTERVAL '1 minute'"
140
+ }
141
+ }
142
+
143
+ create_continuous_aggregates('ohlc_1m', query, **options)
144
+ ```
145
+
146
+ ### Hypertable Helpers
147
+
148
+ You can also use `HypertableHelpers` to get access to some basic scopes for your
149
+ model:
150
+
151
+ ```ruby
152
+ class Event < ActiveRecord::Base
153
+ self.primary_key = "identifier"
154
+
155
+ include Timescale::HypertableHelpers
156
+ end
157
+ ```
158
+
159
+ After including the helpers, several methods from timescaledb will be available in the
160
+ model.
161
+
162
+ ### Chunks
163
+
164
+ To get chunks from a single hypertable, you can use the `.chunks` directly from
165
+ the model name.
166
+
167
+ ```ruby
168
+ Event.chunks
169
+ # DEBUG: Timescale::Chunk Load (9.0ms) SELECT "timescaledb_information"."chunks".* FROM "timescaledb_information"."chunks" WHERE "timescaledb_information"."chunks"."hypertable_name" = $1 [["hypertable_name", "events"]]
170
+ # => [#<Timescale::Chunk:0x00007f94b0c86008
171
+ # hypertable_schema: "public",
172
+ # hypertable_name: "events",
173
+ # chunk_schema: "_timescaledb_internal",
174
+ # chunk_name: "_hyper_180_74_chunk",
175
+ # primary_dimension: "created_at",
176
+ # primary_dimension_type: "timestamp without time zone",
177
+ # range_start: 2021-09-22 21:28:00 +0000,
178
+ # range_end: 2021-09-22 21:29:00 +0000,
179
+ # range_start_integer: nil,
180
+ # range_end_integer: nil,
181
+ # is_compressed: false,
182
+ # chunk_tablespace: nil,
183
+ # data_nodes: nil>
184
+ ```
185
+
186
+ To get all hypertables you can use `Timescale.hypertables` method.
187
+
188
+ ### Hypertable metadata from model
189
+
190
+ To get all details from hypertable, you can access the `.hypertable` from the
191
+ model.
192
+
193
+ ```ruby
194
+ Event.hypertable
195
+ # Timescale::Hypertable Load (4.8ms) SELECT "timescaledb_information"."hypertables".* FROM "timescaledb_information"."hypertables" WHERE "timescaledb_information"."hypertables"."hypertable_name" = $1 LIMIT $2 [["hypertable_name", "events"], ["LIMIT", 1]]
196
+ # => #<Timescale::Hypertable:0x00007f94c3151cd8
197
+ # hypertable_schema: "public",
198
+ # hypertable_name: "events",
199
+ # owner: "jonatasdp",
200
+ # num_dimensions: 1,
201
+ # num_chunks: 1,
202
+ # compression_enabled: true,
203
+ # is_distributed: false,
204
+ # replication_factor: nil,
205
+ # data_nodes: nil,
206
+ # tablespaces: nil>
207
+ ```
208
+
209
+ You can also use `Timescale.hypertables` to have access of all hypertables
210
+ metadata.
211
+
212
+ ### Compression Settings
213
+
214
+ Compression settings are accessible through the hypertable.
215
+
216
+ ```ruby
217
+ Event.hypertable.compression_settings
218
+ # Timescale::Hypertable Load (1.2ms) SELECT "timescaledb_information"."hypertables".* FROM "timescaledb_information"."hypertables" WHERE "timescaledb_information"."hypertables"."hypertable_name" = $1 LIMIT $2 [["hypertable_name", "events"], ["LIMIT", 1]]
219
+ # Timescale::CompressionSettings Load (1.2ms) SELECT "timescaledb_information"."compression_settings".* FROM "timescaledb_information"."compression_settings" WHERE "timescaledb_information"."compression_settings"."hypertable_name" = $1 [["hypertable_name", "events"]]
220
+ # => [#<Timescale::CompressionSettings:0x00007f94b0bf7010
221
+ # hypertable_schema: "public",
222
+ # hypertable_name: "events",
223
+ # attname: "identifier",
224
+ # segmentby_column_index: 1,
225
+ # orderby_column_index: nil,
226
+ # orderby_asc: nil,
227
+ # orderby_nullsfirst: nil>,
228
+ # #<Timescale::CompressionSettings:0x00007f94b0c3e460
229
+ # hypertable_schema: "public",
230
+ # hypertable_name: "events",
231
+ # attname: "created_at",
232
+ # segmentby_column_index: nil,
233
+ # orderby_column_index: 1,
234
+ # orderby_asc: true,
235
+ # orderby_nullsfirst: false>]
236
+ ```
237
+
238
+ It's also possible to access all data calling `Timescale.compression_settings`.
239
+
240
+ ### RSpec Hooks
241
+
242
+ In case you want to use TimescaleDB on a Rails environment, you may have some
243
+ issues as the schema dump used for tests is not considering hypertables
244
+ metadata.
245
+
246
+ If you add the `Timescale::HypertableHelpers` to your model, you can dynamically
247
+ create the hypertable adding this hook to your `spec/rspec_helper.rb` file:
248
+
249
+ ```ruby
250
+ config.before(:suite) do
251
+ hypertable_models = ApplicationRecord
252
+ .descendants
253
+ .select{|clazz| clazz.ancestors.include?( Timescale::HypertableHelpers)}
254
+ hypertable_models.each do |clazz|
255
+ if clazz.hypertable.exists?
256
+ ApplicationRecord.logger.info "skip recreating hypertable for '#{clazz.table_name}'."
257
+ next
258
+ end
259
+ ApplicationRecord.connection.execute <<~SQL
260
+ SELECT create_hypertable('#{clazz.table_name}', 'created_at')
261
+ SQL
262
+ end
263
+ end
264
+ ```
265
+
266
+ ## Development
267
+
268
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
269
+
270
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
271
+
272
+ ### TODO
273
+
274
+ Here is a list of functions that would be great to have:
275
+
276
+ - [ ] Dump and Restore Timescale metadata - Like db/schema.rb but for Timescale configuration.
277
+ - [ ] Add data nodes support
278
+ - [ ] Implement the `timescale` CLI to explore the full API.
279
+
280
+ ## Contributing
281
+
282
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jonatas/timescale. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jonatas/timescale/blob/master/CODE_OF_CONDUCT.md).
283
+
284
+ ## License
285
+
286
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
287
+
288
+ ## Code of Conduct
289
+
290
+ Everyone interacting in the Timescale project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jonatas/timescale/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "timescale"
5
+
6
+ def uri_from_test
7
+ require 'dotenv'
8
+ Dotenv.load!
9
+ ENV['PG_URI_TEST']
10
+ end
11
+
12
+ ActiveRecord::Base.establish_connection(ARGV[0] || uri_from_test)
13
+
14
+ Timescale::Hypertable.find_each do |hypertable|
15
+ class_name = hypertable.hypertable_name.singularize.camelize
16
+ model = Class.new(ActiveRecord::Base) do
17
+ self.table_name = hypertable.hypertable_name
18
+ self.primary_key = self.column_names.first
19
+ include Timescale::HypertableHelpers
20
+ end
21
+ Timescale.const_set(class_name, model)
22
+ end
23
+
24
+ require "pry"
25
+ Pry.start(Timescale)
data/bin/setup ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+
6
+ set -vx
7
+
8
+ bundle install
9
+
10
+ # For running tests it's going to use PG_URI_TEST env variable.
11
+ # Please make sure you set it properly to a TEST database!"
data/examples/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+
2
+ source 'https://rubygems.org'
3
+
4
+ gem "timescale", path: "../"
5
+ gem "pg"
6
+ gem "activerecord"
7
+ gem "composite_primary_keys", "~> 6.0"
8
+ gem 'pry'
9
+
10
+
11
+ gem "dotenv", "~> 2.7"
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ timescale (0.1.0)
5
+ activerecord
6
+ pg (~> 1.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (6.1.4.1)
12
+ activesupport (= 6.1.4.1)
13
+ activerecord (6.1.4.1)
14
+ activemodel (= 6.1.4.1)
15
+ activesupport (= 6.1.4.1)
16
+ activesupport (6.1.4.1)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ zeitwerk (~> 2.3)
22
+ coderay (1.1.3)
23
+ composite_primary_keys (6.0.5)
24
+ activerecord (>= 4.0.0)
25
+ concurrent-ruby (1.1.9)
26
+ dotenv (2.7.6)
27
+ i18n (1.8.10)
28
+ concurrent-ruby (~> 1.0)
29
+ method_source (1.0.0)
30
+ minitest (5.14.4)
31
+ pg (1.2.3)
32
+ pry (0.14.0)
33
+ coderay (~> 1.1)
34
+ method_source (~> 1.0)
35
+ tzinfo (2.0.4)
36
+ concurrent-ruby (~> 1.0)
37
+ zeitwerk (2.4.2)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ activerecord
44
+ composite_primary_keys (~> 6.0)
45
+ dotenv (~> 2.7)
46
+ pg
47
+ pry
48
+ timescale!
49
+
50
+ BUNDLED WITH
51
+ 2.1.4
@@ -0,0 +1,63 @@
1
+ require 'bundler/setup'
2
+ require 'timescale'
3
+ require 'pp'
4
+ require 'pry'
5
+ require 'dotenv'
6
+ Dotenv.load!
7
+ # set PG_URI=postgres://user:pass@host:port/db_name
8
+ ActiveRecord::Base.establish_connection(ENV['PG_URI_TEST'])
9
+
10
+ # Simple example
11
+ class Event < ActiveRecord::Base
12
+ self.primary_key = "identifier"
13
+
14
+ include Timescale::HypertableHelpers
15
+ end
16
+
17
+ # Setup Hypertable as in a migration
18
+ ActiveRecord::Base.connection.instance_exec do
19
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
20
+
21
+ drop_table(:events) if Event.table_exists?
22
+
23
+ hypertable_options = {
24
+ time_column: 'created_at',
25
+ chunk_time_interval: '1 min',
26
+ compress_segmentby: 'identifier',
27
+ compression_interval: '7 days'
28
+ }
29
+
30
+ create_table(:events, id: false, hypertable: hypertable_options) do |t|
31
+ t.string :identifier, null: false
32
+ t.jsonb :payload
33
+ t.timestamps
34
+ end
35
+ end
36
+
37
+ # Create some data just to see how it works
38
+ 1.times do
39
+ Event.transaction do
40
+ Event.create identifier: "sign_up", payload: {"name" => "Eon"}
41
+ Event.create identifier: "login", payload: {"email" => "eon@timescale.com"}
42
+ Event.create identifier: "click", payload: {"user" => "eon", "path" => "/install/timescaledb"}
43
+ Event.create identifier: "scroll", payload: {"user" => "eon", "path" => "/install/timescaledb"}
44
+ Event.create identifier: "logout", payload: {"email" => "eon@timescale.com"}
45
+ end
46
+ end
47
+
48
+ # Now let's see what we have in the scopes
49
+ Event.last_hour.group(:identifier).count # => {"login"=>2, "click"=>1, "logout"=>1, "sign_up"=>1, "scroll"=>1}
50
+
51
+
52
+ puts "compressing #{ Event.chunks.count }"
53
+ Event.chunks.first.compress!
54
+
55
+ puts "detailed size"
56
+ pp Event.hypertable.detailed_size
57
+
58
+ puts "compression stats"
59
+ pp Event.hypertable.compression_stats
60
+
61
+ puts "decompressing"
62
+ Event.chunks.first.decompress!
63
+ Pry.start
@@ -0,0 +1,27 @@
1
+ module Timescale
2
+ class Chunk < ActiveRecord::Base
3
+ self.table_name = "timescaledb_information.chunks"
4
+ self.primary_key = "chunk_name"
5
+
6
+ belongs_to :hypertable, foreign_key: :hypertable_name
7
+
8
+ scope :compressed, -> { where(is_compressed: true) }
9
+ scope :uncompressed, -> { where(is_compressed: false) }
10
+
11
+ def compress!
12
+ execute("SELECT compress_chunk(#{chunk_relation})")
13
+ end
14
+
15
+ def decompress!
16
+ execute("SELECT decompress_chunk(#{chunk_relation})")
17
+ end
18
+
19
+ def chunk_relation
20
+ "('#{chunk_schema}.#{chunk_name}')::regclass"
21
+ end
22
+
23
+ def execute(sql)
24
+ self.class.connection.execute(sql)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module Timescale
2
+ class CompressionSettings < ActiveRecord::Base
3
+ self.table_name = "timescaledb_information.compression_settings"
4
+ belongs_to :hypertable, foreign_key: :hypertable_name
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Timescale
2
+ class ContinuousAggregates < ActiveRecord::Base
3
+ self.table_name = "timescaledb_information.continuous_aggregates"
4
+ self.primary_key = 'materialization_hypertable_name'
5
+
6
+ has_many :jobs, foreign_key: "hypertable_name",
7
+ class_name: "Timescale::Job"
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ module Timescale
2
+ class Hypertable < ActiveRecord::Base
3
+ self.table_name = "timescaledb_information.hypertables"
4
+
5
+ self.primary_key = "hypertable_name"
6
+
7
+ has_many :jobs, foreign_key: "hypertable_name"
8
+ has_many :chunks, foreign_key: "hypertable_name"
9
+
10
+ has_many :compression_settings,
11
+ foreign_key: "hypertable_name",
12
+ class_name: "Timescale::CompressionSettings"
13
+
14
+ has_many :continuous_aggregates,
15
+ foreign_key: "hypertable_name",
16
+ class_name: "Timescale::ContinuousAggregates"
17
+
18
+ def detailed_size
19
+ struct_from "SELECT * from chunks_detailed_size('#{self.hypertable_name}')"
20
+ end
21
+
22
+ def compression_stats
23
+ struct_from "SELECT * from hypertable_compression_stats('#{self.hypertable_name}')"
24
+ end
25
+
26
+ private
27
+ def struct_from(sql)
28
+ self.class.connection.execute(sql).map(&OpenStruct.method(:new))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'chunk'
2
+ require_relative 'hypertable'
3
+ module Timescale
4
+ module HypertableHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :chunks, -> () do
9
+ Chunk.where(hypertable_name: self.table_name)
10
+ end
11
+
12
+ scope :hypertable, -> () do
13
+ Hypertable.find_by(hypertable_name: self.table_name)
14
+ end
15
+
16
+ scope :jobs, -> () do
17
+ Job.where(hypertable_name: self.table_name)
18
+ end
19
+
20
+ scope :job_stats, -> () do
21
+ JobStats.where(hypertable_name: self.table_name)
22
+ end
23
+
24
+ scope :compression_settings, -> () do
25
+ CompressionSettings.where(hypertable_name: self.table_name)
26
+ end
27
+
28
+ scope :continuous_aggregates, -> () do
29
+ ContinuousAggregates.where(hypertable_name: self.table_name)
30
+ end
31
+
32
+ scope :last_month, -> { where('created_at > ?', 1.month.ago) }
33
+ scope :last_week, -> { where('created_at > ?', 1.week.ago) }
34
+ scope :last_hour, -> { where('created_at > ?', 1.hour.ago) }
35
+ scope :yesterday, -> { where('DATE(created_at) = ?', 1.day.ago.to_date) }
36
+ scope :today, -> { where('DATE(created_at) = ?', Date.today) }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ module Timescale
2
+ class Job < ActiveRecord::Base
3
+ self.table_name = "timescaledb_information.jobs"
4
+ self.primary_key = "job_id"
5
+
6
+ attribute :schedule_interval, :interval
7
+ attribute :max_runtime, :interval
8
+ attribute :retry_period, :interval
9
+
10
+ scope :compression, -> { where(proc_name: "tsbs_compress_chunks") }
11
+ scope :scheduled, -> { where(scheduled: true) }
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Timescale
2
+ class JobStats < ActiveRecord::Base
3
+ self.table_name = "timescaledb_information.job_stats"
4
+
5
+ belongs_to :job
6
+
7
+ attribute :last_run_duration, :interval
8
+
9
+ scope :success, -> { where(last_run_status: "Success") }
10
+ scope :scheduled, -> { where(job_status: "Scheduled") }
11
+ end
12
+ end
@@ -0,0 +1,75 @@
1
+ require 'active_record/connection_adapters/postgresql_adapter'
2
+
3
+ # Useful methods to run TimescaleDB in you Ruby app.
4
+ module Timescale
5
+ # Migration helpers can help you to setup hypertables by default.
6
+ module MigrationHelpers
7
+ # create_table can receive `hypertable` argument
8
+ # @example
9
+ # options = {
10
+ # time_column: 'created_at',
11
+ # chunk_time_interval: '1 min',
12
+ # compress_segmentby: 'identifier',
13
+ # compress_orderby: 'created_at',
14
+ # compression_interval: '7 days'
15
+ # }
16
+ #
17
+ # create_table(:events, id: false, hypertable: options) do |t|
18
+ # t.string :identifier, null: false
19
+ # t.jsonb :payload
20
+ # t.timestamps
21
+ # end
22
+ def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
23
+ super
24
+ setup_hypertable_options(table_name, **options[:hypertable]) if options.key?(:hypertable)
25
+ end
26
+
27
+ # Setup hypertable from options
28
+ # @see create_table with the hypertable options.
29
+ def setup_hypertable_options(table_name,
30
+ time_column: 'created_at',
31
+ chunk_time_interval: '1 week',
32
+ compress_segmentby: nil,
33
+ compress_orderby: 'created_at',
34
+ compression_interval: nil
35
+ )
36
+
37
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
38
+ execute "SELECT create_hypertable('#{table_name}', '#{time_column}', chunk_time_interval => INTERVAL '#{chunk_time_interval}')"
39
+
40
+ if compress_segmentby
41
+ execute <<~SQL
42
+ ALTER TABLE #{table_name} SET (
43
+ timescaledb.compress,
44
+ timescaledb.compress_orderby = '#{compress_orderby}',
45
+ timescaledb.compress_segmentby = '#{compress_segmentby}'
46
+ )
47
+ SQL
48
+ end
49
+ if compression_interval
50
+ execute "SELECT add_compression_policy('#{table_name}', INTERVAL '#{compression_interval}')"
51
+ end
52
+ end
53
+
54
+ def create_continuous_aggregates(name, query, **options)
55
+ execute <<~SQL
56
+ CREATE MATERIALIZED VIEW #{name}
57
+ WITH (timescaledb.continuous) AS
58
+ #{query.respond_to?(:to_sql) ? query.to_sql : query}
59
+ WITH #{"NO" unless options[:with_data]} DATA;
60
+ SQL
61
+
62
+ if (policy = options[:refresh_policies])
63
+ # TODO: assert valid keys
64
+ execute <<~SQL
65
+ SELECT add_continuous_aggregate_policy('#{name}',
66
+ start_offset => #{policy[:start_offset]},
67
+ end_offset => #{policy[:end_offset]},
68
+ schedule_interval => #{policy[:schedule_interval]});
69
+ SQL
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include(Timescale::MigrationHelpers)
@@ -0,0 +1,3 @@
1
+ module Timescale
2
+ VERSION = "0.1.0"
3
+ end
data/lib/timescale.rb ADDED
@@ -0,0 +1,37 @@
1
+ require "timescale/version"
2
+ require 'active_record'
3
+ require_relative 'timescale/chunk'
4
+ require_relative 'timescale/hypertable'
5
+ require_relative 'timescale/job'
6
+ require_relative 'timescale/job_stats'
7
+ require_relative 'timescale/continuous_aggregates'
8
+ require_relative 'timescale/compression_settings'
9
+ require_relative 'timescale/hypertable_helpers'
10
+ require_relative 'timescale/migration_helpers'
11
+
12
+ module Timescale
13
+ module_function
14
+ def chunks
15
+ Chunk.all
16
+ end
17
+
18
+ def hypertables
19
+ Hypertable.all
20
+ end
21
+
22
+ def continuous_aggregates
23
+ ContinuousAggregates.all
24
+ end
25
+
26
+ def compression_settings
27
+ CompressionSettings.all
28
+ end
29
+
30
+ def jobs
31
+ Job.all
32
+ end
33
+
34
+ def job_stats
35
+ JobStats.all
36
+ end
37
+ end
data/timescale.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ require_relative 'lib/timescale/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "timescaledb"
5
+ spec.version = Timescale::VERSION
6
+ spec.authors = ["Jônatas Davi Paganini"]
7
+ spec.email = ["jonatasdp@gmail.com"]
8
+
9
+ spec.summary = %q{TimesaleDB helpers for Ruby ecosystem.}
10
+ spec.description = %q{Functions from timescaledb available in the ActiveRecord models.}
11
+ spec.homepage = "https://github.com/jonatas/timescale"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ #spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
19
+ #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "pg", "~> 1.2"
31
+ spec.add_dependency 'activerecord'
32
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timescaledb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jônatas Davi Paganini
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-09-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Functions from timescaledb available in the ActiveRecord models.
42
+ email:
43
+ - jonatasdp@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - ".travis.yml"
51
+ - CODE_OF_CONDUCT.md
52
+ - Gemfile
53
+ - Gemfile.lock
54
+ - LICENSE.txt
55
+ - README.md
56
+ - Rakefile
57
+ - bin/console
58
+ - bin/setup
59
+ - examples/Gemfile
60
+ - examples/Gemfile.lock
61
+ - examples/all_in_one.rb
62
+ - lib/timescale.rb
63
+ - lib/timescale/chunk.rb
64
+ - lib/timescale/compression_settings.rb
65
+ - lib/timescale/continuous_aggregates.rb
66
+ - lib/timescale/hypertable.rb
67
+ - lib/timescale/hypertable_helpers.rb
68
+ - lib/timescale/job.rb
69
+ - lib/timescale/job_stats.rb
70
+ - lib/timescale/migration_helpers.rb
71
+ - lib/timescale/version.rb
72
+ - timescale.gemspec
73
+ homepage: https://github.com/jonatas/timescale
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ allowed_push_host: https://rubygems.org
78
+ homepage_uri: https://github.com/jonatas/timescale
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: 2.3.0
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 3.0.3
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: TimesaleDB helpers for Ruby ecosystem.
98
+ test_files: []