drysql 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -113,7 +113,7 @@ by the DB.
113
113
  I did not publish the tests with the first release of DrySQL because they are built on a clone of a corporate database, and I need to obfuscate them first.
114
114
  I will publish tests with the next release
115
115
 
116
- Enjoy!
116
+ Enjoy, and please give me your feedback!
117
117
 
118
118
 
119
119
 
data/lib/associations.rb CHANGED
@@ -32,8 +32,8 @@ module ActiveRecord
32
32
  foreign_keys = table_constraints.select {|constraint| constraint.foreign_key?}
33
33
  foreign_keys.each do |foreign_key|
34
34
  belongs_to_class_name = (class_name(foreign_key.referenced_table_name)).downcaseFirstLetter
35
- self.send(:belongs_to, :"#{belongs_to_class_name}", :foreign_key=>foreign_key.column_name)
36
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} belongs_to :#{belongs_to_class_name}, :foreign_key=>#{foreign_key.column_name}")
35
+ self.send(:belongs_to, :"#{belongs_to_class_name}", :foreign_key=>foreign_key.column_name.to_a[0])
36
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} belongs_to :#{belongs_to_class_name}, :foreign_key=>#{foreign_key.column_name.to_a[0]}")
37
37
  end
38
38
  end
39
39
 
@@ -43,12 +43,12 @@ module ActiveRecord
43
43
  foreign_constraints.each do |foreign_constraint|
44
44
  if foreign_constraint_column_is_unique?(foreign_constraint)
45
45
  has_one_class_name = (class_name(foreign_constraint.table_name)).downcaseFirstLetter
46
- self.send(:has_one, :"#{has_one_class_name}", :foreign_key=>foreign_constraint.referenced_column_name)
47
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_one :#{has_one_class_name}, :foreign_key=>#{foreign_constraint.referenced_column_name}")
46
+ self.send(:has_one, :"#{has_one_class_name}", :foreign_key=>foreign_constraint.column_name.to_a[0])
47
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_one :#{has_one_class_name}, :foreign_key=>#{foreign_constraint.column_name.to_a[0]}")
48
48
  else
49
49
  has_many_class_name = (class_name(foreign_constraint.table_name)).downcaseFirstLetter.pluralize
50
- self.send(:has_many, :"#{has_many_class_name}", :foreign_key=>foreign_constraint.referenced_column_name)
51
- logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :foreign_key=>#{foreign_constraint.referenced_column_name}")
50
+ self.send(:has_many, :"#{has_many_class_name}", :foreign_key=>foreign_constraint.column_name.to_a[0])
51
+ logger.info("DRYSQL >> GENERATED ASSOCIATION: #{self.name} has_many :#{has_many_class_name}, :foreign_key=>#{foreign_constraint.column_name.to_a[0]}")
52
52
  end
53
53
  end
54
54
  end
@@ -56,7 +56,9 @@ module ActiveRecord
56
56
  def foreign_constraint_column_is_unique?(constraint)
57
57
  class_name = class_name(constraint.table_name)
58
58
  klass = instance_eval(class_name)
59
- constraints_on_given_column = klass.table_constraints.select {|current| current.column_name == constraint.column_name}
59
+ # FIXME Is there a more efficient way of doing this?
60
+ # The uniqueness check requires that all constraints for each table that references the current table must be retrieved
61
+ constraints_on_given_column = klass.table_constraints.select {|current| current.column_name.include?(constraint.column_name.to_a[0])}
60
62
  constraints_on_given_column.any? {|current| current.unique_key?}
61
63
  end
62
64
 
@@ -92,4 +94,26 @@ module ActiveRecord
92
94
 
93
95
  end # end of class Base
94
96
 
97
+
98
+ module Associations
99
+
100
+ module ClassMethods
101
+
102
+ class JoinDependency # :nodoc:
103
+
104
+ # Intercept initialization of JoinDependency objects
105
+ # in order to ensure that associations have been generated
106
+ # before we attempt to create joins for eager loading
107
+ alias :base_initialize :initialize
108
+ def initialize(base, associations, joins)
109
+ base.generate_associations
110
+ base_initialize(base, associations, joins)
111
+ end
112
+
113
+ end
114
+
115
+ end # end of class JoinDependency
116
+
117
+ end # end of module Associations
118
+
95
119
  end # end of module ActiveRecord
data/lib/base.rb CHANGED
@@ -48,10 +48,13 @@ module ActiveRecord
48
48
  # ---------------------------------------------------------------------------------------------------
49
49
  def primary_key
50
50
  primary = table_constraints.detect {|constraint| constraint.primary_key?}
51
- if primary.nil? then logger.error("DRYSQL >> No primary key defined for table #{table_name}") end
52
- primary_name = primary.column_name
53
- set_primary_key(primary_name)
54
- logger.info("DRYSQL >> Identified PRIMARY KEY for #{self}: #{primary_name}")
51
+ if primary.nil?
52
+ logger.error("DRYSQL >> No primary key defined for table #{table_name}")
53
+ else
54
+ primary_name = primary.column_name.to_a[0]
55
+ set_primary_key(primary_name)
56
+ logger.info("DRYSQL >> Identified PRIMARY KEY for #{self}: #{primary_name}")
57
+ end
55
58
  primary_name
56
59
  end
57
60
 
@@ -76,16 +79,13 @@ module ActiveRecord
76
79
 
77
80
  # table_constraints are those constraints defined on this table
78
81
  def table_constraints
79
- constraints.select {|constraint| constraint.referenced_table_name.nil? || constraint.table_name.upcase == table_name.upcase}
82
+ constraints.reject {|constraint| constraint.is_foreign_constraint?(table_name)}
80
83
  end
81
84
 
82
85
  # foreign_constraints are those constraints that are defined on other tables, but
83
86
  # reference this table (i.e. FKs into this table)
84
- def foreign_constraints
85
- constraints.select {|constraint|
86
- !constraint.referenced_table_name.nil? &&
87
- constraint.referenced_table_name.upcase == table_name.upcase &&
88
- constraint.table_name.upcase != table_name.upcase}
87
+ def foreign_constraints
88
+ constraints.select {|constraint| constraint.is_foreign_constraint?(table_name)}
89
89
  end
90
90
 
91
91
  def associations
@@ -114,6 +114,31 @@ module ActiveRecord
114
114
  set_primary_key(primary_name)
115
115
  logger.info("DRYSQL >> Reset PRIMARY KEY for #{self}: #{primary_name}")
116
116
  end
117
+
118
+
119
+ # This method will generate associations and validations for all defined
120
+ # subclasses of ActiveRecord::Base.
121
+ #
122
+ # If any of the constraints processed refer to undefined model classes,
123
+ # these classes will be defined as long as they conform to the ActiveRecord
124
+ # naming conventions.
125
+ #
126
+ # This method swallows exceptions that occur during the generation of associations
127
+ # and validations, but logs these exceptions in detail to STDERR.
128
+ def generate_orm
129
+ models = @@subclasses.to_a.flatten.uniq
130
+ models.each do |model|
131
+ begin
132
+ if model != Base
133
+ model.generate_associations
134
+ model.generate_validations
135
+ end
136
+ rescue Exception
137
+ logger.error("DRYSQL ERROR >> Could not generate ORM for #{model}")
138
+ logger.error($!.backtrace.join("\n"))
139
+ end
140
+ end
141
+ end
117
142
 
