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
@@ -0,0 +1,287 @@
1
+ #
2
+ # ActiveFacts PostgreSQL Traits
3
+ #
4
+ # Copyright (c) 2017 Clifford Heath. Read the LICENSE file.
5
+ #
6
+ # Reserved words gathered from:
7
+ # https://www.postgresql.org/docs/9.5/static/sql-keywords-appendix.html
8
+ #
9
+ require 'digest/sha1'
10
+ require 'activefacts/metamodel'
11
+ require 'activefacts/compositions'
12
+ require 'activefacts/generator/traits/sql'
13
+
14
+ module ActiveFacts
15
+ module Generators
16
+ module Traits
17
+ module SQL
18
+ module Postgres
19
+ include Traits::SQL
20
+
21
+ def options
22
+ super.merge({
23
+ # no: [String, "no new options defined here"]
24
+ })
25
+ end
26
+
27
+ # The options parameter overrides any default options set by sub-traits
28
+ def defaults_and_options options
29
+ {'tables' => 'snake', 'columns' => 'snake'}.merge(options)
30
+ end
31
+
32
+ def process_options options
33
+ # No extra options to process
34
+ super
35
+ end
36
+
37
+ def data_type_context_class
38
+ PostgresDataTypeContext
39
+ end
40
+
41
+ def table_name_max
42
+ 63
43
+ end
44
+
45
+ def column_name_max
46
+ 63
47
+ end
48
+
49
+ def index_name_max
50
+ 63
51
+ end
52
+
53
+ def schema_name_max
54
+ 63
55
+ end
56
+
57
+ def schema_prefix
58
+ go('CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public') +
59
+ go('CREATE EXTENSION IF NOT EXISTS fuzzystrmatch WITH SCHEMA public') +
60
+ "\n"
61
+ end
62
+
63
+ def choose_sql_type(type_name, value_constraint, component, options)
64
+ type = MM::DataType.intrinsic_type(type_name)
65
+ case type
66
+ when MM::DataType::TYPE_Integer
67
+ # The :auto_assign key is set for auto-assigned types, but with a nil value in foreign keys
68
+ if options.has_key?(:auto_assign)
69
+ if options[:auto_assign]
70
+ 'BIGSERIAL' # This doesn't need an auto_increment default
71
+ else
72
+ 'BIGINT'
73
+ end
74
+ else
75
+ super
76
+ end
77
+
78
+ when MM::DataType::TYPE_Money
79
+ 'MONEY'
80
+
81
+ when MM::DataType::TYPE_DateTime
82
+ 'TIMESTAMP'
83
+
84
+ when MM::DataType::TYPE_Timestamp
85
+ 'TIMESTAMP'
86
+
87
+ when MM::DataType::TYPE_Binary
88
+ case binary_surrogate(type_name, value_constraint, options)
89
+ when :guid_fk # A surrogate that's auto-assigned elsewhere
90
+ options[:length] = nil
91
+ 'UUID'
92
+ when :guid # A GUID
93
+ # This requires the pgcrypto extension
94
+ options[:length] = nil
95
+ options[:default] = " DEFAULT gen_random_uuid()"
96
+ 'UUID'
97
+ when :hash # A hash of the natural key
98
+ options.delete(:length) # 20 bytes, assuming SHA-1, but we don't need to specify it. SHA-256 would need 32 bytes
99
+ options[:delayed] = trigger_hash_assignment(component, component.root.natural_index.all_index_field.map(&:component))
100
+ 'BYTEA'
101
+ else # Not a surrogate
102
+ options.delete(:length)
103
+ 'BYTEA'
104
+ end
105
+
106
+ else
107
+ super
108
+ end
109
+ end
110
+
111
+ # Return an array of SQL statements that arrange for the hash_field
112
+ # to be populated with a hash of the values of the leaves.
113
+ def trigger_hash_assignment hash_field, leaves
114
+ table_name = safe_table_name(hash_field.root)
115
+ trigger_function = escape('assign_'+column_name(hash_field), 128)
116
+ [
117
+ %Q{
118
+ CREATE OR REPLACE FUNCTION #{trigger_function}() RETURNS TRIGGER AS $$
119
+ BEGIN
120
+ NEW.#{safe_column_name(hash_field)} = #{
121
+ hash(concatenate(coalesce(as_text(safe_column_exprs(leaves, 'NEW')))))
122
+ };
123
+ RETURN NEW;
124
+ END
125
+ $$ language 'plpgsql'}.
126
+ unindent,
127
+ %Q{
128
+ CREATE TRIGGER trig_#{trigger_function}
129
+ BEFORE INSERT OR UPDATE ON #{table_name}
130
+ FOR EACH ROW EXECUTE PROCEDURE #{trigger_function}()}.
131
+ unindent
132
+ ]
133
+ end
134
+
135
+ # Some or all of the SQL expressions may have non-text values.
136
+ # Return an SQL expression that coerces them to text.
137
+ def as_text exprs
138
+ return exprs.map{|e| as_text(e)} if Array === exprs
139
+
140
+ Expression.new("#{exprs}::text", MM::DataType::TYPE_String, exprs.is_mandatory)
141
+ end
142
+
143
+ # Return an SQL expression that concatenates the given expressions (which must yield a string type)
144
+ def concatenate expressions
145
+ Expression.new(
146
+ "'|'::text || " +
147
+ expressions.map(&:to_s) * " || '|'::text || " +
148
+ " || '|'::text",
149
+ MM::DataType::TYPE_String,
150
+ true
151
+ )
152
+ end
153
+
154
+ # Return an expression that yields a hash of the given expression
155
+ def hash expr, algo = 'sha1'
156
+ Expression.new("digest(#{expr}, '#{algo}')", MM::DataType::TYPE_Binary, expr.is_mandatory)
157
+ end
158
+
159
+ def truncate expr, length
160
+ Expression.new("substring(#{expr} for #{length})", MM::DataType::TYPE_String, expr.is_mandatory)
161
+ end
162
+
163
+ def trigram expr
164
+ Expression.new("show_trgm(#{expr})", MM::DataType::TYPE_String, expr.is_mandatory, true)
165
+ end
166
+
167
+ # Produce a lexically-sortable decimal representation of the given numeric expression, to the overall specified length and scale
168
+ def lexical_decimal expr, length, scale = 0
169
+ fraction_pattern = scale > 0 ? '.'+'0'*scale : ''
170
+ Expression.new(
171
+ "to_char(#{expr}, 'MI#{'0'*(length-fraction_pattern.length-1)+fraction_pattern})",
172
+ MM::DataType::TYPE_String,
173
+ expr.is_mandatory
174
+ )
175
+ end
176
+
177
+ def lexical_date expr
178
+ Expression.new("to_char(#{expr}, 'YYYY-MM-DD')", MM::DataType::TYPE_String, expr.is_mandatory)
179
+ end
180
+
181
+ def lexical_datetime expr
182
+ Expression.new("to_char(#{expr}, 'YYYY-MM-DD HH24:MI:SS.US')", MM::DataType::TYPE_String, expr.is_mandatory)
183
+ end
184
+
185
+ def lexical_time expr
186
+ Expression.new("to_char(#{expr}, 'HH24:MI:SS.US')", MM::DataType::TYPE_String, expr.is_mandatory)
187
+ end
188
+
189
+ def as_alpha expr
190
+ Expression.new("btrim(lower(regexp_replace(#{expr}, '[^[:alnum:]]+', ' ', 'g')))", MM::DataType::TYPE_String, expr.is_mandatory)
191
+ end
192
+
193
+ def phonetics expr
194
+ dmetaphone = "dmetaphone(#{expr})"
195
+ dmetaphone_alt = "dmetaphone_alt(#{expr})"
196
+ [
197
+ Expression.new(dmetaphone, MM::DataType::TYPE_String, expr.is_mandatory),
198
+ Expression.new("CASE WHEN #{dmetaphone} <> #{dmetaphone_alt} THEN #{dmetaphone_alt} ELSE NULL END", MM::DataType::TYPE_String, expr.is_mandatory)
199
+ ]
200
+ end
201
+
202
+ # Reserved words cannot be used anywhere without quoting.
203
+ # Keywords have existing definitions, so should not be used without quoting.
204
+ # Both lists here are added to the supertype's lists
205
+ def reserved_words
206
+ @postgres_reserved_words ||= %w{
207
+ ANALYSE ANALYZE LIMIT PLACING RETURNING VARIADIC
208
+ }
209
+ super + @postgres_reserved_words
210
+ end
211
+
212
+ def key_words
213
+ # These keywords should not be used for columns or tables:
214
+ @postgres_key_words ||= %w{
215
+ ABORT ACCESS AGGREGATE ALSO BACKWARD CACHE CHECKPOINT
216
+ CLASS CLUSTER COMMENT COMMENTS CONFIGURATION CONFLICT
217
+ CONVERSION COPY COST CSV DATABASE DELIMITER DELIMITERS
218
+ DICTIONARY DISABLE DISCARD ENABLE ENCRYPTED ENUM EVENT
219
+ EXCLUSIVE EXPLAIN EXTENSION FAMILY FORCE FORWARD FUNCTIONS
220
+ HEADER IMMUTABLE IMPLICIT INDEX INDEXES INHERIT INHERITS
221
+ INLINE LABEL LEAKPROOF LISTEN LOAD LOCK LOCKED LOGGED
222
+ MATERIALIZED MODE MOVE NOTHING NOTIFY NOWAIT OIDS
223
+ OPERATOR OWNED OWNER PARSER PASSWORD PLANS POLICY
224
+ PREPARED PROCEDURAL PROGRAM QUOTE REASSIGN RECHECK
225
+ REFRESH REINDEX RENAME REPLACE REPLICA RESET RULE
226
+ SEQUENCES SHARE SHOW SKIP SNAPSHOT STABLE STATISTICS
227
+ STDIN STDOUT STORAGE STRICT SYSID TABLES TABLESPACE
228
+ TEMP TEMPLATE TEXT TRUSTED TYPES UNENCRYPTED UNLISTEN
229
+ UNLOGGED VACUUM VALIDATE VALIDATOR VIEWS VOLATILE
230
+ }
231
+
232
+ # These keywords cannot be used for type or functions (and should not for columns or tables)
233
+ @postgres_key_words_func_type ||= %w{
234
+ GREATEST LEAST SETOF XMLROOT
235
+ }
236
+ super + @postgres_key_words + @postgres_key_words_func_type
237
+ end
238
+
239
+ def open_escape
240
+ '"'
241
+ end
242
+
243
+ def close_escape
244
+ '"'
245
+ end
246
+
247
+ def index_kind(index)
248
+ ''
249
+ end
250
+
251
+ class PostgresDataTypeContext < SQLDataTypeContext
252
+ def integer_ranges
253
+ super
254
+ end
255
+
256
+ def boolean_type
257
+ 'BOOLEAN'
258
+ end
259
+
260
+ # See https://www.postgresql.org/docs/9.0/static/datatype-boolean.html
261
+ def boolean_expr safe_column_name
262
+ safe_column_name # psql outputs as 't' or 'f', but the bare column is a boolean expression
263
+ end
264
+
265
+ # There is no performance benefit in using fixed-length CHAR fields,
266
+ # and an added burden of trimming the implicitly added white-space
267
+ def default_char_type
268
+ (@unicode ? 'N' : '') +
269
+ 'VARCHAR'
270
+ end
271
+
272
+ def default_varchar_type
273
+ (@unicode ? 'N' : '') +
274
+ 'VARCHAR'
275
+ end
276
+
277
+ def date_time_type
278
+ 'TIMESTAMP'
279
+ end
280
+ end
281
+
282
+ end
283
+
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,262 @@
1
+ #
2
+ # ActiveFacts SQL Server Traits
3
+ #
4
+ # Copyright (c) 2009-2016 Clifford Heath. Read the LICENSE file.
5
+ #
6
+ # Reserved words gathered from:
7
+ # https://technet.microsoft.com/en-us/library/ms189822(v=sql.110).aspx
8
+ #
9
+ require 'digest/sha1'
10
+ require 'activefacts/metamodel'
11
+ require 'activefacts/compositions'
12
+ require 'activefacts/generator/traits/sql'
13
+
14
+ module ActiveFacts
15
+ module Generators
16
+ module Traits
17
+ module SQL
18
+ module Server
19
+ include Traits::SQL
20
+
21
+ def options
22
+ super.merge({
23
+ # no: [String, "no new options defined here"]
24
+ })
25
+ end
26
+
27
+ # The options parameter overrides any default options set by sub-traits
28
+ def defaults_and_options options
29
+ super
30
+ end
31
+
32
+ def process_options options
33
+ # No extra options to process
34
+ super
35
+ @closed_world_indices = true
36
+ end
37
+
38
+ def data_type_context_class
39
+ SQLServerDataTypeContext
40
+ end
41
+
42
+ def table_name_max
43
+ 128
44
+ end
45
+
46
+ def column_name_max
47
+ 128
48
+ end
49
+
50
+ def index_name_max
51
+ 128
52
+ end
53
+
54
+ def schema_name_max
55
+ 128
56
+ end
57
+
58
+ def schema_prefix
59
+ ''
60
+ end
61
+
62
+ def choose_sql_type(type_name, value_constraint, component, options)
63
+ type = MM::DataType.intrinsic_type(type_name)
64
+ case type
65
+ when MM::DataType::TYPE_Integer
66
+ # The :auto_assign key is set for auto-assigned types, but with a nil value in foreign keys
67
+ if options.has_key?(:auto_assign)
68
+ options[:default] = ' IDENTITY' if options[:auto_assign]
69
+ 'BIGINT'
70
+ else
71
+ super
72
+ end
73
+
74
+ when MM::DataType::TYPE_Money
75
+ 'MONEY'
76
+
77
+ when MM::DataType::TYPE_DateTime
78
+ 'DATETIME'
79
+
80
+ when MM::DataType::TYPE_Timestamp
81
+ 'DATETIME'
82
+
83
+ when MM::DataType::TYPE_Binary
84
+ length = options[:length]
85
+ case binary_surrogate(type_name, value_constraint, options)
86
+ when :guid_fk # A GUID surrogate that's auto-assigned elsewhere
87
+ 'UNIQUEIDENTIFIER'
88
+ when :guid # A GUID
89
+ options[:default] = " DEFAULT NEWID()"
90
+ # NEWSEQUENTIALID improves indexing locality and page fill factor.
91
+ # However, it makes values more easily guessable, and
92
+ # exposes the MAC address of the generating computer(!)
93
+ # options[:default] = " DEFAULT NEWSEQUENTIALID()"
94
+ 'UNIQUEIDENTIFIER'
95
+ when :hash # A hash of the natural key
96
+ options[:length] = 20 # Assuming SHA-1. SHA-256 would need 32 bytes
97
+ options[:computed] = hash_assignment(component, component.root.natural_index.all_index_field.map(&:component))
98
+ 'BINARY'
99
+ else # Not a surrogate
100
+ length = options[:length]
101
+ if length && length <= 8192
102
+ super
103
+ else
104
+ 'IMAGE'
105
+ end
106
+ end
107
+ else
108
+ super
109
+ end
110
+ end
111
+
112
+ def create_or_replace(name, kind)
113
+ # From SQL Server 2016 onwards, you can use "CREATE OR ALTER ..."
114
+ go("IF OBJECT_ID('#{name}') IS NOT NULL\n\tDROP #{kind} #{name}") +
115
+ "CREATE #{kind} #{name}"
116
+ end
117
+
118
+ def hash_assignment hash_field, leaves
119
+ table_name = safe_table_name(hash_field.root)
120
+ trigger_function = escape('assign_'+column_name(hash_field), 128)
121
+ %Q{
122
+ AS #{hash(concatenate(coalesce(as_text(safe_column_exprs(leaves)))))}
123
+ PERSISTED}.gsub(/\s+/,' ').strip
124
+ end
125
+
126
+ # Some or all of the SQL expressions may have non-text values.
127
+ # Return an SQL expression that coerces them to text.
128
+ def as_text exprs
129
+ return exprs.map{|e| as_text(e)} if Array === exprs
130
+
131
+ return exprs.map{|e| as_text(e)} if Array === exprs
132
+
133
+ style =
134
+ case exprs.type_num
135
+ when MM::DataType::TYPE_Date, MM::DataType::TYPE_DateTime, MM::DataType::TYPE_Timestamp
136
+ ', 121'
137
+ # REVISIT: What about MM::DataType::TYPE_Time?
138
+ else
139
+ ''
140
+ end
141
+ Expression.new("CONVERT(VARCHAR, #{exprs}#{style})", MM::DataType::TYPE_String, exprs.is_mandatory)
142
+ end
143
+
144
+ # Return an SQL expression that concatenates the given expressions (which must be text)
145
+ def concatenate exprs
146
+ # SQL Server 2012 onwards: %Q{CONCAT('|'+#{exprs.flat_map{|e| [e.to_s, "+'|'"]}*''})}
147
+ Expression.new(
148
+ %Q{('|'+#{
149
+ exprs.flat_map{|e| [e.to_s, "'|'"] } * '+'
150
+ })},
151
+ MM::DataType::TYPE_String,
152
+ true
153
+ )
154
+ end
155
+
156
+ # Return an expression that yields a hash of the given expression
157
+ def hash expr, algo = 'SHA1'
158
+ Expression.new("CONVERT(BINARY(32), HASHBYTES('#{algo}', #{expr}), 2)", MM::DataType::TYPE_Binary, expr.is_mandatory)
159
+ end
160
+
161
+ # Reserved words cannot be used anywhere without quoting.
162
+ # Keywords have existing definitions, so should not be used without quoting.
163
+ # Both lists here are added to the supertype's lists
164
+ def reserved_words
165
+ @sqlserver_reserved_words ||= %w{
166
+ BACKUP BREAK BROWSE BULK CHECKPOINT CLUSTERED COMPUTE
167
+ CONTAINSTABLE DATABASE DBCC DENY DISK DISTRIBUTED DUMP
168
+ ERRLVL FILE FILLFACTOR FREETEXT FREETEXTTABLE HOLDLOCK
169
+ IDENTITYCOL IDENTITY_INSERT INDEX KILL LINENO LOAD
170
+ NOCHECK NONCLUSTERED OFF OFFSETS OPENDATASOURCE OPENQUERY
171
+ OPENROWSET OPENXML PIVOT PLAN PRINT PROC RAISERROR
172
+ READTEXT RECONFIGURE REPLICATION RESTORE REVERT ROWCOUNT
173
+ ROWGUIDCOL RULE SAVE SECURITYAUDIT SEMANTICKEYPHRASETABLE
174
+ SEMANTICSIMILARITYDETAILSTABLE SEMANTICSIMILARITYTABLE
175
+ SETUSER SHUTDOWN STATISTICS TEXTSIZE TOP TRAN TRY_CONVERT
176
+ TSEQUAL UNPIVOT UPDATETEXT USE WAITFOR WITHIN GROUP
177
+ WRITETEXT
178
+ }
179
+ super + @sqlserver_reserved_words
180
+ end
181
+
182
+ def key_words
183
+ # These keywords should not be used for columns or tables:
184
+ @sqlserver_key_words ||= %w{
185
+ INCLUDE INDEX SQLCA
186
+ }
187
+ super + @sqlserver_key_words
188
+ end
189
+
190
+ # Although SQL Server accepts ; as a statement separator,
191
+ # it runs commands in batches when the "GO" command is issued.
192
+ def go s = ''
193
+ "#{s.sub(/\A\n+/,'')}\nGO\n"
194
+ end
195
+
196
+ def open_escape
197
+ '['
198
+ end
199
+
200
+ def close_escape
201
+ ']'
202
+ end
203
+
204
+ def index_kind(index)
205
+ (index.composite_as_primary_index ? ' CLUSTERED' : ' NONCLUSTERED')
206
+ end
207
+
208
+ class SQLServerDataTypeContext < SQLDataTypeContext
209
+ def integer_ranges
210
+ [
211
+ ['BIT', 0, 1],
212
+ ['TINYINT', -2**7, 2**7-1],
213
+ ] +
214
+ super
215
+ end
216
+
217
+ def boolean_type
218
+ 'BIT'
219
+ end
220
+
221
+ def boolean_expr safe_column_name
222
+ "{safe_column_name} = 1"
223
+ end
224
+
225
+ def default_char_type
226
+ (@unicode ? 'N' : '') +
227
+ 'CHAR'
228
+ end
229
+
230
+ def default_varchar_type
231
+ (@unicode ? 'N' : '') +
232
+ 'VARCHAR'
233
+ end
234
+
235
+ def date_time_type
236
+ 'DATETIME'
237
+ end
238
+ end
239
+
240
+ def cast_as_string d
241
+ # select right('0000000000'+cast(1234.45 as varchar(10)), 11); -- Only good for +ve numbers!
242
+ # set @x = -12345;
243
+ # select right('0000000000'+convert(varchar(10), 1234.45, style), 11); -- Use styles
244
+ # select convert(varchar, GETDATE(), 21); -- 'YYYY-MM-DD HH:mm:ss.123'
245
+ #
246
+ # declare @x decimal(10,3);
247
+ # set @x = -12345.67;
248
+ # select case when @x < 0 then '-'+right('0000000000'+cast(-@x as varchar(10)), 11)
249
+ # else ' '+right('0000000000'+cast(@x as varchar(10)), 11)
250
+ # end;
251
+ #
252
+ # declare @x money; set @x = 123.456; select CONVERT(varchar, @x, 0);
253
+ #
254
+ # declare @x float; set @x = 123.456; select CONVERT(varchar, @x, 2); -- Always 16 characters, exponential notation
255
+ end
256
+
257
+ end
258
+
259
+ end
260
+ end
261
+ end
262
+ end