activefacts 0.7.2 → 0.7.3
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.
- data/Manifest.txt +1 -0
- data/Rakefile +3 -0
- data/bin/afgen +9 -3
- data/bin/cql +0 -0
- data/examples/CQL/Address.cql +7 -7
- data/examples/CQL/Blog.cql +8 -8
- data/examples/CQL/CompanyDirectorEmployee.cql +3 -3
- data/examples/CQL/Death.cql +2 -2
- data/examples/CQL/Genealogy.cql +21 -21
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +34 -29
- data/examples/CQL/MultiInheritance.cql +3 -3
- data/examples/CQL/OilSupply.cql +9 -9
- data/examples/CQL/Orienteering.cql +27 -27
- data/examples/CQL/PersonPlaysGame.cql +2 -2
- data/examples/CQL/SchoolActivities.cql +3 -3
- data/examples/CQL/SimplestUnary.cql +1 -1
- data/examples/CQL/SubtypePI.cql +4 -4
- data/examples/CQL/Warehousing.cql +12 -12
- data/examples/CQL/WindowInRoomInBldg.cql +4 -4
- data/lib/activefacts/api/concept.rb +3 -2
- data/lib/activefacts/api/constellation.rb +1 -1
- data/lib/activefacts/api/entity.rb +12 -1
- data/lib/activefacts/api/instance.rb +1 -1
- data/lib/activefacts/api/role.rb +1 -1
- data/lib/activefacts/api/standard_types.rb +9 -1
- data/lib/activefacts/api/support.rb +4 -0
- data/lib/activefacts/api/value.rb +1 -0
- data/lib/activefacts/api/vocabulary.rb +2 -59
- data/lib/activefacts/cql/DataTypes.treetop +10 -1
- data/lib/activefacts/cql/Expressions.treetop +1 -1
- data/lib/activefacts/cql/FactTypes.treetop +1 -1
- data/lib/activefacts/cql/Language/English.treetop +2 -2
- data/lib/activefacts/generate/absorption.rb +0 -2
- data/lib/activefacts/generate/cql.rb +6 -8
- data/lib/activefacts/generate/cql/html.rb +1 -1
- data/lib/activefacts/generate/oo.rb +60 -40
- data/lib/activefacts/generate/ordered.rb +30 -21
- data/lib/activefacts/generate/ruby.rb +38 -15
- data/lib/activefacts/generate/sql/mysql.rb +257 -0
- data/lib/activefacts/generate/sql/server.rb +0 -1
- data/lib/activefacts/input/cql.rb +0 -2
- data/lib/activefacts/persistence/columns.rb +51 -24
- data/lib/activefacts/persistence/concept.rb +158 -36
- data/lib/activefacts/persistence/reference.rb +13 -8
- data/lib/activefacts/support.rb +40 -2
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +5 -6
- data/spec/absorption_spec.rb +8 -11
- data/spec/api/autocounter.rb +1 -1
- data/spec/api/constellation.rb +1 -1
- data/spec/api/entity_type.rb +1 -1
- data/spec/api/instance.rb +1 -1
- data/spec/api/roles.rb +1 -1
- data/spec/api/value_type.rb +1 -1
- data/spec/cql_cql_spec.rb +2 -4
- data/spec/cql_parse_spec.rb +2 -4
- data/spec/cql_ruby_spec.rb +2 -4
- data/spec/cql_sql_spec.rb +4 -4
- data/spec/cql_symbol_tables_spec.rb +1 -1
- data/spec/cql_unit_spec.rb +6 -6
- data/spec/cqldump_spec.rb +6 -6
- data/spec/norma_cql_spec.rb +2 -4
- data/spec/norma_ruby_spec.rb +2 -4
- data/spec/norma_sql_spec.rb +2 -4
- data/spec/norma_tables_spec.rb +4 -7
- metadata +29 -6
@@ -10,8 +10,6 @@ module ActiveFacts
|
|
10
10
|
module Generate #:nodoc:
|
11
11
|
class OrderedDumper #:nodoc:
|
12
12
|
# Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
|
13
|
-
include Metamodel
|
14
|
-
|
15
13
|
def initialize(vocabulary, *options)
|
16
14
|
@vocabulary = vocabulary
|
17
15
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
@@ -49,13 +47,13 @@ module ActiveFacts
|
|
49
47
|
|
50
48
|
@vocabulary.all_constraint.each { |c|
|
51
49
|
case c
|
52
|
-
when PresenceConstraint
|
50
|
+
when ActiveFacts::Metamodel::PresenceConstraint
|
53
51
|
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq # All fact types spanned by this constraint
|
54
52
|
if fact_types.size == 1 # There's only one, save it:
|
55
53
|
# debug "Single-fact constraint on #{fact_types[0].fact_type_id}: #{c.name}"
|
56
54
|
(@presence_constraints_by_fact[fact_types[0]] ||= []) << c
|
57
55
|
end
|
58
|
-
when RingConstraint
|
56
|
+
when ActiveFacts::Metamodel::RingConstraint
|
59
57
|
(@ring_constraints_by_fact[c.role.fact_type] ||= []) << c
|
60
58
|
else
|
61
59
|
# debug "Found unhandled constraint #{c.class} #{c.name}"
|
@@ -66,18 +64,27 @@ module ActiveFacts
|
|
66
64
|
|
67
65
|
def value_types_dump
|
68
66
|
done_banner = false
|
67
|
+
@value_type_dumped = {}
|
69
68
|
@vocabulary.all_feature.sort_by{|o| o.name}.each{|o|
|
70
|
-
next unless ValueType
|
69
|
+
next unless o.is_a?(ActiveFacts::Metamodel::ValueType)
|
71
70
|
|
72
71
|
value_type_banner unless done_banner
|
73
72
|
done_banner = true
|
74
73
|
|
75
|
-
|
74
|
+
value_type_chain_dump(o)
|
76
75
|
@concept_types_dumped[o] = true
|
77
76
|
}
|
78
77
|
value_type_end if done_banner
|
79
78
|
end
|
80
79
|
|
80
|
+
# Ensure that supertype gets dumped first
|
81
|
+
def value_type_chain_dump(o)
|
82
|
+
return if @value_type_dumped[o]
|
83
|
+
value_type_chain_dump(o.supertype) if (o.supertype && !@value_type_dumped[o.supertype])
|
84
|
+
value_type_dump(o)
|
85
|
+
@value_type_dumped[o] = true
|
86
|
+
end
|
87
|
+
|
81
88
|
# Try to dump entity types in order of name, but we need
|
82
89
|
# to dump ETs before they're referenced in preferred ids
|
83
90
|
# if possible (it's not always, there may be loops!)
|
@@ -86,7 +93,9 @@ module ActiveFacts
|
|
86
93
|
precursors, followers = *build_entity_dependencies
|
87
94
|
|
88
95
|
done_banner = false
|
89
|
-
sorted = @vocabulary.all_feature.select{|o|
|
96
|
+
sorted = @vocabulary.all_feature.select{|o|
|
97
|
+
o.is_a?(ActiveFacts::Metamodel::EntityType) and !o.fact_type
|
98
|
+
}.sort_by{|o| o.name}
|
90
99
|
panic = nil
|
91
100
|
while true do
|
92
101
|
count_this_pass = 0
|
@@ -154,7 +163,7 @@ module ActiveFacts
|
|
154
163
|
supers = o.supertypes
|
155
164
|
if (supers.size > 0)
|
156
165
|
# Ignore identification by a supertype:
|
157
|
-
pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(TypeInheritance) }
|
166
|
+
pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
|
158
167
|
subtype_dump(o, supers, pi)
|
159
168
|
else
|
160
169
|
non_subtype_dump(o, pi)
|
@@ -222,7 +231,7 @@ module ActiveFacts
|
|
222
231
|
|
223
232
|
constraint = fact_constraints.find{|c| # Find a UC that spans all other Roles
|
224
233
|
# internal uniqueness constraints span all roles but one, the residual:
|
225
|
-
PresenceConstraint
|
234
|
+
c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
|
226
235
|
!@constraints_used[c] && # Already verbalised
|
227
236
|
roles-c.role_sequence.all_role_ref.map(&:role) == [role]
|
228
237
|
}
|
@@ -271,7 +280,7 @@ module ActiveFacts
|
|
271
280
|
# The values of each hash entry are the precursors and followers (respectively) of that entity.
|
272
281
|
def build_entity_dependencies
|
273
282
|
@vocabulary.all_feature.inject([{},{}]) { |a, o|
|
274
|
-
if EntityType
|
283
|
+
if o.is_a?(ActiveFacts::Metamodel::EntityType) && !o.fact_type
|
275
284
|
precursor = a[0]
|
276
285
|
follower = a[1]
|
277
286
|
blocked = false
|
@@ -280,7 +289,7 @@ module ActiveFacts
|
|
280
289
|
pi.role_sequence.all_role_ref.each{|rr|
|
281
290
|
role = rr.role
|
282
291
|
player = role.concept
|
283
|
-
next unless EntityType
|
292
|
+
next unless player.is_a?(ActiveFacts::Metamodel::EntityType)
|
284
293
|
# player is a precursor of o
|
285
294
|
(precursor[o] ||= []) << player if (player != o)
|
286
295
|
(follower[player] ||= []) << o if (player != o)
|
@@ -324,7 +333,7 @@ module ActiveFacts
|
|
324
333
|
# REVISIT: There might be constraints we have to merge into the nested entity or subtype.
|
325
334
|
# These will come up as un-handled constraints:
|
326
335
|
pcs = @presence_constraints_by_fact[f]
|
327
|
-
TypeInheritance
|
336
|
+
f.is_a?(ActiveFacts::Metamodel::TypeInheritance) ||
|
328
337
|
(pcs && pcs.size > 0 && !pcs.detect{|c| !@constraints_used[c] })
|
329
338
|
end
|
330
339
|
|
@@ -395,10 +404,10 @@ module ActiveFacts
|
|
395
404
|
fact_collection = @vocabulary.constellation.FactType
|
396
405
|
fact_collection.keys.select{|fact_id|
|
397
406
|
fact_type = fact_collection[fact_id] and
|
398
|
-
!(TypeInheritance
|
407
|
+
!fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) and
|
399
408
|
!@fact_types_dumped[fact_type] and
|
400
409
|
!skip_fact_type(fact_type) and
|
401
|
-
!fact_type.all_role.detect{|r| r.concept.is_a?(EntityType) }
|
410
|
+
!fact_type.all_role.detect{|r| r.concept.is_a?(ActiveFacts::Metamodel::EntityType) }
|
402
411
|
}.sort_by{|fact_id|
|
403
412
|
fact_type = fact_collection[fact_id]
|
404
413
|
fact_type_key(fact_type)
|
@@ -412,7 +421,7 @@ module ActiveFacts
|
|
412
421
|
|
413
422
|
# REVISIT: Find out why some fact types are missed during entity dumping:
|
414
423
|
@vocabulary.constellation.FactType.values.select{|fact_type|
|
415
|
-
!(TypeInheritance
|
424
|
+
!fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
416
425
|
}.sort_by{|fact_type|
|
417
426
|
fact_type_key(fact_type)
|
418
427
|
}.each{|fact_type|
|
@@ -442,13 +451,13 @@ module ActiveFacts
|
|
442
451
|
|
443
452
|
def constraint_sort_key(c)
|
444
453
|
case c
|
445
|
-
when RingConstraint
|
454
|
+
when ActiveFacts::Metamodel::RingConstraint
|
446
455
|
[1, c.ring_type, c.role.concept.name, c.other_role.concept.name, c.name||""]
|
447
|
-
when SetComparisonConstraint
|
456
|
+
when ActiveFacts::Metamodel::SetComparisonConstraint
|
448
457
|
[2, c.all_set_comparison_roles.map{|scrs| scrs.role_sequence.all_role_ref.map{|rr| role_ref_key(rr)}}, c.name||""]
|
449
|
-
when SubsetConstraint
|
458
|
+
when ActiveFacts::Metamodel::SubsetConstraint
|
450
459
|
[3, [c.superset_role_sequence, c.subset_role_sequence].map{|rs| rs.all_role_ref.map{|rr| role_ref_key(rr)}}, c.name||""]
|
451
|
-
when PresenceConstraint
|
460
|
+
when ActiveFacts::Metamodel::PresenceConstraint
|
452
461
|
[4, c.role_sequence.all_role_ref.map{|rr| role_ref_key(rr)}, c.name||""]
|
453
462
|
end
|
454
463
|
end
|
@@ -457,7 +466,7 @@ module ActiveFacts
|
|
457
466
|
heading = false
|
458
467
|
@vocabulary.all_constraint.reject{|c| except[c]}.sort_by{ |c| constraint_sort_key(c) }.each do|c|
|
459
468
|
# Skip some PresenceConstraints:
|
460
|
-
if PresenceConstraint
|
469
|
+
if c.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
|
461
470
|
# Skip uniqueness constraints that cover all roles of a fact type, they're implicit
|
462
471
|
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
|
463
472
|
next if fact_types.size == 1 &&
|
@@ -466,7 +475,7 @@ module ActiveFacts
|
|
466
475
|
|
467
476
|
# Skip internal PresenceConstraints over TypeInheritances:
|
468
477
|
next if c.role_sequence.all_role_ref.size == 1 &&
|
469
|
-
|
478
|
+
fact_types[0].is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
470
479
|
end
|
471
480
|
|
472
481
|
constraint_banner unless heading
|
@@ -37,7 +37,7 @@ module ActiveFacts
|
|
37
37
|
puts "require 'activefacts/persistence'\n"
|
38
38
|
@tables = vocabulary.tables
|
39
39
|
end
|
40
|
-
puts "\nmodule
|
40
|
+
puts "\nmodule ::#{vocabulary.name}\n\n"
|
41
41
|
end
|
42
42
|
|
43
43
|
def vocabulary_end
|
@@ -45,26 +45,37 @@ module ActiveFacts
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def value_type_dump(o)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
is_special_supertype = !o.supertype && %w{Date Time DateAndTime}.include?(o.name)
|
49
|
+
|
50
|
+
# We map DateAndTime to DateTime; if such a ValueType exists, don't dump this one
|
51
|
+
return if is_special_supertype && o.name == 'DateAndTime' && o.constellation.ValueType[[[o.vocabulary.name], 'DateTime']]
|
52
|
+
|
53
|
+
return if !o.supertype && !is_special_supertype
|
54
|
+
if o.supertype && o.name == o.supertype.name
|
55
|
+
# In ActiveFacts, parameterising a ValueType will create a new datatype
|
56
|
+
# throw Can't handle parameterized value type of same name as its datatype" if ...
|
52
57
|
end
|
53
58
|
|
54
59
|
length = (l = o.length) && l > 0 ? ":length => #{l}" : nil
|
55
60
|
scale = (s = o.scale) && s > 0 ? ":scale => #{s}" : nil
|
56
61
|
params = [length,scale].compact * ", "
|
57
62
|
|
63
|
+
name = o.name
|
58
64
|
ruby_type_name =
|
59
|
-
case o.supertype.name
|
65
|
+
case o.supertype ? o.supertype.name : o.name
|
60
66
|
when "VariableLengthText"; "String"
|
61
67
|
when "Date"; "::Date"
|
68
|
+
when "DateAndTime"; "::DateTime"
|
69
|
+
when "Time"; "::Time"
|
62
70
|
else o.supertype.name
|
63
71
|
end
|
64
72
|
|
65
|
-
|
73
|
+
name = name.sub(/^[a-z]/) {|i| i.upcase}
|
74
|
+
puts " class #{name} < #{ruby_type_name}\n" +
|
66
75
|
" value_type #{params}\n"
|
67
|
-
|
76
|
+
if @sql and o.is_table
|
77
|
+
puts " table"
|
78
|
+
end
|
68
79
|
puts " \# REVISIT: #{o.name} has restricted values\n" if o.value_restriction
|
69
80
|
puts " \# REVISIT: #{o.name} is in units of #{o.unit.name}\n" if o.unit
|
70
81
|
roles_dump(o)
|
@@ -78,7 +89,9 @@ module ActiveFacts
|
|
78
89
|
puts " class #{o.name} < #{ primary_supertype.name }"
|
79
90
|
puts " identified_by #{identified_by(o, pi)}" if pi
|
80
91
|
puts " supertypes "+secondary_supertypes.map(&:name)*", " if secondary_supertypes.size > 0
|
81
|
-
|
92
|
+
if @sql and o.is_table
|
93
|
+
puts " table"
|
94
|
+
end
|
82
95
|
fact_roles_dump(o.fact_type) if o.fact_type
|
83
96
|
roles_dump(o)
|
84
97
|
puts " end\n\n"
|
@@ -87,8 +100,12 @@ module ActiveFacts
|
|
87
100
|
|
88
101
|
def non_subtype_dump(o, pi)
|
89
102
|
puts " class #{o.name}"
|
103
|
+
|
104
|
+
# We want to name the absorption role only when it's absorbed along its single identifying role.
|
90
105
|
puts " identified_by #{identified_by(o, pi)}"
|
91
|
-
|
106
|
+
if @sql and o.is_table
|
107
|
+
puts " table"
|
108
|
+
end
|
92
109
|
fact_roles_dump(o.fact_type) if o.fact_type
|
93
110
|
roles_dump(o)
|
94
111
|
puts " end\n\n"
|
@@ -122,7 +139,7 @@ module ActiveFacts
|
|
122
139
|
|
123
140
|
def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
|
124
141
|
identifying_roles.map{|role|
|
125
|
-
":"+preferred_role_name(role)
|
142
|
+
":"+preferred_role_name(role, entity_type)
|
126
143
|
}*", "
|
127
144
|
end
|
128
145
|
|
@@ -132,13 +149,11 @@ module ActiveFacts
|
|
132
149
|
|
133
150
|
def binary_dump(role, role_name, role_player, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
|
134
151
|
# Find whether we need the name of the other role player, and whether it's defined yet:
|
135
|
-
if role_name.camelcase(true) == role_player.name
|
152
|
+
if role_name.camelcase(true) == role_player.name.sub(/^[a-z]/) {|i| i.upcase}
|
136
153
|
# Don't use Class name if implied by rolename
|
137
154
|
role_reference = nil
|
138
|
-
elsif !@concept_types_dumped[role_player]
|
139
|
-
role_reference = '"'+role_player.name+'"'
|
140
155
|
else
|
141
|
-
role_reference = role_player
|
156
|
+
role_reference = concept_reference(role_player)
|
142
157
|
end
|
143
158
|
other_role_name = ":"+other_role_name if other_role_name
|
144
159
|
|
@@ -154,6 +169,14 @@ module ActiveFacts
|
|
154
169
|
puts " \# REVISIT: #{other_role_name} has restricted values\n" if role.role_value_restriction
|
155
170
|
end
|
156
171
|
|
172
|
+
def concept_reference concept
|
173
|
+
if !@concept_types_dumped[concept]
|
174
|
+
'"'+concept.name+'"'
|
175
|
+
else
|
176
|
+
role_reference = concept.name
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
157
180
|
end
|
158
181
|
end
|
159
182
|
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate SQL for MySQL from an ActiveFacts vocabulary.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Daniel Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
require 'activefacts/vocabulary'
|
8
|
+
require 'activefacts/persistence'
|
9
|
+
|
10
|
+
module ActiveFacts
|
11
|
+
module Generate
|
12
|
+
class SQL #:nodoc:
|
13
|
+
# Generate SQL for MySQL for an ActiveFacts vocabulary.
|
14
|
+
# Invoke as
|
15
|
+
# afgen --sql/mysql[=options] <file>.cql
|
16
|
+
# Options are comma or space separated:
|
17
|
+
# * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
|
18
|
+
# * norma Translate datatypes from NORMA to SQL Server
|
19
|
+
class MYSQL
|
20
|
+
private
|
21
|
+
include Persistence
|
22
|
+
ColumnNameMax = 63
|
23
|
+
DefaultCharColLength = 63
|
24
|
+
|
25
|
+
RESERVED_WORDS = %w{
|
26
|
+
ACCESSIBLE ADD ALL ALTER ANALYZE AND AS ASC ASENSITIVE
|
27
|
+
BEFORE BETWEEN BIGINT BINARY BLOB BOTH BY CALL CASCADE
|
28
|
+
CASE CHANGE CHAR CHARACTER CHECK COLLATE COLUMN CONNECTION
|
29
|
+
CONDITION CONSTRAINT CONTINUE CONVERT CREATE CROSS
|
30
|
+
CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
|
31
|
+
CURSOR DATABASE DATABASES DAY_HOUR DAY_MICROSECOND
|
32
|
+
DAY_MINUTE DAY_SECOND DEC DECIMAL DECLARE DEFAULT DELAYED
|
33
|
+
DELETE DESC DESCRIBE DETERMINISTIC DISTINCT DISTINCTROW
|
34
|
+
DIV DOUBLE DROP DUAL EACH ELSE ELSEIF ENCLOSED ESCAPED
|
35
|
+
EXISTS EXIT EXPLAIN FALSE FETCH FLOAT FLOAT4 FLOAT8 FOR
|
36
|
+
FORCE FOREIGN FROM FULLTEXT GRANT GROUP HAVING HIGH_PRIORITY
|
37
|
+
HOUR_MICROSECOND HOUR_MINUTE HOUR_SECOND IF IGNORE IN
|
38
|
+
INDEX INFILE INNER INOUT INSENSITIVE INSERT INT INT1 INT2
|
39
|
+
INT3 INT4 INT8 INTEGER INTERVAL INTO IS ITERATE JOIN KEY
|
40
|
+
KEYS KILL LEADING LEAVE LEFT LIKE LIMIT LINEAR LINES LOAD
|
41
|
+
LOCALTIME LOCALTIMESTAMP LOCK LONG LONGBLOB LONGTEXT LOOP
|
42
|
+
LOW_PRIORITY MASTER_SSL_VERIFY_SERVER_CERT MATCH MEDIUMBLOB
|
43
|
+
MEDIUMINT MEDIUMTEXT MIDDLEINT MINUTE_MICROSECOND
|
44
|
+
MINUTE_SECOND MOD MODIFIES NATURAL NOT NO_WRITE_TO_BINLOG
|
45
|
+
NULL NUMERIC ON OPTIMIZE OPTION OPTIONALLY OR ORDER OUT
|
46
|
+
OUTER OUTFILE PRECISION PRIMARY PROCEDURE PURGE RANGE
|
47
|
+
READ READ_ONLY READS READ_WRITE READ_WRITE REAL REFERENCES
|
48
|
+
REGEXP RELEASE RENAME REPEAT REPLACE REQUIRE RESTRICT
|
49
|
+
RETURN REVOKE RIGHT RLIKE SCHEMA SCHEMAS SECOND_MICROSECOND
|
50
|
+
SELECT SENSITIVE SEPARATOR SET SHOW SMALLINT SPATIAL
|
51
|
+
SPECIFIC SQL SQL_BIG_RESULT SQL_CALC_FOUND_ROWS SQLEXCEPTION
|
52
|
+
SQL_SMALL_RESULT SQLSTATE SQLWARNING SSL STARTING
|
53
|
+
STRAIGHT_JOIN TABLE TERMINATED THEN TINYBLOB TINYINT
|
54
|
+
TINYTEXT TO TRAILING TRIGGER TRUE UNDO UNION UNIQUE UNLOCK
|
55
|
+
UNSIGNED UPDATE UPGRADE USAGE USE USING UTC_DATE UTC_TIME
|
56
|
+
UTC_TIMESTAMP VALUES VARBINARY VARCHAR VARCHARACTER VARYING
|
57
|
+
WHEN WHERE WHILE WITH WRITE XOR YEAR_MONTH ZEROFILL
|
58
|
+
}.inject({}){ |h,w| h[w] = true; h }
|
59
|
+
|
60
|
+
def initialize(vocabulary, *options)
|
61
|
+
@vocabulary = vocabulary
|
62
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
63
|
+
@delay_fks = options.include? "delay_fks"
|
64
|
+
@norma = options.include? "norma"
|
65
|
+
end
|
66
|
+
|
67
|
+
def puts s
|
68
|
+
@out.puts s
|
69
|
+
end
|
70
|
+
|
71
|
+
def go s
|
72
|
+
puts s + ";\n\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
def escape s
|
76
|
+
# Escape SQL keywords and non-identifiers
|
77
|
+
s = s[0...120]
|
78
|
+
if s =~ /[^A-Za-z0-9_]/ || RESERVED_WORDS[s.upcase]
|
79
|
+
"`#{s}`"
|
80
|
+
else
|
81
|
+
s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return SQL type and (modified?) length for the passed NORMA base type
|
86
|
+
def norma_type(type, length)
|
87
|
+
sql_type = case type
|
88
|
+
when "AutoCounter"; "INT"
|
89
|
+
when "SignedInteger",
|
90
|
+
"SignedSmallInteger"
|
91
|
+
s = case
|
92
|
+
when length <= 8; "TINYINT UNSIGNED"
|
93
|
+
when length <= 16; "SMALLINT UNSIGNED"
|
94
|
+
when length <= 24; "MEDIUMINT UNSIGNED"
|
95
|
+
else "INT UNSIGNED"
|
96
|
+
end
|
97
|
+
length = nil
|
98
|
+
s
|
99
|
+
when "UnsignedInteger",
|
100
|
+
"UnsignedSmallInteger",
|
101
|
+
"UnsignedTinyInteger"
|
102
|
+
s = case
|
103
|
+
when length <= 8; "TINYINT"
|
104
|
+
when length <= 16; "SMALLINT"
|
105
|
+
when length <= 24; "MEDIUMINT"
|
106
|
+
when length <= 32; "INT"
|
107
|
+
else "BIGINT"
|
108
|
+
end
|
109
|
+
length = nil
|
110
|
+
s
|
111
|
+
when "Decimal"; "DECIMAL"
|
112
|
+
|
113
|
+
when "FixedLengthText";
|
114
|
+
length ||= DefaultCharColLength
|
115
|
+
"CHAR"
|
116
|
+
when "VariableLengthText";
|
117
|
+
length ||= DefaultCharColLength
|
118
|
+
"VARCHAR"
|
119
|
+
# There are several large length text types; If you need to store more than 65k chars, look at using MEDIUMTEXT or LONGTEXT
|
120
|
+
# CQL does not yet allow you to specify a length for LargeLengthText.
|
121
|
+
when "LargeLengthText"; "TEXT"
|
122
|
+
|
123
|
+
when "DateAndTime"; "DATETIME"
|
124
|
+
when "Date"; "DATE"
|
125
|
+
when "Time"; "TIME"
|
126
|
+
when "AutoTimestamp"; "TIMESTAMP"
|
127
|
+
|
128
|
+
when "Money"; "DECIMAL"
|
129
|
+
# Warning: Max 65 kbytes. To use larger types, try MediumBlob (16mb) or LongBlob (4gb)
|
130
|
+
when "PictureRawData"; "BLOB"
|
131
|
+
when "VariableLengthRawData"; "BLOB"
|
132
|
+
# Assuming you only want a boolean out of this. Should we specify length instead?
|
133
|
+
when "BIT"; "BIT"
|
134
|
+
else raise "SQL type unknown for NORMA type #{type}"
|
135
|
+
end
|
136
|
+
[sql_type, length]
|
137
|
+
end
|
138
|
+
|
139
|
+
public
|
140
|
+
def generate(out = $>) #:nodoc:
|
141
|
+
@out = out
|
142
|
+
#go "CREATE SCHEMA #{@vocabulary.name}"
|
143
|
+
|
144
|
+
tables_emitted = {}
|
145
|
+
delayed_foreign_keys = []
|
146
|
+
|
147
|
+
@vocabulary.tables.each do |table|
|
148
|
+
puts "CREATE TABLE #{escape table.name} ("
|
149
|
+
|
150
|
+
pk = table.identifier_columns
|
151
|
+
identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
|
152
|
+
|
153
|
+
fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
|
154
|
+
fk_columns = table.columns.select do |column|
|
155
|
+
column.references[0].is_simple_reference
|
156
|
+
end
|
157
|
+
|
158
|
+
# We sort the columns here, not in the persistence layer, because it affects
|
159
|
+
# the ordering of columns in an index :-(.
|
160
|
+
columns = table.columns.sort_by { |column| column.name(nil) }.map do |column|
|
161
|
+
name = escape column.name("")
|
162
|
+
padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
|
163
|
+
type, params, restrictions = column.type
|
164
|
+
restrictions = [] if (fk_columns.include?(column)) # Don't enforce VT restrictions on FK columns
|
165
|
+
length = params[:length]
|
166
|
+
length &&= length.to_i
|
167
|
+
scale = params[:scale]
|
168
|
+
scale &&= scale.to_i
|
169
|
+
type, length = norma_type(type, length) if @norma
|
170
|
+
sql_type = "#{type}#{
|
171
|
+
if !length
|
172
|
+
""
|
173
|
+
else
|
174
|
+
"(" + length.to_s + (scale ? ", #{scale}" : "") + ")"
|
175
|
+
end
|
176
|
+
}"
|
177
|
+
identity = column == identity_column ? " AUTO_INCREMENT" : ""
|
178
|
+
null = (column.is_mandatory ? "NOT " : "") + "NULL"
|
179
|
+
check = check_clause(name, restrictions)
|
180
|
+
comment = column.comment
|
181
|
+
[ "-- #{comment}", "#{name}#{padding}#{sql_type}#{identity} #{null}#{check}" ]
|
182
|
+
end.flatten
|
183
|
+
|
184
|
+
pk_def = (pk.detect{|column| !column.is_mandatory} ? "UNIQUE(" : "PRIMARY KEY(") +
|
185
|
+
pk.map{|column| escape column.name("")}*", " +
|
186
|
+
")"
|
187
|
+
|
188
|
+
inline_fks = []
|
189
|
+
table.foreign_keys.each do |fk|
|
190
|
+
fk_text = "FOREIGN KEY (" +
|
191
|
+
fk.from_columns.map{|column| column.name}*", " +
|
192
|
+
") REFERENCES #{escape fk.to.name} (" +
|
193
|
+
fk.to_columns.map{|column| column.name}*", " +
|
194
|
+
")"
|
195
|
+
if !@delay_fks and # We don't want to delay all Fks
|
196
|
+
(tables_emitted[fk.to] or # The target table has been emitted
|
197
|
+
fk.to == table && !fk.to_columns.detect{|column| !column.is_mandatory}) # The reference columns already have the required indexes
|
198
|
+
inline_fks << fk_text
|
199
|
+
else
|
200
|
+
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name}\n\tADD " + fk_text)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
indices = table.indices
|
205
|
+
inline_indices = []
|
206
|
+
delayed_indices = []
|
207
|
+
indices.each do |index|
|
208
|
+
next if index.over == table && index.is_primary # Already did the primary keys
|
209
|
+
abbreviated_column_names = index.abbreviated_column_names*""
|
210
|
+
column_names = index.column_names
|
211
|
+
column_name_list = column_names.map{|n| escape(n)}*", "
|
212
|
+
inline_indices << "UNIQUE(#{column_name_list})"
|
213
|
+
end
|
214
|
+
|
215
|
+
tables_emitted[table] = true
|
216
|
+
|
217
|
+
puts("\t" + (columns + [pk_def] + inline_indices + inline_fks)*",\n\t")
|
218
|
+
go ")"
|
219
|
+
delayed_indices.each {|index_text|
|
220
|
+
go index_text
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
delayed_foreign_keys.each do |fk|
|
225
|
+
go fk
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
def check_clause(column_name, restrictions)
|
231
|
+
return "" if restrictions.empty?
|
232
|
+
# REVISIT: Merge all restrictions (later; now just use the first)
|
233
|
+
" CHECK(" +
|
234
|
+
restrictions[0].all_allowed_range.sort_by do |ar|
|
235
|
+
# Put the allowed ranges into a defined order:
|
236
|
+
((min = ar.value_range.minimum_bound) && min.value) ||
|
237
|
+
((max = ar.value_range.maximum_bound) && max.value)
|
238
|
+
end.map do |ar|
|
239
|
+
vr = ar.value_range
|
240
|
+
min = vr.minimum_bound
|
241
|
+
max = vr.maximum_bound
|
242
|
+
if (min && max && max.value == min.value)
|
243
|
+
"#{column_name} = #{min.value}"
|
244
|
+
else
|
245
|
+
inequalities = [
|
246
|
+
min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{min.value}",
|
247
|
+
max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{max.value}"
|
248
|
+
].compact
|
249
|
+
inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
|
250
|
+
end
|
251
|
+
end*" OR " +
|
252
|
+
")"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|