activefacts 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -0
- data/examples/CQL/Metamodel.cql +5 -0
- data/lib/activefacts/cql/compiler.rb +25 -2
- data/lib/activefacts/cql/compiler/constraint.rb +9 -1
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -0
- data/lib/activefacts/cql/compiler/shared.rb +5 -1
- data/lib/activefacts/dependency_analyser.rb +182 -0
- data/lib/activefacts/generate/composition.rb +118 -0
- data/lib/activefacts/generate/cql.rb +3 -1
- data/lib/activefacts/generate/helpers/inject.rb +16 -0
- data/lib/activefacts/generate/rails/models.rb +1 -1
- data/lib/activefacts/generate/stats.rb +69 -0
- data/lib/activefacts/generate/topics.rb +265 -0
- data/lib/activefacts/generate/traits/oo.rb +73 -0
- data/lib/activefacts/generate/traits/ordered.rb +33 -0
- data/lib/activefacts/generate/traits/ruby.rb +210 -0
- data/lib/activefacts/input/orm.rb +158 -121
- data/lib/activefacts/persistence/columns.rb +1 -11
- data/lib/activefacts/persistence/foreignkey.rb +5 -6
- data/lib/activefacts/persistence/reference.rb +21 -1
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +236 -8
- data/lib/activefacts/vocabulary/metamodel.rb +11 -0
- data/lib/activefacts/vocabulary/query_evaluator.rb +304 -0
- metadata +11 -2
@@ -0,0 +1,304 @@
|
|
1
|
+
module ActiveFacts
|
2
|
+
module Metamodel
|
3
|
+
class QueryEvaluator
|
4
|
+
AFMM = ActiveFacts::Metamodel
|
5
|
+
|
6
|
+
def initialize query, population = nil
|
7
|
+
@query = query
|
8
|
+
|
9
|
+
raise "Query must be in the specified model" unless @query
|
10
|
+
|
11
|
+
# In theory a query can span more than one vocabulary. I don't do that!
|
12
|
+
@vocabulary = @query.all_variable.to_a.first.object_type.vocabulary
|
13
|
+
|
14
|
+
set_population(population)
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_population population
|
18
|
+
if population.is_a?(String) || population == nil
|
19
|
+
@population = @constellation.Population[[[@vocabulary.name], population ||= '']]
|
20
|
+
raise "Population #{population.inspect} does not exist" unless @population
|
21
|
+
elsif population.is_a?(AFMM::Population)
|
22
|
+
raise "Population #{population.name} is not in the correct vocabulary #{@vocabulary.name}" unless population.vocabulary == @vocabulary
|
23
|
+
@population = population
|
24
|
+
else
|
25
|
+
raise "Population #{population.inspcct} must be a Population or a Population name"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_variables
|
30
|
+
@query.all_variable.to_a
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :free_variables
|
34
|
+
|
35
|
+
def evaluate
|
36
|
+
@result_projection = {} # A set of {hash of Variable to Instance}
|
37
|
+
@free_variables = all_variables
|
38
|
+
hypothesis = trivial_bindings @free_variables
|
39
|
+
trace :query, "Evaluating query with free variables #{@free_variables.map{|v|v.object_type.name}.inspect} and hypothesis #{hypothesis.inspect}" do
|
40
|
+
query_level(@free_variables, hypothesis)
|
41
|
+
end
|
42
|
+
@result_projection
|
43
|
+
end
|
44
|
+
|
45
|
+
# From the array of variables, delete those that are already bound and return the hypothesis hash
|
46
|
+
def trivial_bindings variables
|
47
|
+
hypothesis = {}
|
48
|
+
|
49
|
+
variables.each do |var|
|
50
|
+
next if var.value == nil
|
51
|
+
# Look up the instances for this bound variables
|
52
|
+
instance = lookup_instance_by_value(var.object_type, var.value)
|
53
|
+
trace :result, "Binding was #{instance ? '' : 'un'}successful for #{var.object_type.name} #{var.value.inspect}"
|
54
|
+
hypothesis[var] = instance
|
55
|
+
end
|
56
|
+
|
57
|
+
hypothesis.each do |v, i|
|
58
|
+
variables.delete(v)
|
59
|
+
end
|
60
|
+
|
61
|
+
trace :result, "starting hypothesis: {#{hypothesis.map{|var, instance| [var.object_type.name, instance.verbalise] }*'=>'}}"
|
62
|
+
trace :result, "free variables are #{variables.map{|v| v.object_type.name}.inspect}"
|
63
|
+
|
64
|
+
hypothesis
|
65
|
+
end
|
66
|
+
|
67
|
+
def lookup_instance_by_value(object_type, value)
|
68
|
+
pi = object_type.is_a?(ActiveFacts::Metamodel::EntityType) && object_type.preferred_identifier
|
69
|
+
|
70
|
+
# A multi-role identifier cannot be satisfied by a single value:
|
71
|
+
return nil if pi && pi.role_sequence.all_role_ref.size > 1
|
72
|
+
|
73
|
+
if pi
|
74
|
+
identifying_role = pi.role_sequence.all_role_ref.single.role
|
75
|
+
identifying_instance = lookup_instance_by_value(identifying_role.object_type, value)
|
76
|
+
return nil unless identifying_instance
|
77
|
+
identifying_role_values =
|
78
|
+
identifying_instance.all_role_value.select{|rv| rv.role == identifying_role }
|
79
|
+
raise "Faulty Population has duplicate #{object_type.name} instance #{value}" if identifying_role_values.size > 1
|
80
|
+
identifying_role_value = identifying_role_values[0]
|
81
|
+
return nil unless identifying_role_value # The value exists, but doesn't identify an existing entity
|
82
|
+
identified_instance =
|
83
|
+
(identifying_role_value.fact.all_role_value.to_a-[identifying_role_values])[0].instance
|
84
|
+
else
|
85
|
+
object_type.all_instance.detect do |instance|
|
86
|
+
instance.population == @population and instance.value == value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def conforms_to_plays_and_steps(hypothesis, variable, instance)
|
92
|
+
variable.all_play.to_a.map do |play|
|
93
|
+
# No play may fail (be in a step with/out a matching fact)
|
94
|
+
step = play.step
|
95
|
+
|
96
|
+
# REVISIT: We can skip steps that have been checked in an outer loop
|
97
|
+
# REVISIT: We need to check AlternateSets here.
|
98
|
+
|
99
|
+
# We cannot check a step that involves an unbound variable.
|
100
|
+
# Allow it for now (there will be nil instead of a matching fact)
|
101
|
+
next if step.all_play.to_a.any? do |other_play|
|
102
|
+
# REVISIT: Unless the step is optional
|
103
|
+
!(hypothesis.has_key?(other_play.variable) || other_play.variable == variable)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Select all the RoleValues where this instance plays the required role.
|
107
|
+
# REVISIT: We've already selected instances in the correct population.
|
108
|
+
# Did RoleValues have to be further qualified? That might be redundant.
|
109
|
+
role_values = instance.all_role_value.select{|rv| rv.role == play.role }
|
110
|
+
|
111
|
+
satisfying_fact = nil
|
112
|
+
|
113
|
+
trace :result, "Trying #{role_values.size} values of #{play.role.object_type.name} to see whether #{instance.verbalise} participates in #{step.fact_type.default_reading} with bound variables #{hypothesis.map{|v,i| "#{v.object_type.name}=>#{i.verbalise}"}*', '}" do
|
114
|
+
|
115
|
+
role_values.any? do |rv|
|
116
|
+
fact = rv.fact
|
117
|
+
next false unless fact.population == @population
|
118
|
+
|
119
|
+
# If the step designates an objectification, this fact must be that objectification
|
120
|
+
if step.objectification_variable
|
121
|
+
next false unless fact.instance && step.objectification_variable.object_type == fact.instance.object_type
|
122
|
+
# REVISIT: fact.instance must be saved in the hypothesis under step.objectification_variable
|
123
|
+
end
|
124
|
+
|
125
|
+
# Succeed if this fact has all its role_values set to our bound variables
|
126
|
+
if fact.all_role_value.all? do |rv|
|
127
|
+
relevant_play = step.all_play.detect{|p| p.role == rv.role}
|
128
|
+
relevant_variable = relevant_play.variable
|
129
|
+
relevant_value = variable == relevant_variable ? instance : hypothesis[relevant_variable]
|
130
|
+
match = relevant_value == rv.instance
|
131
|
+
trace :result, "#{relevant_play.variable.object_type.name} (bound to #{hypothesis[relevant_play.variable].verbalise}) #{match ? 'MATCHES' : 'DOES NOT MATCH'} role value #{rv.instance.verbalise}" if match
|
132
|
+
match
|
133
|
+
end # of fact.all_role_value.all?
|
134
|
+
satisfying_fact = fact
|
135
|
+
satisfying_objectification_variable = step.objectification_variable
|
136
|
+
end
|
137
|
+
end # of role_values.any?
|
138
|
+
end # of trace
|
139
|
+
|
140
|
+
if satisfying_fact
|
141
|
+
trace :result, "#{instance.verbalise} #{
|
142
|
+
satisfying_fact ? 'participates' : 'does not participate'
|
143
|
+
} in #{step.is_disallowed ? 'disallowed ' : ''}step over #{step.fact_type.default_reading} "+
|
144
|
+
"with bound variables #{hypothesis.map{|v,i| "#{v.object_type.name}=>#{i.verbalise}"}*', '}"
|
145
|
+
end
|
146
|
+
|
147
|
+
if step.is_disallowed
|
148
|
+
satisfying_fact = !satisfying_fact # yield true if no fact satisfied the step, as sought
|
149
|
+
end
|
150
|
+
return nil unless satisfying_fact # This play fails the hypothesis
|
151
|
+
|
152
|
+
[ play, satisfying_fact ] +
|
153
|
+
Array(step.objectification_variable ? satisfying_fact.instance : nil)
|
154
|
+
end # of all_play
|
155
|
+
end # of conforms_to_plays_and_steps
|
156
|
+
|
157
|
+
def query_level unbound_variables = [], hypothesis = {}
|
158
|
+
# unbound_variables is an array of the remaining dimensions of the search
|
159
|
+
# hypothesis is a hash of Variable to Instance containing the values we think will satisfy the query
|
160
|
+
|
161
|
+
# Choose an unbound variable and test the instances of its object type
|
162
|
+
# This is a dumb choice for now. We should choose a variable that's
|
163
|
+
# closely connected to a bound variable, to narrow it down.
|
164
|
+
variable = unbound_variables[0]
|
165
|
+
return unless variable
|
166
|
+
|
167
|
+
# Set candidate_instances to the instances of this variable:
|
168
|
+
if hypothesis.has_key?(variable)
|
169
|
+
# REVISIT: I thought we were searching an unbound variable here? When does this happen:
|
170
|
+
candidate_instances = Array(hypothesis[variable])
|
171
|
+
else
|
172
|
+
candidate_instances = variable.object_type.all_instance
|
173
|
+
# candidate_instances = find_candidate_sets variable, hypothesis
|
174
|
+
end
|
175
|
+
|
176
|
+
population_candidates = candidate_instances.reject do |instance|
|
177
|
+
instance.population != @population # Wrong population
|
178
|
+
end
|
179
|
+
|
180
|
+
trace :result, "Query search through #{population_candidates.size} #{variable.role_name || variable.object_type.name}" do
|
181
|
+
population_candidates.each do |instance|
|
182
|
+
# filter by population:
|
183
|
+
trace :result, "Considering #{instance.verbalise}" do
|
184
|
+
|
185
|
+
# Check whether this instance satisfies the query steps of this play
|
186
|
+
played = conforms_to_plays_and_steps(hypothesis, variable, instance)
|
187
|
+
unless played
|
188
|
+
trace :result, "Does not conform to all steps"
|
189
|
+
next
|
190
|
+
else
|
191
|
+
trace :result, "Conforms to steps #{played.inspect}" unless played.empty?
|
192
|
+
end
|
193
|
+
|
194
|
+
# If this variable is the result of an objectification, the objectified fact
|
195
|
+
# has role players that might fill some remaining variables:
|
196
|
+
implications = {variable => instance}
|
197
|
+
if variable.step
|
198
|
+
objectification_plays = variable.step.all_play
|
199
|
+
|
200
|
+
# Ensure that the objectified instances don't contradict some bound variable:
|
201
|
+
next unless objectification_plays.to_a.all? do |play|
|
202
|
+
objectified_instance = instance.fact.all_role_value.select{|rv| rv.role == play.role}[0].instance
|
203
|
+
trace :result, "objectified instance includes #{play.variable.object_type.name}, checking it conforms" do
|
204
|
+
if hypothesis[play.variable]
|
205
|
+
unless hypothesis[play.variable] == objectified_instance
|
206
|
+
trace :result, "Objectified player #{objectified_instance.verbalise.inspect} does not match our hypothesis"
|
207
|
+
next false
|
208
|
+
end
|
209
|
+
true
|
210
|
+
else
|
211
|
+
implications[play.variable] = objectified_instance
|
212
|
+
conforms = conforms_to_plays_and_steps(hypothesis, play.variable, objectified_instance)
|
213
|
+
trace :result, "Objectified player #{objectified_instance.verbalise.inspect} does not conform to all steps" unless conforms
|
214
|
+
conforms
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Some of the variable's Plays might be matched by objectified facts.
|
221
|
+
# Record these values of the objectification variables.
|
222
|
+
next unless played.compact.all? do |playing|
|
223
|
+
play, fact, objectifying_variable = *playing
|
224
|
+
next true unless objectifying_variable
|
225
|
+
if i = hypothesis[objectifying_variable] and i != fact.instance
|
226
|
+
trace :result, "Objectification of #{fact.instance.verbalise.inspect} does not match our hypothesis"
|
227
|
+
next false
|
228
|
+
end
|
229
|
+
implications[objectifying_variable] = fact.instance
|
230
|
+
end
|
231
|
+
|
232
|
+
# accept the implications of this variable->instance assignment:
|
233
|
+
new_hypothesis = hypothesis.dup.merge(implications)
|
234
|
+
remaining_variables = unbound_variables - implications.keys
|
235
|
+
|
236
|
+
new_hypothesis.freeze
|
237
|
+
if remaining_variables.empty?
|
238
|
+
# This is a complete result set (no unbound variables) so record it:
|
239
|
+
@result_projection[new_hypothesis] = true
|
240
|
+
end
|
241
|
+
query_level(remaining_variables, new_hypothesis)
|
242
|
+
end # trace each instance
|
243
|
+
end # each instance
|
244
|
+
end # trace search through instances
|
245
|
+
end # query_level
|
246
|
+
|
247
|
+
# This method is an exploration into finding a smaller search space for the given variable.
|
248
|
+
# It relies on the values of bindings being Instance objects.
|
249
|
+
def find_candidate_sets variable, bindings
|
250
|
+
trace :result, "Looking for a smaller candidate set by traversing from these bound variables: #{bindings.keys.map{|v| (v.object_type.name+v.ordinal.to_s).inspect }*', '}" do
|
251
|
+
# If this variable is connected via a single step to one or more bound variables,
|
252
|
+
# that's probably a better place to search than object_type.all_instance.
|
253
|
+
bindable_plays =
|
254
|
+
variable.all_play.map do |play|
|
255
|
+
steps =
|
256
|
+
play.all_step_as_output_play.to_a + play.all_step_as_input_play.to_a + [play.step].compact
|
257
|
+
trace :result, "Considering steps over #{steps.map{|s|s.fact_type.default_reading.inspect}*', '}"
|
258
|
+
bound_steps =
|
259
|
+
steps.reject{|s| (s.all_play-[play]).detect{|p|
|
260
|
+
# trace :result, "Rejecting steps over #{s.fact_type.default_reading.inspect} because #{p.variable.object_type.name}#{p.variable.ordinal} is not bound" unless bindings[p.variable]
|
261
|
+
!bindings[p.variable]}
|
262
|
+
}
|
263
|
+
trace :result, "Bound steps are #{bound_steps.map{|s|s.fact_type.default_reading.inspect}*', '}"
|
264
|
+
counterpart_plays =
|
265
|
+
bound_steps.map{|s| (s.all_play-[play]) }.flatten.uniq
|
266
|
+
trace :result, "bound_steps.size = #{bound_steps.size}, counterpart_plays.size = #{counterpart_plays.size}"
|
267
|
+
|
268
|
+
# Consider only counterpart_plays for bound variables:
|
269
|
+
#counterpart_plays.reject!{|cp| !bindings[cp.variable] }
|
270
|
+
|
271
|
+
counterpart_plays
|
272
|
+
end.flatten.uniq
|
273
|
+
|
274
|
+
trace :result, "Connections of #{variable.object_type.name}#{variable.ordinal}:" do
|
275
|
+
if bindable_plays.empty?
|
276
|
+
trace :result, "NONE"
|
277
|
+
else
|
278
|
+
# Show the candidate instances here:
|
279
|
+
if trace :result
|
280
|
+
bindable_plays.each do |pl|
|
281
|
+
# We have bound instances of pl.variable, and the step(s) from this play reach the variable
|
282
|
+
trace :result, "Candidate facts which include this play are" do
|
283
|
+
|
284
|
+
bound_instance =
|
285
|
+
bindings[pl.variable] # This is the instance under consideration
|
286
|
+
bound_instance.
|
287
|
+
all_role_value.select{|rv| # Select its RoleValues for roles in this play's step
|
288
|
+
pl.all_step.detect{|s| s.fact_type == rv.fact.fact_type }
|
289
|
+
}.
|
290
|
+
each{|rv| # Verbalise other role_values of this fact
|
291
|
+
trace :result, "#{rv.fact.verbalise.inspect}"
|
292
|
+
}
|
293
|
+
end
|
294
|
+
trace :result, "-> Role of #{pl.variable.object_type.name}#{pl.variable.ordinal} in #{pl.role.fact_type.default_reading}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
end # QueryEvaluator
|
303
|
+
end # Metamodel
|
304
|
+
end # ActiveFacts
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activefacts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clifford Heath
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activefacts-api
|
@@ -288,10 +288,13 @@ files:
|
|
288
288
|
- lib/activefacts/cql/compiler/value_type.rb
|
289
289
|
- lib/activefacts/cql/nodes.rb
|
290
290
|
- lib/activefacts/cql/parser.rb
|
291
|
+
- lib/activefacts/dependency_analyser.rb
|
291
292
|
- lib/activefacts/generate/absorption.rb
|
293
|
+
- lib/activefacts/generate/composition.rb
|
292
294
|
- lib/activefacts/generate/cql.rb
|
293
295
|
- lib/activefacts/generate/dm.rb
|
294
296
|
- lib/activefacts/generate/help.rb
|
297
|
+
- lib/activefacts/generate/helpers/inject.rb
|
295
298
|
- lib/activefacts/generate/helpers/oo.rb
|
296
299
|
- lib/activefacts/generate/helpers/ordered.rb
|
297
300
|
- lib/activefacts/generate/helpers/rails.rb
|
@@ -304,7 +307,12 @@ files:
|
|
304
307
|
- lib/activefacts/generate/ruby.rb
|
305
308
|
- lib/activefacts/generate/sql/mysql.rb
|
306
309
|
- lib/activefacts/generate/sql/server.rb
|
310
|
+
- lib/activefacts/generate/stats.rb
|
307
311
|
- lib/activefacts/generate/text.rb
|
312
|
+
- lib/activefacts/generate/topics.rb
|
313
|
+
- lib/activefacts/generate/traits/oo.rb
|
314
|
+
- lib/activefacts/generate/traits/ordered.rb
|
315
|
+
- lib/activefacts/generate/traits/ruby.rb
|
308
316
|
- lib/activefacts/generate/transform/surrogate.rb
|
309
317
|
- lib/activefacts/generate/version.rb
|
310
318
|
- lib/activefacts/input/cql.rb
|
@@ -323,6 +331,7 @@ files:
|
|
323
331
|
- lib/activefacts/vocabulary.rb
|
324
332
|
- lib/activefacts/vocabulary/extensions.rb
|
325
333
|
- lib/activefacts/vocabulary/metamodel.rb
|
334
|
+
- lib/activefacts/vocabulary/query_evaluator.rb
|
326
335
|
- lib/activefacts/vocabulary/verbaliser.rb
|
327
336
|
- script/txt2html
|
328
337
|
- spec/absorption_spec.rb
|