activefacts-compositions 1.9.17 → 1.9.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/activefacts-compositions.gemspec +2 -2
  3. data/lib/activefacts/compositions/binary.rb +1 -1
  4. data/lib/activefacts/compositions/compositor.rb +16 -12
  5. data/lib/activefacts/compositions/datavault.rb +110 -115
  6. data/lib/activefacts/compositions/relational.rb +137 -94
  7. data/lib/activefacts/compositions/staging.rb +89 -27
  8. data/lib/activefacts/compositions/traits/datavault.rb +116 -49
  9. data/lib/activefacts/compositions/traits/rails.rb +2 -2
  10. data/lib/activefacts/compositions/version.rb +1 -1
  11. data/lib/activefacts/generator/doc/cwm.rb +6 -18
  12. data/lib/activefacts/generator/doc/ldm.rb +1 -1
  13. data/lib/activefacts/generator/etl/unidex.rb +341 -0
  14. data/lib/activefacts/generator/oo.rb +31 -14
  15. data/lib/activefacts/generator/rails/models.rb +6 -5
  16. data/lib/activefacts/generator/rails/schema.rb +5 -9
  17. data/lib/activefacts/generator/ruby.rb +2 -2
  18. data/lib/activefacts/generator/sql/mysql.rb +3 -184
  19. data/lib/activefacts/generator/sql/oracle.rb +3 -152
  20. data/lib/activefacts/generator/sql/postgres.rb +3 -145
  21. data/lib/activefacts/generator/sql/server.rb +3 -126
  22. data/lib/activefacts/generator/sql.rb +54 -422
  23. data/lib/activefacts/generator/summary.rb +15 -6
  24. data/lib/activefacts/generator/traits/expr.rb +41 -0
  25. data/lib/activefacts/generator/traits/sql/mysql.rb +280 -0
  26. data/lib/activefacts/generator/traits/sql/oracle.rb +265 -0
  27. data/lib/activefacts/generator/traits/sql/postgres.rb +287 -0
  28. data/lib/activefacts/generator/traits/sql/server.rb +262 -0
  29. data/lib/activefacts/generator/traits/sql.rb +538 -0
  30. metadata +13 -8
  31. data/lib/activefacts/compositions/docgraph.rb +0 -798
  32. data/lib/activefacts/compositions/staging/persistent.rb +0 -107
@@ -13,6 +13,7 @@ require 'activefacts/metamodel/datatypes'
13
13
  require 'activefacts/compositions'
14
14
  require 'activefacts/compositions/names'
15
15
  require 'activefacts/generator'
16
+ require 'activefacts/generator/traits/sql'
16
17
 
17
18
  module ActiveFacts
18
19
  module Generators
@@ -20,52 +21,12 @@ module ActiveFacts
20
21
  # * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
21
22
  # * underscore
22
23
  class SQL
23
- MM = ActiveFacts::Metamodel unless const_defined?(:MM)
24
- def self.options
25
- {
26
- delay_fks: ['Boolean', "Delay emitting all foreign keys until the bottom of the file"],
27
- keywords: ['Boolean', "Quote all keywords, not just reserved words"],
28
- restrict: ['String', "Restrict generation to tables in the specified group (e.g. bdv, rdv)"],
29
- joiner: ['String', "Use 'str' instead of the default joiner between words in table and column names"],
30
- unicode: ['Boolean', "Use Unicode for all text fields by default"],
31
- tables: [%w{cap title camel snake shout}, "Case to use for table names"],
32
- columns: [%w{cap title camel snake shout}, "Case to use for table names"],
33
- # Legacy: datavault: ['String', "Generate 'raw' or 'business' data vault tables"],
34
- }
35
- end
24
+ include Traits::SQL
25
+ extend Traits::SQL
36
26
 
37
27
  def initialize composition, options = {}
38
28
  @composition = composition
