From 810eab5d4c339e1edd24d1acdb7fb034162d490d Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Sun, 2 Apr 2023 18:45:12 -0700 Subject: [PATCH 01/11] Allow timescale to be a non-primary rails db --- .../extensions/active_record/schema_dumper.rb | 20 +++++++++++++------ .../active_record/schema_statements.rb | 11 ++++++++-- lib/timescaledb/rails/model/hyperfunctions.rb | 16 ++++++++++----- lib/timescaledb/rails/models/chunk.rb | 8 ++++---- .../rails/models/compression_setting.rb | 2 +- .../rails/models/concerns/durationable.rb | 2 ++ .../rails/models/continuous_aggregate.rb | 4 ++-- lib/timescaledb/rails/models/dimension.rb | 2 +- lib/timescaledb/rails/models/hypertable.rb | 2 +- lib/timescaledb/rails/models/job.rb | 2 +- lib/timescaledb/rails/railtie.rb | 6 ++++-- .../rails/extensions/database_tasks_spec.rb | 2 +- .../rails/model/hyperfunctions_spec.rb | 4 ++-- 13 files changed, 53 insertions(+), 28 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index 4384af7..906a729 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -2,6 +2,7 @@ require 'active_record/connection_adapters/postgresql_adapter' require 'timescaledb/rails/orderby_compression' +require 'tsort' module Timescaledb module Rails @@ -19,14 +20,20 @@ def tables(stream) def continuous_aggregates(stream) return unless timescale_enabled? - Timescaledb::Rails::ContinuousAggregate.all.each do |continuous_aggregate| - continuous_aggregate(continuous_aggregate, stream) - continuous_aggregate_policy(continuous_aggregate, stream) + deps = Timescaledb::Rails::ContinuousAggregate.find_each.index_by(&:materialization_hypertable_name) + + TSort.tsort( + ->(&b) { deps.each_value(&b) }, + ->(n, &b) { Array.wrap(deps[n.hypertable_name]).each(&b) } + ).each do |ca| + continuous_aggregate(ca, stream) + continuous_aggregate_policy(ca, stream) end end - def continuous_aggregate(continuous_aggregate, stream) - stream.puts " create_continuous_aggregate #{continuous_aggregate.view_name.inspect}, <<-SQL" + def continuous_aggregate(continuous_aggregate, stream, force: false) + stream.puts " create_continuous_aggregate #{continuous_aggregate.view_name.inspect}, <<-SQL, " \ + "force: #{force.inspect}" stream.puts " #{continuous_aggregate.view_definition.strip.indent(2)}" stream.puts ' SQL' stream.puts @@ -149,7 +156,8 @@ def format_hypertable_option_value(value) end def timescale_enabled? - Timescaledb::Rails::Hypertable.table_exists? + @connection.pool.db_config.name == Timescaledb::Rails::Hypertable.connection.pool.db_config.name && + Timescaledb::Rails::Hypertable.table_exists? end end end diff --git a/lib/timescaledb/rails/extensions/active_record/schema_statements.rb b/lib/timescaledb/rails/extensions/active_record/schema_statements.rb index ff8c1ab..196e2ca 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_statements.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_statements.rb @@ -116,8 +116,15 @@ def remove_hypertable_reorder_policy(table_name, _index_name = nil) # 'temperature_events', "SELECT * FROM events where event_type = 'temperature'" # ) # - def create_continuous_aggregate(view_name, view_query) - execute "CREATE MATERIALIZED VIEW #{view_name} WITH (timescaledb.continuous) AS #{view_query};" + def create_continuous_aggregate(view_name, view_query, force: false) + if force + execute "DROP MATERIALIZED VIEW #{quote_table_name(view_name)} CASCADE;" if view_exists? view_name + else + schema_cache.clear_data_source_cache!(view_name.to_s) + end + + execute "CREATE MATERIALIZED VIEW #{quote_table_name(view_name)} " \ + "WITH (timescaledb.continuous) AS #{view_query};" end # Drops a continuous aggregate diff --git a/lib/timescaledb/rails/model/hyperfunctions.rb b/lib/timescaledb/rails/model/hyperfunctions.rb index 39c156a..cf8a0e2 100644 --- a/lib/timescaledb/rails/model/hyperfunctions.rb +++ b/lib/timescaledb/rails/model/hyperfunctions.rb @@ -10,12 +10,18 @@ module Hyperfunctions TIME_BUCKET_ALIAS = 'time_bucket' # @return [ActiveRecord::Relation] - def time_bucket(interval, target_column = nil) - target_column ||= hypertable_time_column_name + def time_bucket(interval, target_column = nil, select_alias: TIME_BUCKET_ALIAS) + target_column &&= Arel.sql(target_column.to_s) + target_column ||= arel_table[hypertable_time_column_name] - select("time_bucket('#{format_interval_value(interval)}', #{target_column}) as #{TIME_BUCKET_ALIAS}") - .group(TIME_BUCKET_ALIAS) - .order(TIME_BUCKET_ALIAS) + time_bucket = Arel::Nodes::NamedFunction.new( + 'time_bucket', + [Arel::Nodes.build_quoted(format_interval_value(interval)), target_column] + ) + + select(time_bucket.dup.as(select_alias)) + .group(time_bucket) + .order(time_bucket) .extending(AggregateFunctions) end diff --git a/lib/timescaledb/rails/models/chunk.rb b/lib/timescaledb/rails/models/chunk.rb index 7ba2cb4..a948a83 100644 --- a/lib/timescaledb/rails/models/chunk.rb +++ b/lib/timescaledb/rails/models/chunk.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class Chunk < ::ActiveRecord::Base + class Chunk < Railtie.config.record_base.constantize self.table_name = 'timescaledb_information.chunks' self.primary_key = 'hypertable_name' @@ -17,13 +17,13 @@ def chunk_full_name end def compress! - ::ActiveRecord::Base.connection.execute( + self.class.connection.execute( "SELECT compress_chunk('#{chunk_full_name}')" ) end def decompress! - ::ActiveRecord::Base.connection.execute( + self.class.connection.execute( "SELECT decompress_chunk('#{chunk_full_name}')" ) end @@ -40,7 +40,7 @@ def reorder!(index = nil) options = ["'#{chunk_full_name}'"] options << "'#{index}'" if index.present? - ::ActiveRecord::Base.connection.execute( + self.class.connection.execute( "SELECT reorder_chunk(#{options.join(', ')})" ) end diff --git a/lib/timescaledb/rails/models/compression_setting.rb b/lib/timescaledb/rails/models/compression_setting.rb index 83ef630..7ab8f33 100644 --- a/lib/timescaledb/rails/models/compression_setting.rb +++ b/lib/timescaledb/rails/models/compression_setting.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class CompressionSetting < ::ActiveRecord::Base + class CompressionSetting < Railtie.config.record_base.constantize self.table_name = 'timescaledb_information.compression_settings' self.primary_key = 'hypertable_name' diff --git a/lib/timescaledb/rails/models/concerns/durationable.rb b/lib/timescaledb/rails/models/concerns/durationable.rb index 266fa81..735d208 100644 --- a/lib/timescaledb/rails/models/concerns/durationable.rb +++ b/lib/timescaledb/rails/models/concerns/durationable.rb @@ -11,6 +11,8 @@ module Durationable # @return [String] def parse_duration(duration) + return if duration.nil? + duration_in_seconds = duration_in_seconds(duration) duration_to_interval( diff --git a/lib/timescaledb/rails/models/continuous_aggregate.rb b/lib/timescaledb/rails/models/continuous_aggregate.rb index b4bd79a..f474b63 100644 --- a/lib/timescaledb/rails/models/continuous_aggregate.rb +++ b/lib/timescaledb/rails/models/continuous_aggregate.rb @@ -5,7 +5,7 @@ module Timescaledb module Rails # :nodoc: - class ContinuousAggregate < ::ActiveRecord::Base + class ContinuousAggregate < Railtie.config.record_base.constantize include Timescaledb::Rails::Models::Durationable self.table_name = 'timescaledb_information.continuous_aggregates' @@ -19,7 +19,7 @@ class ContinuousAggregate < ::ActiveRecord::Base # @param [DateTime] end_time # def refresh!(start_time = 'NULL', end_time = 'NULL') - ::ActiveRecord::Base.connection.execute( + self.class.connection.execute( "CALL refresh_continuous_aggregate('#{view_name}', #{start_time}, #{end_time});" ) end diff --git a/lib/timescaledb/rails/models/dimension.rb b/lib/timescaledb/rails/models/dimension.rb index d186d01..838d880 100644 --- a/lib/timescaledb/rails/models/dimension.rb +++ b/lib/timescaledb/rails/models/dimension.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class Dimension < ::ActiveRecord::Base + class Dimension < Railtie.config.record_base.constantize TIME_TYPE = 'Time' self.table_name = 'timescaledb_information.dimensions' diff --git a/lib/timescaledb/rails/models/hypertable.rb b/lib/timescaledb/rails/models/hypertable.rb index bcaec84..2fe6c26 100644 --- a/lib/timescaledb/rails/models/hypertable.rb +++ b/lib/timescaledb/rails/models/hypertable.rb @@ -5,7 +5,7 @@ module Timescaledb module Rails # :nodoc: - class Hypertable < ::ActiveRecord::Base + class Hypertable < Railtie.config.record_base.constantize include Timescaledb::Rails::Models::Durationable self.table_name = 'timescaledb_information.hypertables' diff --git a/lib/timescaledb/rails/models/job.rb b/lib/timescaledb/rails/models/job.rb index ff99302..10d5d9a 100644 --- a/lib/timescaledb/rails/models/job.rb +++ b/lib/timescaledb/rails/models/job.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class Job < ::ActiveRecord::Base + class Job < Railtie.config.record_base.constantize self.table_name = 'timescaledb_information.jobs' self.primary_key = 'hypertable_name' diff --git a/lib/timescaledb/rails/railtie.rb b/lib/timescaledb/rails/railtie.rb index b9f4647..868ac51 100644 --- a/lib/timescaledb/rails/railtie.rb +++ b/lib/timescaledb/rails/railtie.rb @@ -6,13 +6,15 @@ module Timescaledb module Rails # :nodoc: class Railtie < ::Rails::Railtie - initializer 'timescaledb-rails.require_timescale_models' do + config.record_base = '::ActiveRecord::Base' + + config.to_prepare do ActiveSupport.on_load(:active_record) do require 'timescaledb/rails/models' end end - initializer 'timescaledb-rails.add_timescale_support_to_active_record' do + config.to_prepare do ActiveSupport.on_load(:active_record) do Timescaledb::Rails.load end diff --git a/spec/timescaledb/rails/extensions/database_tasks_spec.rb b/spec/timescaledb/rails/extensions/database_tasks_spec.rb index c6cf342..9b2ed3b 100644 --- a/spec/timescaledb/rails/extensions/database_tasks_spec.rb +++ b/spec/timescaledb/rails/extensions/database_tasks_spec.rb @@ -125,7 +125,7 @@ expect(database_structure).to include( <<-QUERY - create_continuous_aggregate "temperature_events", <<-SQL + create_continuous_aggregate "temperature_events", <<-SQL, force: false SELECT time_bucket('#{interval}'::interval, events.created_at) AS time_bucket, avg(events.value) AS avg FROM events diff --git a/spec/timescaledb/rails/model/hyperfunctions_spec.rb b/spec/timescaledb/rails/model/hyperfunctions_spec.rb index 917473d..c722a31 100644 --- a/spec/timescaledb/rails/model/hyperfunctions_spec.rb +++ b/spec/timescaledb/rails/model/hyperfunctions_spec.rb @@ -40,7 +40,7 @@ it 'uses the default date column' do result = Payload.time_bucket(interval) - expect(result.to_sql).to include("SELECT time_bucket('1 day', created_at) as time_bucket") + expect(result.to_sql).to include("SELECT time_bucket('1 day', \"payloads\".\"created_at\") AS time_bucket") end it 'returns an active record relation' do @@ -78,7 +78,7 @@ it 'uses the specified date column' do result = Payload.time_bucket(interval, date_column) - expect(result.to_sql).to include("SELECT time_bucket('1 day', #{date_column}) as time_bucket") + expect(result.to_sql).to include("SELECT time_bucket('1 day', #{date_column}) AS time_bucket") end context 'when the interval is a string' do From d038ec105116dfe7efb35cd6eee3bc215a0b0a9a Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 11:09:05 -0700 Subject: [PATCH 02/11] Add application record as common superclass --- bin/console | 2 +- lib/timescaledb/rails.rb | 1 + lib/timescaledb/rails/models.rb | 2 ++ lib/timescaledb/rails/models/application_record.rb | 10 ++++++++++ lib/timescaledb/rails/models/chunk.rb | 2 +- lib/timescaledb/rails/models/compression_setting.rb | 2 +- lib/timescaledb/rails/models/continuous_aggregate.rb | 2 +- lib/timescaledb/rails/models/dimension.rb | 2 +- lib/timescaledb/rails/models/hypertable.rb | 2 +- lib/timescaledb/rails/models/job.rb | 2 +- 10 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 lib/timescaledb/rails/models/application_record.rb diff --git a/bin/console b/bin/console index 745bd72..327d952 100755 --- a/bin/console +++ b/bin/console @@ -22,7 +22,7 @@ Timescaledb::Rails::Hypertable.all.each do |hypertable| Timescaledb::Rails.const_set( class_name, - Class.new(ActiveRecord::Base) do + Class.new(Timescaledb::Rails::ApplicationRecord) do include Timescaledb::Rails::Model self.table_name = [hypertable.hypertable_schema, hypertable.hypertable_name].join('.') diff --git a/lib/timescaledb/rails.rb b/lib/timescaledb/rails.rb index 9443e39..84ead4a 100644 --- a/lib/timescaledb/rails.rb +++ b/lib/timescaledb/rails.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative './rails/railtie' require_relative './rails/model' require_relative './rails/extensions/active_record/base' diff --git a/lib/timescaledb/rails/models.rb b/lib/timescaledb/rails/models.rb index 6384a1c..18567c7 100644 --- a/lib/timescaledb/rails/models.rb +++ b/lib/timescaledb/rails/models.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative './models/application_record' + require_relative './models/chunk' require_relative './models/compression_setting' require_relative './models/continuous_aggregate' diff --git a/lib/timescaledb/rails/models/application_record.rb b/lib/timescaledb/rails/models/application_record.rb new file mode 100644 index 0000000..2ae80c2 --- /dev/null +++ b/lib/timescaledb/rails/models/application_record.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Timescaledb + module Rails + # :nodoc: + class ApplicationRecord < Railtie.config.record_base.constantize + self.abstract_class = true + end + end +end diff --git a/lib/timescaledb/rails/models/chunk.rb b/lib/timescaledb/rails/models/chunk.rb index a948a83..56beb74 100644 --- a/lib/timescaledb/rails/models/chunk.rb +++ b/lib/timescaledb/rails/models/chunk.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class Chunk < Railtie.config.record_base.constantize + class Chunk < ApplicationRecord self.table_name = 'timescaledb_information.chunks' self.primary_key = 'hypertable_name' diff --git a/lib/timescaledb/rails/models/compression_setting.rb b/lib/timescaledb/rails/models/compression_setting.rb index 7ab8f33..b5acae1 100644 --- a/lib/timescaledb/rails/models/compression_setting.rb +++ b/lib/timescaledb/rails/models/compression_setting.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class CompressionSetting < Railtie.config.record_base.constantize + class CompressionSetting < ApplicationRecord self.table_name = 'timescaledb_information.compression_settings' self.primary_key = 'hypertable_name' diff --git a/lib/timescaledb/rails/models/continuous_aggregate.rb b/lib/timescaledb/rails/models/continuous_aggregate.rb index f474b63..f6d590e 100644 --- a/lib/timescaledb/rails/models/continuous_aggregate.rb +++ b/lib/timescaledb/rails/models/continuous_aggregate.rb @@ -5,7 +5,7 @@ module Timescaledb module Rails # :nodoc: - class ContinuousAggregate < Railtie.config.record_base.constantize + class ContinuousAggregate < ApplicationRecord include Timescaledb::Rails::Models::Durationable self.table_name = 'timescaledb_information.continuous_aggregates' diff --git a/lib/timescaledb/rails/models/dimension.rb b/lib/timescaledb/rails/models/dimension.rb index 838d880..a08dfc9 100644 --- a/lib/timescaledb/rails/models/dimension.rb +++ b/lib/timescaledb/rails/models/dimension.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class Dimension < Railtie.config.record_base.constantize + class Dimension < ApplicationRecord TIME_TYPE = 'Time' self.table_name = 'timescaledb_information.dimensions' diff --git a/lib/timescaledb/rails/models/hypertable.rb b/lib/timescaledb/rails/models/hypertable.rb index 2fe6c26..f72ce3f 100644 --- a/lib/timescaledb/rails/models/hypertable.rb +++ b/lib/timescaledb/rails/models/hypertable.rb @@ -5,7 +5,7 @@ module Timescaledb module Rails # :nodoc: - class Hypertable < Railtie.config.record_base.constantize + class Hypertable < ApplicationRecord include Timescaledb::Rails::Models::Durationable self.table_name = 'timescaledb_information.hypertables' diff --git a/lib/timescaledb/rails/models/job.rb b/lib/timescaledb/rails/models/job.rb index 10d5d9a..2915cc7 100644 --- a/lib/timescaledb/rails/models/job.rb +++ b/lib/timescaledb/rails/models/job.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class Job < Railtie.config.record_base.constantize + class Job < ApplicationRecord self.table_name = 'timescaledb_information.jobs' self.primary_key = 'hypertable_name' From 0d2e6e50f634338dfe92a479cf6e08b62d1036e3 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 11:09:26 -0700 Subject: [PATCH 03/11] Schema dumper works on rails 6 too --- .../active_record/postgresql_database_tasks.rb | 15 +++++++++++++-- .../extensions/active_record/schema_dumper.rb | 12 +++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb index 2107b84..6c48e82 100644 --- a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb +++ b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb @@ -8,7 +8,7 @@ module Rails module ActiveRecord # :nodoc: # rubocop:disable Layout/LineLength - module PostgreSQLDatabaseTasks + module PostgreSQLDatabaseTasks # rubocop:disable Metrics/ModuleLength # @override def structure_dump(filename, extra_flags) extra_flags = Array(extra_flags) @@ -145,7 +145,18 @@ def timescale_structure_dump_default_flags # @return [Boolean] def timescale_enabled? - Timescaledb::Rails::Hypertable.table_exists? + pool_name(connection.pool) == pool_name(Timescaledb::Rails::Hypertable.connection.pool) && + Timescaledb::Rails::Hypertable.table_exists? + end + + def pool_name(pool) + if pool.respond_to?(:db_config) + pool.db_config.name + elsif pool.respond_to?(:spec) + pool.spec.name + else + raise "Don't know how to get pool name from #{pool.inspect}" + end end end # rubocop:enable Layout/LineLength diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index 906a729..894fd6e 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -156,9 +156,19 @@ def format_hypertable_option_value(value) end def timescale_enabled? - @connection.pool.db_config.name == Timescaledb::Rails::Hypertable.connection.pool.db_config.name && + pool_name(@connection.pool) == pool_name(Timescaledb::Rails::Hypertable.connection.pool) && Timescaledb::Rails::Hypertable.table_exists? end + + def pool_name(pool) + if pool.respond_to?(:db_config) + pool.db_config.name + elsif pool.respond_to?(:spec) + pool.spec.name + else + raise "Don't know how to get pool name from #{pool.inspect}" + end + end end end end From 14432ded5954a9cf46dc3c40faa62857dfef63db Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 11:09:45 -0700 Subject: [PATCH 04/11] Ensure methods with kwargs are invertible --- .../active_record/command_recorder.rb | 71 ++++++------------- .../active_record/schema_statements.rb | 4 +- 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/command_recorder.rb b/lib/timescaledb/rails/extensions/active_record/command_recorder.rb index 207acc7..eba764c 100644 --- a/lib/timescaledb/rails/extensions/active_record/command_recorder.rb +++ b/lib/timescaledb/rails/extensions/active_record/command_recorder.rb @@ -5,56 +5,27 @@ module Rails module ActiveRecord # :nodoc: module CommandRecorder - def create_hypertable(*args, &block) - record(:create_hypertable, args, &block) - end - - def enable_hypertable_compression(*args, &block) - record(:enable_hypertable_compression, args, &block) - end - - def disable_hypertable_compression(*args, &block) - record(:disable_hypertable_compression, args, &block) - end - - def add_hypertable_compression_policy(*args, &block) - record(:add_hypertable_compression_policy, args, &block) - end - - def remove_hypertable_compression_policy(*args, &block) - record(:remove_hypertable_compression_policy, args, &block) - end - - def add_hypertable_reorder_policy(*args, &block) - record(:add_hypertable_reorder_policy, args, &block) - end - - def remove_hypertable_reorder_policy(*args, &block) - record(:remove_hypertable_reorder_policy, args, &block) - end - - def add_hypertable_retention_policy(*args, &block) - record(:add_hypertable_retention_policy, args, &block) - end - - def remove_hypertable_retention_policy(*args, &block) - record(:remove_hypertable_retention_policy, args, &block) - end - - def create_continuous_aggregate(*args, &block) - record(:create_continuous_aggregate, args, &block) - end - - def drop_continuous_aggregate(*args, &block) - record(:drop_continuous_aggregate, args, &block) - end - - def add_continuous_aggregate_policy(*args, &block) - record(:add_continuous_aggregate_policy, args, &block) - end - - def remove_continuous_aggregate_policy(*args, &block) - record(:remove_continuous_aggregate_policy, args, &block) + %w[ + create_hypertable + enable_hypertable_compression + disable_hypertable_compression + add_hypertable_compression_policy + remove_hypertable_compression_policy + add_hypertable_reorder_policy + remove_hypertable_reorder_policy + add_hypertable_retention_policy + remove_hypertable_retention_policy + create_continuous_aggregate + drop_continuous_aggregate + add_continuous_aggregate_policy + remove_continuous_aggregate_policy + ].each do |method| + module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) # def create_table(*args, &block) + record(:"#{method}", args, &block) # record(:create_table, args, &block) + end # end + METHOD + ruby2_keywords(method) end def invert_create_hypertable(args, &block) diff --git a/lib/timescaledb/rails/extensions/active_record/schema_statements.rb b/lib/timescaledb/rails/extensions/active_record/schema_statements.rb index 196e2ca..4b79296 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_statements.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_statements.rb @@ -56,7 +56,7 @@ def enable_hypertable_compression(table_name, segment_by: nil, order_by: nil) # # disable_hypertable_compression('events') # - def disable_hypertable_compression(table_name, _segment_by: nil, _order_by: nil) + def disable_hypertable_compression(table_name, segment_by: nil, order_by: nil) # rubocop:disable Lint/UnusedMethodArgument execute "ALTER TABLE #{table_name} SET (timescaledb.compress = false);" end @@ -131,7 +131,7 @@ def create_continuous_aggregate(view_name, view_query, force: false) # # drop_continuous_aggregate('temperature_events') # - def drop_continuous_aggregate(view_name, _view_query = nil) + def drop_continuous_aggregate(view_name, _view_query = nil, force: false) # rubocop:disable Lint/UnusedMethodArgument execute "DROP MATERIALIZED VIEW #{view_name};" end From 7982ddc421e2f00ee5e49eb44fa064452c071d7e Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 11:22:59 -0700 Subject: [PATCH 05/11] Extract out dependency ordering of CAs --- .../active_record/postgresql_database_tasks.rb | 2 +- .../rails/extensions/active_record/schema_dumper.rb | 7 +------ lib/timescaledb/rails/models/continuous_aggregate.rb | 9 +++++++++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb index 6c48e82..665c7ef 100644 --- a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb +++ b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb @@ -37,7 +37,7 @@ def hypertables(filename) def continuous_aggregates(filename) File.open(filename, 'a') do |file| - Timescaledb::Rails::ContinuousAggregate.all.each do |continuous_aggregate| + Timescaledb::Rails::ContinuousAggregate.dependency_ordered.each do |continuous_aggregate| create_continuous_aggregate_statement(continuous_aggregate, file) add_continuous_aggregate_policy_statement(continuous_aggregate, file) end diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index 894fd6e..c3bdb3e 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -20,12 +20,7 @@ def tables(stream) def continuous_aggregates(stream) return unless timescale_enabled? - deps = Timescaledb::Rails::ContinuousAggregate.find_each.index_by(&:materialization_hypertable_name) - - TSort.tsort( - ->(&b) { deps.each_value(&b) }, - ->(n, &b) { Array.wrap(deps[n.hypertable_name]).each(&b) } - ).each do |ca| + Timescaledb::Rails::ContinuousAggregate.dependency_ordered.each do |ca| continuous_aggregate(ca, stream) continuous_aggregate_policy(ca, stream) end diff --git a/lib/timescaledb/rails/models/continuous_aggregate.rb b/lib/timescaledb/rails/models/continuous_aggregate.rb index f6d590e..c883ce5 100644 --- a/lib/timescaledb/rails/models/continuous_aggregate.rb +++ b/lib/timescaledb/rails/models/continuous_aggregate.rb @@ -13,6 +13,15 @@ class ContinuousAggregate < ApplicationRecord has_many :jobs, foreign_key: 'hypertable_name', class_name: 'Timescaledb::Rails::Job' + def self.dependency_ordered + deps = find_each.index_by(&:materialization_hypertable_name) + + TSort.tsort_each( + ->(&b) { deps.each_value.sort_by(&:hypertable_name).each(&b) }, + ->(n, &b) { Array.wrap(deps[n.hypertable_name]).each(&b) } + ) + end + # Manually refresh a continuous aggregate. # # @param [DateTime] start_time From 0fec356a4c0f26070ef90d45403e0f72d3c9b8d3 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 13:38:15 -0700 Subject: [PATCH 06/11] Remove unused force param --- .../rails/extensions/active_record/schema_dumper.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index c3bdb3e..6d03f1f 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -26,9 +26,8 @@ def continuous_aggregates(stream) end end - def continuous_aggregate(continuous_aggregate, stream, force: false) - stream.puts " create_continuous_aggregate #{continuous_aggregate.view_name.inspect}, <<-SQL, " \ - "force: #{force.inspect}" + def continuous_aggregate(continuous_aggregate, stream) + stream.puts " create_continuous_aggregate #{continuous_aggregate.view_name.inspect}, <<-SQL" stream.puts " #{continuous_aggregate.view_definition.strip.indent(2)}" stream.puts ' SQL' stream.puts From b76f2df8ef41bc2a14e4e9a39078f49c4dc3088f Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 13:39:49 -0700 Subject: [PATCH 07/11] Remove record base config --- lib/timescaledb/rails.rb | 1 - lib/timescaledb/rails/models/application_record.rb | 2 +- lib/timescaledb/rails/railtie.rb | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/timescaledb/rails.rb b/lib/timescaledb/rails.rb index 84ead4a..9443e39 100644 --- a/lib/timescaledb/rails.rb +++ b/lib/timescaledb/rails.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require_relative './rails/railtie' require_relative './rails/model' require_relative './rails/extensions/active_record/base' diff --git a/lib/timescaledb/rails/models/application_record.rb b/lib/timescaledb/rails/models/application_record.rb index 2ae80c2..72f574a 100644 --- a/lib/timescaledb/rails/models/application_record.rb +++ b/lib/timescaledb/rails/models/application_record.rb @@ -3,7 +3,7 @@ module Timescaledb module Rails # :nodoc: - class ApplicationRecord < Railtie.config.record_base.constantize + class ApplicationRecord < ::ActiveRecord::Base self.abstract_class = true end end diff --git a/lib/timescaledb/rails/railtie.rb b/lib/timescaledb/rails/railtie.rb index 868ac51..14bbd6a 100644 --- a/lib/timescaledb/rails/railtie.rb +++ b/lib/timescaledb/rails/railtie.rb @@ -6,8 +6,6 @@ module Timescaledb module Rails # :nodoc: class Railtie < ::Rails::Railtie - config.record_base = '::ActiveRecord::Base' - config.to_prepare do ActiveSupport.on_load(:active_record) do require 'timescaledb/rails/models' From fffb8e6d42b953537bf652995a5c27e881938762 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Tue, 4 Apr 2023 13:55:04 -0700 Subject: [PATCH 08/11] De-dupe connection matching logic --- .../active_record/postgresql_database_tasks.rb | 15 ++------------- .../extensions/active_record/schema_dumper.rb | 13 +------------ .../rails/models/application_record.rb | 14 ++++++++++++++ .../rails/extensions/database_tasks_spec.rb | 2 +- 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb index 665c7ef..1ae82e3 100644 --- a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb +++ b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb @@ -8,7 +8,7 @@ module Rails module ActiveRecord # :nodoc: # rubocop:disable Layout/LineLength - module PostgreSQLDatabaseTasks # rubocop:disable Metrics/ModuleLength + module PostgreSQLDatabaseTasks # @override def structure_dump(filename, extra_flags) extra_flags = Array(extra_flags) @@ -145,18 +145,7 @@ def timescale_structure_dump_default_flags # @return [Boolean] def timescale_enabled? - pool_name(connection.pool) == pool_name(Timescaledb::Rails::Hypertable.connection.pool) && - Timescaledb::Rails::Hypertable.table_exists? - end - - def pool_name(pool) - if pool.respond_to?(:db_config) - pool.db_config.name - elsif pool.respond_to?(:spec) - pool.spec.name - else - raise "Don't know how to get pool name from #{pool.inspect}" - end + ApplicationRecord.timescale_connection?(connection) end end # rubocop:enable Layout/LineLength diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index 6d03f1f..ff8af4a 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -150,18 +150,7 @@ def format_hypertable_option_value(value) end def timescale_enabled? - pool_name(@connection.pool) == pool_name(Timescaledb::Rails::Hypertable.connection.pool) && - Timescaledb::Rails::Hypertable.table_exists? - end - - def pool_name(pool) - if pool.respond_to?(:db_config) - pool.db_config.name - elsif pool.respond_to?(:spec) - pool.spec.name - else - raise "Don't know how to get pool name from #{pool.inspect}" - end + ApplicationRecord.timescale_connection?(@connection) end end end diff --git a/lib/timescaledb/rails/models/application_record.rb b/lib/timescaledb/rails/models/application_record.rb index 72f574a..e7d302d 100644 --- a/lib/timescaledb/rails/models/application_record.rb +++ b/lib/timescaledb/rails/models/application_record.rb @@ -5,6 +5,20 @@ module Rails # :nodoc: class ApplicationRecord < ::ActiveRecord::Base self.abstract_class = true + + def self.timescale_connection?(connection) + pool_name = lambda do |pool| + if pool.respond_to?(:db_config) + pool.db_config.name + elsif pool.respond_to?(:spec) + pool.spec.name + else + raise "Don't know how to get pool name from #{pool.inspect}" + end + end + + pool_name[connection.pool] == pool_name[self.connection.pool] && Hypertable.table_exists? + end end end end diff --git a/spec/timescaledb/rails/extensions/database_tasks_spec.rb b/spec/timescaledb/rails/extensions/database_tasks_spec.rb index 9b2ed3b..c6cf342 100644 --- a/spec/timescaledb/rails/extensions/database_tasks_spec.rb +++ b/spec/timescaledb/rails/extensions/database_tasks_spec.rb @@ -125,7 +125,7 @@ expect(database_structure).to include( <<-QUERY - create_continuous_aggregate "temperature_events", <<-SQL, force: false + create_continuous_aggregate "temperature_events", <<-SQL SELECT time_bucket('#{interval}'::interval, events.created_at) AS time_bucket, avg(events.value) AS avg FROM events From f2c1607b0ab288d81857172e223cdac8aceb3a83 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Mon, 10 Apr 2023 16:11:37 -0700 Subject: [PATCH 09/11] Respond to PR comments --- .../rails/extensions/active_record/schema_dumper.rb | 1 - lib/timescaledb/rails/railtie.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index ff8af4a..24a7e3b 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -2,7 +2,6 @@ require 'active_record/connection_adapters/postgresql_adapter' require 'timescaledb/rails/orderby_compression' -require 'tsort' module Timescaledb module Rails diff --git a/lib/timescaledb/rails/railtie.rb b/lib/timescaledb/rails/railtie.rb index 14bbd6a..b9f4647 100644 --- a/lib/timescaledb/rails/railtie.rb +++ b/lib/timescaledb/rails/railtie.rb @@ -6,13 +6,13 @@ module Timescaledb module Rails # :nodoc: class Railtie < ::Rails::Railtie - config.to_prepare do + initializer 'timescaledb-rails.require_timescale_models' do ActiveSupport.on_load(:active_record) do require 'timescaledb/rails/models' end end - config.to_prepare do + initializer 'timescaledb-rails.add_timescale_support_to_active_record' do ActiveSupport.on_load(:active_record) do Timescaledb::Rails.load end From 55af7539c976a34c8e73b7cef33535df5aef8f83 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 19 Apr 2023 18:56:54 +0200 Subject: [PATCH 10/11] Move hypertable exists check --- .../rails/extensions/active_record/postgresql_database_tasks.rb | 2 +- lib/timescaledb/rails/extensions/active_record/schema_dumper.rb | 2 +- lib/timescaledb/rails/models/application_record.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb index 1ae82e3..6aa9ccb 100644 --- a/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb +++ b/lib/timescaledb/rails/extensions/active_record/postgresql_database_tasks.rb @@ -145,7 +145,7 @@ def timescale_structure_dump_default_flags # @return [Boolean] def timescale_enabled? - ApplicationRecord.timescale_connection?(connection) + ApplicationRecord.timescale_connection?(connection) && Hypertable.table_exists? end end # rubocop:enable Layout/LineLength diff --git a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb index 24a7e3b..c942a6e 100644 --- a/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb +++ b/lib/timescaledb/rails/extensions/active_record/schema_dumper.rb @@ -149,7 +149,7 @@ def format_hypertable_option_value(value) end def timescale_enabled? - ApplicationRecord.timescale_connection?(@connection) + ApplicationRecord.timescale_connection?(@connection) && Hypertable.table_exists? end end end diff --git a/lib/timescaledb/rails/models/application_record.rb b/lib/timescaledb/rails/models/application_record.rb index e7d302d..55e48c8 100644 --- a/lib/timescaledb/rails/models/application_record.rb +++ b/lib/timescaledb/rails/models/application_record.rb @@ -17,7 +17,7 @@ def self.timescale_connection?(connection) end end - pool_name[connection.pool] == pool_name[self.connection.pool] && Hypertable.table_exists? + pool_name[connection.pool] == pool_name[self.connection.pool] end end end From 835d2d4859a3fb47e40df8d6d9ab1619f866d301 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Wed, 19 Apr 2023 19:26:36 +0200 Subject: [PATCH 11/11] Only call ruby2_keywords when method is defined --- .../rails/extensions/active_record/command_recorder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timescaledb/rails/extensions/active_record/command_recorder.rb b/lib/timescaledb/rails/extensions/active_record/command_recorder.rb index eba764c..a77b93e 100644 --- a/lib/timescaledb/rails/extensions/active_record/command_recorder.rb +++ b/lib/timescaledb/rails/extensions/active_record/command_recorder.rb @@ -25,7 +25,7 @@ def #{method}(*args, &block) # def create_table(*args, &block) record(:"#{method}", args, &block) # record(:create_table, args, &block) end # end METHOD - ruby2_keywords(method) + ruby2_keywords(method) if respond_to?(:ruby2_keywords) end def invert_create_hypertable(args, &block)