aspgems-redhillonrails_core 2.0.0.beta2 → 2.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +8 -0
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG +16 -0
  4. data/README.md +50 -10
  5. data/README_DEV.md +41 -0
  6. data/Rakefile +43 -13
  7. data/Thorfile +45 -0
  8. data/gemfiles/rails-3.0.7 +13 -0
  9. data/gemfiles/rails-3.0.7.lock +90 -0
  10. data/gemfiles/rails-3.0.8 +13 -0
  11. data/gemfiles/rails-3.0.8.lock +90 -0
  12. data/gemfiles/rails-3.0.9 +13 -0
  13. data/gemfiles/rails-3.0.9.lock +92 -0
  14. data/gemfiles/rails-3.1.0.rc5 +13 -0
  15. data/gemfiles/rails-3.1.0.rc5.lock +106 -0
  16. data/lib/redhillonrails_core.rb +12 -0
  17. data/lib/redhillonrails_core/active_record/base.rb +1 -1
  18. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/foreign_key_definition.rb +13 -6
  19. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/index_definition.rb +3 -0
  20. data/lib/redhillonrails_core/active_record/connection_adapters/abstract_adapter.rb +10 -6
  21. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_adapter.rb +27 -53
  22. data/lib/redhillonrails_core/active_record/connection_adapters/postgresql_adapter.rb +82 -57
  23. data/lib/redhillonrails_core/active_record/migration/command_recorder.rb +30 -0
  24. data/lib/redhillonrails_core/active_record/schema_dumper.rb +18 -6
  25. data/lib/redhillonrails_core/version.rb +1 -1
  26. data/redhillonrails_core.gemspec +2 -1
  27. data/spec/command_recorder_spec.rb +39 -0
  28. data/spec/connections/mysql/connection.rb +4 -6
  29. data/spec/connections/mysql2/connection.rb +4 -6
  30. data/spec/connections/postgresql/connection.rb +4 -3
  31. data/spec/connections/sqlite3/connection.rb +4 -3
  32. data/spec/foreign_key_definition_spec.rb +31 -2
  33. data/spec/foreign_key_spec.rb +15 -2
  34. data/spec/migration_spec.rb +34 -0
  35. data/spec/table_definition_spec.rb +28 -0
  36. metadata +30 -11
@@ -3,7 +3,7 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
3
3
  def self.included(base)
4
4
  base.class_eval do
5
5
  alias_method_chain :remove_column, :redhillonrails_core
6
- alias_method_chain :remove_columns, :redhillonrails_core
6
+ alias_method_chain :structure_dump, :views
7
7
  end
8
8
  end
9
9
 
@@ -16,7 +16,7 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
16
16
  end
17
17
 
18
18
  def remove_foreign_key(table_name, foreign_key_name)
19
- execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{foreign_key_name}"
19
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{foreign_key_name}"
20
20
  end
21
21
 
22
22
  def remove_column_with_redhillonrails_core(table_name, *column_names)
@@ -28,7 +28,6 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
28
28
  remove_column_without_redhillonrails_core(table_name, column_name)
29
29
  end
30
30
  end
31
- alias :remove_columns_with_redhillonrails_core :remove_column_with_redhillonrails_core
32
31
 
33
32
  def foreign_keys(table_name, name = nil)
34
33
  results = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}", name)
@@ -58,64 +57,19 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
58
57
  foreign_keys
59
58
  end
60
59
 
61
- # def reverse_foreign_keys(table_name, name = nil)
62
- # results = execute(<<-SQL, name)
63
- # SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
64
- # FROM information_schema.key_column_usage
65
- # WHERE table_schema = SCHEMA()
66
- # AND referenced_table_schema = table_schema
67
- # ORDER BY constraint_name, ordinal_position;
68
- # SQL
69
- # current_foreign_key = nil
70
- # foreign_keys = []
71
- #
72
- # results.each do |row|
73
- # next unless table_name.casecmp(row[3]) == 0
74
- # if current_foreign_key != row[0]
75
- # foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
76
- # current_foreign_key = row[0]
77
- # end
78
- #
79
- # foreign_keys.last.column_names << row[2]
80
- # foreign_keys.last.references_column_names << row[4]
81
- # end
82
- #
83
- # foreign_keys
84
- # end
85
-
86
60
  def reverse_foreign_keys(table_name, name = nil)
