activefacts-cql 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +19 -0
  8. data/Rakefile +6 -0
  9. data/activefacts-cql.gemspec +29 -0
  10. data/bin/setup +7 -0
  11. data/lib/activefacts/cql.rb +7 -0
  12. data/lib/activefacts/cql/.gitignore +0 -0
  13. data/lib/activefacts/cql/Rakefile +14 -0
  14. data/lib/activefacts/cql/compiler.rb +156 -0
  15. data/lib/activefacts/cql/compiler/clause.rb +1137 -0
  16. data/lib/activefacts/cql/compiler/constraint.rb +581 -0
  17. data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
  18. data/lib/activefacts/cql/compiler/expression.rb +443 -0
  19. data/lib/activefacts/cql/compiler/fact.rb +390 -0
  20. data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
  21. data/lib/activefacts/cql/compiler/query.rb +106 -0
  22. data/lib/activefacts/cql/compiler/shared.rb +161 -0
  23. data/lib/activefacts/cql/compiler/value_type.rb +174 -0
  24. data/lib/activefacts/cql/parser.rb +234 -0
  25. data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
  26. data/lib/activefacts/cql/parser/Context.treetop +48 -0
  27. data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
  28. data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
  29. data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
  30. data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
  31. data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
  32. data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
  33. data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
  34. data/lib/activefacts/cql/parser/Terms.treetop +183 -0
  35. data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
  36. data/lib/activefacts/cql/parser/nodes.rb +49 -0
  37. data/lib/activefacts/cql/require.rb +36 -0
  38. data/lib/activefacts/cql/verbaliser.rb +804 -0
  39. data/lib/activefacts/cql/version.rb +5 -0
  40. data/lib/activefacts/input/cql.rb +43 -0
  41. data/lib/rubygems_plugin.rb +12 -0
  42. metadata +167 -0
