drysql 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,8 +21,7 @@ module ActiveRecord
21
21
  logger.info("DRYSQL >> Cleared cached schema for: #{self}")
22
22
  end
23
23
 
24
-
25
-
24
+
26
25
  private
27
26
 
28
27
  # A belongs_to association exists from class A to class B if A contains a foreign key into B
@@ -30,8 +29,8 @@ module ActiveRecord
30
29
  foreign_keys = table_constraints.select {|constraint| constraint.foreign_key?}
31
30
  foreign_keys.each do |foreign_key|
32
31
  belongs_to_class_name = association_class_name_from_table_name(class_name(foreign_key.referenced_table_name))
33
- self.send(:belongs_to, :"#{belongs_to_class_name}", :foreign_key=>foreign_key.column_name.to_a[0])
34
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} belongs_to :#{belongs_to_class_name}, :foreign_key=>#{foreign_key.column_name.to_a[0]}")
32
+ self.send(:belongs_to, :"#{belongs_to_class_name}", :foreign_key=>foreign_key.column_names[0])
33
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} belongs_to :#{belongs_to_class_name}, :foreign_key=>#{foreign_key.column_names[0]}")
35
34
  end
36
35
  end
37
36
 
@@ -41,12 +40,12 @@ module ActiveRecord
41
40
  foreign_constraints.each do |foreign_constraint|
42
41
  if foreign_constraint_column_is_unique?(foreign_constraint)
43
42
  has_one_class_name = association_class_name_from_table_name(class_name(foreign_constraint.table_name))
44
- self.send(:has_one, :"#{has_one_class_name}", :foreign_key=>foreign_constraint.column_name.to_a[0])
45
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_one :#{has_one_class_name}, :foreign_key=>#{foreign_constraint.column_name.to_a[0]}")
43
+ self.send(:has_one, :"#{has_one_class_name}", :foreign_key=>foreign_constraint.column_names[0])
44
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_one :#{has_one_class_name}, :foreign_key=>#{foreign_constraint.column_names[0]}")
46
45
  else
47
46
  has_many_class_name = association_class_name_from_table_name(class_name(foreign_constraint.table_name)).pluralize
48
- self.send(:has_many, :"#{has_many_class_name}", :foreign_key=>foreign_constraint.column_name.to_a[0])
49
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :foreign_key=>#{foreign_constraint.column_name.to_a[0]}")
47
+ self.send(:has_many, :"#{has_many_class_name}", :foreign_key=>foreign_constraint.column_names[0])
48
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :foreign_key=>#{foreign_constraint.column_names[0]}")
50
49
  end
51
50
  end
52
51
  end
@@ -56,8 +55,8 @@ module ActiveRecord
56
55
  klass = instance_eval(class_name)
57
56
  # FIXME Is there a more efficient way of doing this?
58
57
  # The uniqueness check requires that all constraints for each table that references the current table must be retrieved
59
- constraints_on_given_column = klass.table_constraints.select {|current| current.column_name.include?(constraint.column_name.to_a[0])}
60
- constraints_on_given_column.any? {|current| current.unique_key?}
58
+ unique_constraints = klass.table_constraints.select {|current| current.unique_key?}
59
+ unique_constraints.any? {|current| current.column_names.size == constraint.column_names.size && current.column_names.all? {|col| constraint.column_names.include?(col)}}
61
60
  end
62
61
 
63
62
  # Identifying has_many :through associations:
@@ -78,12 +77,21 @@ module ActiveRecord
78
77
  has_many_associations.each do |association|
79
78
  # FIXME the singularization is a hack. We should be able to figure the exact class name without relying
80
79
  # on naming conventions
