drysql 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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