From 22a86fa44dd87b1553ccc7d7c8e418c73f30da0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B4natas=20Davi=20Paganini?= Date: Fri, 6 Sep 2024 17:15:28 -0300 Subject: [PATCH] Make sure views are included in the hierarchical order --- README.md | 28 ++++++++++++++++++++++++ lib/timescaledb/acts_as_hypertable.rb | 1 - lib/timescaledb/continuous_aggregates.rb | 24 ++++++++++++++++++++ lib/timescaledb/schema_dumper.rb | 2 +- spec/timescaledb/schema_dumper_spec.rb | 11 ++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15778f4..327d1d7 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,12 @@ which will correctly handle schema dumping. ### Enable ActsAsHypertable +Create your `config/initializers/timescaledb.rb` file and add the following line: + +```ruby +ActiveRecord::Base.extend Timescaledb::ActsAsHypertable +``` + You can declare a Rails model as a Hypertable by invoking the `acts_as_hypertable` macro. This macro extends your existing model with timescaledb-related functionality. model: @@ -330,6 +336,27 @@ end By default, ActsAsHypertable assumes a record's _time_column_ is called `created_at`. +You may isolate your hypertables in another database, so, creating an abstract +layer for your hypertables is a good idea: + +```ruby +class TimescaledbModel < ActiveRecord::Base + self.abstract_class = true + + extend Timescaledb::ActsAsHypertable + + establish_connection :timescaledb +end +``` + +And then, you can inherit from this model: + +```ruby +class Event < TimescaledbModel + acts_as_hypertable time_column: "time" +end +``` + ### Options If you are using a different time_column name, you can specify it as follows when invoking the `acts_as_hypertable` macro: @@ -435,6 +462,7 @@ define in `spec/rspec_helper.rb`: ```ruby config.before(:suite) do + hypertable_models = ActiveRecord::Base.descendants.select(&:acts_as_hypertable?) hypertable_models.each do |klass| diff --git a/lib/timescaledb/acts_as_hypertable.rb b/lib/timescaledb/acts_as_hypertable.rb index ac081bf..a50af62 100644 --- a/lib/timescaledb/acts_as_hypertable.rb +++ b/lib/timescaledb/acts_as_hypertable.rb @@ -62,4 +62,3 @@ def acts_as_hypertable(options = {}) end end -ActiveRecord::Base.extend Timescaledb::ActsAsHypertable diff --git a/lib/timescaledb/continuous_aggregates.rb b/lib/timescaledb/continuous_aggregates.rb index f4ca3b8..6f00d22 100644 --- a/lib/timescaledb/continuous_aggregates.rb +++ b/lib/timescaledb/continuous_aggregates.rb @@ -14,6 +14,30 @@ class ContinuousAggregate < ::Timescaledb::ApplicationRecord total: count } end + + scope :hierarchical, -> do + with_recursive = <<~SQL + WITH RECURSIVE caggs AS ( + SELECT mat_hypertable_id, parent_mat_hypertable_id, user_view_name + FROM _timescaledb_catalog.continuous_agg + UNION ALL + SELECT continuous_agg.mat_hypertable_id, continuous_agg.parent_mat_hypertable_id, continuous_agg.user_view_name + FROM _timescaledb_catalog.continuous_agg + JOIN caggs ON caggs.parent_mat_hypertable_id = continuous_agg.mat_hypertable_id + ) + SELECT * FROM caggs + ORDER BY mat_hypertable_id + SQL + views = unscoped + .select("distinct user_view_name") + .from("(#{with_recursive}) as caggs") + .pluck(:user_view_name) + .uniq + + views.map do |view| + find_by(view_name: view) + end + end end ContinuousAggregates = ContinuousAggregate end diff --git a/lib/timescaledb/schema_dumper.rb b/lib/timescaledb/schema_dumper.rb index 17a9d71..db9e7eb 100644 --- a/lib/timescaledb/schema_dumper.rb +++ b/lib/timescaledb/schema_dumper.rb @@ -154,7 +154,7 @@ def timescale_index_options_for(hypertable) def timescale_continuous_aggregates(stream) return unless Timescaledb::ContinuousAggregates.table_exists? - Timescaledb::ContinuousAggregates.all.find_each do |aggregate| + Timescaledb::ContinuousAggregates.hierarchical.each do |aggregate| refresh_policies_opts = if (refresh_policy = aggregate.jobs.refresh_continuous_aggregate.first) interval = timescale_interval(refresh_policy.schedule_interval) end_offset = timescale_interval(refresh_policy.config["end_offset"]) diff --git a/spec/timescaledb/schema_dumper_spec.rb b/spec/timescaledb/schema_dumper_spec.rb index c4245aa..0bf72b6 100644 --- a/spec/timescaledb/schema_dumper_spec.rb +++ b/spec/timescaledb/schema_dumper_spec.rb @@ -6,6 +6,13 @@ identifier as label, count(*) as value").group("1,2") end + let(:query_daily) do + Event + .from("event_counts") + .select("time_bucket('1d', time) as time, + identifier as label, + sum(value) as value").group("1,2") + end context "schema" do it "should include the timescaledb extension" do @@ -73,6 +80,7 @@ it "dumps a create_continuous_aggregate for a view in the database" do con.execute("DROP MATERIALIZED VIEW IF EXISTS event_counts") con.create_continuous_aggregate(:event_counts, query, materialized_only: true, finalized: true) + con.create_continuous_aggregate(:event_daily_counts, query_daily, materialized_only: true, finalized: true) if defined?(Scenic) Scenic.load # Normally this happens in a railtie, but we aren't loading a full rails env here @@ -93,6 +101,9 @@ caggs_creation = dump.index('create_continuous_aggregate("event_counts"') expect(hypertable_creation).to be < caggs_creation + + caggs_dependent_creation = dump.index('create_continuous_aggregate("event_daily_counts"') + expect(caggs_creation).to be < caggs_dependent_creation end describe "dumping hypertable options" do