81
- foreign_class = instance_eval(association[0].to_s.camelize.singularize)
82
- eligible_foreign_keys = foreign_class.table_constraints.select {|c| c.foreign_key? && c.referenced_table_name.upcase != table_name.upcase}
83
- eligible_foreign_keys.select {|key| !is_associated_with(association_class_name_from_table_name(key.referenced_table_name))}.each do |fk|
84
- has_many_class_name = association_class_name_from_table_name(class_name(fk.referenced_table_name)).pluralize
85
- self.send(:has_many, :"#{has_many_class_name}", :through=>association[0])
86
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :through=>:#{association[0]}")
80
+ foreign_class = instance_eval(association[0].to_s.camelize.singularize)
81
+ eligible_foreign_keys = foreign_class.constraints.select {|c| c.foreign_key? && c.referenced_table_name.upcase != table_name.upcase && c.table_name.upcase != table_name.upcase}
82
+ eligible_foreign_keys.each do |key|
83
+ if foreign_class.table_name == key.table_name
84
+ # outgoing FK
85
+ association_table_name = key.referenced_table_name
86
+ else
87
+ #incoming FK
88
+ association_table_name = key.table_name
89
+ end
90
+ if !is_associated_with(association_class_name_from_table_name(association_table_name))
91
+ has_many_class_name = association_class_name_from_table_name(class_name(association_table_name)).pluralize
92
+ self.send(:has_many, :"#{has_many_class_name}", :through=>association[0])
93
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :through=>:#{association[0]}")
94
+ end
87
95
  end
88
96
  end
89
97
  end
@@ -30,6 +30,11 @@ module ActiveRecord
30
30
  def logger
31
31
  @@logger
32
32
  end
33
+
34
+ def set_drysql_log_output(file)
35
+ @@logger = Logger.new(file)
36
+ @@logger.level = Logger::INFO
37
+ end
33
38
 
34
39
  # Save table name keys as all UPPERCASE to simplify lookup
35
40
  alias :base_set_table_name :set_table_name
@@ -51,7 +56,7 @@ module ActiveRecord
51
56
  if primary.nil?
52
57
  logger.error("DRYSQL >> No primary key defined for table #{table_name}")
53
58
  else
54
- primary_name = primary.column_name.to_a[0]
59
+ primary_name = primary.column_names[0]
55
60
  set_primary_key(primary_name)
56
61
  logger.info("DRYSQL >> Identified PRIMARY KEY for #{self}: #{primary_name}")
57
62
  end
@@ -110,7 +115,7 @@ module ActiveRecord
110
115
  @constraints = nil
111
116
  @columns = nil
112
117
  primary = table_constraints.detect {|constraint| constraint.primary_key?}
113
- primary_name = primary.column_name
118
+ primary_name = primary.column_names[0]
114
119
  set_primary_key(primary_name)
115
120
  logger.info("DRYSQL >> Reset PRIMARY KEY for #{self}: #{primary_name}")
116
121
  end
@@ -139,17 +144,16 @@ module ActiveRecord
139
144
  end
140
145
  end
141
146
  end
142
-
143
-
144
- private
147
+
145
148
 
146
- def constraints
147
- unless @constraints
148
- @constraints = connection.constraints(table_name, "#{name} Constraints")
149
- end
150
- @constraints
149
+ def constraints
150
+ unless @constraints
151
+ @constraints = connection.constraints(table_name, "#{name} Constraints")
151
152
  end
153
+ @constraints
154
+ end
152
155
 
156
+ private
153
157
  # FIXME MacroReflection needs an instance method has_many?
154
158
  # This logic does not belong in Base
155
159
  def has_many_associations
@@ -167,13 +171,13 @@ module ActiveRecord
167
171
  public
168
172
  alias :base_initialize :initialize
169
173
  def initialize(attributes = nil)
170
- base_initialize
174
+ base_initialize(attributes)
171
175
  self.class.generate_associations
172
176
  self.class.generate_validations
173
177
  end
174
178
 
175
179
  def constraints
176
- self.class.table_constraints
180
+ self.class.constraints
177
181
  end
178
182
 
179
183
 
@@ -3,17 +3,12 @@ module ActiveRecord
3
3
 
4
4
  class Column
5
5
 
6
- def generated?
6
+ # This method should return true if the DB will accept a null value for this column.
7
+ # Note that this does not equate to a NOT NULL constraint, as the DB will still
8
+ # accept null values for columns with NOT NULL constraints under certain conditions
9
+ def is_nullable?
7
10
  raise NotImplementedError, "Column subclass did not implement this method"
8
- end
9
-
10
- # MySQL sinks the boat on this one.
11
- # If no default is specified for a non-nullable column, then MySQL assigns an implicit default value.
12
- # This makes it impossible to determine whether or not a default value has been specified for any
13
- # non-nullable column without parsing the SHOW CREATE TABLE output, which is non-standard.
14
- def default_specified?
15
- raise NotImplementedError, "Column subclass did not implement this method"
16
- end
11
+ end
17
12
 
18
13
  end
19
14
 
@@ -11,10 +11,10 @@ module ActiveRecord
11
11
  UNIQUE_KEY_TYPE = "UNIQUE"
