activefacts-compositions 1.9.6 → 1.9.8

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.
@@ -5,6 +5,7 @@
5
5
  #
6
6
  require 'digest/sha1'
7
7
  require 'activefacts/metamodel'
8
+ require 'activefacts/metamodel/datatypes'
8
9
  require 'activefacts/registry'
9
10
  require 'activefacts/compositions'
10
11
  require 'activefacts/generator'
@@ -15,416 +16,397 @@ module ActiveFacts
15
16
  # * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
16
17
  # * underscore
17
18
  class SQL
19
+ MM = ActiveFacts::Metamodel unless const_defined?(:MM)
20
+ def self.options
21
+ {
22
+ delay_fks: ['Boolean', "Delay emitting all foreign keys until the bottom of the file"],
23
+ underscore: [String, "Use 'str' instead of underscore between words in table names"],
24
+ unicode: ['Boolean', "Use Unicode for all text fields by default"],
25
+ }
26
+ end
27
+
18
28
  def initialize composition, options = {}
19
- @composition = composition
20
- @options = options
21
- @delay_fks = options.include? "delay_fks"
22
- @underscore = options.include?("underscore") ? "_" : ""
29
+ @composition = composition
30
+ @options = options
31
+ @delay_fks = options.delete "delay_fks"
32
+ @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''
33
+ @unicode = options.delete "unicode"
23
34
  end
24
35
 
25
36
  def generate
26
- @tables_emitted = {}
27
- @delayed_foreign_keys = []
28
-
29
- generate_schema +
30
- @composition.
31
- all_composite.
32
- sort_by{|composite| composite.mapping.name}.
33
- map{|composite| generate_table composite}*"\n" + "\n" +
34
- @delayed_foreign_keys.sort*"\n"
37
+ @tables_emitted = {}
38
+ @delayed_foreign_keys = []
39
+
40
+ generate_schema +
41
+ @composition.
42
+ all_composite.
43
+ sort_by{|composite| composite.mapping.name}.
44
+ map{|composite| generate_table composite}*"\n" + "\n" +
45
+ @delayed_foreign_keys.sort*"\n"
46
+ end
47
+
48
+ def data_type_context
49
+ @data_type_context ||= SQLDataTypeContext.new
35
50
  end
36
51
 
37
52
  def table_name_max
38
- 60
53
+ 60
39
54
  end
40
55
 
41
56
  def column_name_max
42
- 40
57
+ 40
43
58
  end
44
59
 
45
60
  def index_name_max
46
- 60
61
+ 60
47
62
  end
48
63
 
49
64
  def schema_name_max
50
- 60
51
- end
52
-
53
- def safe_table_name composite
54
- escape(table_name(composite), table_name_max)
55
- end
56
-
57
- def safe_column_name component
58
- escape(column_name(component), column_name_max)
59
- end
60
-
61
- def table_name composite
62
- composite.mapping.name.gsub(' ', @underscore)
63
- end
64
-
65
- def column_name component
66
- component.column_name.capcase
65
+ 60
67
66
  end
68
67
 
69
68
  def generate_schema
70
- #go "CREATE SCHEMA #{escape(@composition.name, schema_name_max)}" +
71
- ''
69
+ #go "CREATE SCHEMA #{escape(@composition.name, schema_name_max)}" +
70
+ ''
72
71
  end
73
72
 
74
73
  def generate_table composite