@@ -0,0 +1,457 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class ReferenceMode
6
+ attr_reader :name, :value_constraint, :parameters
7
+
8
+ def initialize name, value_constraint, parameters
9
+ @name = name
10
+ @value_constraint = value_constraint
11
+ @parameters = parameters
12
+ end
13
+
14
+ def to_s
15
+ "identified by its #{name}" +
16
+ ((p = @parameters).size > 0 ? '('+p*', '+')' : '') +
17
+ ((v = @value_constraint) ? v.to_s : '')
18
+ end
19
+ end
20
+
21
+ class EntityType < ObjectType
22
+ def initialize name, supertypes, identification, pragmas, clauses, context_note
23
+ super name
24
+ @supertypes = supertypes
25
+ @identification = identification
26
+ @pragmas = pragmas
27
+ @clauses = clauses || []
28
+ @context_note = context_note
29
+ end
30
+
31
+ def compile
32
+ @entity_type = @vocabulary.valid_entity_type_name(@name) ||
33
+ @constellation.EntityType(@vocabulary, @name, :concept => :new)
34
+ @entity_type.is_independent = true if @pragmas.delete('independent')
35
+
36
+ # REVISIT: CQL needs a way to indicate whether subtype migration can occur.
37
+ # For example by saying "Xyz is a role of Abc".
38
+ @supertypes.each_with_index do |supertype_name, i|
39
+ add_supertype(supertype_name, @identification || i > 0)
40
+ end
41
+ @pragmas.each do |p|
42
+ @constellation.ConceptAnnotation(:concept => @entity_type.concept, :mapping_annotation => p)
43
+ end if @pragmas
44
+
45
+ context = CompilationContext.new(@vocabulary)
46
+
47
+ # Identification may be via a mode (create it) or by forward-referenced entity types (allow those):
48
+ prepare_identifier context
49
+
50
+ context.bind @clauses, @identification.is_a?(Array) ? @identification : []
51
+
52
+ # Create the fact types that define the identifying roles:
53
+ fact_types = create_identifying_fact_types context
54
+
55
+ # At this point, @identification is an array of References and/or Clauses (for unary fact types)
56
+ # Have to do this after creating the necessary fact types
57
+ complete_reference_mode_fact_type fact_types
58
+
59
+ # Find the roles to use if we have to create an identifying uniqueness constraint:
60
+ identifying_roles = bind_identifying_roles context
61
+
62
+ make_preferred_identifier_over_roles identifying_roles
63
+
64
+ if @context_note
65
+ @context_note.compile(@constellation, @entity_type)
66
+ end
67
+
68
+ @clauses.each do |clause|
69
+ next unless clause.context_note
70
+ clause.context_note.compile(@constellation, @entity_type)
71
+ end
72
+
73
+ @entity_type
74
+ end
75
+
76
+ def prepare_identifier context
77
+ # Figure out the identification mode or roles, if any:
78
+ if @identification
79
+ if @identification.is_a? ReferenceMode
80
+ make_entity_type_refmode_valuetypes(name, @identification.name, @identification.parameters)
81
+ vt_name = @reference_mode_value_type.name
82
+ @identification = [Compiler::Reference.new(vt_name, nil, nil, nil, nil, nil, @identification.value_constraint, nil)]
83
+ else
84
+ context.allowed_forward_terms = legal_forward_references(@identification)
85
+ end
86
+ end
87
+ end
88
+
89
+ # Names used in the identifying roles list may be forward referenced:
90
+ def legal_forward_references(identification_phrases)
91
+ identification_phrases.map do |phrase|
92
+ phrase.is_a?(Reference) ? phrase.term : nil
93
+ end.compact.uniq
94
+ end
95
+
96
+ def bind_identifying_roles context
97
+ return unless @identification
98
+ @identification.map do |id|
99
+ if id.is_a?(Reference)
100
+ binding = id.binding
101
+ roles = binding.refs.map{|r|r.role || (rr=r.role_ref and rr.role)}.compact.uniq
102
+ raise "Looking for an occurrence of identifying role #{id.inspect}, but found #{roles.size == 0 ? "none" : roles.size}" if roles.size != 1
103
+ roles[0]
104
+ else
105
+ # id is a clause of a unary fact type.
106
+ id.identify_other_players context
107
+ id.bind context
108
+ matching_clause =
109
+ @clauses.detect { |clause| clause.phrases_match id.phrases }
110
+ raise "Unary identifying role '#{id.inspect}' is not found in the defined fact types" unless matching_clause
111
+ matching_clause.fact_type.all_role.single
112
+ end
113
+ end
114
+ end
115
+
116
+ def make_preferred_identifier_over_roles identifying_roles
117
+ return unless identifying_roles && identifying_roles.size > 0
118
+ role_sequence = @constellation.RoleSequence(:new)
119
+ identifying_roles.each_with_index do |identifying_role, index|
120
+ @constellation.RoleRef(role_sequence, index, :role => identifying_role)
121
+ end
122
+
123
+ # Find a uniqueness constraint as PI, or make one
124
+ pc = find_pc_over_roles(identifying_roles)
125
+ if (pc)
126
+ pc.is_preferred_identifier = true
127
+ pc.name = "#{@entity_type.name}PK" unless pc.name
128
+ trace :constraint, "Existing PC #{pc.verbalise} is now PK for #{@entity_type.name}"
129
+ else
130
+ # Add a unique constraint over all identifying roles
131
+ pc = @constellation.PresenceConstraint(
132
+ :new,
133
+ :vocabulary => @vocabulary,
134
+ :name => "#{@entity_type.name}PK", # Is this a useful name?
135
+ :role_sequence => role_sequence,
136
+ :is_preferred_identifier => true,
137
+ :max_frequency => 1 # Unique
138
+ #:is_mandatory => true,
139
+ #:min_frequency => 1,
140
+ )
141
+ trace :constraint, "Made new preferred PC GUID=#{pc.concept.guid} min=nil max=1 over #{role_sequence.describe}"
142
+ end
143
+ end
144
+
145
+ def find_pc_over_roles(roles)
146
+ return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
147
+ roles[0].all_role_ref.each do |role_ref|
148
+ next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
149
+ pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
150
+ #puts "Existing PresenceConstraint matches those roles!" if pc
151
+ return pc if pc
152
+ end
153
+ nil
154
+ end
155
+
156
+ def create_identifying_fact_types context
157
+ fact_types = []
158
+ # Categorise the clauses into fact types according to the roles they play.
159
+ @clauses.inject({}) do |hash, clause|
160
+ players_key = clause.refs.map{|vr| vr.key.compact}.sort
161
+ (hash[players_key] ||= []) << clause
162
+ hash
163
+ end.each do |players_key, clauses|
164
+ # REVISIT: Loose binding goes here; it might merge some Compiler#Roles
165
+
166
+ fact_type = create_identifying_fact_type(context, clauses)
167
+ fact_types << fact_type if fact_type
168
+ unless fact_type.all_role.detect{|r| r.object_type == @entity_type}
169
+ objectify_existing_fact_type(fact_type)
170
+ end
171
+ end
172
+ fact_types
173
+ end
174
+
175
+ def create_identifying_fact_type context, clauses
176
+ # See if any fact type already exists (this ET cannot be a player, but might objectify it)
177
+ existing_clauses = clauses.select{ |clause| clause.match_existing_fact_type context }
178
+ if negation = existing_clauses.detect{|c| c.certainty == false }
179
+ raise "#{@name} cannot be identified by negated fact type #{negation.inspect}"
180
+ end
181
+ any_matched = existing_clauses.size > 0
182
+
183
+ operation = any_matched ? 'Objectifying' : 'Creating'
184
+ player_names = clauses[0].refs.map{|vr| vr.key.compact*'-'}
185
+ trace :matching, "#{operation} fact type for #{clauses.size} clauses over (#{player_names*', '})" do
186
+ if any_matched # There's an existing fact type we must be objectifying
187
+ fact_type = objectify_existing_fact_type(existing_clauses[0].fact_type)
188
+ end
189
+
190
+ unless fact_type
191
+ fact_type = clauses[0].make_fact_type(@vocabulary)
192
+ clauses[0].make_reading(@vocabulary, fact_type)
193
+ clauses[0].make_embedded_constraints vocabulary
194
+ existing_clauses = [clauses[0]]
195
+ end
196
+
197
+ (clauses - existing_clauses).each do |clause|
198
+ clause.make_reading(@vocabulary, fact_type)
199
+ clause.make_embedded_constraints vocabulary
200
+ end
201
+
202
+ fact_type
203
+ end
204
+ end
205
+
206
+ def objectify_existing_fact_type fact_type
207
+ raise "#{@name} cannot objectify fact type '#{fact_type.entity_type.name}' that's already objectified" if fact_type.entity_type
208
+ if @fact_type
209
+ raise "#{@name} cannot objectify '#{fact_type.default_reading}', it already objectifies '#{@fact_type.default_reading}'"
210
+ end
211
+
212
+ if fact_type.internal_presence_constraints.select{|pc| pc.max_frequency == 1}.size == 0
213
+ # If there's no existing uniqueness constraint over this fact type, make a spanning one.
214
+ pc = @constellation.PresenceConstraint(
215
+ :new,
216
+ :vocabulary => @vocabulary,
217
+ :name => @entity_type.name+"UQ",
218
+ :role_sequence => fact_type.preferred_reading.role_sequence,
219
+ :is_preferred_identifier => false, # We only get here when there is a reference mode on the entity type
220
+ :max_frequency => 1
221
+ )
222
+ trace :constraint, "Made new objectification PC GUID=#{pc.concept.guid} min=nil max=1 over #{fact_type.preferred_reading.role_sequence.describe}"
223
+ end
224
+
225
+ @fact_type = @entity_type.fact_type = fact_type
226
+ @entity_type.create_implicit_fact_types # REVISIT: Could there be readings for the implicit fact types here?
227
+ @fact_type
228
+ end
229
+
230
+ def add_supertype(supertype_name, not_identifying)
231
+ trace :supertype, "Adding #{not_identifying ? '' : 'identifying '}supertype #{supertype_name} to #{@entity_type.name}" do
232
+ supertype = @vocabulary.valid_entity_type_name(supertype_name) ||
233
+ @constellation.EntityType(@vocabulary, supertype_name, :concept => :new) # Should always already exist
234
+
235
+ # Did we already know about this supertyping?
236
+ return if @entity_type.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == supertype}
237
+
238
+ # By default, the first supertype identifies this entity type
239
+ is_identifying_supertype = !not_identifying && @entity_type.all_type_inheritance_as_subtype.size == 0
240
+
241
+ inheritance_fact = @constellation.TypeInheritance(@entity_type, supertype, :concept => :new)
242
+
243
+ assimilation_pragmas = ['absorbed', 'separate', 'partitioned']
244
+ assimilations = @pragmas.select { |p| assimilation_pragmas.include? p}
245
+ @pragmas -= assimilation_pragmas
246
+ raise "Conflicting assimilation pragmas #{assimilations*', '}" if assimilations.size > 1
247
+ inheritance_fact.assimilation = assimilations[0]
248
+
249
+ # Create a reading:
250
+ sub_role = @constellation.Role(inheritance_fact, 0, :object_type => @entity_type, :concept => :new)
251
+ super_role = @constellation.Role(inheritance_fact, 1, :object_type => supertype, :concept => :new)
252
+
253
+ rs = @constellation.RoleSequence(:new)
254
+ @constellation.RoleRef(rs, 0, :role => sub_role)
255
+ @constellation.RoleRef(rs, 1, :role => super_role)
256
+ @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}", :is_negative => false)
257
+
258
+ rs2 = @constellation.RoleSequence(:new)
259
+ @constellation.RoleRef(rs2, 0, :role => super_role)
260
+ @constellation.RoleRef(rs2, 1, :role => sub_role)
261
+ # Decide in which order to include is a/is an. Provide both, but in order.
262
+ n = 'aeioh'.include?(sub_role.object_type.name.downcase[0]) ? 'n' : ''
263
+ @constellation.Reading(inheritance_fact, 2, :role_sequence => rs2, :text => "{0} is a#{n} {1}", :is_negative => false)
264
+
265
+ if is_identifying_supertype
266
+ inheritance_fact.provides_identification = true
267
+ end
268
+
269
+ # Create uniqueness constraints over the subtyping fact type.
270
+ p1rs = @constellation.RoleSequence(:new)
271
+ @constellation.RoleRef(p1rs, 0).role = sub_role
272
+ pc1 = @constellation.PresenceConstraint(:new, :vocabulary => @vocabulary)
273
+ pc1.name = "#{@entity_type.name}MustHaveSupertype#{supertype.name}"
274
+ pc1.role_sequence = p1rs
275
+ pc1.is_mandatory = true # A subtype instance must have a supertype instance
276
+ pc1.min_frequency = 1
277
+ pc1.max_frequency = 1
278
+ pc1.is_preferred_identifier = false
279
+ trace :constraint, "Made new subtype PC GUID=#{pc1.concept.guid} min=1 max=1 over #{p1rs.describe}"
280
+
281
+ p2rs = @constellation.RoleSequence(:new)
282
+ constellation.RoleRef(p2rs, 0).role = super_role
283
+ pc2 = constellation.PresenceConstraint(:new, :vocabulary => @vocabulary)
284
+ pc2.name = "#{supertype.name}MayBeA#{@entity_type.name}"
285
+ pc2.role_sequence = p2rs
286
+ pc2.is_mandatory = false
287
+ pc2.min_frequency = 0
288
+ pc2.max_frequency = 1
289
+ # The supertype role often identifies the subtype:
290
+ pc2.is_preferred_identifier = inheritance_fact.provides_identification
291
+ trace :supertype, "identification of #{@entity_type.name} via supertype #{supertype.name} was #{inheritance_fact.provides_identification ? '' : 'not '}added"
292
+ trace :constraint, "Made new supertype PC GUID=#{pc2.concept.guid} min=1 max=1 over #{p2rs.describe}"
293
+ end
294
+ end
295
+
296
+ def make_entity_type_refmode_valuetypes(name, mode, parameters)
297
+ vt_name = "#{name}#{mode}"
298
+ vt = nil
299
+ trace :entity, "Preparing value type #{vt_name} for reference mode" do
300
+ # Find an existing ValueType called 'vt_name' or 'name vtname'
301
+ # or find/create the supertype '#{mode}' and the subtype
302
+ unless vt = @vocabulary.valid_object_type_name(vt_name) or
303
+ vt = @vocabulary.valid_object_type_name(vt_name = "#{name} #{mode}")
304
+ base_vt = @vocabulary.valid_value_type_name(mode) ||
305
+ @constellation.ValueType(@vocabulary, mode, :concept => :new)
306
+ vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt, :concept => :new)
307
+ if parameters
308
+ length, scale = *parameters
309
+ vt.length = length if length
310
+ vt.scale = scale if scale
311
+ end
312
+ else
313
+ trace :entity, "Value type #{vt_name} already exists"
314
+ end
315
+ end
316
+
317
+ # REVISIT: If we do this, it gets emitted twice when we generate CQL.
318
+ # The generator should detect that the value_constraint is the same and not emit it.
319
+ #if (ranges = identification[:value_constraint])
320
+ # vt.value_constraint = value_constraint(ranges, identification[:enforcement])
321
+ #end
322
+ @reference_mode_value_type = vt
323
+ end
324
+
325
+ def complete_reference_mode_fact_type(fact_types)
326
+ return unless identifying_type = @reference_mode_value_type
327
+
328
+ # Find an existing fact type, if any:
329
+ entity_role = identifying_role = nil
330
+ fact_type = fact_types.detect do |ft|
331
+ identifying_role = ft.all_role.detect{|r| r.object_type == identifying_type } and
332
+ entity_role = ft.all_role.detect{|r| r.object_type == @entity_type }
333
+ end
334
+
335
+ # Create an identifying fact type if needed:
336
+ unless fact_type
337
+ fact_type = @constellation.FactType(:new)
338
+ fact_types << fact_type
339
+ entity_role = @constellation.Role(fact_type, 0, :object_type => @entity_type, :concept => :new)
340
+ identifying_role = @constellation.Role(fact_type, 1, :object_type => identifying_type, :concept => :new)
341
+ end
342
+ @identification[0].role = identifying_role
343
+
344
+ if (value_constraint = @identification[0].value_constraint)
345
+ # The value_constraint applies only to the value role, not to the underlying value type
346
+ # Decide whether this puts the value_constraint in the right place:
347
+ value_constraint.constellation = fact_type.constellation
348
+ identifying_role.role_value_constraint = value_constraint.compile
349
+ end
350
+
351
+ # Find all role sequences over the fact type's two roles
352
+ rss = entity_role.all_role_ref.select do |rr|
353
+ rr.role_sequence.all_role_ref.size == 2 &&
354
+ (rr.role_sequence.all_role_ref.to_a-[rr])[0].role == identifying_role
355
+ end.map{|rr| rr.role_sequence}
356
+
357
+ # Make a forward reading, if there is none already:
358
+ # Find or create RoleSequences for the forward and reverse readings:
359
+ rs01 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [entity_role, identifying_role] }[0]
360
+ if !rs01
361
+ rs01 = @constellation.RoleSequence(:new)
362
+ @constellation.RoleRef(rs01, 0, :role => entity_role)
363
+ @constellation.RoleRef(rs01, 1, :role => identifying_role)
364
+ end
365
+ if rs01.all_reading.empty?
366
+ @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}", :is_negative => false)
367
+ trace :mode, "Creating new forward reading '#{entity_role.object_type.name} has #{identifying_type.name}'"
368
+ else
369
+ trace :mode, "Using existing forward reading"
370
+ end
371
+
372
+ # Make a reverse reading if none exists
373
+ rs10 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [identifying_role, entity_role] }[0]
374
+ if !rs10
375
+ rs10 = @constellation.RoleSequence(:new)
376
+ @constellation.RoleRef(rs10, 0, :role => identifying_role)
377
+ @constellation.RoleRef(rs10, 1, :role => entity_role)
378
+ end
379
+ if rs10.all_reading.empty?
380
+ @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}", :is_negative => false)
381
+ trace :mode, "Creating new reverse reading '#{identifying_type.name} is of #{entity_role.object_type.name}'"
382
+ else
383
+ trace :mode, "Using existing reverse reading"
384
+ end
385
+
386
+ # Entity must have one identifying instance. Find or create the role sequence, then create a PC if necessary
387
+ rs0 = entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}[0]
388
+ if rs0
389
+ rs0 = rs0.role_sequence
390
+ trace :mode, "Using existing EntityType role sequence"
391
+ else
392
+ rs0 = @constellation.RoleSequence(:new)
393
+ @constellation.RoleRef(rs0, 0, :role => entity_role)
394
+ trace :mode, "Creating new EntityType role sequence"
395
+ end
396
+ if (rs0.all_presence_constraint.size == 0)
397
+ constraint = @constellation.PresenceConstraint(
398
+ :new,
399
+ :name => '',
400
+ :vocabulary => @vocabulary,
401
+ :role_sequence => rs0,
402
+ :min_frequency => 1,
403
+ :max_frequency => 1,
404
+ :is_preferred_identifier => false,
405
+ :is_mandatory => true
406
+ )
407
+ trace :constraint, "Made new refmode PC GUID=#{constraint.concept.guid} min=1 max=1 over #{rs0.describe}"
408
+ else
409
+ trace :mode, "Using existing EntityType PresenceConstraint"
410
+ end
411
+
412
+ # Value Type must have a value type. Find or create the role sequence, then create a PC if necessary
413
+ trace :mode, "identifying_role has #{identifying_role.all_role_ref.size} attached sequences"
414
+ trace :mode, "identifying_role has #{identifying_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
415
+ rs1 = identifying_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
416
+ if (!rs1)
417
+ rs1 = @constellation.RoleSequence(:new)
418
+ @constellation.RoleRef(rs1, 0, :role => identifying_role)
419
+ trace :mode, "Creating new ValueType role sequence"
420
+ else
421
+ rs1 = rs1.role_sequence
422
+ trace :mode, "Using existing ValueType role sequence"
423
+ end
424
+ if (rs1.all_presence_constraint.size == 0)
425
+ constraint = @constellation.PresenceConstraint(
426
+ :new,
427
+ :name => '',
428
+ :vocabulary => @vocabulary,
429
+ :role_sequence => rs1,
430
+ :min_frequency => 0,
431
+ :max_frequency => 1,
432
+ :is_preferred_identifier => true,
433
+ :is_mandatory => false
434
+ )
435
+ trace :constraint, "Made new refmode ValueType PC GUID=#{constraint.concept.guid} min=0 max=1 over #{rs1.describe}"
436
+ else
437
+ trace :mode, "Marking existing ValueType PresenceConstraint as preferred"
438
+ rs1.all_presence_constraint.single.is_preferred_identifier = true
439
+ end
440
+ end
441
+
442
+ def to_s
443
+ "EntityType: #{super} #{
444
+ @supertypes.size > 0 ? "< #{@supertypes*','} " : ''
445
+ }#{
446
+ @identification.is_a?(ReferenceMode) ? @identification.to_s : @identification.inspect
447
+ }#{
448
+ @clauses.size > 0 ? " where #{@clauses.inspect}" : ''
449
+ }#{
450
+ @pragmas.size > 0 ? ", pragmas [#{@pragmas*','}]" : ''
451
+ };"
452
+ end
453
+ end
454
+
455
+ end
456
+ end
457
+ end