activefacts-cql 1.8.1 → 1.8.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +6 -5
- data/activefacts-cql.gemspec +4 -3
- data/lib/activefacts/cql/compiler.rb +29 -22
- data/lib/activefacts/cql/compiler/clause.rb +86 -84
- data/lib/activefacts/cql/compiler/constraint.rb +53 -53
- data/lib/activefacts/cql/compiler/entity_type.rb +28 -28
- data/lib/activefacts/cql/compiler/expression.rb +27 -27
- data/lib/activefacts/cql/compiler/fact.rb +37 -37
- data/lib/activefacts/cql/compiler/fact_type.rb +91 -85
- data/lib/activefacts/cql/compiler/informal.rb +48 -0
- data/lib/activefacts/cql/compiler/query.rb +52 -52
- data/lib/activefacts/cql/compiler/shared.rb +17 -17
- data/lib/activefacts/cql/compiler/value_type.rb +11 -11
- data/lib/activefacts/cql/parser/CQLParser.treetop +76 -56
- data/lib/activefacts/cql/parser/Context.treetop +15 -17
- data/lib/activefacts/cql/parser/Expressions.treetop +21 -21
- data/lib/activefacts/cql/parser/FactTypes.treetop +216 -216
- data/lib/activefacts/cql/parser/Language/English.treetop +136 -131
- data/lib/activefacts/cql/parser/Language/French.treetop +118 -118
- data/lib/activefacts/cql/parser/Language/Mandarin.treetop +111 -111
- data/lib/activefacts/cql/parser/LexicalRules.treetop +45 -45
- data/lib/activefacts/cql/parser/ObjectTypes.treetop +120 -120
- data/lib/activefacts/cql/parser/Terms.treetop +63 -63
- data/lib/activefacts/cql/parser/ValueTypes.treetop +71 -71
- data/lib/activefacts/cql/require.rb +8 -8
- data/lib/activefacts/cql/verbaliser.rb +88 -88
- data/lib/activefacts/cql/version.rb +1 -1
- data/lib/activefacts/input/cql.rb +7 -7
- metadata +12 -16
@@ -25,12 +25,12 @@ module ActiveFacts
|
|
25
25
|
@identification = identification
|
26
26
|
@pragmas = pragmas
|
27
27
|
@clauses = clauses || []
|
28
|
-
|
28
|
+
@context_note = context_note
|
29
29
|
end
|
30
30
|
|
31
31
|
def compile
|
32
32
|
@entity_type = @vocabulary.valid_entity_type_name(@name) ||
|
33
|
-
|
33
|
+
@constellation.EntityType(@vocabulary, @name, :concept => :new)
|
34
34
|
@entity_type.is_independent = true if @pragmas.delete('independent')
|
35
35
|
|
36
36
|
# REVISIT: CQL needs a way to indicate whether subtype migration can occur.
|
@@ -38,9 +38,9 @@ module ActiveFacts
|
|
38
38
|
@supertypes.each_with_index do |supertype_name, i|
|
39
39
|
add_supertype(supertype_name, @identification || i > 0)
|
40
40
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
@pragmas.each do |p|
|
42
|
+
@constellation.ConceptAnnotation(:concept => @entity_type.concept, :mapping_annotation => p)
|
43
|
+
end if @pragmas
|
44
44
|
|
45
45
|
context = CompilationContext.new(@vocabulary)
|
46
46
|
|
@@ -61,9 +61,9 @@ module ActiveFacts
|
|
61
61
|
|
62
62
|
make_preferred_identifier_over_roles identifying_roles
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
if @context_note
|
65
|
+
@context_note.compile(@constellation, @entity_type)
|
66
|
+
end
|
67
67
|
|
68
68
|
@clauses.each do |clause|
|
69
69
|
next unless clause.context_note
|
@@ -138,7 +138,7 @@ module ActiveFacts
|
|
138
138
|
#:is_mandatory => true,
|
139
139
|
#:min_frequency => 1,
|
140
140
|
)
|
141
|
-
|
141
|
+
trace :constraint, "Made new preferred PC GUID=#{pc.concept.guid} min=nil max=1 over #{role_sequence.describe}"
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
@@ -165,9 +165,9 @@ module ActiveFacts
|
|
165
165
|
|
166
166
|
fact_type = create_identifying_fact_type(context, clauses)
|
167
167
|
fact_types << fact_type if fact_type
|
168
|
-
|
169
|
-
|
170
|
-
|
168
|
+
unless fact_type.all_role.detect{|r| r.object_type == @entity_type}
|
169
|
+
objectify_existing_fact_type(fact_type)
|
170
|
+
end
|
171
171
|
end
|
172
172
|
fact_types
|
173
173
|
end
|
@@ -175,9 +175,9 @@ module ActiveFacts
|
|
175
175
|
def create_identifying_fact_type context, clauses
|
176
176
|
# See if any fact type already exists (this ET cannot be a player, but might objectify it)
|
177
177
|
existing_clauses = clauses.select{ |clause| clause.match_existing_fact_type context }
|
178
|
-
|
179
|
-
|
180
|
-
|
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
181
|
any_matched = existing_clauses.size > 0
|
182
182
|
|
183
183
|
operation = any_matched ? 'Objectifying' : 'Creating'
|
@@ -205,9 +205,9 @@ module ActiveFacts
|
|
205
205
|
|
206
206
|
def objectify_existing_fact_type fact_type
|
207
207
|
raise "#{@name} cannot objectify fact type '#{fact_type.entity_type.name}' that's already objectified" if fact_type.entity_type
|
208
|
-
|
209
|
-
|
210
|
-
|
208
|
+
if @fact_type
|
209
|
+
raise "#{@name} cannot objectify '#{fact_type.default_reading}', it already objectifies '#{@fact_type.default_reading}'"
|
210
|
+
end
|
211
211
|
|
212
212
|
if fact_type.internal_presence_constraints.select{|pc| pc.max_frequency == 1}.size == 0
|
213
213
|
# If there's no existing uniqueness constraint over this fact type, make a spanning one.
|
@@ -219,7 +219,7 @@ module ActiveFacts
|
|
219
219
|
:is_preferred_identifier => false, # We only get here when there is a reference mode on the entity type
|
220
220
|
:max_frequency => 1
|
221
221
|
)
|
222
|
-
|
222
|
+
trace :constraint, "Made new objectification PC GUID=#{pc.concept.guid} min=nil max=1 over #{fact_type.preferred_reading.role_sequence.describe}"
|
223
223
|
end
|
224
224
|
|
225
225
|
@fact_type = @entity_type.fact_type = fact_type
|
@@ -230,7 +230,7 @@ module ActiveFacts
|
|
230
230
|
def add_supertype(supertype_name, not_identifying)
|
231
231
|
trace :supertype, "Adding #{not_identifying ? '' : 'identifying '}supertype #{supertype_name} to #{@entity_type.name}" do
|
232
232
|
supertype = @vocabulary.valid_entity_type_name(supertype_name) ||
|
233
|
-
|
233
|
+
@constellation.EntityType(@vocabulary, supertype_name, :concept => :new) # Should always already exist
|
234
234
|
|
235
235
|
# Did we already know about this supertyping?
|
236
236
|
return if @entity_type.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == supertype}
|
@@ -238,12 +238,12 @@ module ActiveFacts
|
|
238
238
|
# By default, the first supertype identifies this entity type
|
239
239
|
is_identifying_supertype = !not_identifying && @entity_type.all_type_inheritance_as_subtype.size == 0
|
240
240
|
|
241
|
-
|
241
|
+
assimilation_pragmas = ['absorbed', 'separate', 'partitioned']
|
242
242
|
assimilations = @pragmas.select { |p| assimilation_pragmas.include? p}
|
243
|
-
|
243
|
+
@pragmas -= assimilation_pragmas
|
244
244
|
raise "Conflicting assimilation pragmas #{assimilations*', '}" if assimilations.size > 1
|
245
245
|
|
246
|
-
|
246
|
+
@entity_type.add_supertype(supertype, is_identifying_supertype, assimilations[0])
|
247
247
|
end
|
248
248
|
end
|
249
249
|
|
@@ -252,11 +252,11 @@ module ActiveFacts
|
|
252
252
|
vt = nil
|
253
253
|
trace :entity, "Preparing value type #{vt_name} for reference mode" do
|
254
254
|
# Find an existing ValueType called 'vt_name' or 'name vtname'
|
255
|
-
|
255
|
+
# or find/create the supertype '#{mode}' and the subtype
|
256
256
|
unless vt = @vocabulary.valid_object_type_name(vt_name) or
|
257
|
-
|
257
|
+
vt = @vocabulary.valid_object_type_name(vt_name = "#{name} #{mode}")
|
258
258
|
base_vt = @vocabulary.valid_value_type_name(mode) ||
|
259
|
-
|
259
|
+
@constellation.ValueType(@vocabulary, mode, :concept => :new)
|
260
260
|
vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt, :concept => :new)
|
261
261
|
if parameters
|
262
262
|
length, scale = *parameters
|
@@ -358,7 +358,7 @@ module ActiveFacts
|
|
358
358
|
:is_preferred_identifier => false,
|
359
359
|
:is_mandatory => true
|
360
360
|
)
|
361
|
-
|
361
|
+
trace :constraint, "Made new refmode PC GUID=#{constraint.concept.guid} min=1 max=1 over #{rs0.describe}"
|
362
362
|
else
|
363
363
|
trace :mode, "Using existing EntityType PresenceConstraint"
|
364
364
|
end
|
@@ -386,7 +386,7 @@ module ActiveFacts
|
|
386
386
|
:is_preferred_identifier => true,
|
387
387
|
:is_mandatory => false
|
388
388
|
)
|
389
|
-
|
389
|
+
trace :constraint, "Made new refmode ValueType PC GUID=#{constraint.concept.guid} min=0 max=1 over #{rs1.describe}"
|
390
390
|
else
|
391
391
|
trace :mode, "Marking existing ValueType PresenceConstraint as preferred"
|
392
392
|
rs1.all_presence_constraint.single.is_preferred_identifier = true
|
@@ -28,7 +28,7 @@ module ActiveFacts
|
|
28
28
|
def trailing_adjective; nil; end
|
29
29
|
def value_constraint; nil; end
|
30
30
|
def literal; nil; end
|
31
|
-
|
31
|
+
def side_effects; nil; end
|
32
32
|
attr_accessor :player # What ObjectType does the Binding denote
|
33
33
|
attr_accessor :binding # What Binding for that ObjectType
|
34
34
|
attr_accessor :clause # What clause does the result participate in?
|
@@ -48,9 +48,9 @@ module ActiveFacts
|
|
48
48
|
attr_reader :fact_type
|
49
49
|
def objectified_as; self; end # The Reference which objectified this fact type
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
def initialize
|
52
|
+
@certainty = true # Assume it's definite
|
53
|
+
end
|
54
54
|
|
55
55
|
def operands context = nil
|
56
56
|
raise "REVISIT: Implement operand enumeration in the operator subclass #{self.class.name}"
|
@@ -92,8 +92,8 @@ module ActiveFacts
|
|
92
92
|
def result_value_type(context, name)
|
93
93
|
vocabulary = context.vocabulary
|
94
94
|
constellation = vocabulary.constellation
|
95
|
-
|
96
|
-
|
95
|
+
vocabulary.valid_value_type_name(name) ||
|
96
|
+
constellation.ValueType(vocabulary, name, :concept => :new)
|
97
97
|
end
|
98
98
|
|
99
99
|
def is_naked_object_type
|
@@ -117,9 +117,9 @@ module ActiveFacts
|
|
117
117
|
# REVISIT: We should auto-create steps from Entity Types to an identifying ValueType
|
118
118
|
# REVISIT: We should traverse up the supertype of ValueTypes to find a DataType
|
119
119
|
@fact_type = clause_ast.match_existing_fact_type(context, :exact_type => true)
|
120
|
-
|
121
|
-
|
122
|
-
|
120
|
+
if clause.certainty == false
|
121
|
+
raise "Negated fact types in expressions are not yet supported: #{clause.inspect}"
|
122
|
+
end
|
123
123
|
return @fact_type if @fact_type
|
124
124
|
|
125
125
|
@fact_type = clause_ast.make_fact_type context.vocabulary
|
@@ -130,9 +130,9 @@ module ActiveFacts
|
|
130
130
|
opnds.each do |opnd|
|
131
131
|
next unless opnd.is_a?(Operation)
|
132
132
|
opnd.match_existing_fact_type context
|
133
|
-
|
134
|
-
|
135
|
-
|
133
|
+
if opnd.certainty == false
|
134
|
+
raise "Negated fact types in expressions are not yet supported: #{opnd.inspect}"
|
135
|
+
end
|
136
136
|
end
|
137
137
|
@fact_type
|
138
138
|
end
|
@@ -189,8 +189,8 @@ module ActiveFacts
|
|
189
189
|
end
|
190
190
|
v = context.vocabulary
|
191
191
|
@boolean ||=
|
192
|
-
|
193
|
-
|
192
|
+
v.constellation.ValueType[[[v.name], 'Boolean']] ||
|
193
|
+
v.constellation.ValueType(v, 'Boolean', :concept => :new)
|
194
194
|
@player = @boolean
|
195
195
|
end
|
196
196
|
end
|
@@ -207,20 +207,20 @@ module ActiveFacts
|
|
207
207
|
|
208
208
|
def to_s
|
209
209
|
"COMPARE#{
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
210
|
+
operator
|
211
|
+
}(#{
|
212
|
+
case @certainty
|
213
|
+
when nil; 'maybe '
|
214
|
+
when false; 'negated '
|
215
|
+
# else 'definitely '
|
216
|
+
end
|
217
|
+
}#{
|
218
|
+
e1.to_s
|
219
|
+
} WITH #{
|
220
|
+
e2.to_s
|
217
221
|
}#{
|
218
|
-
|
219
|
-
|
220
|
-
e2.to_s
|
221
|
-
}#{
|
222
|
-
@qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']'
|
223
|
-
})"
|
222
|
+
@qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']'
|
223
|
+
})"
|
224
224
|
end
|
225
225
|
end
|
226
226
|
|
@@ -10,17 +10,17 @@ module ActiveFacts
|
|
10
10
|
|
11
11
|
def compile
|
12
12
|
@population = @constellation.Population[[@vocabulary.identifying_role_values, @population_name]] ||
|
13
|
-
|
13
|
+
@constellation.Population(@vocabulary, @population_name, :concept => :new)
|
14
14
|
|
15
15
|
@context = CompilationContext.new(@vocabulary)
|
16
16
|
@context.bind @clauses
|
17
17
|
@context.left_contraction_allowed = true
|
18
18
|
@clauses.each do |clause|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
ft = clause.match_existing_fact_type @context
|
20
|
+
if clause.certainty == false
|
21
|
+
raise "Negated fact #{clause.inspect} is not supported"
|
22
|
+
end
|
23
|
+
end
|
24
24
|
|
25
25
|
# Figure out the simple existential facts and find fact types:
|
26
26
|
@bound_facts = []
|
@@ -84,16 +84,16 @@ module ActiveFacts
|
|
84
84
|
# Find the roles of this clause that do not yet have an instance
|
85
85
|
bare_roles = clause.refs.
|
86
86
|
select do |ref|
|
87
|
-
|
87
|
+
if !ref.binding.instance and ref.literal
|
88
88
|
ref.binding.instance = instance_identified_by_literal(ref.binding.player, ref.literal)
|
89
|
-
|
89
|
+
end
|
90
90
|
|
91
91
|
next false if ref.binding.instance
|
92
92
|
true
|
93
93
|
end
|
94
94
|
|
95
|
-
|
96
|
-
|
95
|
+
trace :instance_detail, "Considering '#{clause.display}' with "+
|
96
|
+
(bare_roles.empty? ? "no bare roles" : "bare roles: #{bare_roles.map{|ref| ref.player.name}*", "}") do
|
97
97
|
|
98
98
|
# If all the roles are in place, we can bind the rest of this clause:
|
99
99
|
return true if bare_roles.size == 0 && bind_complete_fact(clause)
|
@@ -149,7 +149,7 @@ module ActiveFacts
|
|
149
149
|
fact = clause.refs[0].binding.instance.fact
|
150
150
|
else
|
151
151
|
# Check that this fact doesn't already exist
|
152
|
-
|
152
|
+
trace :instance_detail, "Searching for existing fact instance"
|
153
153
|
|
154
154
|
fact = clause.fact_type.all_fact.detect do |f|
|
155
155
|
|
@@ -207,7 +207,7 @@ module ActiveFacts
|
|
207
207
|
return false unless identifying_binding # This happens when we have a bare objectification
|
208
208
|
identifying_instance = identifying_binding.instance
|
209
209
|
preferred_identifier = entity_type.preferred_identifier
|
210
|
-
|
210
|
+
role_count = preferred_identifier.role_sequence.all_role_ref.size
|
211
211
|
|
212
212
|
trace :instance, "A #{binding.player.name} is #{role_count > 1 ? 'partly ':''}identified in #{clause.inspect}"
|
213
213
|
|
@@ -251,21 +251,21 @@ module ActiveFacts
|
|
251
251
|
return false
|
252
252
|
end
|
253
253
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
254
|
+
instance = @constellation.Instance(:new, :object_type => entity_type, :population => @population)
|
255
|
+
binding.instance = instance
|
256
|
+
@bound_facts << instance
|
257
|
+
identifiers.each do |rr, identifying_clause, identifying_binding, identifying_instance|
|
258
|
+
# This clause provides the identifying literal for the entity_type
|
259
|
+
id_fact =
|
260
|
+
identifying_clause.fact =
|
261
|
+
@constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => @population)
|
262
|
+
@bound_facts << id_fact
|
263
|
+
role = (rr.role.fact_type.all_role.to_a-[rr.role])[0]
|
264
|
+
@constellation.RoleValue(:instance => instance, :fact => id_fact, :population => @population, :role => role)
|
265
|
+
@constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => @population)
|
266
|
+
trace :instance, "Assert #{id_fact.verbalise.inspect} (existential) #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
|
267
|
+
end
|
268
|
+
trace :instance, "Assert #{instance.verbalise.inspect} #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
|
269
269
|
|
270
270
|
true # Done with this clause
|
271
271
|
end
|
@@ -276,11 +276,11 @@ module ActiveFacts
|
|
276
276
|
else
|
277
277
|
trace :instance_detail, "Assert Value #{object_type.name} #{literal.inspect}" do
|
278
278
|
is_literal_string = literal.literal.is_a?(String)
|
279
|
-
|
280
|
-
|
279
|
+
# REVISIT: Check for subtypes and supertypes also, and promote type if necessary
|
280
|
+
instance = object_type.all_instance.detect do |i|
|
281
281
|
#instance = @constellation.Instance.detect do |key, i|
|
282
282
|
# REVISIT: And same unit
|
283
|
-
|
283
|
+
trace :instance_detail2, "Comparing #{i.value.literal.inspect} to #{literal.literal.to_s.inspect}"
|
284
284
|
i.population == @population &&
|
285
285
|
i.value &&
|
286
286
|
i.value.literal.inspect == literal.literal.to_s.inspect &&
|
@@ -289,14 +289,14 @@ module ActiveFacts
|
|
289
289
|
#instance = object_type.all_instance.detect { |instance|
|
290
290
|
# instance.population == @population && instance.value == literal
|
291
291
|
#}
|
292
|
-
|
293
|
-
|
292
|
+
if instance
|
293
|
+
trace :instance, "Instance already known: #{instance.verbalise.inspect}"
|
294
294
|
else
|
295
295
|
instance = @constellation.Instance(:new)
|
296
296
|
instance.object_type = object_type
|
297
297
|
instance.population = @population
|
298
298
|
instance.value = [literal.to_s, is_literal_string, nil]
|
299
|
-
|
299
|
+
trace :instance, "Assert #{instance.verbalise.inspect} #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
|
300
300
|
@bound_facts << instance
|
301
301
|
end
|
302
302
|
instance
|
@@ -323,20 +323,20 @@ module ActiveFacts
|
|
323
323
|
}
|
324
324
|
if instance_rv
|
325
325
|
instance = existing_instance
|
326
|
-
|
326
|
+
trace :instance, "Already known: #{instance.verbalise.inspect}"
|
327
327
|
else
|
328
328
|
# This fact has no clause.
|
329
|
-
|
329
|
+
trace :instance_detail, "Creating implicit existential fact #{role.fact_type.default_reading}"
|
330
330
|
fact = @constellation.Fact(:new, :fact_type => role.fact_type, :population => @population)
|
331
331
|
@bound_facts << fact
|
332
332
|
# This instance will be associated with its binding by our caller
|
333
333
|
instance = @constellation.Instance(:new, :object_type => object_type, :population => @population)
|
334
|
-
|
334
|
+
trace :instance_detail, "Creating Entity #{object_type.name} identified by '#{literal}' #{@population.name.size>0 ? " in "+@population.name.inspect : ''}"
|
335
335
|
@bound_facts << instance
|
336
336
|
# The identifying fact type has two roles; create both role instances:
|
337
337
|
@constellation.RoleValue(:instance => identifying_instance, :fact => fact, :population => @population, :role => role)
|
338
338
|
@constellation.RoleValue(:instance => instance, :fact => fact, :population => @population, :role => (role.fact_type.all_role-[role])[0])
|
339
|
-
|
339
|
+
trace :instance, "Assert #{instance.verbalise.inspect}"
|
340
340
|
end
|
341
341
|
instance
|
342
342
|
end
|
@@ -12,17 +12,17 @@ module ActiveFacts
|
|
12
12
|
@returning = returning || []
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def to_s
|
16
|
+
inspect
|
17
|
+
end
|
18
18
|
|
19
19
|
def inspect
|
20
20
|
"Query: " +
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
if @conditions.empty?
|
22
|
+
''
|
23
|
+
else
|
24
|
+
'where ' + @conditions.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.inspect}*' '
|
25
|
+
end
|
26
26
|
# REVISIT: @returning = returning
|
27
27
|
end
|
28
28
|
|
@@ -55,10 +55,10 @@ module ActiveFacts
|
|
55
55
|
|
56
56
|
def match_condition_fact_types
|
57
57
|
@conditions.each do |condition|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
trace :projection, "matching condition fact_type #{condition.inspect}" do
|
59
|
+
fact_type = condition.match_existing_fact_type @context
|
60
|
+
raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
|
61
|
+
end
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
@@ -105,12 +105,12 @@ module ActiveFacts
|
|
105
105
|
# * Objectify the fact type if @name
|
106
106
|
#
|
107
107
|
|
108
|
-
|
108
|
+
# Prepare to objectify the fact type (so readings for link fact types can be created)
|
109
109
|
if @name
|
110
|
-
|
110
|
+
entity_type = @vocabulary.valid_entity_type_name(@name)
|
111
111
|
raise "You can't objectify #{@name}, it already exists" if entity_type
|
112
112
|
@entity_type = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type, :concept => :new)
|
113
|
-
|
113
|
+
end
|
114
114
|
|
115
115
|
prepare_roles @clauses
|
116
116
|
|
@@ -119,13 +119,13 @@ module ActiveFacts
|
|
119
119
|
|
120
120
|
return true unless @clauses.size > 0 # Nothing interesting was said.
|
121
121
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
122
|
+
if @entity_type
|
123
|
+
# Extract readings for link fact types
|
124
|
+
@link_fact_type_clauses, @clauses =
|
125
|
+
@clauses.partition do |clause|
|
126
|
+
clause.refs.size == 2 and clause.refs.detect{|ref| ref.player == @entity_type}
|
127
|
+
end
|
128
|
+
end
|
129
129
|
|
130
130
|
# See if any existing fact type is being invoked (presumably to objectify or extend it)
|
131
131
|
@fact_type = check_compatibility_of_matched_clauses
|
@@ -139,7 +139,7 @@ module ActiveFacts
|
|
139
139
|
first_clause.make_embedded_constraints vocabulary
|
140
140
|
@existing_clauses = [first_clause]
|
141
141
|
elsif (n = @clauses.size - @existing_clauses.size) > 0
|
142
|
-
|
142
|
+
raise "Cannot extend a negated fact type" if @existing_clauses.detect {|clause| clause.certainty == false }
|
143
143
|
trace :binding, "Extending existing fact type with #{n} new readings"
|
144
144
|
end
|
145
145
|
|
@@ -158,20 +158,20 @@ module ActiveFacts
|
|
158
158
|
end
|
159
159
|
|
160
160
|
if @name
|
161
|
-
|
161
|
+
# Objectify the fact type:
|
162
162
|
@entity_type.fact_type = @fact_type
|
163
163
|
if @fact_type.entity_type and @name != @fact_type.entity_type.name
|
164
164
|
raise "Cannot objectify fact type as #{@name} and as #{@fact_type.entity_type.name}"
|
165
165
|
end
|
166
166
|
ifts = @entity_type.create_link_fact_types
|
167
|
-
|
167
|
+
create_link_fact_type_readings(ifts)
|
168
168
|
if @pragmas
|
169
169
|
@entity_type.is_independent = true if @pragmas.delete('independent')
|
170
170
|
end
|
171
171
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
172
|
+
@pragmas.each do |p|
|
173
|
+
@constellation.ConceptAnnotation(:concept => (@entity_type||@fact_type).concept, :mapping_annotation => p)
|
174
|
+
end if @pragmas
|
175
175
|
|
176
176
|
@clauses.each do |clause|
|
177
177
|
next unless clause.context_note
|
@@ -182,36 +182,40 @@ module ActiveFacts
|
|
182
182
|
make_default_identifier_for_fact_type if @conditions.empty?
|
183
183
|
|
184
184
|
# Compile the conditions:
|
185
|
-
super
|
185
|
+
query = super
|
186
186
|
unless @conditions.empty?
|
187
187
|
@clauses.each do |clause|
|
188
188
|
project_clause_roles(clause)
|
189
189
|
end
|
190
|
+
|
191
|
+
# Add the head to complete the derived fact type
|
192
|
+
query.derived_fact_type = @fact_type unless query.nil?
|
193
|
+
|
190
194
|
end
|
191
195
|
|
192
196
|
@fact_type
|
193
197
|
end
|
194
198
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
199
|
+
def create_link_fact_type_readings(ifts)
|
200
|
+
@link_fact_type_clauses.each do |clause|
|
201
|
+
#next false unless clause.refs.size == 2 and clause.refs.detect{|r| r.role.object_type == @entity_type }
|
202
|
+
other_ref = clause.refs.detect{|ref| ref.player != @entity_type}
|
203
|
+
ift = ifts.detect do |ift|
|
204
|
+
other_ref.binding.refs.map{|r| r.role}.include?(ift.implying_role)
|
205
|
+
end
|
206
|
+
unless ift
|
207
|
+
raise "Clause #{clause} in unmatched in definition of objectified fact type #{@name}"
|
208
|
+
next
|
209
|
+
end
|
210
|
+
# This clause is a reading for the LinkFactType ift
|
211
|
+
i = 0
|
212
|
+
reading_text = clause.phrases.map do |phrase|
|
213
|
+
next phrase if String === phrase
|
214
|
+
"{#{(i += 1)-1}}"
|
215
|
+
end*' '
|
216
|
+
clause.make_reading(@vocabulary, ift)
|
217
|
+
end
|
218
|
+
end
|
215
219
|
|
216
220
|
def project_clause_roles(clause)
|
217
221
|
# Attach the clause's role references to the projected roles of the query
|
@@ -235,7 +239,7 @@ module ActiveFacts
|
|
235
239
|
def check_compatibility_of_matched_clauses
|
236
240
|
# REVISIT: If we have conditions, we must match all given clauses exactly (no side-effects)
|
237
241
|
@existing_clauses = @clauses.
|
238
|
-
select{ |clause| clause.match_existing_fact_type
|
242
|
+
select{ |clause| clause.match_existing_fact_type(@context, exact_type: true) }.
|
239
243
|
# subtyping match is not allowed for fact type extension:
|
240
244
|
reject{ |clause| clause.side_effects.role_side_effects.detect{|se| se.common_supertype } }.
|
241
245
|
sort_by{ |clause| clause.side_effects.cost }
|
@@ -292,41 +296,43 @@ module ActiveFacts
|
|
292
296
|
|
293
297
|
# We need to check uniqueness constraints after processing the whole vocabulary
|
294
298
|
# raise "Fact type must be named as it has no identifying uniqueness constraint" unless @name || @fact_type.all_role.size == 1
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
299
|
+
unless @fact_type.all_role.size == 1
|
300
|
+
trace :constraint, "Need to check #{@fact_type.default_reading.inspect} for a uniqueness constraint"
|
301
|
+
fact_type.check_and_add_spanning_uniqueness_constraint = proc do
|
302
|
+
trace :constraint, "Checking #{@fact_type.default_reading.inspect} for a uniqueness constraint"
|
303
|
+
existing_pc = nil
|
304
|
+
found = @fact_type.all_role.
|
305
|
+
detect do |role|
|
306
|
+
role.all_role_ref.detect do |rr|
|
307
|
+
# This RoleSequence, to be relevant, must only reference roles of this fact type
|
308
|
+
rr.role_sequence.all_role_ref.all? {|rr2| rr2.role.fact_type == @fact_type} and
|
309
|
+
# The RoleSequence must have at least one uniqueness constraint
|
310
|
+
rr.role_sequence.all_presence_constraint.detect do |pc|
|
311
|
+
if pc.max_frequency == 1
|
312
|
+
existing_pc = pc
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
true # A place for a breakpoint
|
318
|
+
|
319
|
+
if !found
|
320
|
+
# There's no existing uniqueness constraint over the roles of this fact type. Add one
|
321
|
+
pc = @constellation.PresenceConstraint(
|
322
|
+
:concept => [:new, {:implication_rule => "spanning"}],
|
323
|
+
:vocabulary => @vocabulary,
|
324
|
+
:name => @fact_type.entity_type ? @fact_type.entity_type.name+"PK" : '',
|
325
|
+
:role_sequence => (rs = @fact_type.preferred_reading.role_sequence),
|
326
|
+
:max_frequency => 1,
|
327
|
+
:is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
|
328
|
+
)
|
329
|
+
pc.concept.topic = @fact_type.concept.topic
|
330
|
+
trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
|
331
|
+
elsif pc
|
332
|
+
trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
|
333
|
+
end
|
328
334
|
end
|
329
|
-
|
335
|
+
end
|
330
336
|
end
|
331
337
|
|
332
338
|
def has_more_adjectives(less, more)
|
@@ -407,7 +413,7 @@ module ActiveFacts
|
|
407
413
|
end
|
408
414
|
|
409
415
|
def inspect
|
410
|
-
|
416
|
+
s = super
|
411
417
|
"FactType: #{@conditions.size > 0 ? super+' ' : '' }#{@clauses.inspect}" +
|
412
418
|
(@pragmas && @pragmas.size > 0 ? ", pragmas [#{@pragmas.flatten.sort*','}]" : '')
|
413
419
|
|