75
- @tables_emitted[composite] = true
76
- delayed_indices = []
77
-
78
- "CREATE TABLE #{safe_table_name composite} (\n" +
79
- (
80
- composite.mapping.leaves.flat_map do |leaf|
81
- # Absorbed empty subtypes appear as leaves
82
- next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)
83
-
84
- generate_column leaf
85
- end +
86
- composite.all_index.map do |index|
87
- generate_index index, delayed_indices
88
- end.compact.sort +
89
- composite.all_foreign_key_as_source_composite.map do |fk|
90
- fk_text = generate_foreign_key fk
91
- if !@delay_fks and @tables_emitted[fk.composite]
92
- fk_text
93
- else
94
- @delayed_foreign_keys <<
95
- go("ALTER TABLE #{safe_table_name fk.composite}\n\tADD " + fk_text)
96
- nil
97
- end
98
- end.compact.sort +
99
- composite.all_local_constraint.map do |constraint|
100
- '-- '+constraint.inspect # REVISIT: Emit local constraints
101
- end
102
- ).compact.flat_map{|f| "\t#{f}" }*",\n"+"\n" +
103
- go(")") +
104
- delayed_indices.sort.map do |delayed_index|
105
- go delayed_index
106
- end*"\n"
74
+ @tables_emitted[composite] = true
75
+ delayed_indices = []
76
+
77
+ "CREATE TABLE #{safe_table_name composite} (\n" +
78
+ (
79
+ composite.mapping.all_leaf.flat_map do |leaf|
80
+ # Absorbed empty subtypes appear as leaves
81
+ next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)
82
+
83
+ generate_column leaf
84
+ end +
85
+ composite.all_index.map do |index|
86
+ generate_index index, delayed_indices
87
+ end.compact.sort +
88
+ composite.all_foreign_key_as_source_composite.map do |fk|
89
+ fk_text = generate_foreign_key fk
90
+ if !@delay_fks and @tables_emitted[fk.composite]
91
+ fk_text
92
+ else
93
+ @delayed_foreign_keys <<
94
+ go("ALTER TABLE #{safe_table_name fk.source_composite}\n\tADD " + fk_text)
95
+ nil
96
+ end
97
+ end.compact.sort +
98
+ composite.all_local_constraint.map do |constraint|
99
+ '-- '+constraint.inspect # REVISIT: Emit local constraints
100
+ end
101
+ ).compact.flat_map{|f| "\t#{f}" }*",\n"+"\n" +
102
+ go(")") +
103
+ delayed_indices.sort.map do |delayed_index|
104
+ go delayed_index
105
+ end*"\n"
107
106
  end
108
107
 
109
108
  def generate_column leaf
110
- column_name = safe_column_name(leaf)
111
- padding = " "*(column_name.size >= column_name_max ? 1 : column_name_max-column_name.size)
112
- constraints = leaf.all_leaf_constraint
113
-
114
- identity = ''
115
- "-- #{column_comment leaf}\n\t#{column_name}#{padding}#{component_type leaf, column_name}#{identity}"
116
- end
117
-
118
- def column_comment component
119
- return '' unless cp = component.parent
120
- prefix = column_comment(cp)
121
- name = component.name
122
- if component.is_a?(MM::Absorption)
123
- reading = component.parent_role.fact_type.reading_preferably_starting_with_role(component.parent_role).expand([], false)
124
- maybe = component.parent_role.is_mandatory ? '' : 'maybe '
125
- cpname = cp.name
126
- if prefix[(-cpname.size-1)..-1] == ' '+cpname && reading[0..cpname.size] == cpname+' '
127
- prefix+' that ' + maybe + reading[cpname.size+1..-1]
128
- else
129
- (prefix.empty? ? '' : prefix+' and ') + maybe + reading
130
- end
131
- else
132
- name
133
- end
134
- end
109
+ column_name = safe_column_name(leaf)
110
+ padding = " "*(column_name.size >= column_name_max ? 1 : column_name_max-column_name.size)
111
+ constraints = leaf.all_leaf_constraint
135
112
 
136
- def boolean_type
137
- 'BOOLEAN'
113
+ "-- #{leaf.comment}\n" +
114
+ "\t#{column_name}#{padding}#{column_type leaf, column_name}"
138
115
  end
139
116
 
140
- def surrogate_type
141
- 'BIGINT IDENTITY NOT NULL'
117
+ def auto_assign_type
118
+ ' GENERATED ALWAYS AS IDENTITY'
142
119
  end
143
120
 
