drysql 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,157 +1,138 @@
1
- module ActiveRecord
2
-
3
- module ConnectionAdapters
4
-
5
- class OracleColumn < Column
6
-
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
-
14
-
15
- def default_specified?
16
- @default_specified
17
- end
18
-
19
- # The only means that Oracle currently provides for auto-generating a column
20
- # is to define a sequence and a trigger on insert that inserts sequence.nextval
21
- #
22
- # As a result of this, there is no reliable way to determine whether a particular
23
- # column is generated by the DB.
24
- #
25
- # DrySQL currently makes the assumption that a column is auto-generated
26
- # if the column is of type integer and is the primary key for the table
27
- #
28
- # Ideally a future release of Oracle will support declaring a default value
29
- # of sequence.nextval for a column, which will eliminate this problem.
30
- def generated?
31
- @type == :integer && @primary_key == true
32
- end
33
-
34
- end
35
-
36
- class OracleConstraint < AbstractTableConstraint
37
-
38
- PRIMARY_KEY_TYPE = "P"
39
- FOREIGN_KEY_TYPE = "R"
40
- UNIQUE_KEY_TYPE = "U"
41
- CHECK_CONSTRAINT_TYPE = "C"
42
-
43
- def initialize(constraint_name, constraint_type, table_name, column_name,
44
- referenced_constraint_name, referenced_table_name, referenced_column_name, delete_rule)
45
- @constraint_name = constraint_name
46
- @table_schema = table_schema
47
- @table_name = table_name
48
- @constraint_type = constraint_type
49
- @column_name = Set.new [column_name]
50
- @referenced_table_name = referenced_table_name
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
61
- end
62
-
63
- def primary_key?
64
- constraint_type == PRIMARY_KEY_TYPE
65
- end
66
-
67
- def foreign_key?
68
- constraint_type == FOREIGN_KEY_TYPE
69
- end
70
-
71
- def component_of_unique_key?
72
- constraint_type == UNIQUE_KEY_TYPE
73
- end
74
-
75
- end
76
-
77
- class OracleAdapter < AbstractAdapter
78
-
79
- alias :base_columns :columns
80
- def columns(table_name, name = nil) #:nodoc:
81
- columns = base_columns(table_name)
82
-
83
- # This is a hack. Once Oracle offers a better means of auto-generating IDs,
84
- # this will be fixed
85
- # FIXME must be fixed once composite primary keys are supported
86
- sql = "select t1.column_name from all_cons_columns t1 inner join all_constraints t2 on \
87
- (t1.owner = t2.owner AND t1.constraint_name = t2.constraint_name) where t1.table_name='#{table_name.upcase}' and t2.constraint_type='P'"
88
- primary_key_column_name = select_one(sql, "Primary Key")['column_name']
89
-
90
- columns.each do |column|
91
- column.primary_key=(oracle_downcase(primary_key_column_name) == column.name)
92
- end
93
- end
94
-
95
- def constraints(table_name, name = nil)#:nodoc:
96
- constraints = []
97
- upcase_table_name = table_name.upcase
98
- sql = "select UC.constraint_name, UC.constraint_type, UC.table_name, COL.column_name, \
99
- REF.r_constraint_name as referenced_constraint_name, REF.constraint_name as foreign_constraint_name, \
100
- REF.delete_rule as foreign_delete_rule, COLREF.table_name as foreign_table_name, COLREF.column_name as foreign_column_name from \
101
- (select owner, constraint_name, constraint_type, table_name, r_owner, r_constraint_name \
102
- from all_constraints where table_name='#{upcase_table_name}') UC inner join \
103
- (select constraint_name, table_name, column_name from all_cons_columns where table_name='#{upcase_table_name}') COL on \
104
- (COL.constraint_name = UC.constraint_name) left join all_constraints REF on \
105
- (UC.constraint_name=REF.constraint_name OR UC.constraint_name=REF.r_constraint_name) left join all_cons_columns COLREF on \
106
- (not COLREF.table_name='#{upcase_table_name}' AND (REF.constraint_name=COLREF.constraint_name OR REF.r_constraint_name=COLREF.constraint_name))"
107
-
108
- results = select_all(sql, name)
109
- constraint_name_hash = {}
110
- results.each do |row|
111
- # The author(s) of the Oracle adapter chose to selectively downcase column names for
112
- # "cleanliness" within our Rails code. I had to follow suit with constraint column & table names
113
- # so that model objects could look up their data values using the generated column accessor methods
114
- column_name = oracle_downcase(row['column_name'])
115
- unless row['foreign_column_name'].nil? then row['foreign_column_name'] = oracle_downcase(row['foreign_column_name']) end
116
- unless row['table_name'].nil? then row['table_name'] = oracle_downcase(row['table_name']) end
117
- unless row['foreign_table_name'].nil? then row['foreign_table_name'] = oracle_downcase(row['foreign_table_name']) end
118
- constraint_name = row['constraint_name']
119
- foreign_constraint_name = row['foreign_constraint_name']
120
-
121
- # Process constraints local to this table
122
- if !constraint_name_hash.has_key?(constraint_name)
123
- current_constraint = OracleConstraint.new(constraint_name, row['constraint_type'], row['table_name'],
124
- column_name, row['referenced_constraint_name'], row['foreign_table_name'],
125
- row['foreign_column_name'], row['foreign_delete_rule'])
126
- constraints << current_constraint
127
- constraint_name_hash[constraint_name] = current_constraint
128
- # This key is a composite
129
- else
130
- current_constraint = constraint_name_hash[constraint_name]
131
- # Unique Keys are currently the only type of composite keys supported
132
- if current_constraint.component_of_unique_key?
133
- current_constraint.column_name.add(column_name)
134
- end
135
- end
136
-
137
- # Process constraints that reference this table's local constraints
138
- if !foreign_constraint_name.nil? && !constraint_name_hash.has_key?(foreign_constraint_name)
139
- current_foreign_constraint = OracleConstraint.new(foreign_constraint_name, OracleConstraint::FOREIGN_KEY_TYPE,
140
- row['foreign_table_name'], row['foreign_column_name'], constraint_name,
141
- row['table_name'], row['column_name'], row['foreign_delete_rule'])
142
- constraints << current_foreign_constraint
143
- constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
144
- # Composite FKs are currently not supported
145
- # else
146
- # constraint_name_hash[foreign_constraint_name].column_name.add(row['foreign_column_name'])
147
- end
148
- end
149
- constraints
150
- end
151
-
152
-
153
- end
154
-
155
- end
156
-
1
+ module ActiveRecord
2
+
3
+ module ConnectionAdapters
4
+
5
+ class OracleColumn < Column
6
+
7
+ # The default initialize method must be overridden here in order to retrieve the
8
+ # original default value. The standard Rails adapter nils out the default value
9
+ # if it is not of a recognized type, so it then appears as if no default value
10
+ # was specified. DrySQL grabs the default value before it gets nil'd out
11
+ def initialize(name, default, sql_type = nil, null = true)
12
+ if !(default.nil? || default.blank?) then @default_specified = true end
13
+ super(name, default, sql_type, null)
14
+ end
15
+
16
+ def is_nullable?
17
+ @null || @default_specified
18
+ end
19
+
20
+ end
21
+
22
+ class OracleConstraint < AbstractTableConstraint
23
+
24
+ PRIMARY_KEY_TYPE = "P"
25
+ FOREIGN_KEY_TYPE = "R"
26
+ UNIQUE_KEY_TYPE = "U"
27
+ CHECK_CONSTRAINT_TYPE = "C"
28
+
29
+ def initialize(constraint_name, constraint_type, table_name, column_name,
30
+ referenced_constraint_name, referenced_table_name, referenced_column_name, delete_rule)
31
+ @constraint_name = constraint_name
32
+ @table_schema = table_schema
33
+ @table_name = table_name
34
+ @constraint_type = constraint_type
35
+ @column_names = [column_name]
36
+ @referenced_table_name = referenced_table_name
37
+ @referenced_column_names = [ referenced_column_name ] if referenced_column_name
38
+ @delete_rule = delete_rule
39
+ end
40
+
41
+ def is_foreign_constraint?(table_name)
42
+ @table_name.upcase != table_name.upcase
43
+ end
44
+
45
+ def primary_key?
46
+ constraint_type == PRIMARY_KEY_TYPE
47
+ end
48
+
49
+ def foreign_key?
50
+ constraint_type == FOREIGN_KEY_TYPE
51
+ end
52
+
53
+ def unique_key?
54
+ constraint_type == UNIQUE_KEY_TYPE
55
+ end
56
+
57
+ end
58
+
59
+
60
+ class OracleAdapter < AbstractAdapter
61
+
62
+ def constraints(table_name, name = nil)#:nodoc:
63
+ constraints = []
64
+ upcase_table_name = table_name.upcase
65
+ sql = %Q{
66
+ select UC.constraint_name, UC.constraint_type, UC.table_name, COL.column_name,
67
+ REF.r_constraint_name as referenced_constraint_name, REF.constraint_name as foreign_constraint_name,
68
+ REF.delete_rule as foreign_delete_rule,
69
+ COLREF.table_name as foreign_table_name,
70
+ COLREF.column_name as foreign_column_name
71
+ from (select owner, constraint_name, constraint_type, table_name, r_owner, r_constraint_name
72
+ from all_constraints
73
+ where table_name='#{upcase_table_name}') UC
74
+ inner join (select constraint_name, table_name, column_name from all_cons_columns where table_name='#{upcase_table_name}') COL
75
+ on (COL.constraint_name = UC.constraint_name)
76
+ left join all_constraints REF
77
+ on (UC.constraint_name=REF.constraint_name OR UC.constraint_name=REF.r_constraint_name)
78
+ left join all_cons_columns COLREF
79
+ on (not COLREF.table_name='#{upcase_table_name}'
80
+ AND (REF.constraint_name=COLREF.constraint_name OR REF.r_constraint_name=COLREF.constraint_name))
81
+ }
82
+
83
+ results = select_all(sql, name)
84
+ constraint_name_hash = {}
85
+ results.each do |row|
86
+ # The author(s) of the Oracle adapter chose to selectively downcase column names for
87
+ # "cleanliness" within our Rails code. I had to follow suit with constraint column & table names
88
+ # so that model objects could look up their data values using the generated column accessor methods
89
+ unless row['column_name'].nil? then row['column_name'] = oracle_downcase(row['column_name']) end
90
+ unless row['foreign_column_name'].nil? then row['foreign_column_name'] = oracle_downcase(row['foreign_column_name']) end
91
+ unless row['table_name'].nil? then row['table_name'] = oracle_downcase(row['table_name']) end
92
+ unless row['foreign_table_name'].nil? then row['foreign_table_name'] = oracle_downcase(row['foreign_table_name']) end
93
+ constraint_name = row['constraint_name']
94
+ foreign_constraint_name = row['foreign_constraint_name']
95
+ column_name = row['column_name']
96
+
97
+ # Process constraints local to this table
98
+ if !(current_constraint = constraint_name_hash[constraint_name])
99
+ current_constraint = OracleConstraint.new(constraint_name, row['constraint_type'], row['table_name'],
100
+ column_name, row['referenced_constraint_name'], row['foreign_table_name'],
101
+ row['foreign_column_name'], row['foreign_delete_rule'])
102
+ constraints << current_constraint
103
+ constraint_name_hash[constraint_name] = current_constraint
104
+ # This key is a composite
105
+ else
106
+ current_constraint.column_names << column_name unless current_constraint.column_names.include?(column_name)
107
+ referenced_column_name = row['foreign_column_name']
108
+ if referenced_column_name
109
+ current_constraint.referenced_column_names << referenced_column_name unless current_constraint.referenced_column_names.include?(referenced_column_name)
110
+ end
111
+ end
112
+
113
+ # Process constraints that reference this table's local constraints
114
+ if foreign_constraint_name && foreign_constraint_name != constraint_name
115
+ if !(current_foreign_constraint = constraint_name_hash[foreign_constraint_name])
116
+ current_foreign_constraint = OracleConstraint.new(foreign_constraint_name, OracleConstraint::FOREIGN_KEY_TYPE,
117
+ row['foreign_table_name'], row['foreign_column_name'], constraint_name,
118
+ row['table_name'], row['column_name'], row['foreign_delete_rule'])
119
+ constraints << current_foreign_constraint
120
+ constraint_name_hash[foreign_constraint_name] = current_foreign_constraint
121
+ # This key is a composite
122
+ else
123
+ referenced_column_name = row['foreign_column_name']
124
+ if referenced_column_name
125
+ current_foreign_constraint.column_names << referenced_column_name unless current_foreign_constraint.column_names.include?(referenced_column_name)
126
+ end
127
+ current_foreign_constraint.referenced_column_names << column_name unless current_foreign_constraint.referenced_column_names.include?(column_name)
128
+ end
129
+ end
130
+ end
131
+ constraints
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
157
138
  end
