drysql 0.1.8 → 0.2.0

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/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: []