87
- # @@schema ||= nil
88
- # @@schema_version ||= 0
89
- # current_version = ActiveRecord::Migrator.current_version
90
- # if @@schema.nil? || @@schema_version != current_version
91
- # @@schema_version = current_version
92
- # ans = execute(<<-SQL, name)
93
- # SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
94
- # FROM information_schema.key_column_usage
95
- # WHERE table_schema = SCHEMA()
96
- # AND referenced_table_schema = table_schema
97
- # ORDER BY constraint_name, ordinal_position;
98
- # SQL
99
- # @@schema = []
100
- # ans.each do | row |
101
- # @@schema << [row[0], row[1], row[2], row[3], row[4]]
102
- # end
103
- # end
104
- # results = @@schema
105
-
106
61
  results = execute(<<-SQL, name)
107
62
  SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
108
- FROM information_schema.key_column_usage
109
- WHERE table_schema = SCHEMA()
110
- AND referenced_table_schema = table_schema
111
- ORDER BY constraint_name, ordinal_position;
63
+ FROM information_schema.key_column_usage
64
+ WHERE table_schema = SCHEMA()
65
+ AND referenced_table_schema = table_schema
66
+ ORDER BY constraint_name, ordinal_position;
112
67
  SQL
113
-
114
68
  current_foreign_key = nil
115
69
  foreign_keys = []
116
70
 
117
71
  results.each do |row|
118
- next if row[3] != table_name
72
+ next unless table_name.to_s.casecmp(row[3].to_s) == 0
119
73
  if current_foreign_key != row[0]
120
74
  foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
121
75
  current_foreign_key = row[0]
@@ -127,5 +81,25 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
127
81
 
128
82
  foreign_keys
129
83
  end
84
+
85
+ def views(name = nil)
86
+ views = []
87
+ execute("SHOW FULL TABLES WHERE TABLE_TYPE='VIEW'", name).each { |row| views << row[0] }
88
+ views
89
+ end
90
+
91
+ def view_definition(view_name, name = nil)
92
+ select_one("SHOW CREATE VIEW #{quote_table_name(view_name)}", name)["Create View"]
93
+ end
94
+
95
+ def structure_dump_with_views
96
+ structure = structure_dump_without_views
97
+
98
+ views.each do |view|
99
+ structure += view_definition(view) + ";\n\n"
100
+ end
101
+
102
+ structure
103
+ end
130
104
  end
131
105
  end
@@ -2,61 +2,75 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
2
  module PostgresqlAdapter
3
3
  def self.included(base)
4
4
  base.class_eval do
5
- alias_method_chain :indexes, :redhillonrails_core
5
+ remove_method :indexes
6
6
  end
7
7
  end
8
8
 
9
- def set_table_comment(table_name, comment)
10
- execute "COMMENT ON TABLE #{table_name} IS '#{quote_string(comment)}'"
11
- end
12
-
13
- def clear_table_comment(table_name)
14
- execute "COMMENT ON TABLE #{table_name} IS NULL"
15
- end
16
-
17
9
  def add_index(table_name, column_name, options = {})
10
+ column_name, options = [], column_name if column_name.is_a?(Hash)
18
11
  column_names = Array(column_name)
19
- index_name = index_name(table_name, :column => column_names)
12
+ if column_names.empty?
13
+ raise ArgumentError, "No columns and :expression missing from options - cannot create index" if options[:expression].blank?
14
+ raise ArgumentError, "Index name not given. Pass :name option" if options[:name].blank?
15
+ end
20
16
 
21
- if Hash === options # legacy support, since this param was a string
22
- index_type = options[:unique] ? "UNIQUE" : ""
23
- index_name = options[:name] || index_name
17
+ index_type = options[:unique] ? "UNIQUE" : ""
18
+ index_name = options[:name] || index_name(table_name, column_names)
19
+ conditions = options[:conditions]
20
+
21
+ if column_names.empty? then
22
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{options[:expression]}"
24
23
  else