@@ -5,11 +5,9 @@ module ActiveRecord
5
5
  class PostgreSQLColumn < Column #:nodoc:
6
6
  attr_accessor :generated, :default_specified
7
7
 
8
- alias :generated? :generated
9
-
10
- def default_specified?
11
- @default_specified
12
- end
8
+ def is_nullable?
9
+ @null || @default_specified || @generated
10
+ end
13
11
 
14
12
  end
15
13
 
@@ -22,12 +20,12 @@ module ActiveRecord
22
20
  @table_catalog = table_catalog
23
21
  @table_schema = table_schema
24
22
  @table_name = table_name
25
- @column_name = column_name
23
+ @column_names = [column_name]
26
24
  @constraint_name = constraint_name
27
25
  @constraint_type = constraint_type
28
26
  @referenced_constraint_name = referenced_constraint_name
29
27
  @referenced_table_name = referenced_table_name
30
- @referenced_column_name = referenced_column_name
28
+ @referenced_column_names = [referenced_column_name] if referenced_column_name
31
29
  @update_rule = update_rule
32
30
  @delete_rule = delete_rule
33
31
  end
@@ -62,30 +60,43 @@ module ActiveRecord
62
60
  def constraints(table_name, name = nil)#:nodoc:
63
61
  constraints = []
