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.
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