Skip to content

Commit

Permalink
Skip schema dumper when extension is not available
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatas committed May 17, 2024
1 parent 263dcb8 commit 0239b57
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 10 deletions.
4 changes: 3 additions & 1 deletion bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ def uri_from_test
ENV['PG_URI_TEST']
end

ActiveRecord::Base.establish_connection(ARGV[0] || uri_from_test)
uri = ARGV[0] || uri_from_test
ActiveRecord::Base.establish_connection(uri)
Timescaledb.establish_connection(uri)

Timescaledb::Hypertable.find_each do |hypertable|
class_name = hypertable.hypertable_name.singularize.camelize
Expand Down
9 changes: 9 additions & 0 deletions lib/timescaledb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,20 @@
require_relative 'timescaledb/stats'
require_relative 'timescaledb/stats_report'
require_relative 'timescaledb/migration_helpers'
require_relative 'timescaledb/extension'
require_relative 'timescaledb/version'

module Timescaledb
module_function

def connection
Connection.instance
end

def extension
Extension
end

def chunks
Chunk.all
end
Expand Down
13 changes: 12 additions & 1 deletion lib/timescaledb/connection.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
require 'singleton'

module Timescaledb
# Minimal connection setup for Timescaledb directly with the PG.
# The concept is use a singleton component that can query
# independently of the ActiveRecord::Base connections.
# This is useful for the extension and hypertable metadata.
# It can also #use_connection from active record if needed.
class Connection
include Singleton

Expand Down Expand Up @@ -34,10 +39,16 @@ def connected?
!@config.nil?
end

# Override the connection with a raw PG connection.
# @param [PG::Connection] connection The raw PG connection.
def use_connection connection
@connection = connection
end

private

def connection
@connection ||= PG.connect(@config)
end
end
end
end
13 changes: 9 additions & 4 deletions lib/timescaledb/connection_handling.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
module Timescaledb
class ConnectionNotEstablishedError < StandardError; end

# @param [String] config The postgres connection string.
module_function

# @param [String] config with the postgres connection string.
def establish_connection(config)
Connection.instance.config = config
end
module_function :establish_connection

# @param [PG::Connection] to use it directly from a raw connection
def use_connection conn
Connection.instance.use_connection conn
end

def connection
raise ConnectionNotEstablishedError.new unless Connection.instance.connected?

Connection.instance
end
module_function :connection
end
end
23 changes: 23 additions & 0 deletions lib/timescaledb/extension.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Timescaledb

# Provides metadata around the extension in the database
module Extension
module_function
# @return String version of the timescaledb extension
def version
@version ||= Timescaledb.connection.query_first(<<~SQL)&.version
SELECT extversion as version
FROM pg_extension
WHERE extname = 'timescaledb'
SQL
end

def installed?
version.present?
end

def update!
Timescaledb.connection.execute('ALTER EXTENSION timescaledb UPDATE')
end
end
end
23 changes: 19 additions & 4 deletions lib/timescaledb/schema_dumper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,29 @@ module Timescaledb
# * retention policies
# * continuous aggregates
# * compression settings
# It also ignores Timescale related schemas when dumping the schema.
# It also ignores dumping options as extension is not installed or no hypertables are available.
module SchemaDumper
def tables(stream)
super # This will call #table for each table in the database
return unless Timescaledb::Hypertable.table_exists?

timescale_hypertables(stream)
timescale_retention_policies(stream)
timescale_continuous_aggregates(stream) # Define these before any Scenic views that might use them
if exports_timescaledb_metadata?
timescale_hypertables(stream)
timescale_retention_policies(stream)
timescale_continuous_aggregates(stream) # Define these before any Scenic views that might use them
end
end

# Ignore dumps in case DB is not eligible for TimescaleDB metadata.
# @return [Boolean] true if the extension is installed and hypertables are available, otherwise false.
private def exports_timescaledb_metadata?
# Note it's safe to use the raw connection here because we're only reading from the database
# and not modifying it. We're also on the same connection pool as ActiveRecord::Base.
# The dump process also runs standalone, so we don't need to worry about the connection being
# used elsewhere.
Timescaledb.use_connection @connection.raw_connection

Timescaledb.extension.installed? && Timescaledb.hypertables.any?
end

# Ignores Timescale related schemas when dumping the schema
Expand Down
39 changes: 39 additions & 0 deletions spec/timescaledb/connection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
RSpec.describe Timescaledb do
describe '.establish_connection' do
it 'returns a PG::Connection object' do
expect do
Timescaledb.establish_connection(ENV['PG_URI_TEST'])
end.to_not raise_error
end
end

describe ::Timescaledb::Connection do
subject(:connection) { Timescaledb::Connection.instance }

it 'returns a Connection object' do
is_expected.to be_a(Timescaledb::Connection)
end

it 'has fast access to the connection' do
expect(connection.send(:connection)).to be_a(PG::Connection)
end

describe '#connected?' do
it { expect(connection.connected?).to be_truthy }
end

describe '#query_first' do
let(:sql) { "select 1 as one" }
subject(:result) { connection.query_first(sql) }

it { expect(result).to be_a(OpenStruct) }
end

describe '#query' do
let(:sql) { "select 1 as one" }
subject(:result) { connection.query(sql) }

it { expect(result).to eq([OpenStruct.new({"one" => "1"})]) }
end
end
end
10 changes: 10 additions & 0 deletions spec/timescaledb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
expect(Timescaledb::VERSION).not_to be nil
end

describe ".extension" do
describe ".installed?" do
it { expect(Timescaledb.extension.installed?).to be_truthy }
end

describe ".version" do
it { expect(Timescaledb.extension.version).not_to be_empty }
end
end

describe ".chunks" do
subject { Timescaledb.chunks }

Expand Down

0 comments on commit 0239b57

Please sign in to comment.