64
62
 
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)"
63
+ sql = %Q{
64
+ select t1.*, KCU.column_name, REF2.unique_constraint_name, KCU2.table_name as referenced_table_name,
65
+ KCU2.column_name as referenced_column_name, REF2.delete_rule, REF2.update_rule
66
+ from (select constraint_name, table_catalog, table_schema, table_name, constraint_type
67
+ from information_schema.table_constraints
68
+ where table_name='#{table_name.downcase}'
69
+ or constraint_name in
70
+ (select REF.constraint_name from information_schema.referential_constraints as REF
71
+ where unique_constraint_name in
72
+ (select constraint_name from information_schema.table_constraints
73
+ where table_name='#{table_name.downcase}'))) as t1
74
+ inner join information_schema.key_column_usage as KCU
75
+ on (t1.constraint_name=KCU.constraint_name)
76
+ left join information_schema.referential_constraints as REF2
77
+ on (REF2.constraint_name=t1.constraint_name)
78
+ left join information_schema.key_column_usage as KCU2
79
+ on (REF2.unique_constraint_name=KCU2.constraint_name)
80
+ }
75
81
 
76
82
  results = query(sql, name)
77
83
  constraint_name_hash = {}
78
84
  results.each do |row|
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
85
  comparable_constraint_name = row[0].upcase
