activefacts 0.6.0

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