144
- def component_type component, column_name
145
- case component
146
- when MM::Indicator
147
- boolean_type
148
- when MM::SurrogateKey
149
- surrogate_type
150
- when MM::ValueField, MM::Absorption
151
- object_type = component.object_type
152
- while object_type.is_a?(MM::EntityType)
153
- rr = object_type.preferred_identifier.role_sequence.all_role_ref.single
154
- raise "Can't produce a column for composite #{component.inspect}" unless rr
155
- object_type = rr.role.object_type
156
- end
157
- raise "A column can only be produced from a ValueType" unless object_type.is_a?(MM::ValueType)
158
-
159
- if component.is_a?(MM::Absorption)
160
- value_constraint ||= component.child_role.role_value_constraint
161
- end
162
-
163
- supertype = object_type
164
- begin
165
- object_type = supertype
166
- length ||= object_type.length
167
- scale ||= object_type.scale
168
- unless component.parent.parent and component.parent.foreign_key
169
- # No need to enforce value constraints that are already enforced by a foreign key
170
- value_constraint ||= object_type.value_constraint
171
- end
172
- end while supertype = object_type.supertype
173
- type, length = normalise_type(object_type.name, length)
174
- sql_type = "#{type}#{
175
- if !length
176
- ''
177
- else
178
- '(' + length.to_s + (scale ? ", #{scale}" : '') + ')'
179
- end
180
- }#{
181
- (component.path_mandatory ? '' : ' NOT') + ' NULL'
182
- }#{
183
- # REVISIT: This is an SQL Server-ism. Replace with a standard SQL SEQUENCE/
184
- # Emit IDENTITY for columns auto-assigned on commit (except FKs)
185
- if a = object_type.is_auto_assigned and a != 'assert' and
186
- !component.all_foreign_key_field.detect{|fkf| fkf.foreign_key.source_composite == component.root}
187
- ' IDENTITY'
188
- else
189
- ''
190
- end
191
- }#{
192
- value_constraint ? check_clause(column_name, value_constraint) : ''
193
- }"
194
- else
195
- raise "Can't make a column from #{component}"
196
- end
121
+ def column_type component, column_name
122
+ type_name, options = component.data_type(data_type_context)
123
+ options ||= {}
124
+ length = options[:length]
125
+ value_constraint = options[:value_constraint]
126
+ type_name, length = normalise_type(type_name, length, value_constraint)
127
+
128
+ "#{
129
+ type_name
130
+ }#{
131
+ "(#{length}#{(s = options[:scale]) ? ", #{s}" : ''})" if length
132
+ }#{
133
+ ((options[:mandatory] ? ' NOT' : '') + ' NULL') if options.has_key?(:mandatory)
134
+ }#{
135
+ auto_assign_type if a = options[:auto_assign] && a != 'assert'
136
+ }#{
137
+ check_clause(column_name, value_constraint) if value_constraint
138
+ }"
197
139
  end
198
140
 
199
141
  def generate_index index, delayed_indices
200
- nullable_columns =
201
- index.all_index_field.select do |ixf|
202
- !ixf.component.path_mandatory
203
- end
204
- contains_nullable_columns = nullable_columns.size > 0
205
-
206
- primary = index.composite_as_primary_index && !contains_nullable_columns
207
- column_names =
208
- index.all_index_field.map do |ixf|
209
- column_name(ixf.component)
210
- end
211
- clustering =
212
- (index.composite_as_primary_index ? ' CLUSTERED' : ' NONCLUSTERED')
213
-
214
- if contains_nullable_columns
215
- delayed_indices <<
216
- 'CREATE UNIQUE'+clustering+' INDEX '+
217
- escape("#{safe_table_name(index.composite)}By#{column_names*''}", index_name_max) +
218
- " ON ("+column_names.map{|n| escape(n, column_name_max)}*', ' +
219
- ") WHERE #{
220
- nullable_columns.
221
- map{|ixf| safe_column_name ixf.component}.
222
- map{|column_name| column_name + ' IS NOT NULL'} *
223
- ' AND '
224
- }"
225
- nil
226
- else
227
- '-- '+index.inspect + "\n\t" +
228
- (primary ? 'PRIMARY KEY' : 'UNIQUE') +
229
- clustering +
230
- "(#{column_names.map{|n| escape(n, column_name_max)}*', '})"
231
- end
142
+ nullable_columns =
143
+ index.all_index_field.select do |ixf|
144
+ !ixf.component.path_mandatory
145
+ end
146
+ contains_nullable_columns = nullable_columns.size > 0
147
+
148
+ primary = index.composite_as_primary_index && !contains_nullable_columns
149
+ column_names =
150
+ index.all_index_field.map do |ixf|
151
+ column_name(ixf.component)
152
+ end
153
+ clustering =
154
+ (index.composite_as_primary_index ? ' CLUSTERED' : ' NONCLUSTERED')
155
+
156
+ if contains_nullable_columns
157
+ table_name = safe_table_name(index.composite)
158
+ delayed_indices <<
159
+ 'CREATE UNIQUE'+clustering+' INDEX '+
160
+ escape("#{table_name(index.composite)}By#{column_names*''}", index_name_max) +
161
+ " ON #{table_name}("+column_names.map{|n| escape(n, column_name_max)}*', ' +
162
+ ") WHERE #{
163
+ nullable_columns.
164
+ map{|ixf| safe_column_name ixf.component}.
165
+ map{|column_name| column_name + ' IS NOT NULL'} *
166
+ ' AND '
167
+ }"
168
+ nil
169
+ else
170
+ '-- '+index.inspect + "\n\t" +
171
+ (primary ? 'PRIMARY KEY' : 'UNIQUE') +
172
+ clustering +
173
+ "(#{column_names.map{|n| escape(n, column_name_max)}*', '})"
174
+ end
232
175
  end