81
- constraint_name_count = constraint_name_hash[comparable_constraint_name]
82
- constraint_name_count ?
83
- constraint_name_hash[comparable_constraint_name] = constraint_name_count + 1 :
84
- constraint_name_hash[comparable_constraint_name] = 1
85
- end
86
-
87
- constraints.each do |constraint|
88
- constraint.member_of_composite=(constraint_name_hash[constraint.constraint_name.upcase] > 1)
86
+ referenced_column_name = row[8]
87
+ column_name = row[5]
88
+ existing_constraint = constraint_name_hash[comparable_constraint_name]
89
+ if !existing_constraint
90
+ new_constraint = PostgreSQLConstraint.new(row[0], row[1], row[2], row[3], row[4], column_name, row[6], row[7],
91
+ referenced_column_name, row[9], row[10])
92
+ constraints << new_constraint
93
+ constraint_name_hash[comparable_constraint_name] = new_constraint
94
+ else
95
+ existing_constraint.column_names << column_name unless existing_constraint.column_names.include?(column_name)
96
+ if referenced_column_name
97
+ existing_constraint.referenced_column_names << referenced_column_name unless existing_constraint.referenced_column_names.include?(referenced_column_name)
98
+ end
99
+ end
89
100
  end
90
101
  constraints
91
102
  end