12
12
  CHECK_CONSTRAINT_TYPE = "CHECK"
13
13
 
14
- attr_reader :constraint_name, :constraint_type, :table_schema, :table_name, :column_name,
15
- :referenced_table_name, :referenced_column_name
14
+ attr_reader :constraint_name, :constraint_type, :table_schema, :table_name, :referenced_table_name,
15
+ :column_names, # An array is used to preserve the order of the elements (A set will sort them alphabetically)
16
+ :referenced_column_names # An array
16
17
 
17
- attr_writer :member_of_composite
18
18
 
19
19
  def raise_subclass_responsibility_error
20
20
  raise NotImplementedError, "AbstractTableConstraint subclass #{self.class} did not implement this method"
@@ -30,17 +30,9 @@ module ActiveRecord
30
30
  def foreign_key?
31
31
  constraint_type == FOREIGN_KEY_TYPE
32
32
  end
33
-
34
- def component_of_unique_key?
35
- constraint_type == UNIQUE_KEY_TYPE
36
- end
37
33
 
38
34
  def unique_key?
39
- component_of_unique_key? and !is_member_of_composite?
40
- end
41
-
42
- def is_member_of_composite?
43
- @member_of_composite ? @member_of_composite : false
35
+ constraint_type == UNIQUE_KEY_TYPE
44
36
  end
45
37
 
46
38
  end