233
176
 
234
177
  def generate_foreign_key fk
235
- '-- '+fk.inspect
236
- "FOREIGN KEY (" +
237
- fk.all_foreign_key_field.map{|fkf| safe_column_name fkf.component}*", " +
238
- ") REFERENCES #{safe_table_name fk.composite} (" +
239
- fk.all_index_field.map{|ixf| safe_column_name ixf.component}*", " +
240
- ")"
178
+ '-- '+fk.inspect
179
+ "FOREIGN KEY (" +
180
+ fk.all_foreign_key_field.map{|fkf| safe_column_name fkf.component}*", " +
181
+ ") REFERENCES #{safe_table_name fk.composite} (" +
182
+ fk.all_index_field.map{|ixf| safe_column_name ixf.component}*", " +
183
+ ")"
184
+ end
185
+
186
+ # Return SQL type and (modified?) length for the passed base type
187
+ def normalise_type(type_name, length, value_constraint)
188
+ type = MM::DataType.normalise(type_name)
189
+
190
+ case type
191
+ when MM::DataType::TYPE_Boolean; data_type_context.boolean_type
192
+ when MM::DataType::TYPE_Integer
193
+ if type_name =~ /^Auto ?Counter$/i
194
+ MM::DataType.normalise_int_length('int', data_type_context.default_surrogate_length, value_constraint, data_type_context)[0]
195
+ else
196
+ v, = MM::DataType.normalise_int_length(type_name, length, value_constraint, data_type_context)
197
+ v
198
+ end
199
+ when MM::DataType::TYPE_Real;
200
+ ["FLOAT", data_type_context.default_length(type, type_name)]
201
+ when MM::DataType::TYPE_Decimal; 'DECIMAL'
202
+ when MM::DataType::TYPE_Money; 'DECIMAL'
203
+ when MM::DataType::TYPE_Char; [data_type_context.default_char_type, length || data_type_context.char_default_length]
204
+ when MM::DataType::TYPE_String; [data_type_context.default_varchar_type, length || data_type_context.varchar_default_length]
205
+ when MM::DataType::TYPE_Text; [data_type_context.default_text_type, length || 'MAX']
206
+ when MM::DataType::TYPE_Date; 'DATE' # SQLSVR 2K5: 'date'
207
+ when MM::DataType::TYPE_Time; 'TIME' # SQLSVR 2K5: 'time'
208
+ when MM::DataType::TYPE_DateTime; 'TIMESTAMP'
209
+ when MM::DataType::TYPE_Timestamp;'TIMESTAMP'
210
+ when MM::DataType::TYPE_Binary;
211
+ length ||= 16 if type_name =~ /^(guid|uuid)$/i
212
+ if length
213
+ ['BINARY', length]
214
+ else
215
+ 'VARBINARY'
216
+ end
217
+ else
218
+ type_name
219
+ end
241
220
  end
242
221
 
243
222
  def reserved_words