25
- index_type = options
26
- end
24
+ quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
27
25
 
28
- quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
26
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names.join(", ")})"
27
+ sql += " WHERE (#{ ::ActiveRecord::Base.send(:sanitize_sql, conditions, quote_table_name(table_name)) })" if conditions
28
+ end
29
+ execute sql
30
+ end
29
31
 
30
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names.join(", ")})"
32
+ def supports_partial_indexes?
33
+ true
31
34
  end
32
35
 
33
- def indexes_with_redhillonrails_core(table_name, name = nil)
34
- indexes = indexes_without_redhillonrails_core(table_name, name)
36
+ def indexes(table_name, name = nil)
37
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
35
38
  result = query(<<-SQL, name)
36
- SELECT c2.relname, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)
37
- FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
38
- WHERE c.relname = '#{table_name}'
39
- AND c.oid = i.indrelid AND i.indexrelid = c2.oid
40
- AND i.indisprimary = 'f'
41
- AND i.indexprs IS NOT NULL
42
- ORDER BY 1
39
+ SELECT distinct i.relname, d.indisunique, d.indkey, m.amname, t.oid,
40
+ pg_get_expr(d.indpred, t.oid), pg_get_expr(d.indexprs, t.oid)
41
+ FROM pg_class t, pg_class i, pg_index d, pg_am m
42
+ WHERE i.relkind = 'i'
43
+ AND i.relam = m.oid
44
+ AND d.indexrelid = i.oid
45
+ AND d.indisprimary = 'f'
46
+ AND t.oid = d.indrelid
47
+ AND t.relname = '#{table_name}'
48
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
49
+ ORDER BY i.relname
43
50
  SQL
44
51
 
45
- result.each do |row|
46
- if row[2]=~ /\((.*LOWER\([^:]+(::text)?\).*)\)$/i
47
- indexes.delete_if { |index| index.name == row[0] }
48
- column_names = $1.split(", ").map do |name|
49
- name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
50
- name = $1 if name =~ /^"(.*)"$/
51
- name
52
- end
53
- index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", column_names)
54
- index.case_sensitive = false
55
- indexes << index
52
+ result.map do |(index_name, is_unique, indkey, kind, oid, conditions, expression)|
53
+ unique = (is_unique == 't')
54
+ index_keys = indkey.split(" ")
55
+
56
+ columns = Hash[query(<<-SQL, "Columns for index #{index_name} on #{table_name}")]
57
+ SELECT a.attnum, a.attname
58
+ FROM pg_attribute a
59
+ WHERE a.attrelid = #{oid}
60
+ AND a.attnum IN (#{index_keys.join(",")})
61
+ SQL
62
+
63
+ column_names = columns.values_at(*index_keys).compact
64
+ if md = expression.try(:match, /^lower\(\(?([^)]+)\)?(::text)?\)$/i)
65
+ column_names << md[1]
56
66
  end
67
+ index = ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names)
68
+ index.conditions = conditions
69
+ index.case_sensitive = !(expression =~ /lower/i)
70
+ index.kind = kind unless kind.downcase == "btree"
71
+ index.expression = expression
72
+ index
57
73
  end
58
-
59
- indexes
60
74
  end
61
75
 
62
76
  def foreign_keys(table_name, name = nil)