@@ -0,0 +1,216 @@
1
+ module ActiveRecord
2
+
3
+ module ConnectionAdapters
4
+
5
+ class IBM_DBColumn < Column
6
+
7
+ # This method will return true if the database will accept a null value for this column on insert/update.
8
+ # Note that even though the column may have a NOT NULL constraint, the DB will accept a null value if
9
+ # a default value is specified
10
+ def is_nullable?
11
+ @null || !default.nil?
12
+ end
13
+
14
+ end
15
+
16
+
17
+ class IBM_DBConstraint < AbstractTableConstraint
18
+
19
+ attr_reader :update_rule, :delete_rule, :referenced_constraint_name
20
+
21
+ ISERIES_PRIMARY_KEY_TYPE = "P"
22
+ ISERIES_FOREIGN_KEY_TYPE = "F"
23
+ ISERIES_UNIQUE_KEY_TYPE = "U"
24
+ ISERIES_CHECK_CONSTRAINT_TYPE = "C"
25
+
26
+ def initialize(constraint_schema, constraint_name, constraint_type, table_name, column_name,
27
+ referenced_constraint_name, referenced_table_name, referenced_column_names, delete_rule)
28
+ @constraint_schema = constraint_schema
29
+ @constraint_name = constraint_name
30
+ @table_name = table_name
31
+ @constraint_type = constraint_type
32
+ @column_names = column_name.to_a if column_name
33
+ @referenced_table_name = referenced_table_name
34
+ @referenced_constraint_name = referenced_constraint_name
35
+ @delete_rule = delete_rule
36
+ @referenced_column_names = referenced_column_names if referenced_column_names
37
+ end
38
+
39
+ def primary_key?
40
+ constraint_type == PRIMARY_KEY_TYPE || constraint_type == ISERIES_PRIMARY_KEY_TYPE
41
+ end
42
+
43
+ def foreign_key?
44
+ constraint_type == FOREIGN_KEY_TYPE || constraint_type == ISERIES_FOREIGN_KEY_TYPE
45
+ end
46
+
47
+ def unique_key?
48
+ constraint_type == UNIQUE_KEY_TYPE || constraint_type == ISERIES_UNIQUE_KEY_TYPE
49
+ end
50
+
51
+ def is_foreign_constraint?(table_name)
52
+ @table_name.upcase != table_name.upcase
53
+ end
54
+
55
+ end
56
+
57
+
58
+ class IBM_DB2_LUW < IBM_DB2
59
+
60
+ def constraints(table_name, name = nil) #:nodoc
61
+ constraints = []
62
+ upcase_table_name = table_name.upcase
63
+ sql = %Q{
64
+ select CST.tabschema, CST.constname, LOWER(CST.tabname) as tabname, CST.type,
65
+ LOWER(COL.colname) as colname, REF.constname as foreign_constraint_name,
66
+ LOWER(REF.tabname) as foreign_table_name, REF.refkeyname,
67
+ LOWER(REF.reftabname) as reftabname, REF.deleterule,
68
+ REF.fk_colnames as foreign_columns, REF.pk_colnames as referenced_columns
69
+ from
70
+ ((select * from SYSCAT.TABCONST where tabname='#{upcase_table_name}') as CST
71
+ inner join
72
+ (select colname, constname from SYSCAT.KEYCOLUSE
73
+ where tabname='#{upcase_table_name}') as COL
74
+ on (CST.constname=COL.constname))
75
+ left outer join SYSCAT.REFERENCES REF
76
+ on (CST.constname=REF.refkeyname or CST.constname=REF.constname)
77
+ }
78
+
79
+ results = IBM_DB::exec(@adapter.connection, sql)
80
+ constraint_name_hash = {}
81
+
82
+ # Note that column names in constraint objects are downcased in order to
83
+ # be comparable with the column names produced by IBM_DBAdapter.columns
84
+ while row = IBM_DB::fetch_assoc(results)
85
+ constraint_name = row['constname']
86
+ foreign_constraint_name = row['foreign_constraint_name']
87
+ column_name = row['colname']
88
+ table_name = row['tabname']
89
+ referenced_columns = row['referenced_columns'].downcase.split if row['referenced_columns']
90
+ foreign_columns = row['foreign_columns'].downcase.split if row['foreign_columns']
91
+
92
+ # Process constraints local to this table
93
+ if !constraint_name_hash.has_key?(constraint_name)
94
+ current_constraint = IBM_DBConstraint.new(row['tabschema'], constraint_name, row['type'],
95
+ table_name, column_name, row['refkeyname'], row['reftabname'],
96
+ referenced_columns, row['deleterule'])
97
+ constraints << current_constraint
98
+ constraint_name_hash[constraint_name] = current_constraint
99
+ # This key is a composite
100
+ else
101
+ current_constraint = constraint_name_hash[constraint_name]
102
+ current_constraint.column_names << column_name unless current_constraint.column_names.include?(column_name)
103
+ referenced_columns.each do |column|
104
+ current_constraint.referenced_column_names << column unless current_constraint.referenced_column_names.include?(column)
105
+ end
106
+ end
107
+
108
+ # Process constraints that reference this table's local constraints
109
+ if foreign_constraint_name
110
+ if !constraint_name_hash.has_key?(foreign_constraint_name)
111
+ current_foreign_constraint = IBM_DBConstraint.new(row['tabschema'], foreign_constraint_name,
112
+ IBM_DBConstraint::FOREIGN_KEY_TYPE, row['foreign_table_name'], foreign_columns,
113
+ constraint_name, table_name, column_name, row['deleterule'])
114
+ constraints << current_foreign_constraint
115
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
116
+ # Composite FK
117
+ else
118
+ current_foreign_constraint = constraint_name_hash[foreign_constraint_name]
119
+ foreign_columns.each do |fc|
120
+ current_foreign_constraint.column_names << fc unless current_foreign_constraint.column_names.include?(fc)
121
+ end
122
+ referenced_columns.each do |rc|
123
+ current_foreign_constraint.referenced_column_names << rc unless current_foreign_constraint.referenced_column_names.include?(rc)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ constraints
129
+ end
130
+
131
+ end
132
+
133
+
134
+ class IBM_DB2_I5 < IBM_DB2
135
+
136
+ def constraints(table_name, name = nil)#:nodoc:
137
+ constraints = []
138
+ upcase_table_name = table_name.upcase
139
+ sql = %Q{
140
+ select CST.constraint_schema, CST.constraint_name, LOWER(CST.table_name) as table_name, CST.constraint_type,
141
+ LOWER(COL.column_name) as column_name, REF.constraint_name as foreign_constraint_name, REF.unique_constraint_name as referenced_constraint_name,
142
+ REF.delete_rule, LOWER(COLREF.table_name) as foreign_table_name, LOWER(COLREF.column_name) as foreign_column_name
143
+ from
144
+ ((select * from SYSCST where table_name='#{upcase_table_name}') as CST
145
+ inner join (select column_name, constraint_name from SYSCSTCOL where table_name='#{upcase_table_name}') as COL
146
+ on (CST.constraint_name=COL.constraint_name)
147
+ left outer join SYSREFCST REF
148
+ on (CST.constraint_name=REF.unique_constraint_name or CST.constraint_name=REF.constraint_name)
149
+ left join SYSCSTCOL AS COLREF
150
+ on (NOT COLREF.table_name='#{upcase_table_name}'
151
+ AND (REF.unique_constraint_name=COLREF.constraint_name or REF.constraint_name=COLREF.constraint_name)))
152
+ }
153
+ results = IBM_DB::exec(@adapter.connection, sql)
154
+ constraint_name_hash = {}
155
+
156
+ # Note that column names in constraint objects are downcased in order to
157
+ # be comparable with the column names produced by IBM_DBAdapter.columns
158
+ while row = IBM_DB::fetch_assoc(results)
159
+ constraint_name = row['constraint_name']
160
+ foreign_constraint_name = row['foreign_constraint_name']
161
+ column_name = row['column_name']
162
+ table_name = row['table_name']
163
+ foreign_column = row['foreign_column_name']
164
+
165
+ # Process constraints local to this table
166
+ if !constraint_name_hash.has_key?(constraint_name)
167
+ current_constraint = IBM_DBConstraint.new(row['constraint_schema'], constraint_name, row['constraint_type'],
168
+ table_name, column_name, row['referenced_constraint_name'], row['foreign_table_name'],
169
+ foreign_column.to_a, row['delete_rule'])
170
+ constraints << current_constraint
171
+ constraint_name_hash[constraint_name] = current_constraint
172
+ # This key is a composite
173
+ else
174
+ current_constraint = constraint_name_hash[constraint_name]
175
+ current_constraint.column_names << column_name unless current_constraint.column_names.include?(column_name)
176
+ current_constraint.referenced_column_names << foreign_column unless current_constraint.referenced_column_names.include?(foreign_column)
177
+ end
178
+
179
+ # Process constraints that reference this table's local constraints
180
+ if foreign_constraint_name && foreign_constraint_name != constraint_name
181
+ if !constraint_name_hash.has_key?(foreign_constraint_name)
182
+ current_foreign_constraint = IBM_DBConstraint.new(row['constraint_schema'], foreign_constraint_name,
183
+ IBM_DBConstraint::FOREIGN_KEY_TYPE, row['foreign_table_name'], foreign_column,
184
+ constraint_name, table_name, column_name.to_a, row['delete_rule'])
185
+ constraints << current_foreign_constraint
186
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
187
+ # Composite FKs
188
+ else
189
+ current_foreign_constraint = constraint_name_hash[foreign_constraint_name]
190
+ current_foreign_constraint.column_names << foreign_column unless current_foreign_constraint.column_names.include?(foreign_column)
191
+ current_foreign_constraint.referenced_column_names << column_name unless current_foreign_constraint.referenced_column_names.include?(column_name)
192
+ end
193
+ end
194
+ end
195
+ constraints
196
+ end
197
+
198
+ end
199
+
200
+ class IBM_DBAdapter < AbstractAdapter
201
+
202
+ # DrySQL queries the current schema's System Catalog Views, which contain metadata
203
+ # only for those constraints that are defined in the current schema.
204
+ #
205
+ # If you have inter-schema referential constraints, and I'm not sure why you would or whether
206
+ # DB2 even supports this, DrySQL will not detect them and will raise an error.
207
+
208
+ def constraints(table_name, name = nil) #:nodoc:
209
+ @servertype.constraints(table_name, name)
210
+ end
211
+
212
+ end
213
+
214
+ end
215
+
216
+ end
@@ -3,31 +3,21 @@ module ActiveRecord
3
3
  module ConnectionAdapters