39
- @options = options
40
- @quote_keywords = {nil=>true, 't'=>true, 'f'=>false, 'y'=>true, 'n'=>false}[options.delete 'keywords']
41
- @quote_keywords = false if @keywords == nil # Set default
42
- @delay_fks = options.delete "delay_fks"
43
- @unicode = options.delete "unicode"
44
- @restrict = options.delete "restrict"
45
-
46
- # Name configuration options:
47
- @joiner = options.delete('joiner')
48
- @table_joiner = options.has_key?('tables') ? @joiner : nil
49
- @table_case = ((options.delete('tables') || 'cap') + 'words').to_sym
50
- @table_joiner ||= [:snakewords, :shoutwords].include?(@table_case) ? '_' : ''
51
- @column_joiner = options.has_key?('columns') ? @joiner : nil
52
- @column_case = ((options.delete('columns') || 'cap') + 'words').to_sym
53
- @column_joiner ||= [:snakewords, :shoutwords].include?(@column_case) ? '_' : ''
54
-
55
- # Legacy option. Use restrict=bdv/rdv instead
56
- @datavault = options.delete "datavault"
57
- case @datavault
58
- when "business"
59
- @restrict = "bdv"
60
- when "raw"
61
- @restrict = "rdv"
62
- end
63
-
64
- # Do not (yet) expose the closed-world vs open world problem.
65
- # Closed World vs Open World uniqueness is a semantic issue,
66
- # and so is OW, CW or CW with negation for unary fact types.
67
- # We need an overall strategy for handling it.
68
- @closed_world_indices = false # Allow for SQL Server's non-standard NULL indexing
29
+ process_options options
69
30
  end
70
31
 
71
32
  def generate
@@ -82,34 +43,13 @@ module ActiveFacts
82
43
  @delayed_foreign_keys.sort*"\n"
83
44
  end
84
45
 
85
- def data_type_context
86
- @data_type_context ||= SQLDataTypeContext.new
87
- end
88
-
89
- def table_name_max
90
- 60
91
- end
92
-
93
- def column_name_max
94
- 40
95
- end
96
-
97
- def index_name_max
98
- 60
99
- end
100
-
101
- def schema_name_max
102
- 60
103
- end
104
-
105
46
  def generate_schema
106
- #go "CREATE SCHEMA #{escape(@composition.name, schema_name_max)}" +
107
- ''
47
+ schema_prefix
108
48
  end
109
49
 
110
50
  def generate_table composite
111
51
  @tables_emitted[composite] = true
112
- delayed_indices = []
52
+ @delayed_statements = []
113
53
 
114
54
  "CREATE TABLE #{safe_table_name composite} (\n" +
115
55
  (
@@ -120,9 +60,10 @@ module ActiveFacts
120
60
  generate_column leaf
121
61
  end +
122
62
  composite.all_index.map do |index|
123
- generate_index index, delayed_indices
63
+ generate_index index
124
64
  end.compact.sort +
125
65
  composite.all_foreign_key_as_source_composite.map do |fk|
66
+ next nil if @fks == false
126
67
  fk_text = generate_foreign_key fk
127
68
  if !@delay_fks and # We're not delaying foreign keys unnecessarily
128
69
  @tables_emitted[fk.composite] || # Already done
@@ -138,31 +79,30 @@ module ActiveFacts
138
79
  '-- '+constraint.inspect # REVISIT: Emit local constraints
139
80
  end
140
81
  ).compact.flat_map{|f| "\t#{f}" }*",\n"+"\n" +
141
- go(")") +
142
- delayed_indices.sort.map do |delayed_index|
143
- go delayed_index
144
- end*"\n"
82
+ go(")") + "\n" +
83
+ @delayed_statements.sort.map do |delayed_statement|
84
+ go delayed_statement
85
+ end*''
145
86
  end
146
87
 
147
88
  def generate_column leaf
148
89
  column_name = safe_column_name(leaf)
149
- padding = " "*(column_name.size >= column_name_max ? 1 : column_name_max-column_name.size)
90
+ padding = " "*(column_name.size >= 40 ? 1 : 40-column_name.size)
150
91
  constraints = leaf.all_leaf_constraint
151
92
 
152
93
  "-- #{leaf.comment}\n" +
153
- "\t#{column_name}#{padding}#{column_type leaf, column_name}"
154
- end
155
-
156
- def auto_assign_modifier
157
- ' GENERATED ALWAYS AS IDENTITY'
94
+ "\t#{column_name}#{padding}#{column_type(leaf, column_name)}"
158
95
  end
159
96
 
160
97
  def column_type component, column_name
98
+ # Get the base data type name and options:
161
99
  type_name, options = component.data_type(data_type_context)
162
100
  options ||= {}
163
- length = options[:length]
164
101
  value_constraint = options[:value_constraint]