@@ -88,7 +102,7 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
88
102
  WHERE schemaname IN (#{schemas})
89
103
  SQL
90
104
  end
91
-
105
+
92
106
  def view_definition(view_name, name = nil)
93
107
  result = query(<<-SQL, name)
94
108
  SELECT pg_get_viewdef(oid)
@@ -106,26 +120,37 @@ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
106
120
  foreign_keys = []
107
121
 
108
122
  query(sql, name).each do |row|
109
- if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE))?$/
110
- name = row[0]
111
- from_table_name = row[2]
112
- column_names = $1
113
- references_table_name = $2
114
- references_column_names = $3
115
- on_update = $5
116
- on_delete = $7
117
- deferrable = $9 == "DEFERRABLE"
118
- on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
119
- on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
120
-
121
- foreign_keys << ForeignKeyDefinition.new(name,
122
- from_table_name, column_names.split(', '),
123
- references_table_name.sub(/^"(.*)"$/, '\1'), references_column_names.split(', '),
124
- on_update, on_delete, deferrable)
125
- end
123
+ if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE))?$/
124
+ name = row[0]
125
+ from_table_name = row[2]
126
+ column_names = $1
127
+ references_table_name = $2
128
+ references_column_names = $3
129
+ on_update = $5
130
+ on_delete = $7
131
+ deferrable = $9 == "DEFERRABLE"
132
+ on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
133
+ on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
134
+
135
+ foreign_keys << ForeignKeyDefinition.new(name,
136
+ from_table_name, column_names.split(', '),
137
+ references_table_name.sub(/^"(.*)"$/, '\1'), references_column_names.split(', '),
138
+ on_update, on_delete, deferrable)
139
+ end
126
140
  end
127
141
 
128
142
  foreign_keys
129
143
  end
144
+
145
+ # Converts form like: column1, LOWER(column2)
146
+ # to: column1, column2
147
+ def determine_index_column_names(column_definitions)
148
+ column_definitions.split(", ").map do |name|
149
+ name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
150
+ name = $1 if name =~ /^"(.*)"$/
151
+ name
152
+ end
153
+ end
154
+
130
155
  end
131
156
  end
@@ -0,0 +1,30 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module Migration
4
+ module CommandRecorder
5
+ def add_foreign_key(*args)
6
+ record(:add_foreign_key, args)
7
+ end
8
+
9
+ def remove_foreign_key(*args)
10
+ record(:remove_foreign_key, args)
11
+ end
12
+
13
+ def invert_add_foreign_key(args)
14
+ from_table, to_table, add_options = *args
15
+ add_options ||= {}
16
+
17
+ if add_options[:name]
18
+ options = {:name => add_options[:name]}
19
+ elsif add_options[:column]
20
+ options = {:column => add_options[:column]}
21
+ else
22
+ options = to_table
23
+ end
24
+
25
+ [:remove_foreign_key, [from_table, options]]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -25,12 +25,24 @@ module RedhillonrailsCore::ActiveRecord
25
25
  def indexes_with_redhillonrails_core(table, stream)
26
26
  if (indexes = @connection.indexes(table)).any?
27
27
  add_index_statements = indexes.map do |index|
28
- statement_parts = [('add_index ' + index.table.inspect)]
29
- statement_parts << index.columns.inspect
30
- statement_parts << (':name => ' + index.name.inspect)
31
- statement_parts << ':unique => true' if index.unique
32
- # This only used in postgresql
33
- statement_parts << ':case_sensitive => false' unless index.case_sensitive?
28
+
29
+ if index.columns.any?
30
+ statement_parts = [('add_index ' + index.table.inspect)]
31
+ statement_parts << index.columns.inspect
32
+ statement_parts << (':name => ' + index.name.inspect)
33
+ statement_parts << ':unique => true' if index.unique
34
+ # This only used in postgresql - :case_sensitive, :conditions, :kind, :expression
35
+ statement_parts << ':case_sensitive => false' unless index.case_sensitive?
36
+ statement_parts << ':conditions => ' + index.conditions.inspect unless index.conditions.blank?
37
+ statement_parts << ':kind => ' + index.kind.inspect unless index.kind.blank?
38
+ statement_parts << ':expression => ' + index.expression.inspect unless index.expression.blank?
39
+ else
40
+ # This only used in postgresql - :case_sensitive, :conditions, :kind, :expression
41
+ statement_parts = [('add_index ' + index.table.inspect)]
42
+ statement_parts << (':name => ' + index.name.inspect)
43
+ statement_parts << ':kind => ' + index.kind.inspect unless index.kind.blank?
44
+ statement_parts << ':expression => ' + index.expression.inspect unless index.expression.blank?
45
+ end
34
46
 
35
47
  if index.respond_to?(:lengths)
36
48
  index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