@@ -3,14 +3,12 @@ module ActiveRecord
3
3
  module ConnectionAdapters
4
4
 
5
5
  class SQLServerColumn < Column# :nodoc:
6
- attr_reader :identity, :is_special
7
- attr_accessor :default_specified
8
- alias :generated? :identity
9
-
10
- def default_specified?
11
- @default_specified
6
+
7
+ def is_nullable?
8
+ @null || @default_specified || @identity
12
9
  end
13
10
 
11
+
14
12
  # Borrowed verbatim from standard Rails sqlserver_adapter.
15
13
  # Had to re-implement a bit of functionality here to avoid
16
14
  # a dependency on SQLServerColumn.identity accessor
@@ -20,10 +18,6 @@ module ActiveRecord
20
18
  def initialize(name, default, sql_type = nil, identity = false, null = true)
21
19
  if !(default.nil? || default.blank?) then @default_specified = true end
22
20
  super(name, default, sql_type, null)
23
- @identity = identity
24
- @is_special = sql_type =~ /text|ntext|image/i
25
- # SQL Server only supports limits on *char and float types
26
- @limit = nil unless @type == :float or @type == :string
27
21
  end
28
22
 
29
23
  end
@@ -34,75 +28,25 @@ module ActiveRecord
34
28
  referenced_constraint_name, referenced_table_name, referenced_column_name)
35
29
  @table_schema = table_schema
36
30
  @table_name = table_name
37
- @column_name = Set.new [column_name]
31
+ @column_names = [column_name]
38
32
  @referenced_table_name = referenced_table_name
39
- @referenced_column_name = referenced_column_name
33
+ @referenced_column_names = [referenced_column_name] if referenced_column_name
40
34
  @constraint_schema = constraint_schema
41
- @referenced_constraint_name = referenced_constraint_name
35
+ @referenced_constraint_name = referenced_constraint_name
42
36
  @constraint_name = constraint_name
43
37
  @constraint_type = constraint_type
44
38
  end
45
39
 
46
40
 
47
- def is_member_of_composite?
48
- @column_name.size > 1
49
- end
50
-
51
41
  def is_foreign_constraint?(table_name)
52
42
  @table_name.upcase != table_name.upcase
53
43
  end
54
44
 
55
45
  end
46
+
56
47
 
57
48
  class SQLServerAdapter < AbstractAdapter
58
49
 
