activefacts 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cql +90 -31
- data/download.html +2 -1
- data/examples/CQL/Diplomacy.cql +8 -8
- data/examples/CQL/Insurance.cql +3 -3
- data/examples/CQL/Metamodel.cql +31 -16
- data/examples/CQL/MetamodelNext.cql +30 -18
- data/examples/CQL/Orienteering.cql +2 -2
- data/examples/CQL/OrienteeringER.cql +6 -6
- data/examples/CQL/SchoolActivities.cql +1 -1
- data/examples/CQL/ServiceDirector.cql +3 -3
- data/examples/CQL/Supervision.cql +3 -2
- data/examples/CQL/Tests.Test5.Load.cql +1 -1
- data/examples/CQL/WaiterTips.cql +1 -1
- data/lib/activefacts/cql/FactTypes.treetop +14 -2
- data/lib/activefacts/cql/compiler/constraint.rb +69 -36
- data/lib/activefacts/cql/compiler/fact.rb +142 -71
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -1
- data/lib/activefacts/cql/compiler/reading.rb +35 -8
- data/lib/activefacts/cql/compiler/shared.rb +4 -3
- data/lib/activefacts/generate/cql.rb +16 -5
- data/lib/activefacts/generate/ordered.rb +1 -1
- data/lib/activefacts/input/orm.rb +136 -57
- data/lib/activefacts/support.rb +40 -16
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +149 -58
- data/lib/activefacts/vocabulary/metamodel.rb +23 -11
- data/lib/activefacts/vocabulary/verbaliser.rb +223 -105
- data/spec/cql/samples_spec.rb +30 -6
- data/spec/cql_mysql_spec.rb +1 -2
- data/spec/norma_ruby_sql_spec.rb +0 -1
- metadata +4 -4
@@ -161,7 +161,7 @@ module ActiveFacts
|
|
161
161
|
key = rr.key.compact
|
162
162
|
role_refs_by_reading_and_key[[reading, key]] = rr
|
163
163
|
key
|
164
|
-
end.
|
164
|
+
end.sort_by{|a| a.map{|k|k.to_s}}
|
165
165
|
raise "Fact types may not have duplicate roles" if keys.uniq.size < keys.size
|
166
166
|
(hash[keys] ||= []) << reading
|
167
167
|
hash
|
@@ -7,6 +7,9 @@ module ActiveFacts
|
|
7
7
|
attr_accessor :qualifiers, :context_note
|
8
8
|
attr_reader :fact_type, :reading, :role_sequence # These are the Metamodel objects
|
9
9
|
attr_reader :side_effects
|
10
|
+
attr_writer :fact_type # Assigned for a bare (existential) objectification fact
|
11
|
+
attr_accessor :fact # When binding fact instances the fact goes here
|
12
|
+
attr_accessor :objectified_as # The Reading::RoleRef which objectified this fact type
|
10
13
|
|
11
14
|
def initialize role_refs_and_words, qualifiers = [], context_note = nil
|
12
15
|
@phrases = role_refs_and_words
|
@@ -27,6 +30,16 @@ module ActiveFacts
|
|
27
30
|
!@phrases[0].literal
|
28
31
|
end
|
29
32
|
|
33
|
+
def display
|
34
|
+
"#{
|
35
|
+
@qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil
|
36
|
+
}#{
|
37
|
+
@phrases.map{|p| p.to_s }*" "
|
38
|
+
}#{
|
39
|
+
@context_note && ' '+@context_note.inspect
|
40
|
+
}"
|
41
|
+
end
|
42
|
+
|
30
43
|
def inspect
|
31
44
|
"#{@qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil}#{@phrases.map{|p|p.inspect}*" "}#{@context_note && ' '+@context_note.inspect}"
|
32
45
|
end
|
@@ -48,7 +61,7 @@ module ActiveFacts
|
|
48
61
|
end
|
49
62
|
|
50
63
|
def includes_literals
|
51
|
-
role_refs.detect{|role_ref| role_ref.literal }
|
64
|
+
role_refs.detect{|role_ref| role_ref.literal || (ja = role_ref.objectification_join and ja.detect{|jr| jr.includes_literals })}
|
52
65
|
end
|
53
66
|
|
54
67
|
def bind_roles context
|
@@ -85,6 +98,7 @@ module ActiveFacts
|
|
85
98
|
# As this match may not necessarily be used (depending on the side effects),
|
86
99
|
# no change is made to this Reading object - those will be done later.
|
87
100
|
def match_existing_fact_type context
|
101
|
+
raise "Internal error, reading already matched, should not match again" if @fact_type
|
88
102
|
rrs = role_refs
|
89
103
|
players = rrs.map{|rr| rr.player}
|
90
104
|
raise "Must identify players before matching fact types" if players.include? nil
|
@@ -97,13 +111,16 @@ module ActiveFacts
|
|
97
111
|
|
98
112
|
# Match existing fact types in objectification joins first:
|
99
113
|
rrs.each do |role_ref|
|
100
|
-
next unless
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
114
|
+
next unless joins = role_ref.objectification_join and !joins.empty?
|
115
|
+
role_ref.objectification_join.each do |oj|
|
116
|
+
ft = oj.match_existing_fact_type(context)
|
117
|
+
raise "Unrecognised fact type #{oj.display}" unless ft
|
118
|
+
if (ft && ft.entity_type == role_ref.player)
|
119
|
+
role_ref.objectification_of = ft
|
120
|
+
oj.objectified_as = role_ref
|
121
|
+
end
|
122
|
+
end
|
123
|
+
raise "#{role_ref.inspect} contains objectification joins that do not objectify it" unless role_ref.objectification_of
|
107
124
|
end
|
108
125
|
|
109
126
|
# For each role player, find the compatible types (the set of all subtypes and supertypes).
|
@@ -673,6 +690,16 @@ module ActiveFacts
|
|
673
690
|
}>"
|
674
691
|
end
|
675
692
|
|
693
|
+
def to_s
|
694
|
+
"#{
|
695
|
+
@quantifier && @quantifier.inspect+' ' }#{
|
696
|
+
@leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ') }#{
|
697
|
+
@role_name || @term }#{
|
698
|
+
@trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-') }#{
|
699
|
+
@literal && ' '+@literal.inspect }#{
|
700
|
+
@value_constraint && ' '+@value_constraint.inspect }"
|
701
|
+
end
|
702
|
+
|
676
703
|
def <=>(other)
|
677
704
|
( 4*(@term <=> other.term) +
|
678
705
|
2*((@leading_adjective||'') <=> (other.leading_adjective||'')) +
|
@@ -11,11 +11,12 @@ module ActiveFacts
|
|
11
11
|
# In some situations a Binding will have some RoleRefs with the same adjectives,
|
12
12
|
# and one or more RoleRefs with no adjectives - this is called "loose binding".
|
13
13
|
class Binding
|
14
|
-
attr_reader :player
|
15
|
-
attr_reader :refs
|
14
|
+
attr_reader :player # The Concept (object type)
|
15
|
+
attr_reader :refs # an array of the RoleRefs
|
16
16
|
attr_reader :role_name
|
17
|
-
attr_accessor :rebound_to
|
17
|
+
attr_accessor :rebound_to # Loose binding may set this to another binding
|
18
18
|
attr_accessor :join_node
|
19
|
+
attr_accessor :instance # When binding fact instances, the instance goes here
|
19
20
|
|
20
21
|
def initialize player, role_name = nil
|
21
22
|
@player = player
|
@@ -365,7 +365,7 @@ module ActiveFacts
|
|
365
365
|
verbaliser.roles_have_same_player(joined_roles) if join_over
|
366
366
|
end
|
367
367
|
|
368
|
-
verbaliser.prepare_role_sequence(c.role_sequence)
|
368
|
+
verbaliser.prepare_role_sequence(c.role_sequence, join_over)
|
369
369
|
|
370
370
|
expanded_readings = verbaliser.verbalise_over_role_sequence(c.role_sequence, nil, role_proximity)
|
371
371
|
if c.min_frequency == 1 && c.max_frequency == nil and c.role_sequence.all_role_ref.size == 2
|
@@ -389,13 +389,24 @@ module ActiveFacts
|
|
389
389
|
verbaliser = ActiveFacts::Metamodel::Verbaliser.new
|
390
390
|
|
391
391
|
# Tell the verbaliser all we know, so it can figure out which players to subscript:
|
392
|
-
|
393
|
-
|
394
|
-
|
392
|
+
players = []
|
393
|
+
debug :subscript, "Preparing join across projected roles in set comparison constraint" do
|
394
|
+
transposed_role_refs.each do |role_refs|
|
395
|
+
verbaliser.role_refs_are_subtype_joined role_refs
|
396
|
+
join_over, = ActiveFacts::Metamodel.join_roles_over(role_refs.map{|rr| rr.role})
|
397
|
+
players << join_over
|
398
|
+
end
|
399
|
+
end
|
400
|
+
debug :subscript, "Preparing join between roles in set comparison constraint" do
|
401
|
+
role_sequences.each do |role_sequence|
|
402
|
+
debug :subscript, "role sequence is #{role_sequence.describe}" do
|
403
|
+
verbaliser.prepare_role_sequence role_sequence
|
404
|
+
end
|
405
|
+
end
|
395
406
|
end
|
396
407
|
verbaliser.create_subscripts
|
397
408
|
|
398
|
-
if role_sequences.detect{|scr| scr.all_role_ref.detect{|rr| rr.
|
409
|
+
if role_sequences.detect{|scr| scr.all_role_ref.detect{|rr| rr.join_role}}
|
399
410
|
# This set constraint has an explicit join. Verbalise it.
|
400
411
|
|
401
412
|
readings_list = role_sequences.
|
@@ -481,7 +481,7 @@ module ActiveFacts
|
|
481
481
|
# Skip uniqueness constraints that cover all roles of a fact type, they're implicit
|
482
482
|
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
|
483
483
|
if fact_types.size == 1 &&
|
484
|
-
!c.role_sequence.all_role_ref.detect{|rr| rr.
|
484
|
+
!c.role_sequence.all_role_ref.detect{|rr| rr.join_role } &&
|
485
485
|
c.max_frequency == 1 && # Uniqueness
|
486
486
|
fact_types[0].all_role.size == c.role_sequence.all_role_ref.size
|
487
487
|
# debugger if !$constraint_id || c.constraint_id.object_id == $foo
|
@@ -417,7 +417,7 @@ module ActiveFacts
|
|
417
417
|
debug :orm, "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.fact_type_id} played by #{concept.name}"
|
418
418
|
|
419
419
|
role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :concept => concept)
|
420
|
-
role.role_name = name if name
|
420
|
+
role.role_name = name if name && name != concept.name
|
421
421
|
debug :orm, "Fact #{fact_name} (id #{fact_type.fact_type_id.object_id}) role #{x['Name']} is played by #{concept.name}, role is #{role.object_id}"
|
422
422
|
|
423
423
|
x_vr = x.xpath("orm:ValueRestriction/orm:RoleValueConstraint")
|
@@ -700,12 +700,19 @@ module ActiveFacts
|
|
700
700
|
end
|
701
701
|
|
702
702
|
mc_id = nil
|
703
|
+
|
703
704
|
if (mc = @mandatory_constraints_by_rs[role_sequence])
|
704
705
|
# Remove absorbed mandatory constraints, leaving residual ones.
|
705
706
|
debug :orm, "Absorbing MC #{mc['Name']} over #{role_sequence.describe}"
|
706
707
|
@mandatory_constraints_by_rs.delete(role_sequence)
|
707
708
|
mc_id = mc['id']
|
708
709
|
@mandatory_constraint_rs_by_id.delete(mc['id'])
|
710
|
+
elsif (fts = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq).size == 1 and
|
711
|
+
fts[0].entity_type
|
712
|
+
# this uniqueness constraint is an internal UC on an objectified fact type,
|
713
|
+
# so the covered roles are always mandatory (wrt the OFT)
|
714
|
+
# That is, the phantom roles are mandatory, even if the visible roles are not.
|
715
|
+
mc = true
|
709
716
|
else
|
710
717
|
debug :orm, "No MC to absorb over #{role_sequence.describe}"
|
711
718
|
end
|
@@ -739,6 +746,39 @@ module ActiveFacts
|
|
739
746
|
end
|
740
747
|
end
|
741
748
|
|
749
|
+
def subtype_join_step(join, ti)
|
750
|
+
subtype_node = join.all_join_node.detect{|jn| jn.concept == ti.subtype } ||
|
751
|
+
@constellation.JoinNode(join, join.all_join_node.size, :concept => ti.subtype)
|
752
|
+
supertype_node = join.all_join_node.detect{|jn| jn.concept == ti.supertype } ||
|
753
|
+
@constellation.JoinNode(join, join.all_join_node.size, :concept => ti.supertype)
|
754
|
+
rs = @constellation.RoleSequence(:new)
|
755
|
+
@constellation.RoleRef(rs, 0, :role => ti.subtype_role)
|
756
|
+
sub_jr = @constellation.JoinRole(subtype_node, ti.subtype_role)
|
757
|
+
@constellation.RoleRef(rs, 1, :role => ti.supertype_role)
|
758
|
+
sup_jr = @constellation.JoinRole(supertype_node, ti.supertype_role)
|
759
|
+
js = @constellation.JoinStep(sub_jr, sup_jr, :fact_type => ti)
|
760
|
+
debug :join, "New subtyping join step #{js.describe}"
|
761
|
+
js
|
762
|
+
end
|
763
|
+
|
764
|
+
# Make as many join steps as it takes to get from subtype to supertype
|
765
|
+
def subtype_join_steps(join, subtype, supertype)
|
766
|
+
primary_ti = nil
|
767
|
+
other_ti = nil
|
768
|
+
subtype.all_type_inheritance_as_subtype.each do |ti|
|
769
|
+
next unless ti.supertype.supertypes_transitive.include? supertype
|
770
|
+
if ti.provides_identification
|
771
|
+
primary_ti ||= ti
|
772
|
+
else
|
773
|
+
other_ti ||= ti
|
774
|
+
end
|
775
|
+
end
|
776
|
+
ti = primary_ti || other_ti
|
777
|
+
# Make supertype join steps first:
|
778
|
+
(ti.supertype == supertype ? [] : subtype_join_steps(join, ti.supertype, supertype)) +
|
779
|
+
[subtype_join_step(join, ti)]
|
780
|
+
end
|
781
|
+
|
742
782
|
# Equality and subset joins involve two or more role sequences,
|
743
783
|
# and the respective roles from each sequence must be compatible,
|
744
784
|
# Compatibility might involve subtyping joins but not objectification joins
|
@@ -774,9 +814,12 @@ module ActiveFacts
|
|
774
814
|
# For each role_sequence, find the object type over which the join is implied (nil if no join)
|
775
815
|
sequence_join_over = []
|
776
816
|
if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence.
|
777
|
-
sequence_join_over =
|
817
|
+
sequence_join_over = []
|
818
|
+
sequence_joined_roles = []
|
819
|
+
role_sequences.map do |rs|
|
778
820
|
join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role})
|
779
|
-
join_over
|
821
|
+
sequence_join_over << join_over
|
822
|
+
sequence_joined_roles << joined_roles
|
780
823
|
end
|
781
824
|
end
|
782
825
|
|
@@ -792,97 +835,133 @@ module ActiveFacts
|
|
792
835
|
end_points.zip(end_joins).map{|(p,j)| p.name+(j ? ' & subtypes':'')}*', '
|
793
836
|
}#{
|
794
837
|
if role_sequences[0].all_role_ref.size > 1
|
795
|
-
", joined over #{
|
838
|
+
", joined over #{
|
839
|
+
sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
|
840
|
+
(o ? o.name : '(none)') +
|
841
|
+
(roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
|
842
|
+
}*', '}"
|
796
843
|
else
|
797
844
|
''
|
798
845
|
end
|
799
846
|
}" do
|
800
847
|
|
801
|
-
end
|
802
|
-
return # REVISIT: Disabled until we get the join verbaliser working.
|
803
|
-
while false
|
804
|
-
|
805
848
|
# There may be one join per role sequence:
|
806
|
-
role_sequences.zip(sequence_join_over).map do |role_sequence, join_over|
|
849
|
+
role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
|
807
850
|
# Skip if there's no join here (sequence join nor end-point subset join)
|
808
|
-
role_refs = role_sequence.
|
809
|
-
|
851
|
+
role_refs = role_sequence.all_role_ref_in_order
|
852
|
+
if !join_over and !role_refs.detect{|rr| rr.role.concept != end_points[rr.ordinal]}
|
853
|
+
# No sequence join nor end_point join here
|
854
|
+
next
|
855
|
+
end
|
810
856
|
|
857
|
+
# A RoleSequence for the actual join end-points
|
811
858
|
replacement_rs = @constellation.RoleSequence(:new)
|
859
|
+
|
812
860
|
join = @constellation.Join(:new)
|
813
861
|
join_node = nil
|
814
|
-
|
862
|
+
join_role = nil
|
863
|
+
role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
|
864
|
+
|
865
|
+
# Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node)
|
866
|
+
|
867
|
+
# Create a join node for the actual end-point (supertype of the constrained roles)
|
815
868
|
end_point = end_points[i]
|
816
869
|
debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}"
|
817
870
|
end_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => end_point)
|
818
|
-
role_node = end_node
|
819
|
-
|
820
|
-
if role_ref.role.concept != end_point
|
821
|
-
subtype = role_ref.role.concept
|
822
|
-
debug :join, "Making subtyping join step #{join.all_join_node.size} from #{subtype.name} to #{end_point.name}" do
|
823
|
-
role_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => role_ref.role.concept)
|
824
|
-
ti = role_ref.role.concept.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == end_point}
|
825
|
-
raise "REVISIT: Can't yet handle multiple subtyping levels in subtyping join" unless ti
|
826
|
-
|
827
|
-
# Make a new role sequence over the subtyping fact type:
|
828
|
-
rs = @constellation.RoleSequence(:new)
|
829
|
-
@constellation.RoleRef(rs, 0, :role => ti.all_role.detect{|r| r.concept == role_ref.role.concept}, :join_node => role_node)
|
830
|
-
@constellation.RoleRef(rs, 1, :role => ti.all_role.detect{|r| r.concept == end_point}, :join_node => end_node)
|
831
871
|
|
832
|
-
|
833
|
-
|
872
|
+
# We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
|
873
|
+
role_node = end_node
|
874
|
+
end_role = role_ref.role
|
875
|
+
|
876
|
+
# Create subtyping join steps at the end-point, if needed:
|
877
|
+
projecting_jr = nil
|
878
|
+
constrained_join_role = nil
|
879
|
+
if (subtype = role_ref.role.concept) != end_point
|
880
|
+
debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do
|
881
|
+
# There may be more than one supertyping level. Make the steps:
|
882
|
+
subtyping_steps = subtype_join_steps(join, subtype, end_point)
|
883
|
+
js = subtyping_steps[0]
|
884
|
+
constrained_join_role = subtyping_steps[-1].input_join_role
|
885
|
+
|
886
|
+
# Replace the constrained role and node with the supertype ones:
|
887
|
+
end_node = join.all_join_node.detect{|jn| jn.concept == end_point }
|
888
|
+
projecting_jr = js.output_join_role
|
889
|
+
end_role = js.fact_type.all_role.detect{|r| r.concept == end_point }
|
890
|
+
role_node = join.all_join_node.detect{|jn| jn.concept == role_ref.role.concept }
|
834
891
|
end
|
835
892
|
end
|
836
893
|
|
837
|
-
|
838
|
-
|
839
|
-
|
894
|
+
raise "Internal error: making illegal reference to join node" if end_role.concept != end_node.concept
|
895
|
+
rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
|
896
|
+
projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role))
|
897
|
+
projecting_jr.role_ref = rr # Project this RoleRef
|
840
898
|
|
841
899
|
if join_over
|
842
|
-
if !join_node
|
900
|
+
if !join_node # Create the JoinNode when processing the first role
|
843
901
|
debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}"
|
844
902
|
join_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => join_over)
|
845
903
|
end
|
846
904
|
debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do
|
847
|
-
role = role_ref.role
|
848
|
-
# Detect an objectification step. Here, the role is in an objectified type, but the end_point isn't another role player of that fact type... or is there a better test?
|
849
|
-
if join_over == role.fact_type.entity_type
|
850
|
-
p join_over.name
|
851
|
-
p role.fact_type.default_reading
|
852
|
-
p end_point.name
|
853
|
-
raise "REVISIT: Incomplete"
|
854
|
-
elsif end_point == role.fact_type.entity_type
|
855
|
-
fact_type = role.implicit_fact_type
|
856
|
-
# REVISIT: Might these be reversed... sometimes?
|
857
|
-
end_role = fact_type.all_role.single
|
858
|
-
join_role = role
|
859
|
-
else
|
860
|
-
fact_type = role.fact_type
|
861
|
-
end_role = role
|
862
|
-
# This is unambiguous, since it's come from NORMA which only supports (unambiguous) implicit joins:
|
863
|
-
join_role = role.fact_type.all_role.detect{|r| r.concept == join_over}
|
864
|
-
# debugger unless join_role && end_role
|
865
|
-
end
|
866
|
-
|
867
905
|
rs = @constellation.RoleSequence(:new)
|
868
906
|
# Detect the fact type over which we're joining (may involve objectification)
|
869
|
-
|
870
|
-
@constellation.RoleRef(rs,
|
871
|
-
|
907
|
+
raise "Internal error: making illegal reference to join node" if role_ref.role.concept != role_node.concept
|
908
|
+
@constellation.RoleRef(rs, 0, :role => role_ref.role)
|
909
|
+
role_jr = @constellation.JoinRole(role_node, role_ref.role)
|
910
|
+
raise "Internal error: making illegal reference to join node" if joined_role.concept != join_node.concept
|
911
|
+
@constellation.RoleRef(rs, 1, :role => joined_role)
|
912
|
+
join_jr = @constellation.JoinRole(join_node, joined_role)
|
913
|
+
|
914
|
+
js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type)
|
872
915
|
debug :join, "New join step #{js.describe}"
|
873
916
|
end
|
874
917
|
else
|
875
918
|
debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
|
876
|
-
if role_ref.role.fact_type.all_role.size > 1
|
877
|
-
|
919
|
+
if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
|
920
|
+
# Here we have an end join (step already created) but no sequence join
|
921
|
+
if join_node
|
922
|
+
raise "Internal error: making illegal join step" if role_ref.role.concept != role_node.concept
|
923
|
+
join_jr = @constellation.JoinRole(join_node, join_role)
|
924
|
+
role_jr = @constellation.JoinRole(role_node, role_ref.role)
|
925
|
+
js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type)
|
926
|
+
roles -= [join_role, role_ref.role]
|
927
|
+
roles.each do |incidental_role|
|
928
|
+
jn = @constellation.JoinNode(join, join.all_join_node.size, :concept => incidental_role.concept)
|
929
|
+
jr = @constellation.JoinRole(jn, incidental_role, :join_step => js)
|
930
|
+
end
|
931
|
+
else
|
932
|
+
if role_sequence.all_role_ref.size > 1
|
933
|
+
join_node = role_node
|
934
|
+
join_role = role_ref.role
|
935
|
+
else
|
936
|
+
# There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why?
|
937
|
+
# Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
|
938
|
+
# I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example)
|
939
|
+
role_jr = @constellation.JoinRole(role_node, role_ref.role)
|
940
|
+
js = nil
|
941
|
+
role_ref.role.fact_type.all_role.each do |role|
|
942
|
+
next if role == role_jr.role
|
943
|
+
next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
|
944
|
+
jn = @constellation.JoinNode(join, join.all_join_node.size, :concept => role.concept)
|
945
|
+
jr = @constellation.JoinRole(jn, role)
|
946
|
+
if js
|
947
|
+
jr.join_step = js # Incidental role
|
948
|
+
else
|
949
|
+
js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type)
|
950
|
+
end
|
951
|
+
end
|
952
|
+
end
|
953
|
+
end
|
878
954
|
else
|
879
|
-
|
955
|
+
# Unary fact type, make a Join Step from and to the constrained_join_role
|
956
|
+
jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role)
|
957
|
+
js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type)
|
880
958
|
end
|
881
959
|
end
|
882
960
|
end
|
883
961
|
|
884
962
|
# Thoroughly check that this is a valid join
|
885
963
|
join.validate
|
964
|
+
debug :join, "Join has projected nodes #{replacement_rs.describe}"
|
886
965
|
|
887
966
|
# Constrain the replacement role sequence, which has the attached join:
|
888
967
|
role_sequences[role_sequences.index(role_sequence)] = replacement_rs
|
data/lib/activefacts/support.rb
CHANGED
@@ -11,7 +11,41 @@
|
|
11
11
|
$debug_keys = nil
|
12
12
|
$debug_available = {}
|
13
13
|
|
14
|
-
def
|
14
|
+
def debug_initialize
|
15
|
+
# First time, initialise the tracing environment
|
16
|
+
$debug_indent = 0
|
17
|
+
$debug_keys = {}
|
18
|
+
if (e = ENV["DEBUG"])
|
19
|
+
e.split(/[^_a-zA-Z0-9]/).each{|k| debug_enable(k) }
|
20
|
+
if $debug_keys[:help]
|
21
|
+
at_exit {
|
22
|
+
$stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug_keys
|
29
|
+
$debug_available.keys
|
30
|
+
end
|
31
|
+
|
32
|
+
def debug_enabled key
|
33
|
+
!key.empty? && $debug_keys[key.to_sym]
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug_enable key
|
37
|
+
!key.empty? && $debug_keys[key.to_sym] = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def debug_disable key
|
41
|
+
!key.empty? && $debug_keys.delete(key.to_sym)
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug_toggle key
|
45
|
+
!key.empty? and debug_enabled(key) ? (debug_disable(key); false) : (debug_enable(key); true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def debug_selected(args)
|
15
49
|
# Figure out whether this trace is enabled and nests:
|
16
50
|
control = (!args.empty? && Symbol === args[0]) ? args.shift : :all
|
17
51
|
key = control.to_s.sub(/_\Z/, '').to_sym
|
@@ -24,24 +58,14 @@
|
|
24
58
|
end
|
25
59
|
|
26
60
|
def debug(*args, &block)
|
27
|
-
unless $debug_indent
|
28
|
-
# First time, initialise the tracing environment
|
29
|
-
$debug_indent = 0
|
30
|
-
$debug_keys = {}
|
31
|
-
if (e = ENV["DEBUG"])
|
32
|
-
e.split(/[^_a-zA-Z0-9]/).each{|k| $debug_keys[k.to_sym] = true }
|
33
|
-
if $debug_keys[:help]
|
34
|
-
at_exit {
|
35
|
-
$stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
|
36
|
-
}
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
61
|
+
debug_initialize unless $debug_indent
|
40
62
|
|
41
|
-
enabled, show_key, old_nested =
|
63
|
+
enabled, show_key, old_nested = debug_selected(args)
|
42
64
|
|
43
65
|
# Emit the message if enabled or a parent is:
|
44
|
-
|
66
|
+
if args.size > 0 && enabled == 1
|
67
|
+
puts "\##{show_key} "+" "*$debug_indent + args.join(' ')
|
68
|
+
end
|
45
69
|
|
46
70
|
if block
|
47
71
|
begin
|