118
143
 
119
144
  private
@@ -11,9 +11,8 @@ module ActiveRecord
11
11
  UNIQUE_KEY_TYPE = "UNIQUE"
12
12
  CHECK_CONSTRAINT_TYPE = "CHECK"
13
13
 
14
- attr_reader :constraint_catalog, :constraint_schema, :constraint_name, :constraint_type,
15
- :table_catalog, :table_schema, :table_name, :column_name,
16
- :referenced_table_catalog, :referenced_table_schema, :referenced_table_name, :referenced_column_name
14
+ attr_reader :constraint_name, :constraint_type, :table_schema, :table_name, :column_name,
15
+ :referenced_table_name, :referenced_column_name
17
16
 
18
17
  attr_writer :member_of_composite
19
18
 
@@ -22,6 +21,7 @@ module ActiveRecord
22
21
  end
23
22
 
24
23
  alias :initialize :raise_subclass_responsibility_error
24
+ alias :is_foreign_constraint? :raise_subclass_responsibility_error
25
25
 
26
26
  def primary_key?
27
27
  constraint_type == PRIMARY_KEY_TYPE
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  def constraints(table_name, name=nil)
52
52
  raise NotImplementedError, "AbstractAdapter subclass #{self.class} did not implement this method"
53
53
  end
54
-
54
+
55
55
  end
56
56
  end
57
57
  end
