drysql 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/associations.rb +24 -16
- data/lib/base.rb +16 -12
- data/lib/connection_adapters/abstract/schema_definitions.rb +5 -10
- data/lib/connection_adapters/abstract_adapter.rb +4 -12
- data/lib/connection_adapters/ibm_db_adapter.rb +216 -0
- data/lib/connection_adapters/mysql_adapter.rb +38 -36
- data/lib/connection_adapters/oracle_adapter.rb +137 -156
- data/lib/connection_adapters/postgresql_adapter.rb +37 -26
- data/lib/connection_adapters/sqlserver_adapter.rb +101 -105
- data/lib/dependencies.rb +11 -7
- data/lib/drysql.rb +2 -2
- data/lib/validations.rb +24 -19
- metadata +9 -9
- data/lib/connection_adapters/ibm_db2_adapter.rb +0 -432
data/lib/associations.rb
CHANGED
@@ -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.
|
34
|
-
logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} belongs_to :#{belongs_to_class_name}, :foreign_key=>#{foreign_key.
|
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.
|
45
|
-
logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_one :#{has_one_class_name}, :foreign_key=>#{foreign_constraint.
|
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.
|
49
|
-
logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :foreign_key=>#{foreign_constraint.
|
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
|
-
|
60
|
-
|
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.
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
data/lib/base.rb
CHANGED
@@ -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.
|
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.
|
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
|
-
|
147
|
-
|
148
|
-
|
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.
|
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
|
-
|
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, :
|
15
|
-
:
|
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
|
-
|
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
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
45
|
-
|
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
|
-
@
|
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 =
|
89
|
-
|
90
|
-
|
91
|
-
(
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|