165
- type_name, length = normalise_type(type_name, length, value_constraint, options)
102
+ type_name = choose_sql_type(type_name, value_constraint, component, options)
103
+ @delayed_statements += options.delete(:delayed) if options[:delayed]
104
+ length = options[:length]
105
+ return options[:computed] if options[:computed]
166
106
 
167
107
  "#{
168
108
  type_name
@@ -172,18 +112,12 @@ module ActiveFacts
172
112
  ((options[:mandatory] ? ' NOT' : '') + ' NULL') if options.has_key?(:mandatory)
173
113
  }#{
174
114
  options[:default] || ''
175
- }#{
176
- auto_assign_modifier if a = options[:auto_assign] && a != 'assert'
177
115
  }#{
178
116
  check_clause(column_name, value_constraint) if value_constraint
179
117
  }"
180
118
  end
181
119
 
182
- def index_kind(index)
183
- ''
184
- end
185
-
186
- def generate_index index, delayed_indices
120
+ def generate_index index
187
121
  nullable_columns =
188
122
  index.all_index_field.select do |ixf|
189
123
  !ixf.component.path_mandatory
@@ -198,25 +132,40 @@ module ActiveFacts
198
132
  column_name(ixf.component)
199
133
  end
200
134
 
201
- if contains_nullable_columns and @closed_world_indices
202
- # Implement open-world uniqueness using a filtered index:
203
- table_name = safe_table_name(index.composite)
204
- delayed_indices <<
205
- 'CREATE UNIQUE'+index_kind(index)+' INDEX '+
206
- escape("#{table_name(index.composite)}By#{column_names*''}", index_name_max) +
207
- " ON #{table_name}("+column_names.map{|n| escape(n, column_name_max)}*', ' +
208
- ") WHERE #{
209
- nullable_columns.
210
- map{|ixf| safe_column_name ixf.component}.
211
- map{|column_name| column_name + ' IS NOT NULL'} *
212
- ' AND '
213
- }"
214
- nil
135
+ if index.is_unique
136
+ if contains_nullable_columns and @closed_world_indices
137
+ # Implement open-world uniqueness using a filtered index:
138
+ table_name = safe_table_name(index.composite)
139
+ @delayed_statements <<
140
+ 'CREATE UNIQUE'+index_kind(index)+' INDEX '+
141
+ escape("#{table_name(index.composite)}By#{column_names*''}", index_name_max) +
142
+ " ON #{table_name}("+column_names.map{|n| escape(n, column_name_max)}*', ' +
143
+ ") WHERE #{
144
+ nullable_columns.
145
+ map{|ixf| safe_column_name ixf.component}.
146
+ map{|column_name| column_name + ' IS NOT NULL'} *
147
+ ' AND '
148
+ }"
149
+ nil # Nothing inline
150
+ else
151
+ '-- '+index.inspect + "\n\t" +
152
+ (primary ? 'PRIMARY KEY' : 'UNIQUE') +
153
+ index_kind(index) +
154
+ "(#{column_names.map{|n| escape(n, column_name_max)}*', '})"
155
+ end
215
156
  else
216
- '-- '+index.inspect + "\n\t" +
217
- (primary ? 'PRIMARY KEY' : 'UNIQUE') +
218
- index_kind(index) +
219
- "(#{column_names.map{|n| escape(n, column_name_max)}*', '})"
157
+ # REVISIT: If the fields of this index is a prefix of another index, it can be omitted
158
+ tn = table_name(index.composite)
159
+ create_index =
160
+ 'CREATE'+index_kind(index)+' INDEX '+
161
+ escape("#{tn}By#{column_names*''}", index_name_max) +
162
+ " ON #{tn}(" +
163
+ column_names.map{|n|
164
+ escape(n, column_name_max)
165
+ }*', ' +
166
+ ')'
167
+ @delayed_statements << create_index
168
+ nil # Nothing inline
220
169
  end
221
170
  end
222
171
 
@@ -228,325 +177,8 @@ module ActiveFacts
228
177
  fk.all_index_field.map{|ixf| safe_column_name ixf.component}*", " +
229
178
  ")"
230
179
  end