59
- # Borrowed verbatim from Rails sqlserver_adapter.
60
- #
61
- # Since we can't create a dependency from ActiveRecord to DrySQL,
62
- # we need to instantiate SQLServer columns in this class extension.
63
- #
64
- # I'll be able to remove this code once the ActiveRecord gem is
65
- # released with the new version of the sqlserver_adapter that defines
66
- # the SQLServerColumn class
67
- def columns(table_name, name = nil)
68
- return [] if table_name.blank?
69
- table_name = table_name.to_s if table_name.is_a?(Symbol)
70
- table_name = table_name.split('.')[-1] unless table_name.nil?
71
- sql = %Q{
72
- SELECT
73
- cols.COLUMN_NAME as ColName,
74
- cols.COLUMN_DEFAULT as DefaultValue,
75
- cols.NUMERIC_SCALE as numeric_scale,
76
- cols.NUMERIC_PRECISION as numeric_precision,
77
- cols.DATA_TYPE as ColType,
78
- cols.IS_NULLABLE As IsNullable,
79
- COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
80
- COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
81
- cols.NUMERIC_SCALE as Scale
82
- FROM INFORMATION_SCHEMA.COLUMNS cols
83
- WHERE cols.TABLE_NAME = '#{table_name}'
84
- }
85
- # Comment out if you want to have the Columns select statment logged.
86
- # Personally, I think it adds unnecessary bloat to the log.
87
- # If you do comment it out, make sure to un-comment the "result" line that follows
88
- result = log(sql, name) { @connection.select_all(sql) }
89
- #result = @connection.select_all(sql)
90
- columns = []
91
- result.each do |field|
92
- default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
93
- if field[:ColType] =~ /numeric|decimal/i
94
- type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
95
- else
96
- type = "#{field[:ColType]}(#{field[:Length]})"
97
- end
98
- is_identity = field[:IsIdentity] == 1
99
- is_nullable = field[:IsNullable] == 'YES'
100
- columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
101
- end
102
- columns
103
- end
104
-
105
-
106
50
  # SQL Server allows you to duplicate table names & constraint names in a single database,
107
51
  # provided that each constraint belongs to a different schema.
108
52
  #
@@ -114,60 +58,112 @@ module ActiveRecord
114
58
  # in the database connection properties and have information_schema queries select only
115
59
  # rows from the desired schema. Until this is implemented, do not duplicate table names
116
60
  # 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
131
61
  def constraints(table_name, name = nil)#:nodoc:
132
62
  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)))"
