diff --git a/lib/viewr.rb b/lib/viewr.rb index e8c11d9..03662fa 100644 --- a/lib/viewr.rb +++ b/lib/viewr.rb @@ -26,23 +26,25 @@ def self.setup_runner(connection, view_files_path, function_files_path) adapter = DatabaseAdapter.new(connection) runner = SchemaObjectRunner.new - self.load_views(view_files_path, runner, adapter) - self.load_functions(function_files_path, runner, adapter) + self.load_view_specs(view_files_path, runner, adapter) + self.load_function_specs(function_files_path, runner, adapter) runner end - def self.load_views(path, runner, database_adapter) + def self.load_view_specs(path, runner, database_adapter) view_files = File.join(path, '*.yml') Dir.glob(view_files).each do |view_file| - runner << View.new_from_yaml(IO.read(view_file), database_adapter) + view_specification = IO.read(view_file) + runner << View.new_from_yaml(view_specification, database_adapter) end end - def self.load_functions(path, runner, database_adapter) + def self.load_function_specs(path, runner, database_adapter) function_files = File.join(path, '*.yml') Dir.glob(function_files).each do |function_file| - runner << Function.new_from_yaml(IO.read(function_file), database_adapter) + function_specification = IO.read(function_file) + runner << Function.new_from_yaml(function_specification, database_adapter) end end end diff --git a/lib/viewr/database_adapter.rb b/lib/viewr/database_adapter.rb index 586446d..af69b50 100644 --- a/lib/viewr/database_adapter.rb +++ b/lib/viewr/database_adapter.rb @@ -10,22 +10,39 @@ def run(statement) connection.run(statement) end - def drop_view(view_name) - return unless view_exists?(view_name) + def create_view(view) + return if view_exists?(view.name) - run(drop_view_sql(view_name)) + run(create_view_sql(view)) end - def drop_function(function_name) - existing_functions_with_argument_types(function_name).each do |function_with_argument_types| + def drop_view(view) + return unless view_exists?(view.name) + + run(drop_view_sql(view)) + end + + def create_function(function) + run(function.sql) + end + + def drop_function(function) + existing_functions_with_argument_types(function.name).each do |function_with_argument_types| run(drop_function_sql(function_with_argument_types)) end end - def drop_view_sql(view_name) - case view_type(view_name) - when :view then "DROP VIEW IF EXISTS #{view_name} CASCADE" - when :materialized_view then "DROP MATERIALIZED VIEW #{view_name} CASCADE" + def create_view_sql(view) + case view.type + when :view then "CREATE OR REPLACE VIEW #{view.name} AS #{view.sql}" + when :materialized_view then "CREATE OR REPLACE MATERIALIZED VIEW #{view.name} AS #{view.sql}" + end + end + + def drop_view_sql(view) + case view.type + when :view then "DROP VIEW IF EXISTS #{view.name} CASCADE" + when :materialized_view then "DROP MATERIALIZED VIEW #{view.name} CASCADE" end end @@ -33,18 +50,18 @@ def drop_function_sql(function_with_argument_types) "DROP FUNCTION IF EXISTS #{function_with_argument_types} CASCADE" end - def view_exists?(view_name) - !view_type(view_name).nil? + def view_exists?(view) + !view_exists_in_database?(view).nil? end private - def view_type(view_name) + def view_exists_in_database?(view) result = @connection.fetch(%Q( SELECT relkind FROM pg_catalog.pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = 'public' AND c.relname = '#{view_name}' + WHERE n.nspname = 'public' AND c.relname = '#{view.name}' )).first case @@ -70,4 +87,3 @@ def existing_functions_with_argument_types(function_name) end end end - diff --git a/lib/viewr/database_object.rb b/lib/viewr/database_object.rb index 8de9dc7..395f2e8 100644 --- a/lib/viewr/database_object.rb +++ b/lib/viewr/database_object.rb @@ -4,10 +4,12 @@ module Viewr class DatabaseObject attr_reader :name, :dependencies, :sql, :adapter - def initialize(function_doc, adapter) - @name = function_doc['name'] - @dependencies = function_doc['dependencies'] - @sql = function_doc['sql'] + def initialize(specification, adapter) + @name = specification.fetch('name') + @sql = specification.fetch('sql') + + @dependencies = specification['dependencies'] + @adapter = adapter end @@ -23,12 +25,16 @@ def dependencies @dependencies || [] end + def type + @type.to_sym if @type + end + def create - raise '#create is not implemented' + fail '#create is not implemented' end def drop - raise '#drop is not implemented' + fail '#drop is not implemented' end def eql?(another_function) @@ -40,4 +46,3 @@ def hash end end end - diff --git a/lib/viewr/function.rb b/lib/viewr/function.rb index 4a03272..ba95f1f 100644 --- a/lib/viewr/function.rb +++ b/lib/viewr/function.rb @@ -2,12 +2,18 @@ module Viewr class Function < DatabaseObject + + def initialize(specification, adapter) + @type = :function + super + end + def create - @adapter.run(@sql) + @adapter.create_function(self) end def drop - @adapter.drop_function(@name) + @adapter.drop_function(self) end end end diff --git a/lib/viewr/view.rb b/lib/viewr/view.rb index bcb2a59..e54a86d 100644 --- a/lib/viewr/view.rb +++ b/lib/viewr/view.rb @@ -2,13 +2,18 @@ module Viewr class View < DatabaseObject + + def initialize(specification, adapter) + @type = specification.fetch('type', :view) + super + end + def create - @adapter.run(@sql) + @adapter.create_view(self) end def drop - @adapter.drop_view(@name) + @adapter.drop_view(self) end end end - diff --git a/spec/support/shared_examples_for_database_objects.rb b/spec/support/shared_examples_for_database_objects.rb index dd814bd..73dc8b7 100644 --- a/spec/support/shared_examples_for_database_objects.rb +++ b/spec/support/shared_examples_for_database_objects.rb @@ -3,18 +3,18 @@ shared_examples 'a database object' do - let (:yaml) { "---\nname: foo\ndependencies:\n - 'bar'\nsql: 'SQL'" } + let (:yaml) { "---\nname: foo\ndependencies:\n - 'bar'\ntype: 'type'\nsql: 'SQL'" } let (:view_doc_with_dependencies) do { 'name' => 'foo', 'dependencies' => ['bar', 'baz'], - 'sql' => 'SOME SQL STATEMENT FOR FOO HERE' + 'sql' => 'SOME SQL STATEMENT FOR FOO HERE', } end let (:view_doc_without_dependencies) do { 'name' => 'bar', - 'sql' => 'SOME SQL STATEMENT FOR BAR HERE' + 'sql' => 'SOME SQL STATEMENT FOR BAR HERE', } end @@ -78,4 +78,3 @@ end end end - diff --git a/spec/viewr/database_adapter_spec.rb b/spec/viewr/database_adapter_spec.rb index 4869b58..40599a0 100644 --- a/spec/viewr/database_adapter_spec.rb +++ b/spec/viewr/database_adapter_spec.rb @@ -4,6 +4,26 @@ let(:connection) { double(:connection) } let(:database_adapter) { Viewr::DatabaseAdapter.new(connection) } + let(:function) { + function_params = { + 'name' => 'function', + 'sql' => 'Select * FROM something;' + } + Viewr::Function.new(function_params, database_adapter) } + let(:view) { + view_params = { + 'name' => 'view', + 'sql' => 'Select * FROM something;' + } + Viewr::View.new(view_params, database_adapter) } + let(:materialized_view) { + materialized_view_params = { + 'name' => 'some_materialized_view', + 'type' => 'materialized_view', + 'sql' => 'Select * FROM something;' + } + Viewr::View.new(materialized_view_params, database_adapter) + } describe '#initialize' do it 'saves the given connection to an attribute' do @@ -21,43 +41,39 @@ describe '#drop_view' do it 'does not run SQL statement if view does not exist' do - allow(database_adapter).to receive(:view_exists?).with(:view_name).and_return(false) + expect(database_adapter).to receive(:view_exists?).with(view.name).and_return(false) expect(database_adapter).not_to receive(:run) - database_adapter.drop_view(:view_name) + database_adapter.drop_view(view) end it 'runs the SQL statement returned by #drop_view_sql' do - allow(database_adapter).to receive(:view_exists?).with(:view_name).and_return(true) - expect(database_adapter).to receive(:drop_view_sql).with(:view_name).and_return(:sql_statement) + expect(database_adapter).to receive(:view_exists?).with(view.name).and_return(true) + expect(database_adapter).to receive(:drop_view_sql).with(view).and_return(:sql_statement) expect(database_adapter).to receive(:run).with(:sql_statement) - database_adapter.drop_view(:view_name) + database_adapter.drop_view(view) end end describe '#drop_function' do it 'runs the SQL statement returned by #drop_function_sql' do - expect(database_adapter).to receive(:existing_functions_with_argument_types).with(:function_name).and_return([:foo]) + expect(database_adapter).to receive(:existing_functions_with_argument_types).with(function.name).and_return([:foo]) expect(database_adapter).to receive(:drop_function_sql).with(:foo).and_return(:sql_statement) expect(database_adapter).to receive(:run).with(:sql_statement) - database_adapter.drop_function(:function_name) + database_adapter.drop_function(function) end end describe '#drop_view_sql' do it 'returns an SQL statement to drop the given view if type is :view' do - allow(database_adapter).to receive(:view_type).with('view_name').and_return(:view) - - expect(database_adapter.drop_view_sql('view_name')).to eql('DROP VIEW IF EXISTS view_name CASCADE') + expect(database_adapter.drop_view_sql(view)).to eql("DROP VIEW IF EXISTS #{view.name} CASCADE") end it 'returns an SQL statement to drop the given materialized view if type is :materialized_view' do - allow(database_adapter).to receive(:view_type).with('view_name').and_return(:materialized_view) - - expect(database_adapter.drop_view_sql('view_name')).to eql('DROP MATERIALIZED VIEW view_name CASCADE') + expect(database_adapter.drop_view_sql(materialized_view)).to eql("DROP MATERIALIZED VIEW #{materialized_view.name} CASCADE") end end @@ -69,17 +85,17 @@ describe '#view_exists?' do it 'returns false if view type is nil' do - allow(database_adapter).to receive(:view_type).with(:view_name).and_return(nil) + expect(database_adapter).to receive(:view_exists_in_database?).with(view).and_return(nil) - result = database_adapter.view_exists?(:view_name) + result = database_adapter.view_exists?(view) expect(result).to be false end it 'returns true if view type is not nil' do - allow(database_adapter).to receive(:view_type).with(:view_name).and_return(:view) + expect(database_adapter).to receive(:view_exists_in_database?).with(view).and_return(:view) - result = database_adapter.view_exists?(:view_name) + result = database_adapter.view_exists?(view) expect(result).to be true end diff --git a/spec/viewr/function_spec.rb b/spec/viewr/function_spec.rb index 7c7c73e..9b3c3e8 100644 --- a/spec/viewr/function_spec.rb +++ b/spec/viewr/function_spec.rb @@ -15,9 +15,16 @@ it_behaves_like 'a database object' + describe '#initialize' do + it 'defaults type to :function' do + function = Viewr::Function.new(function_doc, adapter) + expect(function.type).to eql(:function) + end + end + describe '#create' do it 'should run the SQL on the adapter' do - expect(adapter).to receive(:run).with('SQL STATEMENT') + expect(adapter).to receive(:create_function).with(function) function.create end @@ -25,10 +32,9 @@ describe '#drop' do it 'should drop the function using the adapter' do - expect(adapter).to receive(:drop_function).with('bar') + expect(adapter).to receive(:drop_function).with(function) function.drop end end end - diff --git a/spec/viewr/view_spec.rb b/spec/viewr/view_spec.rb index f8df628..e3cb622 100644 --- a/spec/viewr/view_spec.rb +++ b/spec/viewr/view_spec.rb @@ -15,9 +15,21 @@ it_behaves_like 'a database object' + describe '#initialize' do + it 'defaults type to :view' do + view = Viewr::View.new(view_doc, adapter) + expect(view.type).to eql(:view) + end + + it 'allows to set type' do + view = Viewr::View.new(view_doc.merge('type' => 'materialized_views'), adapter) + expect(view.type).to eql(:materialized_views) + end + end + describe '#create' do it 'should run the SQL on the adapter' do - expect(adapter).to receive(:run).with('SQL STATEMENT') + expect(adapter).to receive(:create_view).with(view) view.create end @@ -25,7 +37,7 @@ describe '#drop' do it 'should drop the view using the adapter' do - expect(adapter).to receive(:drop_view).with('bar') + expect(adapter).to receive(:drop_view).with(view) view.drop end diff --git a/spec/viewr_spec.rb b/spec/viewr_spec.rb index 406544c..b60dcca 100644 --- a/spec/viewr_spec.rb +++ b/spec/viewr_spec.rb @@ -44,14 +44,14 @@ runner = double(:database_object_runner) expect(Viewr::DatabaseAdapter).to receive(:new).with(:connection).and_return(adapter) expect(Viewr::SchemaObjectRunner).to receive(:new).and_return(runner) - expect(Viewr).to receive(:load_views).with(:view_files_path, runner, adapter) - expect(Viewr).to receive(:load_functions).with(:function_files_path, runner, adapter) + expect(Viewr).to receive(:load_view_specs).with(:view_files_path, runner, adapter) + expect(Viewr).to receive(:load_function_specs).with(:function_files_path, runner, adapter) Viewr.setup_runner(:connection, :view_files_path, :function_files_path) end end - describe '.load_views' do + describe '.load_view_specs' do it 'loads all views from the given directory' do runner = double(:database_object_runner) adapter = double(:database_adapter) @@ -61,11 +61,11 @@ .and_return(yaml_data) expect(runner).to receive(:<<).with(yaml_data) - Viewr.load_views('spec/fixtures/views', runner, adapter) + Viewr.load_view_specs('spec/fixtures/views', runner, adapter) end end - describe '.load_functions' do + describe '.load_function_specs' do it 'loads all functions from the given directory' do runner = double(:database_object_runner) adapter = double(:database_adapter) @@ -75,7 +75,7 @@ .and_return(yaml_data) expect(runner).to receive(:<<).with(yaml_data) - Viewr.load_functions('spec/fixtures/functions', runner, adapter) + Viewr.load_function_specs('spec/fixtures/functions', runner, adapter) end end end