@@ -0,0 +1,432 @@
1
+ # Most of this code is borrowed from IBM's rails adapter,
2
+ # which is authored by Antonio Cangiano
3
+ #
4
+ # I have made modifications to the IBM adapter code to support DB2 iSeries
5
+ # as well as to add features for DrySQL on DB2 LUW
6
+
7
+ module ActiveRecord
8
+
9
+ # These extensions in ActiveRecord::Base are for iSeries support
10
+ class Base
11
+
12
+ class << self
13
+
14
+ alias_method :ibm_find_one, :find_one
15
+ # Finds a record based on the +id+ value. The column associated with the primary key
16
+ # can be of a numeric type however DB2 doesn't allow quoted numeric values (e.g WHERE id = '10').
17
+ # Therefore this overrides the default method to check for the column type,
18
+ # and leaves the value in the query unquoted in cases of numeric columns
19
+ def find_one(id, options)
20
+ # If the adapter in use is the IBM DB2 Adapter
21
+ if connection.kind_of?(ConnectionAdapters::IBM_DB2Adapter)
22
+ if !connection.iseries then return ibm_find_one(id, options) end
23
+
24
+ conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
25
+ # Retrieves the sql type of the column associated to the primary key
26
+ cstmt = connection.execute("SELECT coltype FROM SYSCOLUMNS WHERE \
27
+ table_name ='#{table_name.upcase}' AND column_name ='#{primary_key.upcase}'")
28
+ DB2::fetch_row(cstmt)
29
+ primary_key_type = DB2::result(cstmt, 0)
30
+
31
+ # Frees the results set associated with the statement
32
+ DB2::free_result(cstmt)
33
+
34
+ # If the column is a numeric type
35
+ if primary_key_type =~ /int|double|real|decimal|numeric/i
36
+ # Assign the unquoted id value to san_id
37
+ san_id = sanitize_numeric(id)
38
+ else
39
+ # The column is not numeric, so +sanitize+ it
40
+ san_id = sanitize(id)
41
+ end
42
+
43
+ # Adds the options based on the san_id value
44
+ options.update :conditions => "#{table_name}.#{primary_key} = #{san_id}#{conditions}"
45
+
46
+ if result = find_initial(options)
47
+ result
48
+ else
49
+ raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
50
+ end
51
+ else
52
+ # You're not using the IBM DB2 Adapter therefore the default method is recalled
53
+ default_find_one(id, options)
54
+ end
55
+ end
56
+
57
+
58
+ alias_method :ibm_find_some, :find_some
59
+ # Finds a record based on a list of +ids+. The primary key column type could be numeric,
60
+ # and DB2 doesn't allow quoted numeric values. Therefore this version of the method
61
+ # checks for the data type and if it's a numeric type the value in the query is unquoted.
62
+ # If the column datatype is not numeric, the value is properly quoted/sanitized
63
+ def find_some(ids, options)
64
+ # If the adapter in use is the IBM DB2 Adapter
65
+ if connection.kind_of?(ConnectionAdapters::IBM_DB2Adapter)
66
+ if !connection.iseries then return ibm_find_some(ids, options) end
67
+
68
+ conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
69
+ # Retrieves the sql type of the column associated to the primary key
70
+ cstmt = connection.execute("SELECT typename FROM SYSCOLUMNS WHERE \
71
+ table_name ='#{table_name.upcase}' AND column_name ='#{primary_key.upcase}'")
72
+ DB2::fetch_row(cstmt)
73
+ primary_key_type = DB2::result(cstmt, 0)
74
+
75
+ # Frees the results set associated with the statement
76
+ DB2::free_result(cstmt)
77
+
78
+ # If the column is a numeric type
79
+ if primary_key_type =~ /int|double|real|decimal|numeric/i
80
+ # Generates a comma separated list of ids
81
+ ids_list = ids.map {|id| sanitize_numeric(id) }.join(',')
82
+ else
83
+ # Generates a comma separated list of quoted/sanitized ids
84
+ ids_list = ids.map { |id| sanitize(id) }.join(',')
85
+ end
86
+
87
+ # Adds the options to the query, based on the generated +ids_list+
88
+ options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"
89
+
90
+ result = find_every(options)
91
+
92
+ if result.size == ids.size
93
+ result
94
+ else
95
+ raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
96
+ end
97
+ else
98
+ # You're not using the IBM DB2 Adapter therefore the default method is recalled
99
+ default_find_some(ids, options)
100
+ end
101
+ end
102
+ end # class << self
103
+ end # class Base
104
+
105
+
106
+ module ConnectionAdapters
107
+
108
+ class IBM_DB2Column < Column
109
+ attr_accessor :generated, :default_specified
110
+
111
+ def initialize(name, default, generated, sql_type = nil, null = true)
112
+ @name, @type, @null = name, simplified_type(sql_type), null
113
+ @text = [:string, :text, :binary].include? @type
114
+ @sql_type = sql_type
115
+ # have to do this one separately because type_cast depends on #type
116
+ if !(default.nil? || default.blank?) then @default_specified = true end
117
+ @default = type_cast(default)
118
+ # Extracts the limit if it's a :string or :text type
119
+ @limit = extract_limit(sql_type) if !sql_type.nil? && @text
120
+ @primary = nil
121
+ @number = [:float, :integer].include? @type
122
+ @generated = generated
123
+ end
124
+
125
+ def default_specified?
126
+ @default_specified
127
+ end
128
+
129
+ def generated?
130
+ !(generated.nil? || generated.blank?)
131
+ end
132
+ end
133
+
134
+
135
+ class IBM_DB2Constraint < AbstractTableConstraint
136
+
137
+ attr_reader :update_rule, :delete_rule, :referenced_constraint_name
138
+
139
+ ISERIES_PRIMARY_KEY_TYPE = "P"
140
+ ISERIES_FOREIGN_KEY_TYPE = "F"
141
+ ISERIES_UNIQUE_KEY_TYPE = "U"
142
+ ISERIES_CHECK_CONSTRAINT_TYPE = "C"
143
+
144
+ def initialize(constraint_schema, constraint_name, constraint_type, table_name, column_name,
145
+ referenced_constraint_name, referenced_table_name, referenced_column_name, delete_rule)
146
+ @constraint_schema = constraint_schema
147
+ @constraint_name = constraint_name
148
+ @table_name = table_name
149
+ @constraint_type = constraint_type
150
+ if !column_name.nil? then @column_name = Set.new [column_name.downcase] end
151
+ @referenced_table_name = referenced_table_name
152
+ @referenced_constraint_name = referenced_constraint_name
153
+ @delete_rule = delete_rule
154
+ if !referenced_column_name.nil? then @referenced_column_name = referenced_column_name.downcase end
155
+ end
156
+
157
+ def primary_key?
158
+ constraint_type == PRIMARY_KEY_TYPE || constraint_type == ISERIES_PRIMARY_KEY_TYPE
159
+ end
160
+
161
+ def foreign_key?
162
+ constraint_type == FOREIGN_KEY_TYPE || constraint_type == ISERIES_FOREIGN_KEY_TYPE
163
+ end
164
+
165
+ def component_of_unique_key?
166
+ constraint_type == UNIQUE_KEY_TYPE || constraint_type == ISERIES_UNIQUE_KEY_TYPE
167
+ end
168
+
169
+ def is_member_of_composite?
170
+ @column_name.size > 1
171
+ end
172
+
173
+ def is_foreign_constraint?(table_name)
174
+ @table_name.upcase != table_name.upcase
175
+ end
176
+
177
+ end
178
+
179
+
180
+ class IBM_DB2Adapter < AbstractAdapter
181
+ attr_reader :iseries
182
+
183
+ alias :base_initialize :initialize
184
+ def initialize(connection, logger, config)
185
+ if (config[:platform] == "iseries")
186
+ @iseries = true
187
+ logger.info("DRYSQL >> iSeries platform detected")
188
+ end
189
+ base_initialize(connection, logger, config)
190
+ end
191
+
192
+
193
+ alias :base_columns :columns
194
+ # Returns an array of Column objects for the table specified by +table_name+
195
+ # This method re-definition is valid only for iSeries
196
+ def columns(table_name, name = nil)
197
+ # to_s required because it may be a symbol.
198
+ table_name = table_name.to_s.upcase
199
+ # Checks if a blank table name has been given.
200
+ # If so it returns an empty array
201
+ return [] if table_name.strip.empty?
202
+ # +columns+ will contain the resulting array
203
+ columns = []
204
+ if @iseries
205
+ sql = "SELECT column_name, dftvalue as column_default, coltype as type, length, nulls, generated FROM SYSCOLUMNS WHERE TBNAME = '#{table_name}' AND DBNAME = '#{@schema.upcase}' ORDER BY colno"
206
+ else
207
+ sql = "SELECT colname as column_name, default as column_default, typename as type, length, nulls, generated FROM SYSCAT.COLUMNS WHERE TABNAME = '#{table_name}' AND TABSCHEMA = '#{@schema.upcase}' ORDER BY colno"
208
+ end
209
+ # Statement required to access all the columns information
210
+ stmt = execute(sql, name)
211
+ # Fetches all the columns and assigns them to col.
212
+ # +col+ is an hash with keys/value pairs for a column
213
+ while col = DB2::fetch_assoc(stmt)
214
+ column_name = col["column_name"].downcase
215
+ # Assigns the column default value.
216
+ column_default_value = col["column_default"]
217
+ # If there is no default value, it assigns NIL
218
+ column_default_value = nil if (column_default_value && column_default_value.upcase == 'NULL')
219
+ # Removes single quotes from the default value
220
+ column_default_value.gsub!(/^'(.*)'$/, '\1') unless column_default_value.nil?
221
+ # Assigns the column type
222
+ column_type = col["type"].downcase
223
+ # Assigns the field length (size) for the column
224
+ column_length = col["length"]
225
+ # The initializer of the class Column, requires the +column_length+ to be declared between brackets after
226
+ # the datatype(e.g VARCHAR(50)) for :string and :text types. If it's a "for bit data" field it does a subsitution in place, if not
227
+ # it appends the (column_length) string on the supported data types
228
+ unless column_length.nil? || column_length == '' || column_type.sub!(/ \(\) for bit data/i,"(#{column_length}) FOR BIT DATA") || !column_type =~ /char|lob|graphic/i
229
+ column_type << "(#{column_length})"
230
+ end
231
+ # col["NULLABLE"] is 1 if the field is nullable, 0 if not.
232
+ column_nullable = col["nulls"] == 'Y' ? true : false
233
+ generated = col["generated"]
234
+ # Pushes into the array the *IBM_DB2Column* object, created by passing to the initializer
235
+ # +column_name+, +default_value+, +column_type+ and +column_nullable+.
236
+ columns << IBM_DB2Column.new(column_name, column_default_value, generated, column_type, column_nullable)
237
+ end
238
+ # Returns the columns array
239
+ columns
240
+ end
241
+
242
+ alias :base_indexes :indexes
243
+ # Returns an array of indexes for the given table, skipping the primary key.
244
+ # The current implementation accesses SYSINDEXES and QSYS2.SYSKEYS.
245
+ # This method is valid only for iSeries
246
+ def indexes(table_name, name = nil)
247
+ if !@iseries then return base_indexes(table_name, name) end
248
+
249
+ # to_s required because +table_name+ may be a symbol.
250
+ table_name = table_name.to_s
251
+ # Checks if a blank table name has been given.
252
+ # If so it returns an empty array of columns.
253
+ return [] if table_name.strip.empty?
254
+ # +indexes+ will contain the resulting array
255
+ indexes = []
256
+ # Query used to retrieve all the indexes from the given table
257
+ sql = "select * from ((SELECT index_name, is_unique FROM SYSINDEXES WHERE table_name = '#{table_name.upcase}' AND table_schema = '#{@schema.upcase}') \
258
+ as IND inner join (select index_name, column_name from QSYS2.SYSKEYS where index_schema='#{@schema.upcase}') as KEYS on (IND.index_name=KEYS.index_name))"
259
+ stmt = execute(sql, name)
260
+ klass = instance_eval(ActiveRecord::Base.class_name(table_name))
261
+ primary_key = klass.primary_key
262
+ index_hash = {}
263
+ # Fetches all the records available
264
+ while table_index = DB2::fetch_assoc(stmt)
265
+ # Gets the lowercased index name
266
+ index_name = table_index["index_name"].downcase
267
+ # Is the index a primary key?
268
+ unless index_name.upcase == primary_key.upcase
269
+ # Is the index unique?
270
+ index_unique = table_index["is_unique"] == 'U'
271
+ if index_hash[index_name].nil?
272
+ index_hash[index_name] = [index_unique, [column_name]]
273
+ else
274
+ index_hash[index_name][1] << column_name
275
+ end
276
+ end
277
+ end
278
+ # Creates IndexDefinition objects and adds them to the indexes array
279
+ index_hash.keys.each do |key|
280
+ current_index = index_hash[key]
281
+ indexes << IndexDefinition.new(table_name, key, current_index[0], current_index[1])
282
+ end
283
+
284
+ # Returns the indexes array
285
+ return indexes
286
+ # Ensures to free the resources associated with the statement
287
+ ensure
288
+ DB2::free_result(stmt) if stmt
289
+ end
290
+
291
+
292
+ # DrySQL queries the current schema's System Catalog Views, which contain metadata
293
+ # only for those constraints that are defined in the current schema.
294
+ #
295
+ # If you have inter-schema referential constraints, and I'm not sure why you would or whether
296
+ # DB2 even supports this, DrySQL will not detect them and will raise an error.
297
+ def constraints(table_name, name = nil)#:nodoc:
298
+ constraints = []
299
+ if @iseries
300
+ return iseries_constraints(table_name, name)
301
+ else
302
+ sql = "select CST.tabschema, CST.constname, CST.tabname, CST.type, COL.colname, \
303
+ REF.constname as foreign_constraint_name, REF.tabname as foreign_table_name, \
304
+ REF.refkeyname, REF.reftabname, REF.deleterule, REF.fk_colnames as foreign_columns, \
305
+ REF.pk_colnames as referenced_columns from \
306
+ ((select * from SYSCAT.TABCONST where tabname='#{table_name}') as CST \
307
+ inner join (select colname, constname from SYSCAT.KEYCOLUSE where tabname='#{table_name}') \
308
+ as COL on (CST.constname=COL.constname)) left outer join SYSCAT.REFERENCES REF on \
309
+ (CST.constname=REF.refkeyname or CST.constname=REF.constname)"
310
+ end
311
+ results = execute(sql, name)
312
+ constraint_name_hash = {}
313
+
314
+ # Note that column names in constraint objects are downcased in order to
315
+ # be comparable with the column names produced by IBM_DB2Adapter.columns
316
+ while row = DB2::fetch_assoc(results)
317
+ constraint_name = row['constname']
318
+ foreign_constraint_name = row['foreign_constraint_name']
319
+
320
+ # Process constraints local to this table
321
+ if !constraint_name_hash.has_key?(constraint_name)
322
+ current_constraint = IBM_DB2Constraint.new(row['tabschema'], constraint_name, row['type'],
323
+ row['tabname'], row['colname'], row['refkeyname'], row['reftabname'],
324
+ row['referenced_columns'], row['deleterule'])
325
+ constraints << current_constraint
326
+ constraint_name_hash[constraint_name] = current_constraint
327
+ # This key is a composite
328
+ else
329
+ current_constraint = constraint_name_hash[constraint_name]
330
+ # Unique Keys are currently the only type of composite keys supported
331
+ if current_constraint.component_of_unique_key?
332
+ current_constraint.column_name.add(row['colname'])
333
+ end
334
+ end
335
+
336
+ # Process constraints that reference this table's local constraints
337
+ if !foreign_constraint_name.nil? && !constraint_name_hash.has_key?(foreign_constraint_name)
338
+ current_foreign_constraint = IBM_DB2Constraint.new(row['tabschema'], foreign_constraint_name, IBM_DB2Constraint::FOREIGN_KEY_TYPE,
339
+ row['foreign_table_name'], row['foreign_columns'], constraint_name, row['tabname'], row['colname'],
340
+ row['deleterule'])
341
+ constraints << current_foreign_constraint
342
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
343
+ # Composite FKs are currently not supported
344
+ # else
345
+ # constraint_name_hash[foreign_constraint_name].column_name.add(row['foreign_column_name'])
346
+ end
347
+ end
348
+ constraints
349
+ end
350
+
351
+ # This method retrieves constraint information from the iSeries
352
+ # information schema views, which DB2 generates automatically
353
+ # for a schema when it (the schema) is created.
354
+ #
355
+ # Note that the old-style iSeries "libraries" that some people consider to
356
+ # by synonymous with "schemas" do not get the benefit of the information
357
+ # schema views (or journaling, for that matter). DrySQL will not
358
+ # play nicely with iSeries libraries unless you create the information
359
+ # schema views.
360
+ def iseries_constraints(table_name, name = nil)#:nodoc:
361
+ constraints = []
362
+ sql = "select CST.constraint_schema, CST.constraint_name, CST.table_name, CST.constraint_type, COL.column_name, \
363
+ REF.constraint_name as foreign_constraint_name, REF.unique_constraint_name as referenced_constraint_name, \
364
+ REF.delete_rule, COLREF.table_name as foreign_table_name, COLREF.column_name as foreign_column_name from \
365
+ ((select * from SYSCST where table_name='#{table_name}') as CST \
366
+ inner join (select column_name, constraint_name from SYSCSTCOL where table_name='#{table_name}') \
367
+ as COL on (CST.constraint_name=COL.constraint_name) left outer join SYSREFCST REF on \
368
+ (CST.constraint_name=REF.unique_constraint_name or CST.constraint_name=REF.constraint_name) \
369
+ left join SYSCSTCOL AS COLREF on (NOT COLREF.table_name='#{table_name}' AND \
370
+ (REF.unique_constraint_name=COLREF.constraint_name or REF.constraint_name=COLREF.constraint_name)))"
371
+ results = execute(sql, name)
372
+ constraint_name_hash = {}
373
+
374
+ # Note that column names in constraint objects are downcased in order to
375
+ # be comparable with the column names produced by IBM_DB2Adapter.columns
376
+ while row = DB2::fetch_assoc(results)
377
+ constraint_name = row['constraint_name']
378
+ foreign_constraint_name = row['foreign_constraint_name']
379
+
380
+ # Process constraints local to this table
381
+ if !constraint_name_hash.has_key?(constraint_name)
382
+ current_constraint = IBM_DB2Constraint.new(row['constraint_schema'], constraint_name, row['constraint_type'],
383
+ row['table_name'], row['column_name'], row['referenced_constraint_name'], row['foreign_table_name'],
384
+ row['foreign_column_name'], row['delete_rule'])
385
+ constraints << current_constraint
386
+ constraint_name_hash[constraint_name] = current_constraint
387
+ # This key is a composite
388
+ else
389
+ current_constraint = constraint_name_hash[constraint_name]
390
+ # Unique Keys are currently the only type of composite keys supported
391
+ if current_constraint.component_of_unique_key?
392
+ current_constraint.column_name.add(row['column_name'])
393
+ end
394
+ end
395
+
396
+ # Process constraints that reference this table's local constraints
397
+ if !foreign_constraint_name.nil? && !constraint_name_hash.has_key?(foreign_constraint_name)
398
+ current_foreign_constraint = IBM_DB2Constraint.new(row['constraint_schema'], constraint_name, IBM_DB2Constraint::FOREIGN_KEY_TYPE,
399
+ row['foreign_table_name'], row['foreign_column_name'], foreign_constraint_name, row['table_name'], row['column_name'],
400
+ row['delete_rule'])
401
+ constraints << current_foreign_constraint
402
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
403
+ # Composite FKs are currently not supported
404
+ # else
405
+ # constraint_name_hash[foreign_constraint_name].column_name.add(row['foreign_column_name'])
406
+ end
407
+ end
408
+ constraints
409
+ end
410
+
411
+
412
+ alias :base_last_generated_id :last_generated_id
413
+ # Private methods that returns the last automatically generated ID
414
+ # on the given +@connection+. This method is required by the +insert+ method
415
+ # This method is valid only for iSeries
416
+ def last_generated_id
417
+ if !@iseries then return base_last_generated_id end
418
+
419
+ # Queries the db to obtain the last ID that was automatically generated
420
+ stmt = execute("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
421
+ # Fetches the only record available (containing the last id)
422
+ DB2::fetch_row(stmt)
423
+ # Retrieves and returns the result of the query with the last id.
424
+ DB2::result(stmt,0)
425
+ end
426
+ private :last_generated_id
427
+
428
+ end
429
+
430
+ end
431
+
432
+ end
@@ -36,19 +36,22 @@ module ActiveRecord
36
36
  # Specifically: Primary, Foreign, Unique Key definitions
37
37
  class MysqlConstraint < AbstractTableConstraint
38
38
 
39
- def initialize(constraint_catalog, constraint_schema, constraint_name, table_schema, table_name, constraint_type,
40
- column_name, referenced_table_schema, referenced_table_name, referenced_column_name)
41
- @constraint_catalog = constraint_catalog
42
- @constraint_schema = constraint_schema
39
+ def initialize(constraint_name, table_schema, table_name, column_name, constraint_type,
40
+ referenced_table_name, referenced_column_name)
43
41
  @constraint_name = constraint_name
44
42
  @table_schema = table_schema
45
43
  @table_name = table_name
46
44
  @constraint_type = constraint_type
47
45
  @column_name = column_name
48
- @referenced_table_schema = referenced_table_schema
49
46
  @referenced_table_name = referenced_table_name
50
47
  @referenced_column_name = referenced_column_name
51
48
  end
49
+
50
+ def is_foreign_constraint?(table_name)
51
+ !@referenced_table_name.nil? &&
52
+ @referenced_table_name.upcase == table_name.upcase &&
53
+ @table_name.upcase != table_name.upcase
54
+ end
52
55
 
53
56
  end
54
57
 
@@ -82,15 +85,15 @@ module ActiveRecord
82
85
  # depending on access to the complete set of constraints for the table at a later time
83
86
  def constraints(table_name, name = nil)#:nodoc:
84
87
  constraints = []
85
-
86
- sql = "select t2.*, t1.column_name, t1.referenced_table_schema, t1.referenced_table_name, t1.referenced_column_name \
87
- from information_schema.key_column_usage as t1 inner join information_schema.table_constraints as t2 \
88
- using (constraint_name, table_schema, table_name) where table_name='#{table_name}' or t1.referenced_table_name='#{table_name}'"
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))"
89
92
  results = execute(sql, name)
