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