activefacts-compositions 1.9.16 → 1.9.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f365db9787609b86e8184f61d9fc4c2d0b6e76e
4
- data.tar.gz: 66f3cd28bbd6eeadb7a9ee48fb1afd2774697193
3
+ metadata.gz: 629d91efc99ba1f54872c6c668a1ee0f86bf7d04
4
+ data.tar.gz: b2e8c929798553e24d7bf1e8fe0e81718c0b811b
5
5
  SHA512:
6
- metadata.gz: 1b8237ff0549bd7d36c8c8bcae1ad5272a3d13357c61ae4b7bcd4f9c1cd6c88bc46525844128d472231034c4df2c999230c7e964eee40a730515549ab94d2fcc
7
- data.tar.gz: 8057016adea612072f44aa65a84496d6e80660394ae9199157868475dca6e9a39ed96679c0b71cc62408f3a964bca2ebe9fbc1511d4fbb1b8fda9aeeeed81143
6
+ metadata.gz: a5ebe59af26c890d1f5771400d3dced9c123c96b252b10ee0805c04612fed3f7eac0ef4018ecfbdfe09882b43605fa6109307b831c2fce15541c0a5ff43311be
7
+ data.tar.gz: bd9f2f20c79718881cf6a8f7c42b21cb37f6b0d5346302f77d9453341f6aff7768acc9f0e495c7b4e1850879935bddecf72d14e5fb98ce0740b62fa23e7973d1
@@ -18,6 +18,7 @@ module ActiveFacts
18
18
  datavault_options.
19
19
  merge({
20
20
  stgname: ['String', "Suffix or pattern for naming staging tables. Include a + to insert the name. Default 'STG'"],
21
+ fk: ['Boolean', "Retain foreign keys in the output (by default they are deleted)"]
21
22
  }).
22
23
  merge(Relational.options).
23
24
  reject{|k,v| [:surrogates].include?(k) }
@@ -29,6 +30,9 @@ module ActiveFacts
29
30
  @option_stg_name = options.delete('stgname') || 'STG'
30
31
  @option_stg_name.sub!(/^/,'+ ') unless @option_stg_name =~ /\+/
31
32
 
33
+ @option_keep_fks = options.delete('fk') || false
34
+ @option_keep_fks = ['', true, 'true', 'yes'].include?(@option_keep_fks)
35
+
32
36
  super constellation, name, options, 'Staging'
33
37
  end
34
38
 
@@ -39,7 +43,7 @@ module ActiveFacts
39
43
 
40
44
  def inject_value_fields
41
45
  super
42
- inject_loadbatch_relationships
46
+ inject_loadbatch_relationships if @option_loadbatch
43
47
  end
44
48
 
45
49
  def inject_all_datetime_recordsource
@@ -61,6 +65,28 @@ module ActiveFacts
61
65
 
62
66
  inject_all_datetime_recordsource
63
67
  end
68
+
69
+ def complete_foreign_keys
70
+ if @option_keep_fks
71
+ super
72
+ else
73
+ retract_foreign_keys
74
+ end
75
+ end
76
+
77
+ def retract_foreign_keys
78
+ trace :relational_paths, "Retracting foreign keys" do
79
+ @composition.all_composite.each do |composite|
80
+ composite.all_access_path.each do |path|
81
+ next if MM::Index === path
82
+ trace :relational_paths, "Retracting #{path.inspect}" do
83
+ path.retract
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
64
90
  end
65
91
 
66
92
  publish_compositor(Staging)
