From e5e97e9c339c439317e08e16867cc4ade02c5b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=A4=C3=A4rnhielm?= Date: Mon, 5 May 2014 12:48:19 +0200 Subject: [PATCH 1/4] Support creating materialized views --- .../active_record/connection_adapters/abstract_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb b/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb index f0e56c3..d648844 100644 --- a/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb @@ -48,7 +48,7 @@ def initialize_with_schema_plus(*args) #:nodoc: def create_view(view_name, definition, options={}) definition = definition.to_sql if definition.respond_to? :to_sql execute "DROP VIEW IF EXISTS #{quote_table_name(view_name)}" if options[:force] - execute "CREATE VIEW #{quote_table_name(view_name)} AS #{definition}" + execute "CREATE #{options[:create_options]} VIEW #{quote_table_name(view_name)} AS #{definition}" end # Drop the named view From b570af4ad02613664ed97a7a1271e539a5869c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=A4=C3=A4rnhielm?= Date: Mon, 5 May 2014 14:42:48 +0200 Subject: [PATCH 2/4] Correct support for postgres materialized views --- .../connection_adapters/abstract_adapter.rb | 2 +- .../connection_adapters/mysql_adapter.rb | 4 ++++ .../connection_adapters/postgresql_adapter.rb | 24 +++++++++++++++++-- .../connection_adapters/sqlite3_adapter.rb | 6 ++++- .../active_record/schema_dumper.rb | 4 ++-- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb b/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb index d648844..7c9c43c 100644 --- a/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb @@ -48,7 +48,7 @@ def initialize_with_schema_plus(*args) #:nodoc: def create_view(view_name, definition, options={}) definition = definition.to_sql if definition.respond_to? :to_sql execute "DROP VIEW IF EXISTS #{quote_table_name(view_name)}" if options[:force] - execute "CREATE #{options[:create_options]} VIEW #{quote_table_name(view_name)} AS #{definition}" + execute "CREATE #{options[:create_options] || ''} VIEW #{quote_table_name(view_name)} AS #{definition}" end # Drop the named view diff --git a/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb b/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb index 14298ba..a26d36c 100644 --- a/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb @@ -171,6 +171,10 @@ def view_definition(view_name, name = nil) sql end + def view_options(view_name) + return "" + end + module AddColumnOptions def default_expr_valid?(expr) false # only the TIMESTAMP column accepts SQL column defaults and rails uses DATETIME diff --git a/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb b/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb index c727363..d05c849 100644 --- a/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb @@ -266,16 +266,36 @@ def views(name = nil) #:nodoc: FROM pg_views WHERE schemaname = ANY (current_schemas(false)) AND viewname NOT LIKE 'pg\_%' + UNION + SELECT matviewname + FROM pg_matviews + WHERE schemaname = ANY (current_schemas(false)) + AND matviewname NOT LIKE 'pg\_%' SQL sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS' query(sql, name).map { |row| row[0] } end + + def view_options(view_name, name = nil) #:nodoc: + sql = <<-SQL + SELECT relkind + FROM pg_class + WHERE relname = '#{view_name}' + AND relkind IN ('v', 'm') + SQL + result = query(sql, name) + if result[0][0].eql?('m') + return ":create_options => 'MATERIALIZED'" + else + return "" + end + end def view_definition(view_name, name = nil) #:nodoc: result = query(<<-SQL, name) SELECT pg_get_viewdef(oid) FROM pg_class - WHERE relkind = 'v' + WHERE relkind IN ('v', 'm') AND relname = '#{view_name}' SQL row = result.first @@ -283,7 +303,7 @@ def view_definition(view_name, name = nil) #:nodoc: end private - + def namespace_sql(table_name) (table_name.to_s =~ /(.*)[.]/) ? "'#{$1}'" : "ANY (current_schemas(false))" end diff --git a/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb b/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb index 054b05c..2d7243d 100644 --- a/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb @@ -73,12 +73,16 @@ def reverse_foreign_keys(table_name, name = nil) def views(name = nil) execute("SELECT name FROM sqlite_master WHERE type='view'", name).collect{|row| row["name"]} end - + def view_definition(view_name, name = nil) sql = execute("SELECT sql FROM sqlite_master WHERE type='view' AND name=#{quote(view_name)}", name).collect{|row| row["sql"]}.first sql.sub(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil? end + def view_options(view_name) + return "" + end + protected def get_foreign_keys(table_name = nil, name = nil) diff --git a/lib/schema_plus/active_record/schema_dumper.rb b/lib/schema_plus/active_record/schema_dumper.rb index 64a6002..a99ed82 100644 --- a/lib/schema_plus/active_record/schema_dumper.rb +++ b/lib/schema_plus/active_record/schema_dumper.rb @@ -50,11 +50,11 @@ def tables_with_schema_plus(stream) #:nodoc: @dump_dependencies = {} tables_without_schema_plus(nil) - + @connection.views.each do |view_name| next if Array.wrap(::ActiveRecord::SchemaDumper.ignore_tables).any? {|pattern| view_name.match pattern} definition = @connection.view_definition(view_name) - @table_dumps[view_name] = " create_view #{view_name.inspect}, #{definition.inspect}, :force => true\n" + @table_dumps[view_name] = " create_view #{view_name.inspect}, #{definition.inspect}, :force => true, #{@connection.view_options(view_name)}\n" end re_view_referent = %r{(?:(?i)FROM|JOIN) \S*\b(#{(@table_dumps.keys).join('|')})\b} From 0a07ef8ddd4508df626606cf94707ca9961e18be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=A4=C3=A4rnhielm?= Date: Wed, 7 May 2014 13:44:18 +0200 Subject: [PATCH 3/4] Nicer encapsulation of create options --- .../active_record/connection_adapters/abstract_adapter.rb | 4 ++-- .../active_record/connection_adapters/mysql_adapter.rb | 2 +- .../active_record/connection_adapters/postgresql_adapter.rb | 6 ++++-- .../active_record/connection_adapters/sqlite3_adapter.rb | 2 +- lib/schema_plus/active_record/schema_dumper.rb | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb b/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb index 7c9c43c..1979252 100644 --- a/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb @@ -47,13 +47,13 @@ def initialize_with_schema_plus(*args) #:nodoc: # to first drop the view if it already exists. def create_view(view_name, definition, options={}) definition = definition.to_sql if definition.respond_to? :to_sql - execute "DROP VIEW IF EXISTS #{quote_table_name(view_name)}" if options[:force] + execute "DROP #{options[:create_options] || ''} VIEW IF EXISTS #{quote_table_name(view_name)}" if options[:force] execute "CREATE #{options[:create_options] || ''} VIEW #{quote_table_name(view_name)} AS #{definition}" end # Drop the named view def drop_view(view_name) - execute "DROP VIEW #{quote_table_name(view_name)}" + execute "DROP #{view_create_options(view_name)} VIEW #{quote_table_name(view_name)}" end diff --git a/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb b/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb index a26d36c..01367d3 100644 --- a/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb @@ -171,7 +171,7 @@ def view_definition(view_name, name = nil) sql end - def view_options(view_name) + def view_create_options(view_name) return "" end diff --git a/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb b/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb index d05c849..7cdc038 100644 --- a/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb @@ -261,6 +261,8 @@ def reverse_foreign_keys(table_name, name = nil) #:nodoc: end def views(name = nil) #:nodoc: + # This will not work if there are views and materialized views + # with the same name. sql = <<-SQL SELECT viewname FROM pg_views @@ -276,7 +278,7 @@ def views(name = nil) #:nodoc: query(sql, name).map { |row| row[0] } end - def view_options(view_name, name = nil) #:nodoc: + def view_create_options(view_name, name = nil) #:nodoc: sql = <<-SQL SELECT relkind FROM pg_class @@ -285,7 +287,7 @@ def view_options(view_name, name = nil) #:nodoc: SQL result = query(sql, name) if result[0][0].eql?('m') - return ":create_options => 'MATERIALIZED'" + return "MATERIALIZED" else return "" end diff --git a/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb b/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb index 2d7243d..00721c5 100644 --- a/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +++ b/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb @@ -79,7 +79,7 @@ def view_definition(view_name, name = nil) sql.sub(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil? end - def view_options(view_name) + def view_create_options(view_name) return "" end diff --git a/lib/schema_plus/active_record/schema_dumper.rb b/lib/schema_plus/active_record/schema_dumper.rb index a99ed82..00f1596 100644 --- a/lib/schema_plus/active_record/schema_dumper.rb +++ b/lib/schema_plus/active_record/schema_dumper.rb @@ -50,11 +50,11 @@ def tables_with_schema_plus(stream) #:nodoc: @dump_dependencies = {} tables_without_schema_plus(nil) - + @connection.views.each do |view_name| next if Array.wrap(::ActiveRecord::SchemaDumper.ignore_tables).any? {|pattern| view_name.match pattern} definition = @connection.view_definition(view_name) - @table_dumps[view_name] = " create_view #{view_name.inspect}, #{definition.inspect}, :force => true, #{@connection.view_options(view_name)}\n" + @table_dumps[view_name] = " create_view #{view_name.inspect}, #{definition.inspect}, :force => true, :create_options => '#{@connection.view_create_options(view_name)}'\n" end re_view_referent = %r{(?:(?i)FROM|JOIN) \S*\b(#{(@table_dumps.keys).join('|')})\b} From 1e398b0b251e2778ef2af34117f690af51996085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=A4=C3=A4rnhielm?= Date: Wed, 7 May 2014 13:46:13 +0200 Subject: [PATCH 4/4] Support views with indices --- lib/schema_plus/active_record/schema_dumper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/schema_plus/active_record/schema_dumper.rb b/lib/schema_plus/active_record/schema_dumper.rb index 00f1596..3d71acd 100644 --- a/lib/schema_plus/active_record/schema_dumper.rb +++ b/lib/schema_plus/active_record/schema_dumper.rb @@ -89,6 +89,10 @@ def tables_with_schema_plus(stream) #:nodoc: stream.puts dump_foreign_keys(@backref_fks[table], :inline => false)+"\n" if @backref_fks[table].any? end + @connection.views.each do |view_name| + next if Array.wrap(::ActiveRecord::SchemaDumper.ignore_tables).any? {|pattern| view_name.match pattern} + indexes_without_schema_plus(view_name, stream) + end end def tsort_each_node(&block) #:nodoc: