From 62b1350b8df4d6339e4a098e8e058c416f3a7280 Mon Sep 17 00:00:00 2001 From: Boaz Yaniv <boazyan@gmail.com> Date: Tue, 16 Aug 2016 11:27:07 +0900 Subject: [PATCH 1/4] Added new parameter to Query::Exec --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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* From 9d08e3bf992d922f0d7bfa64841797d88dc84d45 Mon Sep 17 00:00:00 2001 From: Boaz Yaniv <boazyan@gmail.com> Date: Sun, 23 Oct 2016 12:54:22 +0900 Subject: [PATCH 2/4] Updated Gemfile specs to exclude Rails 5 beta versions. --- gemfiles/activerecord-5.0/Gemfile.base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From e900d25fe31021d600617c5f529f3f36fcc7e859 Mon Sep 17 00:00:00 2001 From: Boaz Yaniv <boazyan@gmail.com> Date: Sun, 23 Oct 2016 12:55:12 +0900 Subject: [PATCH 3/4] Added dependency on schema_plus_compatibility --- schema_plus_core.gemspec | 1 + 1 file changed, 1 insertion(+) 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" From d5a6c1a665c2cf7d6d54905d5c78bed92425a4e7 Mon Sep 17 00:00:00 2001 From: Boaz Yaniv <boazyan@gmail.com> Date: Sun, 23 Oct 2016 12:55:58 +0900 Subject: [PATCH 4/4] Connection#data_sources now supports custom WHERE constraints. Middlewares can hook into Connection#data_sources and add their own SQL WHERE constraints, e.g. to filter out tables. The rails default implementation is overriden, but results should be the same. Schema query implementation is unified for data sources and views now. --- lib/schema_plus/core.rb | 1 + .../connection_adapters/abstract_adapter.rb | 12 ++++++++ .../connection_adapters/mysql2_adapter.rb | 30 +++++++++++++++++-- .../connection_adapters/postgresql_adapter.rb | 17 +++++++++-- .../connection_adapters/sqlite3_adapter.rb | 23 ++++++++++++-- lib/schema_plus/core/middleware.rb | 6 +++- spec/middleware_spec.rb | 25 +++++++++++++--- spec/support/test_reporter.rb | 1 + 8 files changed, 104 insertions(+), 11 deletions(-) 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/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