diff --git a/README.md b/README.md index a3ba1c9..070f3bc 100644 --- a/README.md +++ b/README.md @@ -337,12 +337,13 @@ Stacks around low-level query execution Callback stack wraps the emission of sql to the underlying dbms gem. - Env Field | Description | Initialized - --- | --- | --- - `:result` | The result of the database query | *unset* - `:caller` | The ActiveRecord::SchemaCreation instance | *context* - `:sql` | The SQL string | *context* - `:binds` | Values to substitute into the SQL string + Env Field | Description | Initialized + ------------- | -------------------------------------------- | ----------- + `:result` | The result of the database query | *unset* + `:caller` | The ActiveRecord::SchemaCreation instance | *context* + `:sql` | The SQL string | *context* + `:binds` | Values to substitute into the SQL string | + `:prepare` | Whether to cache the prepared statement | `:query_name` | Label sometimes used by ActiveRecord logging | *arg* diff --git a/gemfiles/activerecord-5.0/Gemfile.base b/gemfiles/activerecord-5.0/Gemfile.base index b583c0f..b18bf66 100644 --- a/gemfiles/activerecord-5.0/Gemfile.base +++ b/gemfiles/activerecord-5.0/Gemfile.base @@ -1,3 +1,3 @@ eval File.read File.expand_path('../../Gemfile.base', __FILE__) -gem "activerecord", ">= 5.0.0.beta1", "< 5.1" +gem "activerecord", "~> 5.0.0" diff --git a/lib/schema_plus/core.rb b/lib/schema_plus/core.rb index 73f22d5..70fef2c 100644 --- a/lib/schema_plus/core.rb +++ b/lib/schema_plus/core.rb @@ -1,4 +1,5 @@ require "schema_monkey" +require "schema_plus_compatibility" require 'its-it' require "pathname" diff --git a/lib/schema_plus/core/active_record/connection_adapters/abstract_adapter.rb b/lib/schema_plus/core/active_record/connection_adapters/abstract_adapter.rb index 3e1f1b7..6742d5b 100644 --- a/lib/schema_plus/core/active_record/connection_adapters/abstract_adapter.rb +++ b/lib/schema_plus/core/active_record/connection_adapters/abstract_adapter.rb @@ -4,6 +4,18 @@ module ActiveRecord module ConnectionAdapters module AbstractAdapter + def _append_where_constraints(sql, where_constraints) + where_constraints.each do |where_constraints| + sql << " AND (#{where_constraints})" + end + end + + def _select_data_sources(where_constraints = [], types = nil) + sql = _data_sources_sql types + _append_where_constraints sql, where_constraints + select_values sql, 'SCHEMA' + end + def add_column(table_name, name, type, options = {}) options = options.deep_dup SchemaMonkey::Middleware::Migration::Column.start(caller: self, operation: :add, table_name: table_name, column_name: name, type: type, implements_reference: options.delete(:_implements_reference), options: options) do |env| diff --git a/lib/schema_plus/core/active_record/connection_adapters/mysql2_adapter.rb b/lib/schema_plus/core/active_record/connection_adapters/mysql2_adapter.rb index 8721c7a..c6f213b 100644 --- a/lib/schema_plus/core/active_record/connection_adapters/mysql2_adapter.rb +++ b/lib/schema_plus/core/active_record/connection_adapters/mysql2_adapter.rb @@ -4,6 +4,26 @@ module ActiveRecord module ConnectionAdapters module Mysql2Adapter + def _data_sources_sql(types = nil) + sql = "SELECT table_name FROM information_schema.tables\n" + sql << "WHERE table_schema = #{quote(@config[:database])}" + if types + supported_types = types & %i[table view] + if supported_types.length == 0 + raise 'No supported data source types: please specify at least one of :table, :view' + elsif supported_types.length == 1 + # If both tables and views are requested, no need to add an extra clause + if supported_types[0] == :table + table_type = 'BASE_TABLE' + else + table_type = 'VIEW' + end + sql << " AND table_type = '#{table_type}'" + end + end + sql + end + def change_column(table_name, name, type, options = {}) SchemaMonkey::Middleware::Migration::Column.start(caller: self, operation: :change, table_name: table_name, column_name: name, type: type, options: options.deep_dup) do |env| super env.table_name, env.column_name, env.type, env.options @@ -35,11 +55,17 @@ def indexes(table_name, query_name=nil) end def data_sources - SchemaMonkey::Middleware::Schema::DataSources.start(connection: self, sources: []) { |env| - env.sources += super + SchemaMonkey::Middleware::Schema::DataSources.start(connection: self, sources: [], where_constraints: []) { |env| + env.sources += _select_data_sources env.where_constraints }.sources end + def views + SchemaMonkey::Middleware::Schema::Views.start(connection: self, views: [], where_constraints: []) { |env| + env.views += _select_data_sources env.where_constraints, [:view] + }.views + end + def select_rows(sql, name=nil, binds=[]) SchemaMonkey::Middleware::Query::Exec.start(connection: self, sql: sql, query_name: name, binds: binds) { |env| env.result = super env.sql, env.query_name, env.binds diff --git a/lib/schema_plus/core/active_record/connection_adapters/postgresql_adapter.rb b/lib/schema_plus/core/active_record/connection_adapters/postgresql_adapter.rb index 618183d..1c5d970 100644 --- a/lib/schema_plus/core/active_record/connection_adapters/postgresql_adapter.rb +++ b/lib/schema_plus/core/active_record/connection_adapters/postgresql_adapter.rb @@ -3,6 +3,13 @@ module Core module ActiveRecord module ConnectionAdapters module PostgresqlAdapter + def _data_sources_sql(types = nil) + types ||= %i[table view materialized_view] + type_map = { table: 'r', view: 'v', materialized_view: 'm' } + types.map! {|type| type_map[type]} + types.compact! + _pg_relations_sql(types) + end # quick hack fix quoting of column default functions to allow eval() when we # capture the stream. @@ -65,10 +72,16 @@ def indexes(table_name, query_name=nil) end def data_sources - SchemaMonkey::Middleware::Schema::DataSources.start(connection: self, sources: []) { |env| - env.sources += super + SchemaMonkey::Middleware::Schema::DataSources.start(connection: self, sources: [], where_constraints: []) { |env| + env.sources += _select_data_sources env.where_constraints }.sources end + + def views + SchemaMonkey::Middleware::Schema::Views.start(connection: self, views: [], where_constraints: []) { |env| + env.views += _select_data_sources env.where_constraints, %i[view materialized_view] + }.views + end end end end diff --git a/lib/schema_plus/core/active_record/connection_adapters/sqlite3_adapter.rb b/lib/schema_plus/core/active_record/connection_adapters/sqlite3_adapter.rb index dcf8181..19b0c32 100644 --- a/lib/schema_plus/core/active_record/connection_adapters/sqlite3_adapter.rb +++ b/lib/schema_plus/core/active_record/connection_adapters/sqlite3_adapter.rb @@ -3,6 +3,19 @@ module Core module ActiveRecord module ConnectionAdapters module Sqlite3Adapter + def _data_sources_sql(types = nil) + supported_types = %i[table view] + types = types & supported_types || supported_types + if types.length == 0 + raise 'No supported data source types: please specify at least one of :table, :view' + elsif types.length == 1 + type_query = "type = '#{types.first}'" + else + type_list = types.map{|x| "'#{x}'"}.join ',' + type_query = "type IN (#{type_list})" + end + "SELECT name FROM sqlite_master WHERE #{type_query} AND name <> 'sqlite_sequence'" + end def rename_table(table_name, new_name) SchemaMonkey::Middleware::Migration::RenameTable.start(connection: self, table_name: table_name, new_name: new_name) do |env| @@ -35,10 +48,16 @@ def indexes(table_name, query_name=nil) end def data_sources - SchemaMonkey::Middleware::Schema::DataSources.start(connection: self, sources: []) { |env| - env.sources += super + SchemaMonkey::Middleware::Schema::DataSources.start(connection: self, sources: [], where_constraints: []) { |env| + env.sources += _select_data_sources env.where_constraints }.sources end + + def views + SchemaMonkey::Middleware::Schema::Views.start(connection: self, views: [], where_constraints: []) { |env| + env.views += _select_data_sources env.where_constraints, [:view] + }.views + end end end end diff --git a/lib/schema_plus/core/middleware.rb b/lib/schema_plus/core/middleware.rb index 19653bc..bc04433 100644 --- a/lib/schema_plus/core/middleware.rb +++ b/lib/schema_plus/core/middleware.rb @@ -17,7 +17,11 @@ module Indexes end module DataSources - ENV = [:connection, :sources] + ENV = [:connection, :sources, :where_constraints] + end + + module Views + ENV = [:connection, :views, :where_constraints] end end diff --git a/schema_plus_core.gemspec b/schema_plus_core.gemspec index 39e7d5a..eae228d 100644 --- a/schema_plus_core.gemspec +++ b/schema_plus_core.gemspec @@ -27,6 +27,7 @@ Gem::Specification.new do |gem| gem.add_development_dependency "rspec", "~> 3.0.0" gem.add_development_dependency "rspec-given" gem.add_development_dependency "schema_dev", "~> 3.7" + #gem.add_development_dependency "schema_compatibility", "~> 0.3" gem.add_development_dependency "simplecov" gem.add_development_dependency "simplecov-gem-profile" gem.add_development_dependency "its-it" diff --git a/spec/middleware_spec.rb b/spec/middleware_spec.rb index e49c8ec..3f9ff26 100644 --- a/spec/middleware_spec.rb +++ b/spec/middleware_spec.rb @@ -6,7 +6,7 @@ let(:connection) { ::ActiveRecord::Base.connection } Given { - migration.create_table "things" + migration.create_table 'things' class Thing < ActiveRecord::Base ; end } @@ -30,7 +30,12 @@ class Thing < ActiveRecord::Base ; end end context TestReporter::Middleware::Schema::DataSources do - Then { expect_middleware { connection.data_sources() } } + Given { migration.create_table 'other' } + Then { expect_middleware(env: { sources: contain_exactly('things', 'other')}) { connection.data_sources } } + end + + context TestReporter::Middleware::Schema::Views do + Then { expect_middleware { connection.views } } end context TestReporter::Middleware::Schema::Indexes do @@ -169,6 +174,18 @@ def env_match(env, matcher, bool: false) else expect(actual).to match val end + when Proc + if bool + return val.call(actual) + else + expect(actual).to val.call + end + when RSpec::Matchers::BuiltIn::BaseMatcher + if bool + return val.matches? actual + else + expect(actual).to val + end else if bool return false unless actual == val @@ -180,10 +197,10 @@ def env_match(env, matcher, bool: false) true if bool end - def expect_middleware(env: {}, enable: {}) + def expect_middleware(env: {}, enable: {}, before: nil) middleware = described_class begin - middleware.enable(-> (_env) { env_match(_env, enable, bool: true) }) + middleware.enable -> _env { env_match(_env, enable, bool: true) } expect { yield }.to raise_error { |error| expect(error).to be_a TestReporter::Called expect(error.middleware).to eq middleware diff --git a/spec/support/test_reporter.rb b/spec/support/test_reporter.rb index a7a4f35..0a9b51f 100644 --- a/spec/support/test_reporter.rb +++ b/spec/support/test_reporter.rb @@ -28,6 +28,7 @@ module Schema module Define ; include Notify ; end module Indexes ; include Notify ; end module DataSources ; include Notify ; end + module Views ; include Notify ; end end module Migration