63
+
64
+ # May 15/2007 - Constraints query re-written by Clifford Heath
65
+ sql = %Q{
66
+ -- Get fields of all unique indexes:
67
+ SELECT user_name(o.uid) as constraint_schema,
68
+ i.name AS constraint_name,
69
+ o.name AS table_name,
70
+ CASE (SELECT p.xtype FROM sysconstraints t, sysobjects p
71
+ WHERE t.id = o.id
72
+ AND t.constid = p.id
73
+ AND i.name = p.name
74
+ )
75
+ WHEN 'PK' THEN 'PRIMARY KEY'
76
+ WHEN 'UQ' THEN 'UNIQUE' -- unique constraint
77
+ ELSE 'UNIQUE' -- unique index
78
+ END AS constraint_type,
79
+ c.name AS column_name,
80
+ NULL AS foreign_constraint_name,
81
+ NULL AS referenced_constraint_name,
82
+ NULL AS update_rule,
83
+ NULL AS delete_rule,
84
+ NULL AS foreign_table_name,
85
+ NULL AS foreign_column_name
86
+ FROM sysobjects AS o,
87
+ sysindexes AS i,
88
+ sysindexkeys AS k,
89
+ syscolumns AS c
90
+ WHERE ('#{table_name}' = '' -- All tables
91
+ OR '#{table_name}' = o.name)
92
+ AND o.type = 'U' -- Tables
93
+ AND o.status >= 0 -- exclude system tables
94
+ AND o.id = i.id -- indexes for this table
95
+ AND o.id = k.id -- indexkeys for table
96
+ AND i.indid = k.indid -- indexkey for index
97
+ AND k.colid = c.colid -- indexkey for column
98
+ AND o.id = c.id -- column for table
99
+ AND (i.status&2) <> 0 -- unique
100
+ AND i.indid NOT IN (0, 255) -- not base table or text
101
+ -- ORDER BY o.name, i.indid, k.keyno -- Can't do this with UNION
102
+
103
+ UNION ALL -- Don't bother with distinct union!
104
+
105
+ -- Get field pairs of all FK constraints to and from this table
106
+ SELECT user_name(foreign_key.uid) AS constraint_schema,
107
+ foreign_key.name AS constraint_name,
108
+ from_table.name AS table_name,
109
+ 'FOREIGN KEY' AS constraint_type,
110
+ from_column.name AS column_name,
111
+ foreign_key.name AS foreign_constraint_name,
112
+ i.name AS referenced_constraint_name,
113
+ CASE WHEN (ObjectProperty(f.constid, 'CnstIsUpdateCascade')=1)
114
+ THEN 'CASCADE'
115
+ ELSE 'NO ACTION'
116
+ END AS update_rule,
117
+ CASE WHEN (ObjectProperty(f.constid, 'CnstIsDeleteCascade')=1)
118
+ THEN 'CASCADE'
119
+ ELSE 'NO ACTION'
120
+ END AS delete_rule,
121
+ to_table.name AS foreign_table_name,
122
+ to_column.name AS foreign_column_name
123
+ FROM sysobjects AS from_table,
124
+ sysforeignkeys AS f,
125
+ sysobjects AS to_table,
126
+ sysobjects AS foreign_key,
127
+ syscolumns AS from_column,
128
+ syscolumns AS to_column,
129
+ sysreferences AS r,
130
+ sysindexes AS i
131
+ WHERE ('#{table_name}' = '' -- FK's for all tables
132
+ OR from_table.name = '#{table_name}'
133
+ OR to_table.name = '#{table_name}')
134
+ AND from_table.type = 'U' -- All user tables
135
+ AND from_table.status >= 0
136
+ AND from_table.id = f.fkeyid -- All fk's from the table
137
+ AND f.constid = foreign_key.id -- Get the sysobject from fk
138
+ AND f.rkeyid = to_table.id -- Get referenced table
139
+ AND f.fkey = from_column.colid -- Get source table's column
140
+ AND from_column.id = from_table.id
141
+ AND f.rkey = to_column.colid -- And referenced's table's col
142
+ AND to_column.id = to_table.id
143
+ AND foreign_key.id = r.constid
144
+ AND r.rkeyid = i.id
145
+ AND r.rkeyindid = i.indid
146
+ }
142
147
 
143
148
  results = select_all(sql, name)
144
149
  constraint_name_hash = {}
145
150
  results.each do |row|
146
151
  constraint_name = row['constraint_name']
147
152
  foreign_constraint_name = row['foreign_constraint_name']
148
-
153
+ column_name = row['column_name']
149
154
  # 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,
155
+ if !(current_constraint = constraint_name_hash[constraint_name])
156
+ current_constraint = SQLServerConstraint.new(row['constraint_schema'], row['table_name'], column_name, constraint_name,
152
157
  row['constraint_type'], row['referenced_constraint_name'], row['foreign_table_name'], row['foreign_column_name'] )
153
158
  constraints << current_constraint
154
- constraint_name_hash[constraint_name] = current_constraint
159
+ constraint_name_hash[constraint_name] = current_constraint
155
160
  # This key is a composite
156
161
  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'])
162
+ current_constraint.column_names << column_name unless current_constraint.column_names.include?(column_name)
163
+ referenced_column_name = row['foreign_column_name']
164
+ if referenced_column_name
165
+ current_constraint.referenced_column_names << referenced_column_name unless current_constraint.referenced_column_names.include?(referenced_column_name)
166
+ end
171
167
  end
172
168
  end
173
169
  constraints