90
93
  constraint_name_hash = {}
91
94
  results.each do |row|
92
- constraints << MysqlConstraint.new(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9])
93
- comparable_constraint_name = row[2].upcase
95
+ constraints << MysqlConstraint.new(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
96
+ comparable_constraint_name = row[0].upcase + row[2].upcase
94
97
  constraint_name_count = constraint_name_hash[comparable_constraint_name]
95
98
  constraint_name_count ?
96
99
  constraint_name_hash[comparable_constraint_name] = constraint_name_count + 1 :
@@ -98,7 +101,7 @@ module ActiveRecord
98
101
  end
99
102
 
100
103
  constraints.each do | constraint|
101
- constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase] > 1)
104
+ constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase + constraint.table_name.upcase] > 1)
102
105
  end
103
106
  constraints
104
107
  end
@@ -4,10 +4,16 @@ module ActiveRecord
4
4
 
5
5
  class OracleColumn < Column
6
6
 
7
- attr_accessor :primary_key
7
+ attr_accessor :primary_key, :default_spceified
8
+
9
+ def initialize(name, default, sql_type = nil, null = true)
10
+ if !(default.nil? || default.blank?) then @default_specified = true end
11
+ super(name, default, sql_type, null)
12
+ end
13
+
8
14
 
9
15
  def default_specified?