@@ -1,3 +1,3 @@
1
1
  module RedhillonrailsCore
2
- VERSION = "2.0.0.beta2"
2
+ VERSION = "2.0.0.beta4"
3
3
  end
@@ -20,7 +20,8 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency("activerecord", ">= 2")
21
21
  s.add_dependency("activesupport", ">= 2")
22
22
 
23
- s.add_development_dependency("rspec", "~> 2.5.0")
23
+ # Development dependencies through thorfile
24
+ s.add_development_dependency("rspec", "~> 2.6.0")
24
25
  s.add_development_dependency("pg")
25
26
  s.add_development_dependency("mysql")
26
27
  s.add_development_dependency("mysql2")
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe 'Command Recorder' do
4
+ if defined?(ActiveRecord::Migration::CommandRecorder)
5
+ before do
6
+ @recorder = ActiveRecord::Migration::CommandRecorder.new
7
+ end
8
+
9
+ it 'invert_add_foreign_key' do
10
+ @recorder.add_foreign_key(:employees, :companies)
11
+ remove = @recorder.inverse.first
12
+ remove.should == [:remove_foreign_key, [:employees, :companies]]
13
+ end
14
+
15
+ it 'invert_add_foreign_key with column' do
16
+ @recorder.add_foreign_key(:employees, :companies, :column => :place_id)
17
+ remove = @recorder.inverse.first
18
+ remove.should == [:remove_foreign_key, [:employees, {:column => :place_id}]]
19
+ end
20
+
21
+ it 'invert_add_foreign_key with name' do
22
+ @recorder.add_foreign_key(:employees, :companies, :name => 'the_best_fk', :column => :place_id)
23
+ remove = @recorder.inverse.first
24
+ remove.should == [:remove_foreign_key, [:employees, {:name => 'the_best_fk'}]]
25
+
26
+ @recorder.record :rename_table, [:old, :new]
27
+ rename = @recorder.inverse.first
28
+ rename.should == [:rename_table, [:new, :old]]
29
+ end
30
+
31
+ it 'remove_foreign_key is irreversible' do
32
+ @recorder.remove_foreign_key(:employees, :companies)
33
+ expect {
34
+ @recorder.inverse
35
+ }.to raise_error(ActiveRecord::IrreversibleMigration)
36
+ end
37
+ end
38
+
39
+ end
@@ -4,15 +4,13 @@ require 'logger'
4
4
  ActiveRecord::Base.logger = Logger.new("debug.log")
5
5
 
6
6
  ActiveRecord::Base.configurations = {
7
- 'redhillonrails' => {
7
+ 'mysql' => {
8
8
  :adapter => 'mysql',
9
- :database => 'rh_core_unittest',
10
- :username => 'rh_core',
9
+ :database => 'redhillonrails_core',
10
+ :username => (ENV["TRAVIS"] ? '' : 'redhillonrails'),
11
11
  :encoding => 'utf8',
12
- :socket => '/var/run/mysqld/mysqld.sock',
13
- :min_messages => 'warning'
14
12
  }
15
13
 
16
14
  }
17
15
 
18
- ActiveRecord::Base.establish_connection 'redhillonrails'
16
+ ActiveRecord::Base.establish_connection 'mysql'
@@ -4,15 +4,13 @@ require 'logger'
4
4
  ActiveRecord::Base.logger = Logger.new("debug.log")
5
5
 
6
6
  ActiveRecord::Base.configurations = {
7
- 'redhillonrails' => {
7
+ 'mysql2' => {
8
8
  :adapter => 'mysql2',
9
- :database => 'rh_core_unittest',
10
- :username => 'rh_core',
9
+ :database => 'redhillonrails_core',
10
+ :username => (ENV["TRAVIS"] ? '' : 'redhillonrails'),
11
11
  :encoding => 'utf8',
12
- :socket => '/var/run/mysqld/mysqld.sock',
13
- :min_messages => 'warning'
14
12
  }
15
13
 
16
14
  }
17
15
 
18
- ActiveRecord::Base.establish_connection 'redhillonrails'
16
+ ActiveRecord::Base.establish_connection 'mysql2'