activefacts-rmap 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,187 @@
1
+ #
2
+ # ActiveFacts Relational mapping
3
+ # A ForeignKey exists for every Reference from a ObjectType to another ObjectType that's a table.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module RMap
9
+ class ForeignKey
10
+ # What table (ObjectType) is the FK from?
11
+ def from; @from; end
12
+
13
+ # What table (ObjectType) is the FK to?
14
+ def to; @to; end
15
+
16
+ # What reference created the FK?
17
+ def references; @references; end
18
+
19
+ # What columns in the *from* table form the FK
20
+ def from_columns; @from_columns; end
21
+
22
+ # What columns in the *to* table form the identifier
23
+ def to_columns; @to_columns; end
24
+
25
+ def initialize(from, to, references, from_columns, to_columns) #:nodoc:
26
+ @from, @to, @references, @from_columns, @to_columns =
27
+ from, to, references, from_columns, to_columns
28
+ end
29
+
30
+ def describe
31
+ "foreign key from #{from.name}(#{from_columns.map{|c| c.name}*', '}) to #{to.name}(#{to_columns.map{|c| c.name}*', '})"
32
+ end
33
+
34
+ def verbalised_path reverse = false
35
+ # REVISIT: This should be a proper join path verbalisation:
36
+ refs = reverse ? references.reverse : references
37
+ refs.map do |r|
38
+ r.verbalised_path reverse
39
+ end * ' and '
40
+ end
41
+
42
+ # Which references are absorbed into the "from" table?
43
+ def precursor_references
44
+ fk_jump = @references.detect(&:fk_jump)
45
+ jump_index = @references.index(fk_jump)
46
+ @references[0, jump_index]
47
+ end
48
+
49
+ # Which references are absorbed into the "to" table?
50
+ def following_references
51
+ fk_jump = @references.detect(&:fk_jump)
52
+ jump_index = @references.index(fk_jump)
53
+ fk_jump != @references.last ? @references[jump_index+1..-1] : []
54
+ end
55
+
56
+ def jump_reference
57
+ @references.detect(&:fk_jump)
58
+ end
59
+
60
+ def to_name
61
+ p = precursor_references
62
+ f = following_references
63
+ j = jump_reference
64
+
65
+ @references.last.to_names +
66
+ (p.empty? && f.empty? ? [] : ['via'] + p.map{|r| r.to_names}.flatten + f.map{|r| r.from_names}.flatten)
67
+ end
68
+
69
+ # The from_name is the role name of the table with the FK, viewed from the other end
70
+ # When there are no precursor_references or following_references, it's the jump_reference.from_names
71
+ # REVISIT: I'm still working out what to do with precursor_references and following_references
72
+ def from_name
73
+ p = precursor_references
74
+ f = following_references
75
+ j = jump_reference
76
+
77
+ # pluralise unless j.is_one_to_one
78
+
79
+ # REVISIT: references[0].from_names is where the FK lives; but the object of interest may be an absorbed subclass which we should use here instead:
80
+ # REVISIT: Should crunch superclasses in subtype traversals
81
+ # REVISIT: Need to add "_as_rolename" where rolename is not to.name
82
+
83
+ [
84
+ @references[0].from_names,
85
+ (p.empty? && f.empty? ? [] : ['via'] + p.map{|r| r.to_names}.flatten + f.map{|r| r.from_names}.flatten)
86
+ ]
87
+ end
88
+
89
+ end
90
+ end
91
+
92
+ module Metamodel #:nodoc:
93
+ class ObjectType
94
+ # When an EntityType is fully absorbed, its foreign keys are too.
95
+ # Return an Array of Reference paths for such absorbed FKs
96
+ def all_absorbed_foreign_key_reference_path
97
+ references_from.inject([]) do |array, ref|
98
+ if ref.is_simple_reference
99
+ if TypeInheritance === ref.fact_type
100
+ # Ignore references to secondary supertypes, when absorption is through primary.
101
+ next array if absorbed_via && TypeInheritance === absorbed_via.fact_type
102
+ # Ignore the case where a subtype is absorbed elsewhere:
103
+ # REVISIT: Disabled, as this should never happen.
104
+ # next array if ref.to.absorbed_via != ref.fact_type
105
+ end
106
+ ref.fk_jump = true
107
+ array << [ref]
108
+ elsif ref.is_absorbing or (ref.to && !ref.to.is_table)
109
+ trace :fk, "getting fks absorbed into #{name} via #{ref}" do
110
+ ref.to.all_absorbed_foreign_key_reference_path.each do |aref|
111
+ array << aref.insert(0, ref)
112
+ end
113
+ end
114
+ end
115
+ array
116
+ end
117
+ end
118
+
119
+ def foreign_keys_to
120
+ @foreign_keys_to ||= []
121
+ end
122
+
123
+ # Return an array of all the foreign keys from this table
124
+ def foreign_keys
125
+
126
+ # Get the ForeignKey object for each absorbed reference path
127
+ @foreign_keys ||=
128
+ begin
129
+ fk_ref_paths = all_absorbed_foreign_key_reference_path
130
+ fk_ref_paths.map do |fk_ref_path|
131
+ trace :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
132
+
133
+ from_columns = (columns||all_columns({})).select{|column|
134
+ column.references[0...fk_ref_path.size] == fk_ref_path
135
+ }
136
+ trace :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
137
+
138
+ # Figure out absorption on the target end:
139
+ to = fk_ref_path.last.to
140
+ if to.absorbed_via
141
+ trace :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed via:" do
142
+ while (r = to.absorbed_via)
143
+ m = r.reversed
144
+ trace :fk, "#{m.reading}"
145
+ fk_ref_path << m
146
+ to = m.from == to ? m.to : m.from
147
+ end
148
+ trace :fk, "Absorption ends at #{to.name}"
149
+ end
150
+ end
151
+
152
+ # REVISIT: This test may no longer be necessary
153
+ raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns
154
+
155
+ # REVISIT: This fails for absorbed subtypes having their own identification.
156
+ # Check the CompanyDirectorEmployee model for example, EmployeeManagerNr -> Person (should reference EmployeeNr)
157
+ # Need to use the absorbed identifier_columns of the subtype,
158
+ # not the columns of the supertype that absorbs it.
159
+ # But in general, that isn't going to work because in most DBMS
160
+ # there's no suitable uniquen index on the subtype's identifier_columns
161
+
162
+ to_columns = fk_ref_path[-1].to.identifier_columns
163
+
164
+ # Put the column pairs in the correct order. They MUST be in the order they appear in the primary key
165
+ froms, tos = from_columns.zip(to_columns).sort_by { |pair|
166
+ to_columns.index(pair[1])
167
+ }.transpose
168
+
169
+ fk = ActiveFacts::RMap::ForeignKey.new(self, to, fk_ref_path, froms, tos)
170
+ to.foreign_keys_to << fk
171
+ fk
172
+ end
173
+ end.
174
+ sort_by do |fk|
175
+ # Put the foreign keys in a defined order:
176
+ # debugger if !fk.to_columns || fk.to_columns.include?(nil) || !fk.from_columns || fk.from_columns.include?(nil)
177
+ [ fk.to.name,
178
+ fk.to_columns.map{|col| col.name(nil).sort},
179
+ fk.from_columns.map{|col| col.name(nil).sort}
180
+ ]
181
+ end
182
+ end
183
+
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,237 @@
1
+ #
2
+ # ActiveFacts Relational mapping
3
+ # An Index on a ObjectType is used to represent a unique constraint across roles absorbed
4
+ # into that object_type's table.
5
+ #
6
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
7
+ #
8
+
9
+ module ActiveFacts
10
+ module RMap
11
+ class Index
12
+ # The UniquenessConstraint that created this index
13
+ def uniqueness_constraint; @uniqueness_constraint; end
14
+
15
+ # The table that the index is on
16
+ def on; @on; end
17
+
18
+ # If a non-mandatory reference was absorbed, only the non-nil instances are unique.
19
+ # Return the ObjectType that was absorbed, which might differ from this Index's table.
20
+ def over; @over; end
21
+
22
+ # Return the array of columns in this index
23
+ def columns; @columns; end
24
+
25
+ # Is this index the primary key for this table?
26
+ def is_primary; @is_primary; end
27
+
28
+ # Is this index unique?
29
+ def is_unique; @is_unique; end
30
+
31
+ # An Index arises from a uniqueness constraint and applies to a table,
32
+ # but because the UC may actually be over an object absorbed into the table,
33
+ # we must record that object also.
34
+ # We record the columns it's over, whether it's primary (for 'over'),
35
+ # and whether it's unique (always, at present)
36
+ def initialize(uc, on, over, columns, is_primary, is_unique = true) #:nodoc:
37
+ @uniqueness_constraint, @on, @over, @columns, @is_primary, @is_unique =
38
+ uc, on, over, columns, is_primary, is_unique
39
+ end
40
+
41
+ # The name that was assigned (perhaps implicitly by NORMA)
42
+ def real_name
43
+ @uniqueness_constraint.name && @uniqueness_constraint.name != '' ? @uniqueness_constraint.name.gsub(' ','') : nil
44
+ end
45
+
46
+ # This name is either the name explicitly assigned (if any) or is constructed to form a unique index name.
47
+ def name
48
+ uc = @uniqueness_constraint
49
+ r = real_name
50
+ return r if r && r !~ /^(Ex|In)ternalUniquenessConstraint[0-9]+$/
51
+ (uc.is_preferred_identifier ? "PK_" : "IX_") +
52
+ view_name +
53
+ (uc.is_preferred_identifier ? "" : "By"+column_names*"")
54
+ end
55
+
56
+ # An array of the names of the columns this index covers
57
+ def column_names(separator = "")
58
+ columns.map{|column| column.name(separator)}
59
+ end
60
+
61
+ # An array of the names of the columns this index covers, with some lexical truncations.
62
+ def abbreviated_column_names(separator = "")
63
+ columns.map{|column| column.name(separator).sub(/^#{over.name}/,'')}
64
+ end
65
+
66
+ # The name of a view that can be created to enforce uniqueness over non-null key values
67
+ def view_name
68
+ "#{over.name.gsub(' ','')}#{on == over ? "" : "In"+on.name.gsub(' ','')}"
69
+ end
70
+
71
+ def to_s #:nodoc:
72
+ if @uniqueness_constraint
73
+ name = @uniqueness_constraint.name
74
+ preferred = @uniqueness_constraint.is_preferred_identifier ? " (preferred)" : ""
75
+ else
76
+ name = "#{@on.name}IsUnique"
77
+ preferred = !@on.injected_surrogate_role ? " (preferred)" : ""
78
+ end
79
+ colnames = @columns.map(&:name)*", "
80
+ "Index #{name} on #{@on.name} over #{@over.name}(#{colnames})#{preferred}"
81
+ end
82
+ end
83
+ end
84
+
85
+ module Metamodel #:nodoc:
86
+ class EntityType
87
+ def self_index
88
+ nil
89
+ end
90
+ end
91
+
92
+ class ValueType
93
+ def self_index
94
+ ActiveFacts::RMap::Index.new(
95
+ nil, # The implied uniqueness constraint is not created
96
+ self, # ValueType being indexed
97
+ self, # Absorbed object being indexed
98
+ columns.select{|c| c.references[0].is_self_value},
99
+ injected_surrogate_role ? false : true
100
+ )
101
+ end
102
+ end
103
+
104
+ class ObjectType
105
+ # An array of each Index for this table
106
+ def indices
107
+ @indices || populate_indices
108
+ end
109
+
110
+ def clear_indices #:nodoc:
111
+ # Clear any previous indices
112
+ @indices = nil
113
+ end
114
+
115
+ def populate_indices #:nodoc:
116
+ # The absorption path of a column indicates how it came to be in this table.
117
+ # It might be a direct many:one valuetype relationship, or it might be in such
118
+ # a relationship to an entity that was absorbed into this table (and so on).
119
+ # The reference path is the set of absorption references and one past it.
120
+ # Stopping here means we don't dig into the definitions of FK column counterparts.
121
+ # Note that many columns of an object may have the same ref_path.
122
+ #
123
+ # REVISIT:
124
+ # Note also that this produces columns ordered for each refpath the same as the
125
+ # order of the columns, not the same as the columns in the PK for which they might be an FK.
126
+ all_column_by_ref_path =
127
+ trace :index2, "Indexing columns by ref_path" do
128
+ columns.inject({}) do |hash, column|
129
+ trace :index2, "References in column #{name}.#{column.name}" do
130
+ ref_path = column.absorption_references
131
+ raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty?
132
+ (hash[ref_path] ||= []) << column
133
+ trace :index2, "#{column.name} involves #{ref_path.map(&:to_s)*" and "}"
134
+ end
135
+ hash
136
+ end
137
+ end
138
+
139
+ columns_by_unique_constraint = {}
140
+ all_column_by_role_ref =
141
+ all_column_by_ref_path.
142
+ keys. # Go through all refpaths and find uniqueness constraints
143
+ inject({}) do |hash, ref_path|
144
+ ref_path.each do |ref|
145
+ next unless ref.to_role
146
+ # trace :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name('.')}*", "}"
147
+ ref.to_role.all_role_ref.each do |role_ref|
148
+ all_pcs = role_ref.role_sequence.all_presence_constraint
149
+ # puts "pcs over #{ref_path.map{|r| r.to_names}.flatten*'.'}: #{role_ref.role_sequence.all_presence_constraint.map(&:describe)*"; "}" if all_pcs.size > 0
150
+ pcs = all_pcs.
151
+ reject do |pc|
152
+ !pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint
153
+ pc.max_frequency != 1 or # maximum is not 1
154
+ # Constraint is not over a unary fact type role (NORMA does this)
155
+ pc.role_sequence.all_role_ref.size == 1 && ref_path[-1].to_role.fact_type.all_role.size == 1
156
+ end
157
+ next unless pcs.size > 0
158
+ # The columns for this ref_path support the UCs in "pcs".
159
+ pcs.each do |pc|
160
+ ref_columns = all_column_by_ref_path[ref_path]
161
+ ordinal = role_ref.ordinal # Position in priority order
162
+ ref_columns.each_with_index do |column, index|
163
+ #puts "Adding index column #{column.name} in rank[#{ordinal},#{index}]"
164
+ # REVISIT: the "index" here might be a duplicate in some cases: change sort_by below to just sort and run the SeparateSubtypes CQL model for example.
165
+ (columns_by_unique_constraint[pc] ||= []) << [ordinal, index, column]
166
+ end
167
+ end
168
+ hash[role_ref] = all_column_by_ref_path[ref_path]
169
+ end
170
+ end
171
+ hash
172
+ end
173
+
174
+ trace :index, "All Indices in #{name}:" do
175
+ @indices = columns_by_unique_constraint.map do |uc, columns_with_ordinal|
176
+ trace :index, "Index due to uc #{uc.concept.guid} on #{name} over (#{columns_with_ordinal.sort_by{|onc|onc[0]}.map{|ca| ca[2].name}.inspect})"
177
+ columns = columns_with_ordinal.sort_by{|ca| [ca[0,2], ca[2].name]}.map{|ca| ca[2]}
178
+ absorption_level = columns.map(&:absorption_level).min
179
+ over = columns[0].references[absorption_level].from
180
+
181
+ # Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
182
+ if over != self and
183
+ over.absorbed_via == columns[0].references[absorption_level-1] and
184
+ (rr = uc.role_sequence.all_role_ref.single) and
185
+ over.absorbed_via.fact_type.all_role.include?(rr.role)
186
+ next nil
187
+ end
188
+
189
+ index = ActiveFacts::RMap::Index.new(
190
+ uc,
191
+ self,
192
+ over,
193
+ columns,
194
+ uc.is_preferred_identifier
195
+ )
196
+ trace :index, index
197
+ index
198
+ end.
199
+ compact.
200
+ sort_by do |index|
201
+ # Put the indices in a defined order:
202
+ index.columns.map(&:name)+['', index.over.name]
203
+ end
204
+ end
205
+ si = self_index
206
+ @indices.unshift(si) if si
207
+ @indices
208
+ end
209
+
210
+ end
211
+
212
+ class Vocabulary
213
+ def populate_all_indices #:nodoc:
214
+ trace :index, "Populating all object_type indices" do
215
+ all_object_type.each do |object_type|
216
+ object_type.clear_indices
217
+ end
218
+ tables.each do |object_type|
219
+ trace :index, "Populating indices for #{object_type.name}" do
220
+ object_type.populate_indices
221
+ end
222
+ end
223
+ end
224
+ trace :index, "Finished object_type indices" do
225
+ tables.each do |object_type|
226
+ trace :index?, "#{object_type.name}:" do
227
+ object_type.indices.each do |index|
228
+ trace :index, index
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ end
237
+ end
@@ -0,0 +1,198 @@
1
+ require 'activefacts/support'
2
+
3
+ module ActiveFacts
4
+ module API
5
+ module ObjectType
6
+ def table
7
+ @is_table = true
8
+ end
9
+
10
+ def is_table
11
+ @is_table
12
+ end
13
+
14
+ def columns
15
+ raise "This method is no longer in use"
16
+ =begin
17
+ return @columns if @columns
18
+ trace :rmap, "Calculating columns for #{basename}" do
19
+ @columns = (
20
+ if superclass.is_entity_type
21
+ # REVISIT: Need keys to secondary supertypes as well, but no duplicates.
22
+ trace :rmap, "Separate subtype has a foreign key to its supertype" do
23
+ superclass.__absorb([[superclass.basename]], self)
24
+ end
25
+ else
26
+ []
27
+ end +
28
+ # Then absorb all normal roles:
29
+ roles.values.select do |role|
30
+ role.unique && !role.counterpart_unary_has_precedence
31
+ end.inject([]) do |columns, role|
32
+ rn = role.name.to_s.split(/_/)
33
+ trace :rmap, "Role #{rn*'.'}" do
34
+ columns += role.counterpart_object_type.__absorb([rn], role.counterpart)
35
+ end
36
+ end +
37
+ # And finally all absorbed subtypes:
38
+ subtypes.
39
+ select{|subtype| !subtype.is_table}. # Don't absorb separate subtypes
40
+ inject([]) do |columns, subtype|
41
+ # Pass self as 2nd param here, not a role, standing for the supertype role
42
+ subtype_name = subtype.basename
43
+ trace :rmap, "Absorbing subtype #{subtype_name}" do
44
+ columns += subtype.__absorb([[subtype_name]], self)
45
+ end
46
+ end
47
+ ).map do |col_names|
48
+ last = nil
49
+ col_names.flatten.map do |name|
50
+ name.downcase.sub(/^[a-z]/){|c| c.upcase}
51
+ end.
52
+ reject do |n|
53
+ # Remove sequential duplicates:
54
+ dup = last == n
55
+ last = n
56
+ dup
57
+ end*"."
58
+ end
59
+ end
60
+ =end
61
+ end
62
+
63
+ # Return an array of the absorbed columns, using prefix for name truncation
64
+ def __absorb(prefix, except_role = nil)
65
+ # also considered a table if the superclass isn't excluded and is (transitively) a table
66
+ if !@is_table && (except_role == superclass || !is_table_subtype)
67
+ if is_entity_type
68
+ if (role = fully_absorbed) && role != except_role
69
+ # If this non-table is fully absorbed into another table (not our caller!)
70
+ # (another table plays its single identifying role), then absorb that role only.
71
+ # counterpart_object_type = role.counterpart_object_type
72
+ # This omission matches the one in columns.rb, see EntityType#reference_columns
73
+ # new_prefix = prefix + [role.name.to_s.split(/_/)]
74
+ trace :rmap, "Reference to #{role.name} (absorbed elsewhere)" do
75
+ role.counterpart_object_type.__absorb(prefix, role.counterpart)
76
+ end
77
+ else
78
+ # Not a table -> all roles are absorbed
79
+ roles.
80
+ values.
81
+ select do |role|
82
+ role.unique && role != except_role && !role.counterpart_unary_has_precedence
83
+ end.
84
+ inject([]) do |columns, role|
85
+ columns += __absorb_role(prefix, role)
86
+ end +
87
+ subtypes. # Absorb subtype roles too!
88
+ select{|subtype| !subtype.is_table}. # Don't absorb separate subtypes
89
+ inject([]) do |columns, subtype|
90
+ # Pass self as 2nd param here, not a role, standing for the supertype role
91
+ new_prefix = prefix[0..-2] + [[subtype.basename]]
92
+ trace :rmap, "Absorbed subtype #{subtype.basename}" do
93
+ columns += subtype.__absorb(new_prefix, self)
94
+ end
95
+ end
96
+ end
97
+ else
98
+ [prefix]
99
+ end
100
+ else
101
+ # Create a foreign key to the table
102
+ if is_entity_type
103
+ ir = identifying_role_names.map{|role_name| roles(role_name) }
104
+ trace :rmap, "Reference to #{basename} with #{prefix.inspect}" do
105
+ ic = identifying_role_names.map{|role_name| role_name.to_s.split(/_/)}
106
+ ir.inject([]) do |columns, role|
107
+ columns += __absorb_role(prefix, role)
108
+ end
109
+ end
110
+ else
111
+ # Reference to value type which is a table
112
+ col = prefix.clone
113
+ trace :rmap, "Self-value #{col[-1]}.Value"
114
+ col[-1] += ["Value"]
115
+ col
116
+ end
117
+ end
118
+ end
119
+
120
+ def __absorb_role(prefix, role)
121
+ if prefix.size > 0 and
122
+ (c = role.owner).is_entity_type and
123
+ c.identifying_roles == [role] and
124
+ (irn = c.identifying_role_names).size == 1 and
125
+ (n = irn[0].to_s.split(/_/)).size > 1 and
126
+ (owner = role.owner.basename.snakecase.split(/_/)) and
127
+ n[0...owner.size] == owner
128
+ trace :rmap, "truncating transitive identifying role #{n.inspect}"
129
+ owner.size.times { n.shift }
130
+ new_prefix = prefix + [n]
131
+ elsif (c = role.counterpart_object_type).is_entity_type and
132
+ (irn = c.identifying_role_names).size == 1 and
133
+ #irn[0].to_s.split(/_/)[0] == role.owner.basename.downcase
134
+ irn[0] == role.counterpart.name
135
+ #trace :rmap, "=== #{irn[0].to_s.split(/_/)[0]} elided ==="
136
+ new_prefix = prefix
137
+ elsif (fa_role = fully_absorbed) && fa_role == role
138
+ new_prefix = prefix
139
+ else
140
+ new_prefix = prefix + [role.name.to_s.split(/_/)]
141
+ end
142
+ #trace :rmap, "new_prefix is #{new_prefix*"."}"
143
+
144
+ trace :rmap, "Absorbing role #{role.name} as #{new_prefix[prefix.size..-1]*"."}" do
145
+ role.counterpart_object_type.__absorb(new_prefix, role.counterpart)
146
+ end
147
+ end
148
+
149
+ def is_table_subtype
150
+ return true if is_table
151
+ klass = superclass
152
+ while klass.is_entity_type
153
+ return true if klass.is_table
154
+ klass = klass.superclass
155
+ end
156
+ return false
157
+ end
158
+ end
159
+
160
+ module Entity
161
+ module ClassMethods
162
+ def fully_absorbed
163
+ return false unless (ir = identifying_role_names) && ir.size == 1
164
+ role = roles(ir[0])
165
+ return role if ((cp = role.counterpart_object_type).is_table ||
166
+ (cp.is_entity_type && cp.fully_absorbed))
167
+ return superclass if superclass.is_entity_type # Absorbed subtype
168
+ nil
169
+ end
170
+ end
171
+ end
172
+
173
+ # A one-to-one can be absorbed into either table. We decide which by comparing
174
+ # the names, just as happens in ObjectType.populate_reference (see reference.rb)
175
+ class Role
176
+ def counterpart_unary_has_precedence
177
+ counterpart_object_type.is_table_subtype and
178
+ counterpart.unique and
179
+ owner.name.downcase < counterpart.owner.name.downcase
180
+ end
181
+ end
182
+
183
+ end
184
+ end
185
+
186
+ class TrueClass
187
+ def self.__absorb(prefix, except_role = nil)
188
+ [prefix]
189
+ end
190
+
191
+ def self.is_table
192
+ false
193
+ end
194
+
195
+ def self.is_table_subtype
196
+ false
197
+ end
198
+ end