4
4
 
5
5
  class MysqlColumn < Column #:nodoc:
6
- attr_reader :extra
7
6
 
8
- @@auto_increment = "auto_increment"
7
+ AUTO_INCREMENT = "auto_increment"
9
8
 
10
9
  # Capture the value of the "EXTRA" column in the column metadata table.
11
10
  # This can be used to generate validations
12
11
  alias :base_initialize :initialize
13
12
  def initialize(name, default, sql_type = nil, null = true, extra = nil)
13
+ if !(default.nil? || default.blank?) then @default_specified = true end
14
14
  base_initialize(name, default, sql_type, null)
15
15
  @extra = extra
16
16
  end
17
-
18
- def generated?
19
- @extra == @@auto_increment
20
- end
21
-
22
- # Until MySQL properly handles default values, I assume that a default value of NULL or empty
23
- # string means that a default value has not been specified for the column.
24
- #
25
- # We really only care about this when the column is NOT NULL, in which case it should be safe
26
- # to assume that no one would define the column as NOT NULL and then explicitly set the empty string
27
- # as the default value. ...Right?
28
- def default_specified?
29
- !(default.nil? || default.blank?)
30
- end
17
+
18
+ def is_nullable?
19
+ @null || @default_specified || @extra == AUTO_INCREMENT
20
+ end
31
21
 