@@ -0,0 +1,107 @@
1
+ #
2
+ # ActiveFacts Compositions, Staging Compositor.
3
+ #
4
+ # Computes a Persistent Staging schema for Data Vault.
5
+ #
6
+ # Copyright (c) 2017 Clifford Heath. Read the LICENSE file.
7
+ #
8
+ # This style of staging area contains a table for every source table,
9
+ # with injected foreign keys to a LoadBatch table, as for transient
10
+ # staging.
11
+ #
12
+ # Where it differs is that all unique constraints are disabled, and
13
+ # replaced by a primary key that is the source schema's PK appended
14
+ # with the LoadBatchID. Each record also has a ValidUntil timestamp,
15
+ # which is NULL for the most recent record. This allows a load batch
16
+ # to load a new version of any record, and the key value will be
17
+ # adjacent to the previous versions of that record.
18
+ #
19
+ # After a batch is complete, any updated record keys will address
20
+ # more than one record with a NULL ValidUntil timestamp, and this
21
+ # allows delta detection. As a delta is processed, the older record
22
+ # can be marked as valid only until the current LoadBatch time, or
23
+ # it can be deleted. If retained, a purge process can remove records
24
+ # that have exceeded their useful lifetime.
25
+ #
26
+ # Note that this approach doesn't directly detect deleted records.
27
+ # If the source system uses soft deletion, this generator can be
28
+ # configured with the name and value of the deleted flag field(s).
29
+ # This approach also works to manage CDC systems, which provide a
30
+ # record-deleted flag.
31
+ #
32
+
33
+ require "activefacts/compositions/staging"
34
+
35
+ module ActiveFacts
36
+ module Compositions
37
+ class Staging
38
+ class Persistent < Staging
39
+ public
40
+ def self.options
41
+ super.
42
+ merge({
43
+ }).
44
+ merge(Relational.options).
45
+ reject{|k,v| [:surrogates].include?(k) }
46
+ end
47
+
48
+ def initialize constellation, name, options = {}
49
+ # Extract recognised options:
50
+ super(constellation, name, {"loadbatch"=>true}.merge(options))
51
+
52
+ raise "--staging/persistent requires the loadbatch option (you can't disable it)" unless @option_loadbatch
53
+ end
54
+
55
+ def complete_foreign_keys
56
+ super
57
+
58
+ remake_primary_keys
59
+ end
60
+
61
+ def remake_primary_keys
62
+ trace :relational_paths, "Remaking primary keys" do
63
+ @composition.all_composite.each do |composite|
64
+ next if composite.mapping.object_type == @loadbatch_entity_type
65
+ composite.all_access_path.each do |path|
66
+ # Ignore foreign keys:
67
+ next unless MM::Index === path
68
+
69
+ # Don't meddle with the LoadBatch table:
70
+ next if composite.mapping.object_type == @loadbatch_entity_type
71
+ if composite.natural_index == path
72
+ # Add LoadBatchID to the natural index:
73
+ trace :relational_paths, "Appending LoadBatch to primary key #{path.inspect}" do
74
+ load_batch_role =
75
+ composite.mapping.object_type.all_role.detect do |role|
76
+ c = role.counterpart and c.object_type == @loadbatch_entity_type
77
+ end
78
+ trace :relational_paths, "Found LoadBatch role in #{load_batch_role.fact_type.default_reading}" if load_batch_role
79
+ # There can only be one absorption of LoadBatch, because we added it,
80
+ # but if you have separate subtypes, we need to select the one for the right composite:
81
+ absorptions = load_batch_role.
82
+ counterpart.
83
+ all_absorption_as_child_role.
84
+ select{|a| a.root == composite}
85
+ # There should now always be exactly one.
86
+ raise "Missing or ambiguous FK to LoadBatch from #{composite.inspect}" if absorptions.size != 1
87
+ absorption = absorptions[0]
88
+ absorption.all_leaf.each do |leaf|
89
+ @constellation.IndexField(access_path: path, ordinal: path.all_index_field.size, component: leaf)
90
+ end
91
+ end
92
+ elsif path.is_unique
93
+ # Retract other unique keys:
94
+ trace :relational_paths, "Retracting unique secondary index #{path.inspect}" do
95
+ # REVISIT: Or just make it non-unique?
96
+ path.retract
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end # Persistent
104
+ end # Staging
105
+ publish_compositor(Staging::Persistent)
106
+ end
107
+ end
@@ -16,7 +16,8 @@ module ActiveFacts
16
16
  @option_recordsource = options.delete('recordsource')
17
17
  @option_recordsource = 'String' if [true, '', 'true', 'yes', nil].include?(@option_recordsource)
18
18
  @option_loadbatch = options.delete('loadbatch')
19
- @option_loadbatch = 'LoadBatch' if [true, '', 'true', 'yes'].include?(@option_loadbatch)
19
+ @option_loadbatch = 'LoadBatch' if [true, 'true', 'yes'].include?(@option_loadbatch)
20
+ @option_loadbatch = nil if [false, 'false', ''].include?(@option_loadbatch)
20
21
  end
21
22
 
22
23
  def create_loadbatch
@@ -36,6 +37,7 @@ module ActiveFacts
36
37
  end
37
38
 
38
39
  def inject_loadbatch_relationships
40
+ return unless @option_loadbatch
39
41
  @composition.all_composite.each do |composite|
40
42
  next if composite.mapping.object_type.name == @option_loadbatch
41
43
  @compiler.compile("#{composite.mapping.name} was loaded in one #{@option_loadbatch};")
@@ -1,5 +1,5 @@
1
1
  module ActiveFacts
2
2
  module Compositions
3
- VERSION = "1.9.16"
3
+ VERSION = "1.9.17"
4
4
  end
5
5
  end
@@ -60,6 +60,12 @@ module ActiveFacts
60
60
  when "raw"
61
61
  @restrict = "rdv"
62
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
63
69
  end
64
70
 
65
71
  def generate
@@ -184,13 +190,16 @@ module ActiveFacts
184
190
  end
185
191
  contains_nullable_columns = nullable_columns.size > 0
186
192
 
193
+ # The index can only be emitted as PRIMARY if it has no nullable columns:
187
194
  primary = index.composite_as_primary_index && !contains_nullable_columns
195
+
188
196
  column_names =
189
197
  index.all_index_field.map do |ixf|
190
198
  column_name(ixf.component)
191
199
  end
192
200
 
193
- if contains_nullable_columns
201
+ if contains_nullable_columns and @closed_world_indices
202
+ # Implement open-world uniqueness using a filtered index:
194
203
  table_name = safe_table_name(index.composite)
195
204
  delayed_indices <<
196
205
  'CREATE UNIQUE'+index_kind(index)+' INDEX '+
@@ -23,6 +23,11 @@ module ActiveFacts
23
23
  })
24
24
  end
25
25
 
26
+ def initialize composition, options = {}
27
+ super
28
+ @closed_world_indices = true
29
+ end
30
+
26
31
  def table_name_max
27
32
  128
28
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activefacts-compositions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.16
4
+ version: 1.9.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clifford Heath
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-10 00:00:00.000000000 Z
11
+ date: 2017-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -209,6 +209,7 @@ files:
209
209
  - lib/activefacts/compositions/names.rb
210
210
  - lib/activefacts/compositions/relational.rb
211
211
  - lib/activefacts/compositions/staging.rb
212
+ - lib/activefacts/compositions/staging/persistent.rb
212
213
  - lib/activefacts/compositions/traits/datavault.rb
213
214
  - lib/activefacts/compositions/traits/rails.rb
214
215
  - lib/activefacts/compositions/version.rb