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