231
-
232
- # Return SQL type and (modified?) length for the passed base type
233
- def normalise_type(type_name, length, value_constraint, options)
234
- type = MM::DataType.normalise(type_name)
235
-
236
- case type
237
- when MM::DataType::TYPE_Boolean; data_type_context.boolean_type
238
- when MM::DataType::TYPE_Integer
239
- # The :auto_assign key is set for auto-assigned types, but with a nil value in foreign keys
240
- if options.has_key?(:auto_assign)
241
- MM::DataType.normalise_int_length(
242
- 'int',
243
- data_type_context.default_surrogate_length,
244
- value_constraint,
245
- data_type_context
246
- )[0]
247
- else
248
- v, = MM::DataType.normalise_int_length(type_name, length, value_constraint, data_type_context)
249
- v # The typename here has the appropriate length, don't return a length
250
- end
251
- when MM::DataType::TYPE_Real;
252
- ["FLOAT", data_type_context.default_length(type, type_name)]
253
- when MM::DataType::TYPE_Decimal; ['DECIMAL', length]
254
- when MM::DataType::TYPE_Money; ['DECIMAL', length]
255
- when MM::DataType::TYPE_Char; [data_type_context.default_char_type, length || data_type_context.char_default_length]
256
- when MM::DataType::TYPE_String; [data_type_context.default_varchar_type, length || data_type_context.varchar_default_length]
257
- when MM::DataType::TYPE_Text; [data_type_context.default_text_type, length || 'MAX']
258
- when MM::DataType::TYPE_Date; 'DATE' # SQLSVR 2K5: 'date'
259
- when MM::DataType::TYPE_Time; 'TIME' # SQLSVR 2K5: 'time'
260
- when MM::DataType::TYPE_DateTime; 'TIMESTAMP'
261
- when MM::DataType::TYPE_Timestamp;'TIMESTAMP'
262
- when MM::DataType::TYPE_Binary;
263
- if type_name =~ /^(guid|uuid)$/i && (!length || length == 16)
264
- length ||= 16
265
- if ![nil, ''].include?(options[:auto_assign])
266
- options.delete(:auto_assign) # Don't auto-assign foreign keys
267
- end
268
- end
269
- if length
270
- ['BINARY', length]
271
- else
272
- ['VARBINARY', length]
273
- end
274
- else
275
- [type_name, length]
276
- end
277
- end
278
-
279
- def reserved_words
280
- @reserved_words ||= %w{
281
- ABS ABSOLUTE ACTION ADD ALL ALLOCATE ALTER AND ANY ARE
282
- ARRAY ARRAY_AGG ARRAY_MAX_CARDINALITY AS ASC ASENSITIVE
283
- ASSERTION ASYMMETRIC AT ATOMIC AUTHORIZATION AVG BEGIN
284
- BEGIN_FRAME BEGIN_PARTITION BETWEEN BIGINT BINARY BIT
285
- BIT_LENGTH BLOB BOOLEAN BOTH BY CALL CALLED CARDINALITY
286
- CASCADE CASCADED CASE CAST CATALOG CEIL CEILING CHAR
287
- CHARACTER CHARACTER_LENGTH CHAR_LENGTH CHECK CLOB CLOSE
288
- COALESCE COLLATE COLLATION COLLECT COLUMN COMMIT CONDITION
289
- CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONTAINS CONTINUE
290
- CONVERT CORR CORRESPONDING COUNT COVAR_POP COVAR_SAMP
291
- CREATE CROSS CUBE CUME_DIST CURRENT CURRENT_CATALOG
292
- CURRENT_DATE CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_PATH
293
- CURRENT_ROLE CURRENT_ROW CURRENT_SCHEMA CURRENT_TIME
294
- CURRENT_TIMESTAMP CURRENT_TRANSFORM_GROUP_FOR_TYPE
295
- CURRENT_USER CURSOR CYCLE DATALINK DATE DAY DEALLOCATE
296
- DEC DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DELETE
297
- DENSE_RANK DEREF DESC DESCRIBE DESCRIPTOR DETERMINISTIC
298
- DIAGNOSTICS DISCONNECT DISTINCT DLNEWCOPY DLPREVIOUSCOPY
299
- DLURLCOMPLETE DLURLCOMPLETEONLY DLURLCOMPLETEWRITE DLURLPATH
300
- DLURLPATHONLY DLURLPATHWRITE DLURLSCHEME DLURLSERVER
301
- DLVALUE DO DOMAIN DOUBLE DROP DYNAMIC EACH ELEMENT ELSE
302
- ELSEIF END END-EXEC END_FRAME END_PARTITION EQUALS ESCAPE
303
- EVERY EXCEPT EXCEPTION EXEC EXECUTE EXISTS EXIT EXP
304
- EXTERNAL EXTRACT FALSE FETCH FILTER FIRST FIRST_VALUE
305
- FLOAT FLOOR FOR FOREIGN FOUND FRAME_ROW FREE FROM FULL
306
- FUNCTION FUSION GET GLOBAL GO GOTO GRANT GROUP GROUPING
307
- GROUPS HANDLER HAVING HOLD HOUR IDENTITY IF IMMEDIATE
308
- IMPORT IN INDICATOR INITIALLY INNER INOUT INPUT INSENSITIVE
309
- INSERT INT INTEGER INTERSECT INTERSECTION INTERVAL INTO
310
- IS ISOLATION ITERATE JOIN KEY LAG LANGUAGE LARGE LAST
311
- LAST_VALUE LATERAL LEAD LEADING LEAVE LEFT LEVEL LIKE
312
- LIKE_REGEX LN LOCAL LOCALTIME LOCALTIMESTAMP LOOP LOWER
313
- MATCH MAX MAX_CARDINALITY MEMBER MERGE METHOD MIN MINUTE
314
- MOD MODIFIES MODULE MONTH MULTISET NAMES NATIONAL NATURAL
315
- NCHAR NCLOB NEW NEXT NO NONE NORMALIZE NOT NTH_VALUE NTILE
316
- NULL NULLIF NUMERIC OCCURRENCES_REGEX OCTET_LENGTH OF
317
- OFFSET OLD ON ONLY OPEN OPTION OR ORDER OUT OUTER OUTPUT
318
- OVER OVERLAPS OVERLAY PAD PARAMETER PARTIAL PARTITION
319
- PERCENT PERCENTILE_CONT PERCENTILE_DISC PERCENT_RANK
320
- PERIOD PORTION POSITION POSITION_REGEX POWER PRECEDES
321
- PRECISION PREPARE PRESERVE PRIMARY PRIOR PRIVILEGES
322
- PROCEDURE PUBLIC RANGE RANK READ READS REAL RECURSIVE REF
323
- REFERENCES REFERENCING REGR_AVGX REGR_AVGY REGR_COUNT
324
- REGR_INTERCEPT REGR_R2 REGR_SLOPE REGR_SXX REGR_SXY
325
- REGR_SYY RELATIVE RELEASE REPEAT RESIGNAL RESTRICT RESULT
326
- RETURN RETURNS REVOKE RIGHT ROLLBACK ROLLUP ROW ROWS
327
- ROW_NUMBER SAVEPOINT SCHEMA SCOPE SCROLL SEARCH SECOND
328
- SECTION SELECT SENSITIVE SESSION SESSION_USER SET SIGNAL
329
- SIMILAR SIZE SMALLINT SOME SPACE SPECIFIC SPECIFICTYPE
330
- SQL SQLCODE SQLERROR SQLEXCEPTION SQLSTATE SQLWARNING
331
- SQRT START STATIC STDDEV_POP STDDEV_SAMP SUBMULTISET
332
- SUBSTRING SUBSTRING_REGEX SUCCEEDS SUM SYMMETRIC SYSTEM
333
- SYSTEM_TIME SYSTEM_USER TABLE TABLESAMPLE TEMPORARY THEN
334
- TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TO TRAILING
335
- TRANSACTION TRANSLATE TRANSLATE_REGEX TRANSLATION TREAT
336
- TRIGGER TRIM TRIM_ARRAY TRUE TRUNCATE UESCAPE UNDO UNION
337
- UNIQUE UNKNOWN UNNEST UNTIL UPDATE UPPER USAGE USER USING
338
- VALUE VALUES VALUE_OF VARBINARY VARCHAR VARYING VAR_POP
339
- VAR_SAMP VERSIONING VIEW WHEN WHENEVER WHERE WHILE
340
- WIDTH_BUCKET WINDOW WITH WITHIN WITHOUT WORK WRITE XML
341
- XMLAGG XMLATTRIBUTES XMLBINARY XMLCAST XMLCOMMENT XMLCONCAT
342
- XMLDOCUMENT XMLELEMENT XMLEXISTS XMLFOREST XMLITERATE
343
- XMLNAMESPACES XMLPARSE XMLPI XMLQUERY XMLSERIALIZE XMLTABLE
344
- XMLTEXT XMLVALIDATE YEAR ZONE
345
- }
346
- end
347
-
348
- def key_words
349
- @key_words ||= %w{
350
- A ABSENT ACCORDING ADA ADMIN AFTER ALWAYS ASSIGNMENT
351
- ATTRIBUTE ATTRIBUTES BASE64 BEFORE BERNOULLI BLOCKED BOM
352
- BREADTH C CATALOG_NAME CHAIN CHARACTERISTICS CHARACTERS
353
- CHARACTER_SET_CATALOG CHARACTER_SET_NAME CHARACTER_SET_SCHEMA
354
- CLASS_ORIGIN COBOL COLLATION_CATALOG COLLATION_NAME
355
- COLLATION_SCHEMA COLUMNS COLUMN_NAME COMMAND_FUNCTION
356
- COMMAND_FUNCTION_CODE COMMITTED CONDITION_NUMBER
357
- CONNECTION_NAME CONSTRAINT_CATALOG CONSTRAINT_NAME
358
- CONSTRAINT_SCHEMA CONSTRUCTOR CONTENT CONTROL CURSOR_NAME
359
- DATA DATETIME_INTERVAL_CODE DATETIME_INTERVAL_PRECISION
360
- DB DEFAULTS DEFINED DEFINER DEGREE DEPTH DERIVED DISPATCH
361
- DOCUMENT DYNAMIC_FUNCTION DYNAMIC_FUNCTION_CODE EMPTY
362
- ENCODING ENFORCED EXCLUDE EXCLUDING EXPRESSION FILE FINAL
363
- FLAG FOLLOWING FORTRAN FS G GENERAL GENERATED GRANTED HEX
364
- HIERARCHY ID IGNORE IMMEDIATELY IMPLEMENTATION INCLUDING
365
- INCREMENT INDENT INSTANCE INSTANTIABLE INSTEAD INTEGRITY
366
- INVOKER K KEY_MEMBER KEY_TYPE LENGTH LIBRARY LIMIT LINK
367
- LOCATION LOCATOR M MAP MAPPING MATCHED MAXVALUE MESSAGE_LENGTH
368
- MESSAGE_OCTET_LENGTH MESSAGE_TEXT MINVALUE MORE MUMPS
369
- NAME NAMESPACE NESTING NFC NFD NFKC NFKD NIL NORMALIZED
370
- NULLABLE NULLS NUMBER OBJECT OCTETS OFF OPTIONS ORDERING
371
- ORDINALITY OTHERS OVERRIDING P PARAMETER_MODE PARAMETER_NAME
372
- PARAMETER_ORDINAL_POSITION PARAMETER_SPECIFIC_CATALOG
373
- PARAMETER_SPECIFIC_NAME PARAMETER_SPECIFIC_SCHEMA PASCAL
374
- PASSING PASSTHROUGH PATH PERMISSION PLACING PLI PRECEDING
375
- RECOVERY REPEATABLE REQUIRING RESPECT RESTART RESTORE
376
- RETURNED_CARDINALITY RETURNED_LENGTH RETURNED_OCTET_LENGTH
377
- RETURNED_SQLSTATE RETURNING ROLE ROUTINE ROUTINE_CATALOG
378
- ROUTINE_NAME ROUTINE_SCHEMA ROW_COUNT SCALE SCHEMA_NAME
379
- SCOPE_CATALOG SCOPE_NAME SCOPE_SCHEMA SECURITY SELECTIVE
380
- SELF SEQUENCE SERIALIZABLE SERVER SERVER_NAME SETS SIMPLE
381
- SOURCE SPECIFIC_NAME STANDALONE STATE STATEMENT STRIP
382
- STRUCTURE STYLE SUBCLASS_ORIGIN T TABLE_NAME TIES TOKEN
383
- TOP_LEVEL_COUNT TRANSACTIONS_COMMITTED TRANSACTIONS_ROLLED_BACK
384
- TRANSACTION_ACTIVE TRANSFORM TRANSFORMS TRIGGER_CATALOG
385
- TRIGGER_NAME TRIGGER_SCHEMA TYPE UNBOUNDED UNCOMMITTED
386
- UNDER UNLINK UNNAMED UNTYPED URI USER_DEFINED_TYPE_CATALOG
387
- USER_DEFINED_TYPE_CODE USER_DEFINED_TYPE_NAME
388
- USER_DEFINED_TYPE_SCHEMA VALID VERSION WHITESPACE WRAPPER
389
- XMLDECLARATION XMLSCHEMA YES
390
- }
391
- end
392
-
393
- def is_reserved_word w
394
- @reserved_word_hash ||=
395
- ( reserved_words +
396
- (@quote_keywords ? key_words : [])).
397
- inject({}) do |h,w|
398
- h[w] = true
399
- h
400
- end
401
- @reserved_word_hash[w.upcase]
402
- end
403
-
404
- def go s = ''
405
- "#{s};\n\n"
406
- end
407
-
408
- def open_escape
409
- '"'
410
- end
411
-
412
- def close_escape
413
- '"'
414
- end
415
-
416
- def escape s, max = table_name_max
417
- # Escape SQL keywords and non-identifiers
418
- if s.size > max
419
- excess = s[max..-1]
420
- s = s[0...max-(excess.size/8)] +
421
- Digest::SHA1.hexdigest(excess)[0...excess.size/8]
422
- end
423
-
424
- if s =~ /[^A-Za-z0-9_]/ || is_reserved_word(s)
425
- "#{open_escape}#{s}#{close_escape}"
426
- else
427
- s
428
- end
429
- end
430
-
431
- def sql_value(value)
432
- value.is_literal_string ? sql_string(value.literal) : value.literal
433
- end
434
-
435
- def sql_string(str)
436
- "'" + str.gsub(/'/,"''") + "'"
437
- end
438
-
439
- def check_clause column_name, value_constraint
440
- " CHECK(" +
441
- value_constraint.all_allowed_range_sorted.map do |ar|
442
- vr = ar.value_range
443
- min = vr.minimum_bound
444
- max = vr.maximum_bound
445
- if (min && max && max.value.literal == min.value.literal)
446
- "#{column_name} = #{sql_value(min.value)}"
447
- else
448
- inequalities = [
449
- min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
450
- max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
451
- ].compact
452
- inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
453
- end
454
- end*" OR " +
455
- ")"
456
- end
457
-
458
- class SQLDataTypeContext < MM::DataType::Context
459
- def integer_ranges
460
- [
461
- ['SMALLINT', -2**15, 2**15-1], # The standard says -10^5..10^5 (less than 16 bits)
462
- ['INTEGER', -2**31, 2**31-1], # The standard says -10^10..10^10 (more than 32 bits!)
463
- ['BIGINT', -2**63, 2**63-1], # The standard says -10^19..10^19 (less than 64 bits)
464
- ]
465
- end
466
-
467
- def default_length data_type, type_name
468
- case data_type
469
- when MM::DataType::TYPE_Real
470
- 53 # IEEE Double precision floating point
471
- when MM::DataType::TYPE_Integer
472
- case type_name
473
- when /([a-z ]|\b)Tiny([a-z ]|\b)/i
474
- 8
475
- when /([a-z ]|\b)Small([a-z ]|\b)/i,
476
- /([a-z ]|\b)Short([a-z ]|\b)/i
477
- 16
478
- when /([a-z ]|\b)Big(INT)?([a-z ]|\b)/i
479
- 64
480
- else
481
- 32
482
- end
483
- else
484
- nil
485
- end
486
- end
487
-
488
- def boolean_type
489
- 'BOOLEAN'
490
- end
491
-
492
- def surrogate_type
493
- type_name, = choose_integer_type(0, 2**(default_surrogate_length-1)-1)
494
- type_name
495
- end
496
-
497
- def valid_from_type
498
- 'TIMESTAMP'
499
- end
500
-
501
- def date_time_type
502
- 'TIMESTAMP'
503
- end
504
-
505
- def default_char_type
506
- (@unicode ? 'NATIONAL ' : '') +
507
- 'CHARACTER'
508
- end
509
-
510
- def default_varchar_type
511
- (@unicode ? 'NATIONAL ' : '') +
512
- 'VARCHAR'
513
- end
514
-
515
- def char_default_length
516
- nil
517
- end
518
-
519
- def varchar_default_length
520
- nil
521
- end
522
-
523
- def default_surrogate_length
524
- 64
525
- end
526
-
527
- def default_text_type
528
- default_varchar_type
529
- end
530
- end
531
-
532
- def safe_table_name composite
533
- escape(table_name(composite), table_name_max)
534
- end
535
-
536
- def safe_column_name component
537
- escape(column_name(component), column_name_max)
538
- end
539
-
540
- def table_name composite
541
- composite.mapping.name.words.send(@table_case)*@table_joiner
542
- end
543
-
544
- def column_name component
545
- words = component.column_name.send(@column_case)
546
- words*@column_joiner
547
- end
548
-
549
180
  end
181
+
550
182
  publish_generator SQL
551
183
  end
552
184
  end
@@ -29,12 +29,14 @@ module ActiveFacts
29
29
  class Composite
30
30
  def summary
31
31
  indices = self.all_indices_by_rank
32
+ fks = {}
33
+ fk_count = 0
32
34
 
33
35
  (
34
36
  [mapping.name+"\n"] +
35
37
  mapping.
36
38
  all_leaf.
37
- reject{|leaf| leaf.is_a?(Absorption) && leaf.forward_absorption}.
39
+ reject{|leaf| leaf.is_a?(Absorption) && leaf.forward_mapping}.
38
40
  flat_map do |leaf|
39
41
 
40
42
  # Build a display of the names in this absorption path, with FK and optional indicators
@@ -49,10 +51,12 @@ module ActiveFacts
49
51
  is_mandatory = false
50
52
  end
51
53
 
52
- if component.all_foreign_key_field.size > 0
53
- "[#{component.name}]"
54
- elsif component.is_a?(Absorption) && component.foreign_key
55
- "{#{component.name}}"
54
+ # if all_foreign_key.detect{|fk| fk.all_foreign_key_field.detect{|fkf| fkf.component == leaf}}
55
+ if component.is_a?(Mapping) && component.foreign_key && leaf.all_foreign_key_field.size > 0
56
+ fk_number = (fks[component.foreign_key] ||= (fk_count += 1))
57
+ "[F#{fk_number}:#{component.name}"
58
+ elsif component == leaf && leaf.all_foreign_key_field.size > 0
59
+ "#{component.name}]"
56
60
  else
57
61
  component.name
58
62
  end +
@@ -62,9 +66,14 @@ module ActiveFacts
62
66
  # Build a symbolic representation of the index participation of this leaf
63
67
  pos = 0
64
68
  indexing = indices.inject([]) do |a, index|
69
+ # An index can be both Primary and Natural. Otherwise we show if it's Unique
70
+ type_str = ''
71
+ type_str << 'P' if index == primary_index
72
+ type_str << 'N' if index == natural_index
73
+ type_str = 'U' if type_str == '' && index.is_unique
65
74
  pos += 1
66
75
  if part = index.position_in_index(leaf)
67
- a << "#{pos}.#{part}"
76
+ a << "#{type_str}#{pos}" + (index.all_index_field.size > 1 ? ".#{part}" : "")
68
77
  end
69
78
  a
70
79
  end
@@ -0,0 +1,41 @@
1
+ #
2
+ # ActiveFacts Generator Expression List Traits
3
+ #
4
+ # Each expression is a string which, evaluated in some context (usu. SQL),
5
+ # yields a single value of the specified type (perhaps an array value!)
6
+ #
7
+ # Copyright (c) 2017 Clifford Heath. Read the LICENSE file.
8
+ #
9
+ require 'activefacts/metamodel'
10
+ require 'activefacts/metamodel/datatypes'
11
+ require 'activefacts/generator'
12
+
13
+ module ActiveFacts
14
+ module Generators
15
+ class Expression
16
+ private
17
+ MM = ActiveFacts::Metamodel unless const_defined?(:MM)
18
+ public
19
+ attr_reader :type_num # ActiveFacts::Metamodel::DataType number
20
+ attr_reader :value # String representation of the expression
21
+ attr_reader :is_mandatory # false if nullable
22
+ attr_reader :is_array # the expression returns an array of the specified type
23
+
24
+ # Construct an expression that addresses a field from a Metamodel::Component
25
+ def initialize value, type_num, is_mandatory, is_array = false
26
+ @type_num = type_num
27
+ @value = value
28
+ @is_mandatory = is_mandatory
29
+ @is_array = is_array
30
+ end
31
+
32
+ def to_s
33
+ value
34
+ end
35
+
36
+ def inspect
37
+ "Expression(#{value.inspect}, #{@type_num ? ActiveFacts::Metamodel::DataType::TypeNames[@type_num] : 'unknown'}, #{@is_mandatory ? 'mandatory' : 'nullable'}#{@is_array ? ', array' : ''})"
38
+ end
39
+ end
40
+ end
41
+ end