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