activefacts-compositions 1.9.6 → 1.9.8
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/Rakefile +33 -0
- data/activefacts-compositions.gemspec +3 -3
- data/bin/schema_compositor +142 -85
- data/lib/activefacts/compositions/binary.rb +19 -15
- data/lib/activefacts/compositions/compositor.rb +126 -125
- data/lib/activefacts/compositions/constraints.rb +74 -54
- data/lib/activefacts/compositions/datavault.rb +545 -0
- data/lib/activefacts/compositions/names.rb +58 -58
- data/lib/activefacts/compositions/relational.rb +801 -692
- data/lib/activefacts/compositions/traits/rails.rb +180 -0
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/css/ldm.css +45 -0
- data/lib/activefacts/generator/doc/cwm.rb +764 -0
- data/lib/activefacts/generator/doc/glossary.rb +473 -0
- data/lib/activefacts/generator/doc/graphviz.rb +134 -0
- data/lib/activefacts/generator/doc/ldm.rb +698 -0
- data/lib/activefacts/generator/oo.rb +130 -124
- data/lib/activefacts/generator/rails/models.rb +237 -0
- data/lib/activefacts/generator/rails/schema.rb +273 -0
- data/lib/activefacts/generator/ruby.rb +75 -67
- data/lib/activefacts/generator/sql.rb +333 -351
- data/lib/activefacts/generator/sql/server.rb +100 -39
- data/lib/activefacts/generator/summary.rb +67 -59
- data/lib/activefacts/generator/validate.rb +19 -134
- metadata +18 -15
@@ -15,45 +15,106 @@ module ActiveFacts
|
|
15
15
|
# * underscore
|
16
16
|
class SQL
|
17
17
|
class Server < SQL
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
18
|
+
def self.options
|
19
|
+
super.merge({
|
20
|
+
# no: [String, "no new options defined here"]
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def table_name_max
|
25
|
+
128
|
26
|
+
end
|
27
|
+
|
28
|
+
def data_type_context
|
29
|
+
SQLServerDataTypeContext.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def auto_assign_type
|
33
|
+
' IDENTITY'
|
34
|
+
end
|
35
|
+
|
36
|
+
def normalise_type(type_name, length, value_constraint)
|
37
|
+
return ['UNIQUEIDENTIFIER', 16] if type_name =~ /^(guid|uuid)$/i
|
38
|
+
|
39
|
+
type = MM::DataType.normalise(type_name)
|
40
|
+
case type
|
41
|
+
when MM::DataType::TYPE_Money; 'MONEY'
|
42
|
+
when MM::DataType::TYPE_DateTime; 'DATETIME'
|
43
|
+
when MM::DataType::TYPE_Timestamp;'DATETIME'
|
44
|
+
when MM::DataType::TYPE_Binary;
|
45
|
+
if length && length <= 8192
|
46
|
+
super
|
47
|
+
else
|
48
|
+
'IMAGE'
|
49
|
+
end
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def reserved_words
|
56
|
+
@reserved_words ||= %w{
|
57
|
+
ADD ALL ALTER AND ANY AS ASC AUTHORIZATION BACKUP BEGIN
|
58
|
+
BETWEEN BREAK BROWSE BULK BY CASCADE CASE CHECK CHECKPOINT
|
59
|
+
CLOSE CLUSTERED COALESCE COLLATE COLUMN COMMIT COMPUTE
|
60
|
+
CONSTRAINT CONTAINS CONTAINSTABLE CONTINUE CONVERT CREATE
|
61
|
+
CROSS CURRENT CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP
|
62
|
+
CURRENT_USER CURSOR DATABASE DBCC DEALLOCATE DECLARE
|
63
|
+
DEFAULT DELETE DENY DESC DISK DISTINCT DISTRIBUTED DOUBLE
|
64
|
+
DROP DUMMY DUMP ELSE END ERRLVL ESCAPE EXCEPT EXEC EXECUTE
|
65
|
+
EXISTS EXIT FETCH FILE FILLFACTOR FOR FOREIGN FREETEXT
|
66
|
+
FREETEXTTABLE FROM FULL FUNCTION GOTO GRANT GROUP HAVING
|
67
|
+
HOLDLOCK IDENTITY IDENTITYCOL IDENTITY_INSERT IF IN INDEX
|
68
|
+
INNER INSERT INTERSECT INTO IS JOIN KEY KILL LEFT LIKE
|
69
|
+
LINENO LOAD NATIONAL NOCHECK NONCLUSTERED NOT NULL NULLIF
|
70
|
+
OF OFF OFFSETS ON OPEN OPENDATASOURCE OPENQUERY OPENROWSET
|
71
|
+
OPENXML OPTION OR ORDER OUTER OVER PERCENT PLAN PRECISION
|
72
|
+
PRIMARY PRINT PROC PROCEDURE PUBLIC RAISERROR READ READTEXT
|
73
|
+
RECONFIGURE REFERENCES REPLICATION RESTORE RESTRICT RETURN
|
74
|
+
REVOKE RIGHT ROLLBACK ROWCOUNT ROWGUIDCOL RULE SAVE SCHEMA
|
75
|
+
SELECT SESSION_USER SET SETUSER SHUTDOWN SOME STATISTICS
|
76
|
+
SYSTEM_USER TABLE TEXTSIZE THEN TO TOP TRAN TRANSACTION
|
77
|
+
TRIGGER TRUNCATE TSEQUAL UNION UNIQUE UPDATE UPDATETEXT
|
78
|
+
USE USER VALUES VARYING VIEW WAITFOR WHEN WHERE WHILE
|
79
|
+
WITH WRITETEXT
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def go s = ''
|
84
|
+
"#{s}\nGO\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
class SQLServerDataTypeContext < SQLDataTypeContext
|
88
|
+
def integer_ranges
|
89
|
+
[
|
90
|
+
['BIT', 0, 1],
|
91
|
+
['TINYINT', -2**7, 2**7-1],
|
92
|
+
] +
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def boolean_type
|
97
|
+
'BIT'
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_from_type
|
101
|
+
'DATETIME'
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_char_type
|
105
|
+
(@unicode ? 'N' : '') +
|
106
|
+
'CHAR'
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_varchar_type
|
110
|
+
(@unicode ? 'N' : '') +
|
111
|
+
'VARCHAR'
|
112
|
+
end
|
113
|
+
|
114
|
+
def date_time_type
|
115
|
+
'DATETIME'
|
116
|
+
end
|
117
|
+
end
|
57
118
|
end
|
58
119
|
|
59
120
|
end
|
@@ -12,85 +12,93 @@ module ActiveFacts
|
|
12
12
|
module Metamodel
|
13
13
|
class Composition
|
14
14
|
def summary
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
classify_constraints
|
16
|
+
"Summary of #{name}\n" +
|
17
|
+
all_composite.
|
18
|
+
sort_by{|composite| composite.mapping.name}.
|
19
|
+
flat_map do |composite|
|
20
|
+
composite.summary
|
21
|
+
end*''
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
class Composite
|
26
26
|
def summary
|
27
|
-
|
27
|
+
indices = self.all_indices_by_rank
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
(
|
30
|
+
[mapping.name+"\n"] +
|
31
|
+
mapping.
|
32
|
+
all_leaf.
|
33
|
+
reject{|leaf| leaf.is_a?(Absorption) && leaf.forward_absorption}.
|
34
|
+
flat_map do |leaf|
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
# Build a display of the names in this absorption path, with FK and optional indicators
|
37
|
+
path_names = leaf.path.map do |component|
|
38
|
+
is_mandatory = case component
|
39
|
+
when Indicator
|
40
|
+
false
|
41
|
+
when Absorption
|
42
|
+
component.parent_role.is_mandatory
|
43
|
+
else
|
44
|
+
true
|
45
|
+
end
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
if component.all_foreign_key_field.size > 0
|
48
|
+
"[#{component.name}]"
|
49
|
+
elsif component.is_a?(Absorption) && component.foreign_key
|
50
|
+
"{#{component.name}}"
|
51
|
+
else
|
52
|
+
component.name
|
53
|
+
end +
|
54
|
+
(is_mandatory ? '' : '?')
|
55
|
+
end*'->'
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
57
|
+
# Build a symbolic representation of the index participation of this leaf
|
58
|
+
pos = 0
|
59
|
+
indexing = indices.inject([]) do |a, index|
|
60
|
+
pos += 1
|
61
|
+
if part = index.position_in_index(leaf)
|
62
|
+
a << "#{pos}.#{part}"
|
63
|
+
end
|
64
|
+
a
|
65
|
+
end
|
66
|
+
if indexing.empty?
|
67
|
+
indexing = ''
|
68
|
+
else
|
69
|
+
indexing = "[#{indexing*','}]"
|
70
|
+
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
72
|
+
column_name = leaf.column_name.capwords*' '
|
73
|
+
["\t#{path_names}#{indexing} as #{column_name.inspect}\n"] +
|
74
|
+
leaf.all_leaf_constraint.map{|leaf_constraint| "\t\t### #{leaf_constraint.leaf_constraint.describe}\n"}.sort
|
75
|
+
end +
|
76
|
+
all_local_constraint.map do |local_constraint|
|
77
|
+
"\t### #{local_constraint.local_constraint.describe}\n"
|
78
|
+
end.sort +
|
79
|
+
all_spanning_constraint.map do |spanning_constraint|
|
80
|
+
"### #{spanning_constraint.spanning_constraint.describe}\n"
|
81
|
+
end.sort
|
80
82
|
|
81
|
-
|
83
|
+
)*''
|
82
84
|
end
|
83
85
|
end
|
84
86
|
end
|
85
87
|
|
86
88
|
module Generators
|
87
89
|
class Summary
|
88
|
-
def
|
89
|
-
|
90
|
+
def self.options
|
91
|
+
{
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def initialize composition, options = {}
|
96
|
+
@composition = composition
|
97
|
+
@options = options
|
90
98
|
end
|
91
99
|
|
92
100
|
def generate
|
93
|
-
|
101
|
+
@composition.summary
|
94
102
|
end
|
95
103
|
end
|
96
104
|
publish_generator Summary
|
@@ -7,152 +7,37 @@
|
|
7
7
|
# Copyright (c) 2015 Clifford Heath. Read the LICENSE file.
|
8
8
|
#
|
9
9
|
require "activefacts/metamodel"
|
10
|
+
require "activefacts/metamodel/validate/composition"
|
10
11
|
require "activefacts/compositions/compositor"
|
11
12
|
require "activefacts/generator"
|
12
13
|
|
13
14
|
module ActiveFacts
|
14
15
|
module Generators
|
15
16
|
class Validate
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
def generate &b
|
22
|
-
@composition.validate &b
|
23
|
-
nil
|
24
|
-
end
|
25
|
-
end
|
26
|
-
publish_generator Validate
|
27
|
-
end
|
28
|
-
|
29
|
-
module Metamodel
|
30
|
-
class Composition
|
31
|
-
def validate &report
|
32
|
-
trace.enable 'composition_validator'
|
33
|
-
report ||= proc do |component, problem|
|
34
|
-
trace :composition_validator, "!!PROBLEM!! #{component.inspect}: #{problem}"
|
35
|
-
end
|
36
|
-
|
37
|
-
all_composite.each do |composite|
|
38
|
-
composite.validate &report
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class Composite
|
44
|
-
def validate &report
|
45
|
-
trace :composition_validator?, "Validating #{inspect}" do
|
46
|
-
report.call(self, "Has no Mapping") unless mapping
|
47
|
-
report.call(self, "Mapping is not a mapping") unless mapping.class == Mapping
|
48
|
-
report.call(mapping, "Has no ObjectType") unless mapping.object_type
|
49
|
-
report.call(mapping, "Has no Name") unless mapping.name
|
50
|
-
report.call(mapping, "Should not have an Ordinal rank") if mapping.ordinal
|
51
|
-
report.call(mapping, "Should not have a parent mapping") if mapping.parent
|
52
|
-
report.call(mapping, "Should be the root of its mapping") if mapping.root != self
|
53
|
-
|
54
|
-
mapping.validate_members &report
|
55
|
-
validate_access_paths &report
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def validate_access_paths &report
|
60
|
-
all_access_path.each do |access_path|
|
61
|
-
report.call(access_path, "Must contain at least one IndexField") unless access_path.all_index_field.size > 0
|
62
|
-
access_path.all_index_field.each do |index_field|
|
63
|
-
report.call(access_path, "#{index_field.inspect} must be an Indicator or played by a ValueType") unless index_field.component.is_a?(Indicator) || index_field.component.object_type.is_a?(ValueType)
|
64
|
-
report.call(access_path, "#{index_field.inspect} must be within its composite") unless index_field.component.root == self
|
65
|
-
end
|
66
|
-
if ForeignKey === access_path
|
67
|
-
if access_path.all_index_field.size == access_path.all_foreign_key_field.size
|
68
|
-
access_path.all_index_field.to_a.zip(access_path.all_foreign_key_field.to_a).each do |index_field, foreign_key_field|
|
69
|
-
report.call(access_path, "#{index_field.inspect} must have matching target type") unless index_field.component.class == foreign_key_field.component.class
|
70
|
-
unless index_field.component.class == foreign_key_field.component.class
|
71
|
-
report.call(access_path, "#{index_field.inspect} must have component type matching #{foreign_key_field.inspect}")
|
72
|
-
else
|
73
|
-
report.call(access_path, "#{index_field.inspect} must have matching target type") unless !index_field.component.is_a?(Absorption) or index_field.component.object_type == foreign_key_field.component.object_type
|
74
|
-
end
|
75
|
-
report.call(access_path, "#{foreign_key_field.inspect} must be within the target composite") unless foreign_key_field.component.root == access_path.source_composite
|
76
|
-
end
|
77
|
-
else
|
78
|
-
report.call(access_path, "has #{access_path.all_index_field.size} index fields but #{access_path.all_foreign_key_field.size} ForeignKeyField")
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
17
|
+
def self.options
|
18
|
+
{
|
19
|
+
}
|
82
20
|
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class Mapping
|
86
|
-
def validate_members &report
|
87
|
-
# Names (except of subtype/supertype absorption) must be unique:
|
88
|
-
names = all_member.
|
89
|
-
reject{|m| m.is_a?(Absorption) && m.parent_role.fact_type.is_a?(TypeInheritance)}.
|
90
|
-
map(&:name).
|
91
|
-
compact
|
92
|
-
duplicate_names = names.select{|name| names.count(name) > 1}.uniq
|
93
|
-
report.call(self, "Contains duplicated names #{duplicate_names.map(&:inspect)*', '}") unless duplicate_names.empty?
|
94
|
-
|
95
|
-
all_member.each do |member|
|
96
|
-
trace :composition_validator?, "Validating #{member.inspect}" do
|
97
|
-
report.call(member, "Requires a name") unless Absorption === member && member.flattens or member.name && !member.name.empty?
|
98
|
-
case member
|
99
|
-
when Absorption
|
100
|
-
p = member.parent_role
|
101
|
-
c = member.child_role
|
102
|
-
report.call(member, "Roles should belong to the same fact type, but instead we have #{p.name} in #{p.fact_type.default_reading} and #{c.name} in #{c.fact_type.default_reading}") unless p.fact_type == c.fact_type
|
103
|
-
report.call(member, "Object type #{member.object_type.name} should play the child role #{c.name}") unless member.object_type == c.object_type
|
104
|
-
report.call(member, "Parent mapping object type #{object_type.name} should play the parent role #{p.name}") unless object_type == p.object_type
|
105
|
-
|
106
|
-
member.validate_reverse &report
|
107
|
-
member.validate_nesting &report if member.all_nesting.size > 0
|
108
|
-
member.validate_members &report
|
109
|
-
|
110
|
-
when Scoping
|
111
|
-
report.call(member, "REVISIT: Unexpected and unchecked Scoping")
|
112
|
-
|
113
|
-
when ValueField
|
114
|
-
# Nothing to check here
|
115
|
-
|
116
|
-
when SurrogateKey
|
117
|
-
# Nothing to check here
|
118
|
-
|
119
|
-
when Injection
|
120
|
-
report.call(member, "REVISIT: Unexpected and unchecked Injection")
|
121
21
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
when Indicator
|
126
|
-
report.call(member, "Indicator requires a Role") unless member.role
|
127
|
-
|
128
|
-
when Discriminator
|
129
|
-
report.call(member, "Discriminator requires at least one Discriminated Role") if member.all_discriminated_role.empty?
|
130
|
-
member.all_discriminated_role.each do |role|
|
131
|
-
report.call(member, "Discriminated Role #{role.name} is not played by parent object type #{object_type.name}") unless role.object_type == object_type
|
132
|
-
end
|
133
|
-
# REVISIT: Discriminated Roles must have distinct values matching the type of the Role
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
22
|
+
def initialize composition, options = {}
|
23
|
+
@composition = composition
|
24
|
+
@options = options
|
137
25
|
end
|
138
|
-
end
|
139
26
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
27
|
+
def generate &report
|
28
|
+
if !report
|
29
|
+
trace.enable 'composition_validator'
|
30
|
+
report ||= proc do |component, problem|
|
31
|
+
trace :composition_validator, "!!PROBLEM!! #{component.inspect}: #{problem}"
|
32
|
+
debugger if trace :composition_validator_debug
|
33
|
+
component
|
34
|
+
end
|
35
|
+
end
|
147
36
|
|
148
|
-
|
149
|
-
|
150
|
-
report.call(self, "Nesting Mode must be specified") unless self.nesting_mode
|
151
|
-
# REVISIT: Nesting names must be unique
|
152
|
-
# REVISIT: Nesting roles must be played by...
|
153
|
-
# REVISIT: Nesting roles must be value types
|
37
|
+
@composition.validate &report
|
38
|
+
nil
|
154
39
|
end
|
155
40
|
end
|
156
|
-
|
41
|
+
publish_generator Validate
|
157
42
|
end
|
158
43
|
end
|