activefacts 0.6.0

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 (84) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +83 -0
  3. data/README.rdoc +81 -0
  4. data/Rakefile +41 -0
  5. data/bin/afgen +46 -0
  6. data/bin/cql +52 -0
  7. data/examples/CQL/Address.cql +46 -0
  8. data/examples/CQL/Blog.cql +54 -0
  9. data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
  10. data/examples/CQL/Death.cql +16 -0
  11. data/examples/CQL/Genealogy.cql +95 -0
  12. data/examples/CQL/Marriage.cql +18 -0
  13. data/examples/CQL/Metamodel.cql +238 -0
  14. data/examples/CQL/MultiInheritance.cql +19 -0
  15. data/examples/CQL/OilSupply.cql +47 -0
  16. data/examples/CQL/Orienteering.cql +108 -0
  17. data/examples/CQL/PersonPlaysGame.cql +17 -0
  18. data/examples/CQL/SchoolActivities.cql +31 -0
  19. data/examples/CQL/SimplestUnary.cql +12 -0
  20. data/examples/CQL/SubtypePI.cql +32 -0
  21. data/examples/CQL/Warehousing.cql +99 -0
  22. data/examples/CQL/WindowInRoomInBldg.cql +22 -0
  23. data/lib/activefacts.rb +10 -0
  24. data/lib/activefacts/api.rb +25 -0
  25. data/lib/activefacts/api/concept.rb +384 -0
  26. data/lib/activefacts/api/constellation.rb +106 -0
  27. data/lib/activefacts/api/entity.rb +239 -0
  28. data/lib/activefacts/api/instance.rb +54 -0
  29. data/lib/activefacts/api/numeric.rb +158 -0
  30. data/lib/activefacts/api/role.rb +94 -0
  31. data/lib/activefacts/api/standard_types.rb +67 -0
  32. data/lib/activefacts/api/support.rb +59 -0
  33. data/lib/activefacts/api/value.rb +122 -0
  34. data/lib/activefacts/api/vocabulary.rb +120 -0
  35. data/lib/activefacts/cql.rb +31 -0
  36. data/lib/activefacts/cql/CQLParser.treetop +104 -0
  37. data/lib/activefacts/cql/Concepts.treetop +112 -0
  38. data/lib/activefacts/cql/DataTypes.treetop +66 -0
  39. data/lib/activefacts/cql/Expressions.treetop +113 -0
  40. data/lib/activefacts/cql/FactTypes.treetop +185 -0
  41. data/lib/activefacts/cql/Language/English.treetop +92 -0
  42. data/lib/activefacts/cql/LexicalRules.treetop +169 -0
  43. data/lib/activefacts/cql/Rakefile +6 -0
  44. data/lib/activefacts/cql/parser.rb +88 -0
  45. data/lib/activefacts/generate/absorption.rb +87 -0
  46. data/lib/activefacts/generate/cql.rb +441 -0
  47. data/lib/activefacts/generate/cql/html.rb +397 -0
  48. data/lib/activefacts/generate/null.rb +19 -0
  49. data/lib/activefacts/generate/ordered.rb +557 -0
  50. data/lib/activefacts/generate/ruby.rb +326 -0
  51. data/lib/activefacts/generate/sql/server.rb +164 -0
  52. data/lib/activefacts/generate/text.rb +21 -0
  53. data/lib/activefacts/input/cql.rb +1268 -0
  54. data/lib/activefacts/input/orm.rb +926 -0
  55. data/lib/activefacts/persistence.rb +1 -0
  56. data/lib/activefacts/persistence/composition.rb +653 -0
  57. data/lib/activefacts/support.rb +51 -0
  58. data/lib/activefacts/version.rb +3 -0
  59. data/lib/activefacts/vocabulary.rb +6 -0
  60. data/lib/activefacts/vocabulary/extensions.rb +343 -0
  61. data/lib/activefacts/vocabulary/metamodel.rb +303 -0
  62. data/script/txt2html +71 -0
  63. data/spec/absorption_spec.rb +95 -0
  64. data/spec/api/autocounter.rb +82 -0
  65. data/spec/api/constellation.rb +130 -0
  66. data/spec/api/entity_type.rb +101 -0
  67. data/spec/api/instance.rb +428 -0
  68. data/spec/api/roles.rb +122 -0
  69. data/spec/api/value_type.rb +112 -0
  70. data/spec/api_spec.rb +14 -0
  71. data/spec/cql_cql_spec.rb +58 -0
  72. data/spec/cql_parse_spec.rb +31 -0
  73. data/spec/cql_ruby_spec.rb +60 -0
  74. data/spec/cql_sql_spec.rb +54 -0
  75. data/spec/cql_symbol_tables_spec.rb +259 -0
  76. data/spec/cql_unit_spec.rb +336 -0
  77. data/spec/cqldump_spec.rb +169 -0
  78. data/spec/norma_cql_spec.rb +48 -0
  79. data/spec/norma_ruby_spec.rb +50 -0
  80. data/spec/norma_sql_spec.rb +45 -0
  81. data/spec/norma_tables_spec.rb +94 -0
  82. data/spec/spec.opts +1 -0
  83. data/spec/spec_helper.rb +10 -0
  84. metadata +173 -0