244
- @sql_server_reserved_words ||= %w{
245
- ADD ALL ALTER AND ANY AS ASC AUTHORIZATION BACKUP BEGIN
246
- BETWEEN BREAK BROWSE BULK BY CASCADE CASE CHECK CHECKPOINT
247
- CLOSE CLUSTERED COALESCE COLLATE COLUMN COMMIT COMPUTE
248
- CONSTRAINT CONTAINS CONTAINSTABLE CONTINUE CONVERT CREATE
249
- CROSS CURRENT CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP
250
- CURRENT_USER CURSOR DATABASE DBCC DEALLOCATE DECLARE
251
- DEFAULT DELETE DENY DESC DISK DISTINCT DISTRIBUTED DOUBLE
252
- DROP DUMMY DUMP ELSE END ERRLVL ESCAPE EXCEPT EXEC EXECUTE
253
- EXISTS EXIT FETCH FILE FILLFACTOR FOR FOREIGN FREETEXT
254
- FREETEXTTABLE FROM FULL FUNCTION GOTO GRANT GROUP HAVING
255
- HOLDLOCK IDENTITY IDENTITYCOL IDENTITY_INSERT IF IN INDEX
256
- INNER INSERT INTERSECT INTO IS JOIN KEY KILL LEFT LIKE
257
- LINENO LOAD NATIONAL NOCHECK NONCLUSTERED NOT NULL NULLIF
258
- OF OFF OFFSETS ON OPEN OPENDATASOURCE OPENQUERY OPENROWSET
259
- OPENXML OPTION OR ORDER OUTER OVER PERCENT PLAN PRECISION
260
- PRIMARY PRINT PROC PROCEDURE PUBLIC RAISERROR READ READTEXT
261
- RECONFIGURE REFERENCES REPLICATION RESTORE RESTRICT RETURN
262
- REVOKE RIGHT ROLLBACK ROWCOUNT ROWGUIDCOL RULE SAVE SCHEMA
263
- SELECT SESSION_USER SET SETUSER SHUTDOWN SOME STATISTICS
264
- SYSTEM_USER TABLE TEXTSIZE THEN TO TOP TRAN TRANSACTION
265
- TRIGGER TRUNCATE TSEQUAL UNION UNIQUE UPDATE UPDATETEXT
266
- USE USER VALUES VARYING VIEW WAITFOR WHEN WHERE WHILE
267
- WITH WRITETEXT
268
- }
269
-
270
- @reserved_words ||= %w{
271
- ABSOLUTE ACTION ADD AFTER ALL ALLOCATE ALTER AND ANY ARE
272
- ARRAY AS ASC ASSERTION AT AUTHORIZATION BEFORE BEGIN
273
- BETWEEN BINARY BIT BLOB BOOLEAN BOTH BREADTH BY CALL
274
- CASCADE CASCADED CASE CAST CATALOG CHAR CHARACTER CHECK
275
- CLOB CLOSE COLLATE COLLATION COLUMN COMMIT CONDITION
276
- CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONSTRUCTOR
277
- CONTINUE CORRESPONDING CREATE CROSS CUBE CURRENT CURRENT_DATE
278
- CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_TRANSFORM_GROUP_FOR_TYPE
279
- CURRENT_PATH CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP
280
- CURRENT_USER CURSOR CYCLE DATA DATE DAY DEALLOCATE DEC
281
- DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DELETE DEPTH
282
- DEREF DESC DESCRIBE DESCRIPTOR DETERMINISTIC DIAGNOSTICS
283
- DISCONNECT DISTINCT DO DOMAIN DOUBLE DROP DYNAMIC EACH
284
- ELSE ELSEIF END EQUALS ESCAPE EXCEPT EXCEPTION EXEC EXECUTE
285
- EXISTS EXIT EXTERNAL FALSE FETCH FIRST FLOAT FOR FOREIGN
286
- FOUND FROM FREE FULL FUNCTION GENERAL GET GLOBAL GO GOTO
287
- GRANT GROUP GROUPING HANDLE HAVING HOLD HOUR IDENTITY IF
288
- IMMEDIATE IN INDICATOR INITIALLY INNER INOUT INPUT INSERT
289
- INT INTEGER INTERSECT INTERVAL INTO IS ISOLATION JOIN KEY
290
- LANGUAGE LARGE LAST LATERAL LEADING LEAVE LEFT LEVEL LIKE
291
- LOCAL LOCALTIME LOCALTIMESTAMP LOCATOR LOOP MAP MATCH
292
- METHOD MINUTE MODIFIES MODULE MONTH NAMES NATIONAL NATURAL
293
- NCHAR NCLOB NESTING NEW NEXT NO NONE NOT NULL NUMERIC
294
- OBJECT OF OLD ON ONLY OPEN OPTION OR ORDER ORDINALITY OUT
295
- OUTER OUTPUT OVERLAPS PAD PARAMETER PARTIAL PATH PRECISION
296
- PREPARE PRESERVE PRIMARY PRIOR PRIVILEGES PROCEDURE PUBLIC
297
- READ READS REAL RECURSIVE REDO REF REFERENCES REFERENCING
298
- RELATIVE RELEASE REPEAT RESIGNAL RESTRICT RESULT RETURN
299
- RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROW
300
- ROWS SAVEPOINT SCHEMA SCROLL SEARCH SECOND SECTION SELECT
301
- SESSION SESSION_USER SET SETS SIGNAL SIMILAR SIZE SMALLINT
302
- SOME SPACE SPECIFIC SPECIFICTYPE SQL SQLEXCEPTION SQLSTATE
303
- SQLWARNING START STATE STATIC SYSTEM_USER TABLE TEMPORARY
304
- THEN TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TO
305
- TRAILING TRANSACTION TRANSLATION TREAT TRIGGER TRUE UNDER
306
- UNDO UNION UNIQUE UNKNOWN UNNEST UNTIL UPDATE USAGE USER
307
- USING VALUE VALUES VARCHAR VARYING VIEW WHEN WHENEVER
308
- WHERE WHILE WITH WITHOUT WORK WRITE YEAR ZONE
309
- }
223
+ @reserved_words ||= %w{
224
+ ABSOLUTE ACTION ADD AFTER ALL ALLOCATE ALTER AND ANY ARE
225
+ ARRAY AS ASC ASSERTION AT AUTHORIZATION BEFORE BEGIN
226
+ BETWEEN BINARY BIT BLOB BOOLEAN BOTH BREADTH BY CALL
227
+ CASCADE CASCADED CASE CAST CATALOG CHAR CHARACTER CHECK
228
+ CLOB CLOSE COLLATE COLLATION COLUMN COMMIT CONDITION
229
+ CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONSTRUCTOR
230
+ CONTINUE CORRESPONDING CREATE CROSS CUBE CURRENT CURRENT_DATE
231
+ CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_TRANSFORM_GROUP_FOR_TYPE
232
+ CURRENT_PATH CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP
233
+ CURRENT_USER CURSOR CYCLE DATA DATE DAY DEALLOCATE DEC
234
+ DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DELETE DEPTH
235
+ DEREF DESC DESCRIBE DESCRIPTOR DETERMINISTIC DIAGNOSTICS
236
+ DISCONNECT DISTINCT DO DOMAIN DOUBLE DROP DYNAMIC EACH
237
+ ELSE ELSEIF END EQUALS ESCAPE EXCEPT EXCEPTION EXEC EXECUTE
238
+ EXISTS EXIT EXTERNAL FALSE FETCH FIRST FLOAT FOR FOREIGN
239
+ FOUND FROM FREE FULL FUNCTION GENERAL GET GLOBAL GO GOTO
240
+ GRANT GROUP GROUPING HANDLE HAVING HOLD HOUR IDENTITY IF
241
+ IMMEDIATE IN INDICATOR INITIALLY INNER INOUT INPUT INSERT
242
+ INT INTEGER INTERSECT INTERVAL INTO IS ISOLATION JOIN KEY
243
+ LANGUAGE LARGE LAST LATERAL LEADING LEAVE LEFT LEVEL LIKE
244
+ LOCAL LOCALTIME LOCALTIMESTAMP LOCATOR LOOP MAP MATCH
245
+ METHOD MINUTE MODIFIES MODULE MONTH NAMES NATIONAL NATURAL
246
+ NCHAR NCLOB NESTING NEW NEXT NO NONE NOT NULL NUMERIC
247
+ OBJECT OF OLD ON ONLY OPEN OPTION OR ORDER ORDINALITY OUT
248
+ OUTER OUTPUT OVERLAPS PAD PARAMETER PARTIAL PATH PRECISION
249
+ PREPARE PRESERVE PRIMARY PRIOR PRIVILEGES PROCEDURE PUBLIC
250
+ READ READS REAL RECURSIVE REDO REF REFERENCES REFERENCING
251
+ RELATIVE RELEASE REPEAT RESIGNAL RESTRICT RESULT RETURN
252
+ RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROW
253
+ ROWS SAVEPOINT SCHEMA SCROLL SEARCH SECOND SECTION SELECT
254
+ SESSION SESSION_USER SET SETS SIGNAL SIMILAR SIZE SMALLINT
255
+ SOME SPACE SPECIFIC SPECIFICTYPE SQL SQLEXCEPTION SQLSTATE
256
+ SQLWARNING START STATE STATIC SYSTEM_USER TABLE TEMPORARY
257
+ THEN TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TO
258
+ TRAILING TRANSACTION TRANSLATION TREAT TRIGGER TRUE UNDER
259
+ UNDO UNION UNIQUE UNKNOWN UNNEST UNTIL UPDATE USAGE USER
260
+ USING VALUE VALUES VARCHAR VARYING VIEW WHEN WHENEVER
261
+ WHERE WHILE WITH WITHOUT WORK WRITE YEAR ZONE
262
+ }
310
263
  end