10
- !default.nil?
16
+ @default_specified
11
17
  end
12
18
 
13
19
  # The only means that Oracle currently provides for auto-generating a column
@@ -34,18 +40,24 @@ module ActiveRecord
34
40
  UNIQUE_KEY_TYPE = "U"
35
41
  CHECK_CONSTRAINT_TYPE = "C"
36
42
 
37
- def initialize(constraint_catalog, constraint_schema, constraint_name, table_schema, table_name, constraint_type,
38
- column_name, referenced_table_schema, referenced_table_name, referenced_column_name)
39
- @constraint_catalog = constraint_catalog
40
- @constraint_schema = constraint_schema
43
+ def initialize(constraint_name, constraint_type, table_name, column_name,
44
+ referenced_constraint_name, referenced_table_name, referenced_column_name, delete_rule)
41
45
  @constraint_name = constraint_name
42
46
  @table_schema = table_schema
43
47
  @table_name = table_name
44
48
  @constraint_type = constraint_type
45
- @column_name = column_name
46
- @referenced_table_schema = referenced_table_schema
49
+ @column_name = Set.new [column_name]
47
50
  @referenced_table_name = referenced_table_name
48
51
  @referenced_column_name = referenced_column_name
52
+ @delete_rule = delete_rule
53
+ end
54
+
55
+ def is_foreign_constraint?(table_name)
56
+ @table_name.upcase != table_name.upcase
57
+ end
58
+
59
+ def is_member_of_composite?
60
+ @column_name.size > 1
49
61
  end
50
62
 
51
63
  def primary_key?
@@ -82,36 +94,58 @@ module ActiveRecord
82
94
 
83
95
  def constraints(table_name, name = nil)#:nodoc:
84
96
  constraints = []