@@ -0,0 +1,326 @@
1
+ #
2
+ # Generate Ruby for the ActiveFacts API from an ActiveFacts vocabulary.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/vocabulary'
6
+ require 'activefacts/generate/ordered'
7
+
8
+ module ActiveFacts
9
+
10
+ module Generate
11
+ class RUBY < OrderedDumper
12
+ include Metamodel
13
+
14
+ def set_option(option)
15
+ @sql ||= false
16
+ case option
17
+ when 'sql'; @sql = true
18
+ else super
19
+ end
20
+ end
21
+
22
+ def vocabulary_start(vocabulary)
23
+ if @sql
24
+ require 'activefacts/persistence'
25
+ @tables = vocabulary.tables
26
+ end
27
+ puts "require 'activefacts/api'\n\n"
28
+ puts "module #{vocabulary.name}\n\n"
29
+ end
30
+
31
+ def constraints_dump(constraints_used)
32
+ # Stub, not needed.
33
+ end
34
+
35
+ def vocabulary_end
36
+ puts "end"
37
+ end
38
+
39
+ def value_type_banner
40
+ end
41
+
42
+ def value_type_end
43
+ end
44
+
45
+ def value_type_dump(o)
46
+ return if !o.supertype
47
+ if o.name == o.supertype.name
48
+ # In ActiveFacts, parameterising a ValueType will create a new datatype
49
+ # throw Can't handle parameterized value type of same name as its datatype" if ...
50
+ end
51
+
52
+ length = (l = o.length) && l > 0 ? ":length => #{l}" : nil
53
+ scale = (s = o.scale) && s > 0 ? ":scale => #{s}" : nil
54
+ params = [length,scale].compact * ", "
55
+
56
+ ruby_type_name =
57
+ case o.supertype.name
58
+ when "VariableLengthText"; "String"
59
+ when "Date"; "::Date"
60
+ else o.supertype.name
61
+ end
62
+
63
+ puts " class #{o.name} < #{ruby_type_name}\n" +
64
+ " value_type #{params}\n"
65
+ puts " table" if @sql and @tables.include? o
66
+ puts " \# REVISIT: #{o.name} has restricted values\n" if o.value_restriction
67
+ puts " \# REVISIT: #{o.name} is in units of #{o.unit.name}\n" if o.unit
68
+ roles_dump(o)
69
+ puts " end\n\n"
70
+ end
71
+
72
+ def roles_dump(o)
73
+ ar_by_role = nil
74
+ if @sql and @tables.include?(o)
75
+ ar = o.absorbed_roles
76
+ ar_by_role = ar.all_role_ref.inject({}){|h,rr|
77
+ input_role = (j=rr.all_join_path).size > 0 ? j[0].input_role : rr.role
78
+ (h[input_role] ||= []) << rr
79
+ h
80
+ }
81
+ #puts ar.all_role_ref.map{|rr| "\t"+rr.describe}*"\n"
82
+ end
83
+ o.all_role.
84
+ sort_by{|role|
85
+ other_role = role.fact_type.all_role[role.fact_type.all_role[0] != role ? 0 : -1]
86
+ other_role ? preferred_role_name(other_role) : ""
87
+ #puts "\t#{role.fact_type.describe(other_role)} by #{p}"
88
+ }.each{|role|
89
+ other_role = role.fact_type.all_role[role.fact_type.all_role[0] != role ? 0 : -1]
90
+ if ar_by_role and ar_by_role[other_role]
91
+ puts " # role #{role.fact_type.describe(role)}: absorbs in through #{preferred_role_name(other_role)}: "+ar_by_role[other_role].map(&:column_name)*", "
92
+ end
93
+ role_dump(role)
94
+ }
95
+ end
96
+
97
+ def preferred_role_name(role)
98
+ return "" if TypeInheritance === role.fact_type
99
+ # debug "Looking for preferred_role_name of #{describe_fact_type(role.fact_type, role)}"
100
+ reading = role.fact_type.preferred_reading
101
+ preferred_role_ref = reading.role_sequence.all_role_ref.detect{|reading_rr|
102
+ reading_rr.role == role
103
+ }
104
+
105
+ # Unaries are a hack, with only one role for what is effectively a binary:
106
+ if (role.fact_type.all_role.size == 1)
107
+ return (role.role_name && role.role_name.snakecase) ||
108
+ reading.reading_text.gsub(/ *\{0\} */,'').gsub(' ','_').downcase
109
+ end
110
+
111
+ # debug "\tleading_adjective=#{(p=preferred_role_ref).leading_adjective}, role_name=#{role.role_name}, role player=#{role.concept.name}, trailing_adjective=#{p.trailing_adjective}"
112
+ role_words = []
113
+ role_name = role.role_name
114
+ role_name = nil if role_name == ""
115
+
116
+ # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
117
+ la = preferred_role_ref.leading_adjective
118
+ role_words << la.gsub(/ /,'_') if la && la != "" and !role.role_name
119
+
120
+ role_words << (role_name || role.concept.name)
121
+ # REVISIT: Same when trailing_adjective is a suffix of the role_name
122
+ ta = preferred_role_ref.trailing_adjective
123
+ role_words << ta.gsub(/ /,'_') if ta && ta != "" and !role_name
124
+ n = role_words.map{|w| w.gsub(/([a-z])([A-Z]+)/,'\1_\2').downcase}*"_"
125
+ # debug "\tresult=#{n}"
126
+ n
127
+ end
128
+
129
+ def role_dump(role)
130
+ fact_type = role.fact_type
131
+ if fact_type.all_role.size == 1
132
+ # Handle Unary Roles here
133
+ puts " maybe :"+preferred_role_name(role)
134
+ return
135
+ elsif fact_type.all_role.size != 2
136
+ return # ternaries and higher are always objectified
137
+ end
138
+
139
+ # REVISIT: TypeInheritance
140
+ if TypeInheritance === fact_type
141
+ # debug "Ignoring role #{role} in #{fact_type}, subtype fact type"
142
+ return
143
+ end
144
+
145
+ other_role_number = fact_type.all_role[0] == role ? 1 : 0
146
+ other_role = fact_type.all_role[other_role_number]
147
+ other_role_name = preferred_role_name(other_role)
148
+ #other_role_name = ruby_role_name(other_role)
149
+ other_player = other_role.concept
150
+
151
+ # Find any uniqueness constraint over this role:
152
+ fact_constraints = @presence_constraints_by_fact[fact_type]
153
+ #debug "Considering #{fact_constraints.size} fact constraints over fact role #{role.concept.name}"
154
+ ucs = fact_constraints.select{|c| PresenceConstraint === c && c.max_frequency == 1 }
155
+ # Emit "has_one/one_to_one..." only for functional roles here:
156
+ #debug "Considering #{ucs.size} unique constraints over role #{role.concept.name}"
157
+ unless ucs.find {|c|
158
+ roles = c.role_sequence.all_role_ref.map(&:role)
159
+ #debug "Unique constraint over role #{role.concept.name} has roles #{roles.map{|r| describe_fact_type(r.fact_type, r)}*", "}"
160
+ roles == [role]
161
+ }
162
+ #debug "No uniqueness constraint found for #{role} in #{fact_type}"
163
+ return
164
+ end
165
+
166
+ if ucs.find {|c| c.role_sequence.all_role_ref.map(&:role) == [other_role] } &&
167
+ !@concept_types_dumped[other_role.concept]
168
+ #debug "Will dump 1:1 later for #{role} in #{fact_type}"
169
+ return
170
+ end
171
+
172
+ # It's a one_to_one if there's a uniqueness constraint on the other role:
173
+ one_to_one = ucs.find {|c| c.role_sequence.all_role_ref.map(&:role) == [other_role] }
174
+
175
+ # REVISIT: Add readings
176
+
177
+ # Find role name:
178
+ role_method = preferred_role_name(role)
179
+ by = other_role_name != other_player.name.snakecase ? "_by_#{other_role_name}" : ""
180
+ other_role_method = one_to_one ? role_method : "all_"+role_method
181
+ other_role_method += by
182
+
183
+ role_name = role_method
184
+ role_name = nil if role_name == role.concept.name.snakecase
185
+
186
+ binary_dump(other_role_name, other_player, one_to_one, nil, role_name, other_role_method)
187
+ puts " \# REVISIT: #{other_role_name} has restricted values\n" if role.role_value_restriction
188
+ end
189
+
190
+ def subtype_dump(o, supertypes, pi = nil)
191
+ puts " class #{o.name} < #{ supertypes[0].name }"
192
+ puts " identified_by #{identified_by(o, pi)}" if pi
193
+ puts " table" if @sql and @tables.include? o
194
+ fact_roles_dump(o.fact_type) if o.fact_type
195
+ roles_dump(o)
196
+ puts " end\n\n"
197
+ @constraints_used[pi] = true if pi
198
+ end
199
+
200
+ def non_subtype_dump(o, pi)
201
+ puts " class #{o.name}"
202
+ puts " identified_by #{identified_by(o, pi)}"
203
+ puts " table" if @sql and @tables.include? o
204
+ fact_roles_dump(o.fact_type) if o.fact_type
205
+ roles_dump(o)
206
+ puts " end\n\n"
207
+ @constraints_used[pi] = true
208
+ end
209
+
210
+ def skip_fact_type(f)
211
+ # REVISIT: There might be constraints we have to merge into the nested entity or subtype.
212
+ # These will come up as un-handled constraints:
213
+ #debug "Skipping objectified fact type #{f.entity_type.name}" if f.entity_type
214
+ #f.entity_type ||
215
+ TypeInheritance === f
216
+ end
217
+
218
+ # An objectified fact type has internal roles that are always "has_one":
219
+ def fact_roles_dump(fact)
220
+ fact.all_role.sort_by{|role|
221
+ preferred_role_name(role)
222
+ }.each{|role|
223
+ role_name = preferred_role_name(role)
224
+ by = role_name != role.concept.name.snakecase ? "_by_#{role_name}" : ""
225
+ raise "Fact #{fact.describe} type is not objectified" unless fact.entity_type
226
+ other_role_method = "all_"+fact.entity_type.name.snakecase+by
227
+ binary_dump(role_name, role.concept, false, nil, nil, other_role_method)
228
+ }
229
+ end
230
+
231
+ def binary_dump(role_name, role_player, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
232
+ # Find whether we need the name of the other role player, and whether it's defined yet:
233
+ if role_name.camelcase(true) == role_player.name
234
+ # Don't use Class name if implied by rolename
235
+ role_reference = nil
236
+ elsif !@concept_types_dumped[role_player]
237
+ role_reference = '"'+role_player.name+'"'
238
+ else
239
+ role_reference = role_player.name
240
+ end
241
+ other_role_name = ":"+other_role_name if other_role_name
242
+
243
+ line = " #{one_to_one ? "one_to_one" : "has_one" } " +
244
+ [ ":"+role_name,
245
+ role_reference,
246
+ readings,
247
+ other_role_name
248
+ ].compact*", "+" "
249
+ line += " "*(48-line.length) if line.length < 48
250
+ line += "\# See #{role_player.name}.#{other_method_name}" if other_method_name
251
+ puts line
252
+ end
253
+
254
+ # Dump one fact type.
255
+ # Include as many as possible internal constraints in the fact type readings.
256
+ def fact_type_dump(fact_type, name)
257
+ return if skip_fact_type(fact_type) || !(o = fact_type.entity_type)
258
+
259
+ primary_supertype = o && (o.identifying_supertype || o.supertypes[0])
260
+ secondary_supertypes = o.supertypes-[primary_supertype]
261
+
262
+ # Get the preferred identifier, but don't emit it unless it's different from the primary supertype's:
263
+ pi = o.preferred_identifier
264
+ pi = nil if pi && primary_supertype && primary_supertype.preferred_identifier == pi
265
+
266
+ puts " class #{name}" +
267
+ (primary_supertype ? " < "+primary_supertype.name : "") +
268
+ "\n" +
269
+ secondary_supertypes.map{|sst| " supertype :#{sst.name}"}*"\n" +
270
+ (pi ? " identified_by #{identified_by(o, pi)}" : "")
271
+ fact_roles_dump(fact_type)
272
+ roles_dump(o)
273
+ puts " end\n\n"
274
+
275
+ @fact_types_dumped[fact_type] = true
276
+ end
277
+
278
+ def ruby_role_name(role_name)
279
+ if Role === role_name
280
+ role_name = role_name.role_name || role_name.concept.name
281
+ end
282
+ role_name.snakecase.gsub("-",'_')
283
+ end
284
+
285
+ def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
286
+ identifying_roles.map{|role|
287
+ ":"+preferred_role_name(role)
288
+ }*", "
289
+ end
290
+
291
+ def show_role(r)
292
+ puts "Role player #{r.concept.name} facttype #{r.fact_type.name} lead_adj #{r.leading_adjective} trail_adj #{r.trailing_adjective} allows #{r.allowed_values.inspect}"
293
+ end
294
+
295
+ def entity_type_banner
296
+ end
297
+
298
+ def entity_type_group_end
299
+ end
300
+
301
+ def append_ring_to_reading(reading, ring)
302
+ # REVISIT: debug "Should override append_ring_to_reading"
303
+ end
304
+
305
+ def fact_type_banner
306
+ end
307
+
308
+ def fact_type_end
309
+ end
310
+
311
+ def constraint_banner
312
+ # debug "Should override constraint_banner"
313
+ end
314
+
315
+ def constraint_end
316
+ # debug "Should override constraint_end"
317
+ end
318
+
319
+ def constraint_dump(c)
320
+ # debug "Should override constraint_dump"
321
+ end
322
+
323
+ end
324
+
325
+ end
326
+ end
@@ -0,0 +1,164 @@
1
+ #
2
+ # Generate an SQL Server schema from an ActiveFacts vocabulary.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/vocabulary'
6
+ require 'activefacts/persistence'
7
+
8
+ module ActiveFacts
9
+ module Generate
10
+ class SQL
11
+ class SERVER
12
+ include Metamodel
13
+
14
+ RESERVED_WORDS = %w{
15
+ ADD ALL ALTER AND ANY AS ASC AUTHORIZATION BACKUP BEGIN BETWEEN
16
+ BREAK BROWSE BULK BY CASCADE CASE CHECK CHECKPOINT CLOSE CLUSTERED
17
+ COALESCE COLLATE COLUMN COMMIT COMPUTE CONSTRAINT CONTAINS CONTAINSTABLE
18
+ CONTINUE CONVERT CREATE CROSS CURRENT CURRENT_DATE CURRENT_TIME
19
+ CURRENT_TIMESTAMP CURRENT_USER CURSOR DATABASE DBCC DEALLOCATE
20
+ DECLARE DEFAULT DELETE DENY DESC DISK DISTINCT DISTRIBUTED DOUBLE
21
+ DROP DUMMY DUMP ELSE END ERRLVL ESCAPE EXCEPT EXEC EXECUTE EXISTS
22
+ EXIT FETCH FILE FILLFACTOR FOR FOREIGN FREETEXT FREETEXTTABLE FROM
23
+ FULL FUNCTION GOTO GRANT GROUP HAVING HOLDLOCK IDENTITY IDENTITYCOL
24
+ IDENTITY_INSERT IF IN INDEX INNER INSERT INTERSECT INTO IS JOIN KEY
25
+ KILL LEFT LIKE LINENO LOAD NATIONAL NOCHECK NONCLUSTERED NOT NULL
26
+ NULLIF OF OFF OFFSETS ON OPEN OPENDATASOURCE OPENQUERY OPENROWSET
27
+ OPENXML OPTION OR ORDER OUTER OVER PERCENT PLAN PRECISION PRIMARY
28
+ PRINT PROC PROCEDURE PUBLIC RAISERROR READ READTEXT RECONFIGURE
29
+ REFERENCES REPLICATION RESTORE RESTRICT RETURN REVOKE RIGHT ROLLBACK
30
+ ROWCOUNT ROWGUIDCOL RULE SAVE SCHEMA SELECT SESSION_USER SET SETUSER
31
+ SHUTDOWN SOME STATISTICS SYSTEM_USER TABLE TEXTSIZE THEN TO TOP
32
+ TRAN TRANSACTION TRIGGER TRUNCATE TSEQUAL UNION UNIQUE UPDATE
33
+ UPDATETEXT USE USER VALUES VARYING VIEW WAITFOR WHEN WHERE WHILE
34
+ WITH WRITETEXT
35
+ }.inject({}){ |h,w| h[w] = true; h }
36
+
37
+ def initialize(vocabulary, *options)
38
+ @vocabulary = vocabulary
39
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
40
+ @delay_fks = options.include? "delay_fks"
41
+ end
42
+
43
+ def puts s
44
+ @out.puts s
45
+ end
46
+
47
+ def go s
48
+ puts s
49
+ puts "GO\n\n"
50
+ end
51
+
52
+ def escape s
53
+ # Escape SQL keywords and non-identifiers
54
+ if s =~ /[^A-Za-z0-9_]/ || RESERVED_WORDS[s.upcase]
55
+ "[#{s}]"
56
+ else
57
+ s
58
+ end
59
+ end
60
+
61
+ # Return a ValueType definition for the passed role reference
62
+ def sql_type(role_ref)
63
+ if role_ref.role.fact_type.all_role.size == 1
64
+ "bit"
65
+ else
66
+ vt = role_ref.role.concept
67
+ length = vt.length
68
+ scale = vt.scale
69
+ while vt.supertype
70
+ length ||= vt.length
71
+ scale ||= vt.scale
72
+ vt = vt.supertype
73
+ end
74
+ basic_type = case (vt.supertype||vt).name
75
+ when "AutoCounter"; "int"
76
+ when "Date"; "datetime"
77
+ when "UnsignedInteger",
78
+ "SignedInteger"
79
+ l = length
80
+ length = nil
81
+ case
82
+ when l <= 8; "tinyint"
83
+ when l <= 16; "shortint"
84
+ when l <= 32; "int"
85
+ else "bigint"
86
+ end
87
+ when "VariableLengthText"; "varchar"
88
+ when "Decimal"; "decimal"
89
+ else vt.name
90
+ end
91
+ if length && length != 0
92
+ basic_type + ((scale && scale != 0) ? "(#{length}, #{scale})" : "(#{length})")
93
+ else
94
+ basic_type
95
+ end
96
+ end +
97
+ (
98
+ # Is there any role along the path that lacks a mandatory constraint?
99
+ role_ref.output_roles.detect { |role| !role.is_mandatory } ? " NULL" : " NOT NULL"
100
+ )
101
+ end
102
+
103
+ def column_name(role_ref)
104
+ escape(role_ref.column_name(nil).map{|n| n.sub(/^[a-z]/){|s| s.upcase}}*"")
105
+ end
106
+
107
+ def generate(out = $>)
108
+ @out = out
109
+ #go "CREATE SCHEMA #{@vocabulary.name}"
110
+
111
+ tables_emitted = {}
112
+ delayed_foreign_keys = []
113
+
114
+ @vocabulary.tables.sort_by{|table| table.name}.each do |table|
115
+ tables_emitted[table] = true
116
+ puts "CREATE TABLE #{escape table.name} ("
117
+
118
+ pk = table.absorbed_reference_roles.all_role_ref
119
+ pk_names = pk.map{|rr| column_name(rr) }
120
+
121
+ columns = table.absorbed_roles.all_role_ref.sort_by do |role_ref|
122
+ name = column_name(role_ref)
123
+ [pk_names.include?(name) ? 0 : 1, name]
124
+ end.map do |role_ref|
125
+ "\t#{column_name(role_ref)}\t#{sql_type(role_ref)}"
126
+ end
127
+
128
+ pk_def =
129
+ if pk.detect{ |role_ref| !role_ref.role.is_mandatory }
130
+ # Any nullable fields mean this can't be a primary key, just a unique constraint
131
+ "\tUNIQUE("
132
+ else
133
+ "\tPRIMARY KEY("
134
+ end +
135
+ table.absorbed_reference_roles.all_role_ref.map do |role_ref|
136
+ column_name(role_ref)
137
+ end*", " + ")"
138
+
139
+ inline_fks = []
140
+ table.absorbed_references.sort_by { |role, other_table, from_columns, to_columns|
141
+ [ other_table.name, from_columns.map{|c| column_name(c)} ]
142
+ }.each do |role, other_table, from_columns, to_columns|
143
+ fk =
144
+ if tables_emitted[other_table] && !@delay_fks
145
+ inline_fks << "\t"
146
+ else
147
+ delayed_foreign_keys << "ALTER TABLE #{escape table.name}\n\tADD "
148
+ end.last
149
+ fk << "FOREIGN KEY(#{from_columns.map{|c| column_name(c)}*", "})\n"+
150
+ "\tREFERENCES #{escape other_table.name}(#{to_columns.map{|c| column_name(c)}*", "})"
151
+ end
152
+
153
+ puts((columns + [pk_def] + inline_fks)*",\n")
154
+ go ")"
155
+ end
156
+
157
+ delayed_foreign_keys.each do |fk|
158
+ go fk
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end