311
264
 
312
265
  def is_reserved_word w
313
- @reserved_word_hash ||=
314
- reserved_words.inject({}) do |h,w|
315
- h[w] = true
316
- h
317
- end
318
- @reserved_word_hash[w.upcase]
266
+ @reserved_word_hash ||=
267
+ reserved_words.inject({}) do |h,w|
268
+ h[w] = true
269
+ h
270
+ end
271
+ @reserved_word_hash[w.upcase]
319
272
  end
320
273
 
321
274
  def go s = ''
322
- "#{s}\nGO\n" # REVISIT: This is an SQL-Serverism. Move it to a subclass.
275
+ "#{s};\n\n"
323
276
  end
324
277
 
325
278
  def escape s, max = table_name_max
326
- # Escape SQL keywords and non-identifiers
327
- if s.size > max
328
- excess = s[max..-1]
329
- s = s[0...max-(excess.size/8)] +
330
- Digest::SHA1.hexdigest(excess)[0...excess.size/8]
331
- end
332
-
333
- if s =~ /[^A-Za-z0-9_]/ || is_reserved_word(s)
334
- "[#{s}]"
335
- else
336
- s
337
- end
338
- end
339
-
340
- # Return SQL type and (modified?) length for the passed base type
341
- def normalise_type(type, length)
342
- sql_type = case type
343
- when /^Auto ?Counter$/
344
- 'int'
345
-
346
- when /^Unsigned ?Integer$/,
347
- /^Signed ?Integer$/,
348
- /^Unsigned ?Small ?Integer$/,
349
- /^Signed ?Small ?Integer$/,
350
- /^Unsigned ?Tiny ?Integer$/
351
- s = case
352
- when length == nil
353
- 'int'
354
- when length <= 8
355
- 'tinyint'
356
- when length <= 16
357
- 'smallint'
358
- when length <= 32
359
- 'int'
360
- else
361
- 'bigint'
362
- end
363
- length = nil
364
- s
365
-
366
- when /^Decimal$/
367
- 'decimal'
368
-
369
- when /^Fixed ?Length ?Text$/, /^Char$/
370
- 'char'
371
- when /^Variable ?Length ?Text$/, /^String$/
372
- 'varchar'
373
- when /^Large ?Length ?Text$/, /^Text$/
374
- 'text'
375
-
376
- when /^Date ?And ?Time$/, /^Date ?Time$/
377
- 'datetime'
378
- when /^Date$/
379
- 'datetime' # SQLSVR 2K5: 'date'
380
- when /^Time$/
381
- 'datetime' # SQLSVR 2K5: 'time'
382
- when /^Auto ?Time ?Stamp$/
383
- 'timestamp'
384
-
385
- when /^Guid$/
386
- 'uniqueidentifier'
387
- when /^Money$/
388
- 'decimal'
389
- when /^Picture ?Raw ?Data$/, /^Image$/
390
- 'image'
391
- when /^Variable ?Length ?Raw ?Data$/, /^Blob$/
392
- 'varbinary'
393
- when /^BIT$/
394
- 'bit'
395
- else type # raise "SQL type unknown for standard type #{type}"
396
- end
397
- [sql_type, length]
279
+ # Escape SQL keywords and non-identifiers
280
+ if s.size > max
281
+ excess = s[max..-1]
282
+ s = s[0...max-(excess.size/8)] +
283
+ Digest::SHA1.hexdigest(excess)[0...excess.size/8]
284
+ end
285
+
286
+ if s =~ /[^A-Za-z0-9_]/ || is_reserved_word(s)
287
+ "[#{s}]"
288
+ else
289
+ s
290
+ end
398
291
  end
