activefacts 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|