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.
- 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
|