399
292
 
400
293
  def sql_value(value)
401
- value.is_literal_string ? sql_string(value.literal) : value.literal
294
+ value.is_literal_string ? sql_string(value.literal) : value.literal
402
295
  end
403
296
 
404
297
  def sql_string(str)
405
- "'" + str.gsub(/'/,"''") + "'"
298
+ "'" + str.gsub(/'/,"''") + "'"
406
299
  end
407
300
 
408
301
  def check_clause column_name, value_constraint
409
- " CHECK(" +
410
- value_constraint.all_allowed_range_sorted.map do |ar|
411
- vr = ar.value_range
412
- min = vr.minimum_bound
413
- max = vr.maximum_bound
414
- if (min && max && max.value.literal == min.value.literal)
415
- "#{column_name} = #{sql_value(min.value)}"
416
- else
417
- inequalities = [
418
- min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
419
- max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
420
- ].compact
421
- inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
422
- end
423
- end*" OR " +
424
- ")"
302
+ " CHECK(" +
303
+ value_constraint.all_allowed_range_sorted.map do |ar|
304
+ vr = ar.value_range
305
+ min = vr.minimum_bound
306
+ max = vr.maximum_bound
307
+ if (min && max && max.value.literal == min.value.literal)
308
+ "#{column_name} = #{sql_value(min.value)}"
309
+ else
310
+ inequalities = [
311
+ min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
312
+ max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
313
+ ].compact
314
+ inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
315
+ end
316
+ end*" OR " +
317
+ ")"
318
+ end
319
+
320
+ class SQLDataTypeContext < MM::DataType::Context
321
+ def integer_ranges
322
+ [
323
+ ['SMALLINT', -2**15, 2**15-1], # The standard says -10^5..10^5 (less than 16 bits)
324
+ ['INTEGER', -2**31, 2**31-1], # The standard says -10^10..10^10 (more than 32 bits!)
325
+ ['BIGINT', -2**63, 2**63-1], # The standard says -10^19..10^19 (less than 64 bits)
326
+ ]
327
+ end
328
+
329
+ def default_length data_type, type_name
330
+ case data_type
331
+ when MM::DataType::TYPE_Real
332
+ 53 # IEEE Double precision floating point
333
+ when MM::DataType::TYPE_Integer
334
+ case type_name
335
+ when /([a-z ]|\b)Tiny([a-z ]|\b)/i
336
+ 8
337
+ when /([a-z ]|\b)Small([a-z ]|\b)/i,
338
+ /([a-z ]|\b)Short([a-z ]|\b)/i
339
+ 16
340
+ when /([a-z ]|\b)Big(INT)?([a-z ]|\b)/i
341
+ 64
342
+ else
343
+ 32
344
+ end
345
+ else
346
+ nil
347
+ end
348
+ end
349
+
350
+ def boolean_type
351
+ 'BOOLEAN'
352
+ end
353
+
354
+ def surrogate_type
355
+ type_name, = choose_integer_type(0, 2**(default_surrogate_length-1)-1)
356
+ type_name
357
+ end
358
+
359
+ def valid_from_type
360
+ 'TIMESTAMP'
361
+ end
362
+
363
+ def date_time_type
364
+ 'TIMESTAMP'
365
+ end
366
+
367
+ def default_char_type
368
+ (@unicode ? 'NATIONAL ' : '') +
369
+ 'CHARACTER'
370
+ end
371
+
372
+ def default_varchar_type
373
+ (@unicode ? 'NATIONAL ' : '') +
374
+ 'VARCHAR'
375
+ end
376
+
377
+ def char_default_length
378
+ nil
379
+ end
380
+
381
+ def varchar_default_length
382
+ nil
383
+ end
384
+
385
+ def default_surrogate_length
386
+ 64
387
+ end
388
+
389
+ def default_text_type
390
+ default_varchar_type
391
+ end
392
+ end
393
+
394
+ def safe_table_name composite
395
+ escape(table_name(composite), table_name_max)
396
+ end
397
+
398
+ def safe_column_name component
399
+ escape(column_name(component), column_name_max)
400
+ end
401
+
402
+ def table_name composite
403
+ composite.mapping.name.gsub(' ', @underscore)
404
+ end
405
+
406
+ def column_name component
407
+ component.column_name.capcase
425
408
  end
426
409
 
427
- MM = ActiveFacts::Metamodel
428
410
  end
429
411
  publish_generator SQL
430
412
  end