32
22
  end
33
23
 
@@ -41,10 +31,14 @@ module ActiveRecord
41
31
  @constraint_name = constraint_name
42
32
  @table_schema = table_schema
43
33
  @table_name = table_name
44
- @constraint_type = constraint_type
45
- @column_name = column_name
34
+ if constraint_type.nil? && table_name != referenced_table_name
35
+ @constraint_type = FOREIGN_KEY_TYPE
36
+ else
37
+ @constraint_type = constraint_type
38
+ end
39
+ @column_names = [column_name]
46
40
  @referenced_table_name = referenced_table_name
47
- @referenced_column_name = referenced_column_name
41
+ @referenced_column_names = [referenced_column_name] if referenced_column_name
48
42
  end
49
43
 
50
44
  def is_foreign_constraint?(table_name)
@@ -79,29 +73,37 @@ module ActiveRecord
79
73
 
80
74
  # Retrieve each DB constraint from the information_schema database that is either a constraint on
81
75
  # table_name or references table_name (eg. FK into table_name)
82
- #
83
- # Save a boolean value on each constraint indicating whether it is part of a composite constraint or not.
84
- # This allows us to encapsulate is_composite? logic on the constraint object itself, rather than
85
- # depending on access to the complete set of constraints for the table at a later time
86
76
  def constraints(table_name, name = nil)#:nodoc:
87
77
  constraints = []
88
- sql = "select KCU.constraint_name, KCU.table_schema, KCU.table_name, KCU.column_name, TC.constraint_type, KCU.referenced_table_name, KCU.referenced_column_name from \
89
- ((select constraint_name, constraint_type, table_name from information_schema.table_constraints where table_schema='#{schema}' and table_name='#{table_name}') as TC right join \
90
- (select * from information_schema.key_column_usage where table_schema='#{schema}' and (table_name='#{table_name}' or referenced_table_name='#{table_name}')) as KCU on \
91
- (TC.constraint_name=KCU.constraint_name AND TC.table_name=KCU.table_name))"
78
+ sql = %Q{
79
+ select KCU.constraint_name, KCU.table_schema, KCU.table_name, KCU.column_name, TC.constraint_type,
80
+ KCU.referenced_table_name, KCU.referenced_column_name
81
+ from ((select constraint_name, constraint_type, table_name
82
+ from information_schema.table_constraints
83
+ where table_schema='#{schema}' and table_name='#{table_name}') as TC
84
+ right join (select *
85
+ from information_schema.key_column_usage
86
+ where table_schema='#{schema}'
87
+ and (table_name='#{table_name}' or referenced_table_name='#{table_name}')) as KCU
88
+ on (TC.constraint_name=KCU.constraint_name AND TC.table_name=KCU.table_name))}
92
89
  results = execute(sql, name)
90
+
93
91
  constraint_name_hash = {}
94
92
  results.each do |row|
95
- constraints << MysqlConstraint.new(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
96
93
  comparable_constraint_name = row[0].upcase + row[2].upcase
97
- constraint_name_count = constraint_name_hash[comparable_constraint_name]
98
- constraint_name_count ?
99
- constraint_name_hash[comparable_constraint_name] = constraint_name_count + 1 :
100
- constraint_name_hash[comparable_constraint_name] = 1
101
- end
102
-
103
- constraints.each do | constraint|
104
- constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase + constraint.table_name.upcase] > 1)
94
+ referenced_column_name = row[6]
95
+ column_name = row[3]
96
+ existing_constraint = constraint_name_hash[comparable_constraint_name]
97
+ if !existing_constraint
98
+ new_constraint = MysqlConstraint.new(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
99
+ constraints << new_constraint
100
+ constraint_name_hash[comparable_constraint_name] = new_constraint
101
+ else
102
+ existing_constraint.column_names << column_name unless existing_constraint.column_names.include?(column_name)
103
+ if referenced_column_name
104
+ existing_constraint.referenced_column_names << referenced_column_name unless existing_constraint.referenced_column_names.include?(referenced_column_name)
105
+ end
106
+ end
105
107
  end
106
108
  constraints
107
109
  end