85
- sql = "select t1.owner, t1.constraint_name, t1.constraint_type, lower(t1.table_name) as table_name, t1.r_owner, t1.r_constraint_name, \
86
- t1.delete_rule, t2.column_name, lower(t3.table_name) as r_table_name, t3.column_name as r_column_name from all_constraints t1 \
87
- inner join all_cons_columns t2 on ( t1.owner = t2.owner AND t1.constraint_name = t2.constraint_name) left outer join \
88
- all_cons_columns t3 on (t1.r_owner = t3.owner AND t1.r_constraint_name = t3.constraint_name) where \
89
- t1.table_name='#{table_name.upcase}' or t3.table_name='#{table_name.upcase}'"
97
+ sql = "select UC.constraint_name, UC.constraint_type, UC.table_name, COL.column_name, \
98
+ REF.r_constraint_name as referenced_constraint_name, REF.constraint_name as foreign_constraint_name, \
99
+ REF.delete_rule as foreign_delete_rule, COLREF.table_name as foreign_table_name, COLREF.column_name as foreign_column_name from \
100
+ (select owner, constraint_name, constraint_type, table_name, r_owner, r_constraint_name \
101
+ from all_constraints where table_name='#{table_name}') UC inner join \
102
+ (select constraint_name, table_name, column_name from all_cons_columns where table_name='#{table_name}') COL on \
103
+ (COL.constraint_name = UC.constraint_name) left join all_constraints REF on \
104
+ (UC.constraint_name=REF.constraint_name OR UC.constraint_name=REF.r_constraint_name) left join all_cons_columns COLREF on \
105
+ (not COLREF.table_name='#{table_name}' AND (REF.constraint_name=COLREF.constraint_name OR REF.r_constraint_name=COLREF.constraint_name))"
90
106
 
91
107
  results = select_all(sql, name)
92
108
  constraint_name_hash = {}
93
109
  results.each do |row|
94
-
95
110
  # The author(s) of the Oracle adapter chose to selectively downcase column names for
96
111
  # "cleanliness" within our Rails code. I had to follow suit with constraint column names
97
112
  # so that model objects could look up their data values using the generated column accessor methods
98
- row['column_name'] = oracle_downcase(row['column_name'])
99
- unless row['r_column_name'].nil? then row['r_column_name'] = oracle_downcase(row['r_column_name']) end
113
+ column_name = oracle_downcase(row['column_name'])
114
+ unless row['foreign_column_name'].nil? then row['foreign_column_name'] = oracle_downcase(row['foreign_column_name']) end
115
+ constraint_name = row['constraint_name']
116
+ foreign_constraint_name = row['foreign_constraint_name']
100
117
 
101
- constraints << OracleConstraint.new(nil, row['owner'], row['constraint_name'], row['owner'], row['table_name'],
102
- row['constraint_type'], row['column_name'], row['r_owner'], row['r_table_name'], row['r_column_name'])
103
- comparable_constraint_name = row['constraint_name'].upcase
104
- constraint_name_count = constraint_name_hash[comparable_constraint_name]
105
- constraint_name_count ?
106
- constraint_name_hash[comparable_constraint_name] = constraint_name_count + 1 :
107
- constraint_name_hash[comparable_constraint_name] = 1
108
- end
109
-
110
- constraints.each do | constraint|
111
- constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase] > 1)
118
+ # Process constraints local to this table
119
+ if !constraint_name_hash.has_key?(constraint_name)
120
+ current_constraint = OracleConstraint.new(constraint_name, row['constraint_type'], row['table_name'],
121
+ column_name, row['referenced_constraint_name'], row['foreign_table_name'],
122
+ row['foreign_column_name'], row['foreign_delete_rule'])
123
+ constraints << current_constraint
124
+ constraint_name_hash[constraint_name] = current_constraint
125
+ # This key is a composite
126
+ else
127
+ current_constraint = constraint_name_hash[constraint_name]
128
+ # Unique Keys are currently the only type of composite keys supported
129
+ if current_constraint.component_of_unique_key?
130
+ current_constraint.column_name.add(column_name)
131
+ end
132
+ end
133
+
134
+ # Process constraints that reference this table's local constraints
135
+ if !foreign_constraint_name.nil? && !constraint_name_hash.has_key?(foreign_constraint_name)
136
+ current_foreign_constraint = OracleConstraint.new(foreign_constraint_name, OracleConstraint::FOREIGN_KEY_TYPE,
137
+ row['foreign_table_name'], row['foreign_column_name'], constraint_name,
138
+ row['table_name'], row['column_name'], row['foreign_delete_rule'])
139
+ constraints << current_foreign_constraint
140
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
141
+ # Composite FKs are currently not supported
142
+ # else
143
+ # constraint_name_hash[foreign_constraint_name].column_name.add(row['foreign_column_name'])
144
+ end
112
145
  end
113
146
  constraints
114
147
  end
148
+
115
149
 
116
150
  end
117
151
 
@@ -3,12 +3,12 @@ module ActiveRecord
3
3
  module ConnectionAdapters
4
4
 
5
5
  class PostgreSQLColumn < Column #:nodoc:
6
- attr_accessor :generated
6
+ attr_accessor :generated, :default_specified
7
7
 
8
8
  alias :generated? :generated
9
9
 
10
10
  def default_specified?
11
- !default.nil?
11
+ @default_specified
12
12
  end
13
13
 
14
14
  end
@@ -16,21 +16,26 @@ module ActiveRecord
16
16
 
17
17
  class PostgreSQLConstraint < AbstractTableConstraint
18
18
 
19
- def initialize(table_catalog, table_schema, table_name, column_name, constraint_catalog, constraint_schema,
20
- constraint_name, referenced_table_catalog, referenced_table_schema, referenced_table_name, constraint_type,
21
- referenced_column_name)
19
+ def initialize(constraint_name, table_catalog, table_schema, table_name,
20
+ constraint_type, column_name, referenced_constraint_name, referenced_table_name,
21
+ referenced_column_name, delete_rule, update_rule)
22
22
  @table_catalog = table_catalog
23
23
  @table_schema = table_schema
24
24
  @table_name = table_name
25
25
  @column_name = column_name
26
- @constraint_catalog = constraint_catalog
27
- @constraint_schema = constraint_schema
28
26
  @constraint_name = constraint_name
29
27
  @constraint_type = constraint_type
30
- @referenced_table_catalog = referenced_table_catalog
31
- @referenced_table_schema = referenced_table_schema
28
+ @referenced_constraint_name = referenced_constraint_name
32
29
  @referenced_table_name = referenced_table_name
33
30
  @referenced_column_name = referenced_column_name
31
+ @update_rule = update_rule
32
+ @delete_rule = delete_rule
33
+ end
34
+
35
+ def is_foreign_constraint?(table_name)
36
+ !@referenced_table_name.nil? &&
37
+ @referenced_table_name.upcase == table_name.upcase &&
38
+ @table_name.upcase != table_name.upcase
34
39
  end
35
40
 
36
41
  end
@@ -47,6 +52,7 @@ module ActiveRecord
47
52
  # typmod now unused as limit, precision, scale all handled by superclass
48
53
  current = PostgreSQLColumn.new(name, default_value(default), translate_field_type(type), notnull == "f")
