activefacts-compositions 1.9.6 → 1.9.8

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