activefacts 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/Manifest.txt +1 -0
  2. data/Rakefile +3 -0
  3. data/bin/afgen +9 -3
  4. data/bin/cql +0 -0
  5. data/examples/CQL/Address.cql +7 -7
  6. data/examples/CQL/Blog.cql +8 -8
  7. data/examples/CQL/CompanyDirectorEmployee.cql +3 -3
  8. data/examples/CQL/Death.cql +2 -2
  9. data/examples/CQL/Genealogy.cql +21 -21
  10. data/examples/CQL/Marriage.cql +1 -1
  11. data/examples/CQL/Metamodel.cql +34 -29
  12. data/examples/CQL/MultiInheritance.cql +3 -3
  13. data/examples/CQL/OilSupply.cql +9 -9
  14. data/examples/CQL/Orienteering.cql +27 -27
  15. data/examples/CQL/PersonPlaysGame.cql +2 -2
  16. data/examples/CQL/SchoolActivities.cql +3 -3
  17. data/examples/CQL/SimplestUnary.cql +1 -1
  18. data/examples/CQL/SubtypePI.cql +4 -4
  19. data/examples/CQL/Warehousing.cql +12 -12
  20. data/examples/CQL/WindowInRoomInBldg.cql +4 -4
  21. data/lib/activefacts/api/concept.rb +3 -2
  22. data/lib/activefacts/api/constellation.rb +1 -1
  23. data/lib/activefacts/api/entity.rb +12 -1
  24. data/lib/activefacts/api/instance.rb +1 -1
  25. data/lib/activefacts/api/role.rb +1 -1
  26. data/lib/activefacts/api/standard_types.rb +9 -1
  27. data/lib/activefacts/api/support.rb +4 -0
  28. data/lib/activefacts/api/value.rb +1 -0
  29. data/lib/activefacts/api/vocabulary.rb +2 -59
  30. data/lib/activefacts/cql/DataTypes.treetop +10 -1
  31. data/lib/activefacts/cql/Expressions.treetop +1 -1
  32. data/lib/activefacts/cql/FactTypes.treetop +1 -1
  33. data/lib/activefacts/cql/Language/English.treetop +2 -2
  34. data/lib/activefacts/generate/absorption.rb +0 -2
  35. data/lib/activefacts/generate/cql.rb +6 -8
  36. data/lib/activefacts/generate/cql/html.rb +1 -1
  37. data/lib/activefacts/generate/oo.rb +60 -40
  38. data/lib/activefacts/generate/ordered.rb +30 -21
  39. data/lib/activefacts/generate/ruby.rb +38 -15
  40. data/lib/activefacts/generate/sql/mysql.rb +257 -0
  41. data/lib/activefacts/generate/sql/server.rb +0 -1
  42. data/lib/activefacts/input/cql.rb +0 -2
  43. data/lib/activefacts/persistence/columns.rb +51 -24
  44. data/lib/activefacts/persistence/concept.rb +158 -36
  45. data/lib/activefacts/persistence/reference.rb +13 -8
  46. data/lib/activefacts/support.rb +40 -2
  47. data/lib/activefacts/version.rb +1 -1
  48. data/lib/activefacts/vocabulary/extensions.rb +5 -6
  49. data/spec/absorption_spec.rb +8 -11
  50. data/spec/api/autocounter.rb +1 -1
  51. data/spec/api/constellation.rb +1 -1
  52. data/spec/api/entity_type.rb +1 -1
  53. data/spec/api/instance.rb +1 -1
  54. data/spec/api/roles.rb +1 -1
  55. data/spec/api/value_type.rb +1 -1
  56. data/spec/cql_cql_spec.rb +2 -4
  57. data/spec/cql_parse_spec.rb +2 -4
  58. data/spec/cql_ruby_spec.rb +2 -4
  59. data/spec/cql_sql_spec.rb +4 -4
  60. data/spec/cql_symbol_tables_spec.rb +1 -1
  61. data/spec/cql_unit_spec.rb +6 -6
  62. data/spec/cqldump_spec.rb +6 -6
  63. data/spec/norma_cql_spec.rb +2 -4
  64. data/spec/norma_ruby_spec.rb +2 -4
  65. data/spec/norma_sql_spec.rb +2 -4
  66. data/spec/norma_tables_spec.rb +4 -7
  67. 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 === o
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
- value_type_dump(o)
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| EntityType === o and !o.fact_type }.sort_by{|o| o.name}
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 === c &&
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 === o && !o.fact_type
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 === player
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 === f ||
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 === fact_type) and
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 === fact_type)
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 === c
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
- TypeInheritance === fact_types[0]
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 #{vocabulary.name}\n\n"
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
- return if !o.supertype
49
- if o.name == o.supertype.name
50
- # In ActiveFacts, parameterising a ValueType will create a new datatype
51
- # throw Can't handle parameterized value type of same name as its datatype" if ...
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
- puts " class #{o.name} < #{ruby_type_name}\n" +
73
+ name = name.sub(/^[a-z]/) {|i| i.upcase}
74
+ puts " class #{name} < #{ruby_type_name}\n" +
66
75
  " value_type #{params}\n"
67
- puts " table" if @sql and o.is_table
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
- puts " table" if @sql and o.is_table
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
- puts " table" if @sql and o.is_table
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.name
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