49
54
  current.generated= (!default.nil? && default.index('nextval') == 0)
55
+ current.default_specified = !(default.nil? || default.blank?)
50
56
  columns << current
51
57
  end
52
58
  columns
@@ -54,23 +60,30 @@ module ActiveRecord
54
60
 
55
61
 
56
62
  def constraints(table_name, name = nil)#:nodoc:
57
- constraints = []
58
- sql = "select t3.* , t4.* from information_schema.constraint_column_usage as t3 right join \
59
- (select t2.*, t1.column_name from information_schema.key_column_usage as t1 inner join \
60
- information_schema.table_constraints as t2 using (constraint_name, table_catalog, table_schema, table_name)) \
61
- as t4 using (constraint_catalog, constraint_schema, constraint_name) \
62
- where t4.table_name='#{table_name.downcase}' or t3.table_name='#{table_name.downcase}'"
63
+ constraints = []
64
+
65
+ sql = "select t1.*, KCU.column_name, REF2.unique_constraint_name, KCU2.table_name as referenced_table_name, \
66
+ KCU2.column_name as referenced_column_name, REF2.delete_rule, REF2.update_rule from \
67
+ (select constraint_name, table_catalog, table_schema, table_name, constraint_type from \
68
+ information_schema.table_constraints where table_name='#{table_name.downcase}' or constraint_name in \
69
+ (select REF.constraint_name from information_schema.referential_constraints as REF
70
+ where unique_constraint_name in (select constraint_name from information_schema.table_constraints
71
+ where table_name='#{table_name.downcase}'))) as t1 inner join information_schema.key_column_usage as KCU on \
72
+ (t1.constraint_name=KCU.constraint_name) left join information_schema.referential_constraints as REF2 \
73
+ on (REF2.constraint_name=t1.constraint_name) left join information_schema.key_column_usage as KCU2 \
74
+ on (REF2.unique_constraint_name=KCU2.constraint_name)"
75
+
63
76
  results = query(sql, name)
64
77
  constraint_name_hash = {}
65
78
  results.each do |row|
66
- constraints << PostgreSQLConstraint.new(row[10], row[11], row[12], row[16], row[4], row[5], row[6], row[0], row[1], row[2], row[13], row[3])
67
- comparable_constraint_name = row[6].upcase
79
+ constraints << PostgreSQLConstraint.new(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10])
80
+ comparable_constraint_name = row[0].upcase
68
81
  constraint_name_count = constraint_name_hash[comparable_constraint_name]
69
82
  constraint_name_count ?
70
83
  constraint_name_hash[comparable_constraint_name] = constraint_name_count + 1 :
71
84
  constraint_name_hash[comparable_constraint_name] = 1
72
85
  end
73
-
86
+
74
87
  constraints.each do |constraint|
75
88
  constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase] > 1)
76
89
  end
@@ -4,19 +4,21 @@ module ActiveRecord
4
4
 
5
5
  class SQLServerColumn < Column# :nodoc:
6
6
  attr_reader :identity, :is_special
7
+ attr_accessor :default_specified
7
8
  alias :generated? :identity
8
9
 
9
10
  def default_specified?
10
- !default.nil?
11
+ @default_specified
11
12
  end
12
13
 
13
14
  # Borrowed verbatim from standard Rails sqlserver_adapter.
14
15
  # Had to re-implement a bit of functionality here to avoid
15
16
  # a dependency on SQLServerColumn.identity accessor
16
17
  #
17
- # Once the ActiveRecord gem releases the latest version of
18
+ # Once the ActiveRecord gem includes the latest version of
18
19
  # sqlserver_adapter, I'll be to remove this code
19
20
  def initialize(name, default, sql_type = nil, identity = false, null = true)
21
+ if !(default.nil? || default.blank?) then @default_specified = true end
20
22
  super(name, default, sql_type, null)
21
23
  @identity = identity
22
24
  @is_special = sql_type =~ /text|ntext|image/i
@@ -28,23 +30,28 @@ module ActiveRecord
28
30
 
29
31
  class SQLServerConstraint < AbstractTableConstraint
30
32
 
31
- def initialize(table_catalog, table_schema, table_name, column_name,
32
- referenced_table_catalog, referenced_table_schema, referenced_table_name, referenced_column_name,
33
- constraint_catalog, constraint_schema, constraint_name, constraint_type)
34
- @table_catalog = table_catalog
33
+ def initialize(constraint_schema, table_name, column_name, constraint_name, constraint_type,
34
+ referenced_constraint_name, referenced_table_name, referenced_column_name)
35
35
  @table_schema = table_schema
36
36
  @table_name = table_name
37
- @column_name = column_name
38
- @referenced_table_catalog = referenced_table_catalog
39
- @referenced_table_schema = referenced_table_schema
37
+ @column_name = Set.new [column_name]
40
38
  @referenced_table_name = referenced_table_name
41
39
  @referenced_column_name = referenced_column_name
42
- @constraint_catalog = constraint_catalog
43
40
  @constraint_schema = constraint_schema
41
+ @referenced_constraint_name = referenced_constraint_name
44
42
  @constraint_name = constraint_name
45
43
  @constraint_type = constraint_type
46
44
  end
47
45
 
46
+
47
+ def is_member_of_composite?
48
+ @column_name.size > 1
49
+ end
50
+
51
+ def is_foreign_constraint?(table_name)
52
+ @table_name.upcase != table_name.upcase
53
+ end
54
+
48
55
  end
49
56
 
50
57
  class SQLServerAdapter < AbstractAdapter
@@ -96,33 +103,72 @@ module ActiveRecord
96
103
  end
97
104
 
98
105
 
106
+ # SQL Server allows you to duplicate table names & constraint names in a single database,
107
+ # provided that each constraint belongs to a different schema.
108
+ #
109
+ # The nice trickery in Rails and DrySQL that relies on retrieving info from the
110
+ # information_schema views will get confused if you create > 1 table with the same name in
111
+ # the same database or > 1 constraint with the same name.
112
+ #
113
+ # A future solution to this problem would be to have the ability to specify the desired schema
114
+ # in the database connection properties and have information_schema queries select only
115
+ # rows from the desired schema. Until this is implemented, do not duplicate table names
116
+ # or constraint names inside any DB.
117
+ #
118
+ # Constraints Query Optimization:
119
+ # -------------------------------
120
+ # - The current query seems to be optimal for the strategy of retrieving local table constraints
121
+ # as well as foreign keys into the current table in a single query
122
+ # - Since 4 tables need to be joined to return information about FKs into the current table
123
+ # the strategy is to join maximally reduced subsets of each table (i.e. rather than
124
+ # join the entire table, select minimum number of columns necessary and the minimum number of
125
+ # rows necessary from each table and then join the results)
126
+ # - Due to the layout of the information_schema views, the database will always need to scan every
127
+ # row in REFERENTIAL_CONSTRAINTS in order to stitch together table constraints and the FKs from
128
+ # other tables that reference these table constraints, which means that performance is inversely
129
+ # related to the number of FKs defined in the schema. Deleting unnecessary tables & FKs, and keeping
130
+ # logically separate groups of tables in separate schemas will improve performance
99
131
  def constraints(table_name, name = nil)#:nodoc:
