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.
- data/History.txt +4 -0
- data/Manifest.txt +83 -0
- data/README.rdoc +81 -0
- data/Rakefile +41 -0
- data/bin/afgen +46 -0
- data/bin/cql +52 -0
- data/examples/CQL/Address.cql +46 -0
- data/examples/CQL/Blog.cql +54 -0
- data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
- data/examples/CQL/Death.cql +16 -0
- data/examples/CQL/Genealogy.cql +95 -0
- data/examples/CQL/Marriage.cql +18 -0
- data/examples/CQL/Metamodel.cql +238 -0
- data/examples/CQL/MultiInheritance.cql +19 -0
- data/examples/CQL/OilSupply.cql +47 -0
- data/examples/CQL/Orienteering.cql +108 -0
- data/examples/CQL/PersonPlaysGame.cql +17 -0
- data/examples/CQL/SchoolActivities.cql +31 -0
- data/examples/CQL/SimplestUnary.cql +12 -0
- data/examples/CQL/SubtypePI.cql +32 -0
- data/examples/CQL/Warehousing.cql +99 -0
- data/examples/CQL/WindowInRoomInBldg.cql +22 -0
- data/lib/activefacts.rb +10 -0
- data/lib/activefacts/api.rb +25 -0
- data/lib/activefacts/api/concept.rb +384 -0
- data/lib/activefacts/api/constellation.rb +106 -0
- data/lib/activefacts/api/entity.rb +239 -0
- data/lib/activefacts/api/instance.rb +54 -0
- data/lib/activefacts/api/numeric.rb +158 -0
- data/lib/activefacts/api/role.rb +94 -0
- data/lib/activefacts/api/standard_types.rb +67 -0
- data/lib/activefacts/api/support.rb +59 -0
- data/lib/activefacts/api/value.rb +122 -0
- data/lib/activefacts/api/vocabulary.rb +120 -0
- data/lib/activefacts/cql.rb +31 -0
- data/lib/activefacts/cql/CQLParser.treetop +104 -0
- data/lib/activefacts/cql/Concepts.treetop +112 -0
- data/lib/activefacts/cql/DataTypes.treetop +66 -0
- data/lib/activefacts/cql/Expressions.treetop +113 -0
- data/lib/activefacts/cql/FactTypes.treetop +185 -0
- data/lib/activefacts/cql/Language/English.treetop +92 -0
- data/lib/activefacts/cql/LexicalRules.treetop +169 -0
- data/lib/activefacts/cql/Rakefile +6 -0
- data/lib/activefacts/cql/parser.rb +88 -0
- data/lib/activefacts/generate/absorption.rb +87 -0
- data/lib/activefacts/generate/cql.rb +441 -0
- data/lib/activefacts/generate/cql/html.rb +397 -0
- data/lib/activefacts/generate/null.rb +19 -0
- data/lib/activefacts/generate/ordered.rb +557 -0
- data/lib/activefacts/generate/ruby.rb +326 -0
- data/lib/activefacts/generate/sql/server.rb +164 -0
- data/lib/activefacts/generate/text.rb +21 -0
- data/lib/activefacts/input/cql.rb +1268 -0
- data/lib/activefacts/input/orm.rb +926 -0
- data/lib/activefacts/persistence.rb +1 -0
- data/lib/activefacts/persistence/composition.rb +653 -0
- data/lib/activefacts/support.rb +51 -0
- data/lib/activefacts/version.rb +3 -0
- data/lib/activefacts/vocabulary.rb +6 -0
- data/lib/activefacts/vocabulary/extensions.rb +343 -0
- data/lib/activefacts/vocabulary/metamodel.rb +303 -0
- data/script/txt2html +71 -0
- data/spec/absorption_spec.rb +95 -0
- data/spec/api/autocounter.rb +82 -0
- data/spec/api/constellation.rb +130 -0
- data/spec/api/entity_type.rb +101 -0
- data/spec/api/instance.rb +428 -0
- data/spec/api/roles.rb +122 -0
- data/spec/api/value_type.rb +112 -0
- data/spec/api_spec.rb +14 -0
- data/spec/cql_cql_spec.rb +58 -0
- data/spec/cql_parse_spec.rb +31 -0
- data/spec/cql_ruby_spec.rb +60 -0
- data/spec/cql_sql_spec.rb +54 -0
- data/spec/cql_symbol_tables_spec.rb +259 -0
- data/spec/cql_unit_spec.rb +336 -0
- data/spec/cqldump_spec.rb +169 -0
- data/spec/norma_cql_spec.rb +48 -0
- data/spec/norma_ruby_spec.rb +50 -0
- data/spec/norma_sql_spec.rb +45 -0
- data/spec/norma_tables_spec.rb +94 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- 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
|