100
- constraints = []
101
- sql = "select t2.*, t1.COLUMN_NAME, t3.UNIQUE_CONSTRAINT_CATALOG, t3.UNIQUE_CONSTRAINT_SCHEMA, t3.UNIQUE_CONSTRAINT_NAME,
102
- t4.table_catalog as REFERENCED_TABLE_CATALOG, t4.table_schema as REFERENCED_TABLE_SCHEMA, t4.table_name as REFERENCED_TABLE_NAME,
103
- t4.column_name as REFERENCED_COLUMN_NAME from information_schema.constraint_column_usage as t1 inner join
104
- information_schema.table_constraints as t2 on (t2.constraint_name=t1.constraint_name AND t2.table_catalog=t1.table_catalog
105
- AND t2.table_schema=t1.table_schema AND t2.table_name=t1.table_name) left outer join
106
- information_schema.referential_constraints as t3 on (t3.constraint_name=t2.constraint_name AND
107
- t3.constraint_catalog=t2.constraint_catalog AND t3.constraint_schema=t2.constraint_schema) left outer join
108
- information_schema.constraint_column_usage as t4 on (t4.constraint_name=t3.unique_constraint_name AND
109
- t4.constraint_catalog=t3.unique_constraint_catalog AND t4.constraint_schema=t3.unique_constraint_schema)
110
- where t4.table_name='#{table_name}' or t2.table_name='#{table_name}'"
132
+ constraints = []
133
+ sql = "select TC.constraint_schema, TC.constraint_name, TC.table_name, TC.constraint_type, CCU.column_name, \
134
+ REF.constraint_name as foreign_constraint_name, REF.unique_constraint_name as referenced_constraint_name, REF.update_rule, \
135
+ REF.delete_rule, CCUREF.table_name as foreign_table_name, CCUREF.column_name as foreign_column_name from \
136
+ ((select * from information_schema.table_constraints where table_name='#{table_name}') as TC \
137
+ inner join (select column_name, constraint_name from information_schema.constraint_column_usage where table_name='#{table_name}') \
138
+ as CCU on (TC.constraint_name=CCU.constraint_name) left outer join information_schema.referential_constraints REF on \
139
+ (TC.constraint_name=REF.unique_constraint_name or TC.constraint_name=REF.constraint_name) \
140
+ left join information_schema.constraint_column_usage AS CCUREF on (NOT CCUREF.table_name='#{table_name}' AND \
141
+ (REF.unique_constraint_name=CCUREF.constraint_name or REF.constraint_name=CCUREF.constraint_name)))"
142
+
111
143
  results = select_all(sql, name)
112
144
  constraint_name_hash = {}
113
145
  results.each do |row|
114
- constraints << SQLServerConstraint.new(row['TABLE_CATALOG'], row['TABLE_SCHEMA'], row['TABLE_NAME'], row['COLUMN_NAME'],
115
- row['REFERENCED_TABLE_CATALOG'], row['REFERENCED_TABLE_SCHEMA'], row['REFERENCED_TABLE_NAME'], row['REFERENCED_COLUMN_NAME'],
116
- row['CONSTRAINT_CATALOG'], row['CONSTRAINT_SCHEMA'], row['CONSTRAINT_NAME'], row['CONSTRAINT_TYPE'])
117
- comparable_constraint_name = row['CONSTRAINT_NAME'].upcase
118
- constraint_name_count = constraint_name_hash[comparable_constraint_name]
119
- constraint_name_count ?
120
- constraint_name_hash[comparable_constraint_name] = constraint_name_count + 1 :
121
- constraint_name_hash[comparable_constraint_name] = 1
122
- end
123
-
124
- constraints.each do |constraint|
125
- constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase] > 1)
146
+ constraint_name = row['constraint_name']
147
+ foreign_constraint_name = row['foreign_constraint_name']
148
+
149
+ # Process constraints local to this table
150
+ if !constraint_name_hash.has_key?(constraint_name)
151
+ current_constraint = SQLServerConstraint.new(row['constraint_schema'], row['table_name'], row['column_name'], constraint_name,
152
+ row['constraint_type'], row['referenced_constraint_name'], row['foreign_table_name'], row['foreign_column_name'] )
153
+ constraints << current_constraint
154
+ constraint_name_hash[constraint_name] = current_constraint
155
+ # This key is a composite
156
+ else
157
+ current_constraint = constraint_name_hash[constraint_name]
158
+ # Unique Keys are currently the only type of composite keys supported
159
+ if current_constraint.component_of_unique_key? then current_constraint.column_name.add(row['column_name']) end
160
+ end
161
+
162
+ # Process constraints that reference this table's local constraints
163
+ if !foreign_constraint_name.nil? && !constraint_name_hash.has_key?(foreign_constraint_name)
164
+ current_foreign_constraint = SQLServerConstraint.new(row['constraint_schema'], row['foreign_table_name'], row['foreign_column_name'],
165
+ foreign_constraint_name, SQLServerConstraint::FOREIGN_KEY_TYPE, constraint_name, row['table_name'], row['column_name'] )
166
+ constraints << current_foreign_constraint
167
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
168
+ # Composite FKs are currently not supported
169
+ # else
170
+ # constraint_name_hash[foreign_constraint_name].column_name.add(row['foreign_column_name'])
171
+ end
126
172
  end
127
173
  constraints
128
174
  end
data/lib/drysql.rb CHANGED
@@ -8,6 +8,9 @@ require 'connection_adapters/mysql_adapter'
8
8
  require 'connection_adapters/postgresql_adapter'
9
9
  require 'connection_adapters/sqlserver_adapter'
10
10
  require 'connection_adapters/oracle_adapter'
11
+ if defined?(ActiveRecord::ConnectionAdapters::IBM_DB2Adapter)
12
+ require 'connection_adapters/ibm_db2_adapter'
13
+ end
11
14
  require 'connection_adapters/abstract/schema_definitions'
12
15
  require 'associations'
13
16
  require 'validations'
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: drysql
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.8
7
- date: 2007-01-03 00:00:00 -05:00
6
+ version: 0.2.0
7
+ date: 2007-01-29 00:00:00 -05:00
8
8
  summary: Dynamic, Reflective, Invisible Object-Relational Mapping for Ruby
9
9
  require_paths:
10
10
  - lib
@@ -39,6 +39,7 @@ files:
39
39
  - lib/connection_adapters/oracle_adapter.rb
40
40
  - lib/connection_adapters/sqlserver_adapter.rb
41
41
  - lib/connection_adapters/abstract_adapter.rb
42
+ - lib/connection_adapters/ibm_db2_adapter.rb
42
43
  - lib/connection_adapters/abstract/schema_definitions.rb
43
44
  - README
44
45
  test_files: []