activefacts-compositions 1.9.16 → 1.9.17
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.
- checksums.yaml +4 -4
- data/lib/activefacts/compositions/staging.rb +27 -1
- data/lib/activefacts/compositions/staging/persistent.rb +107 -0
- data/lib/activefacts/compositions/traits/datavault.rb +3 -1
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/sql.rb +10 -1
- data/lib/activefacts/generator/sql/server.rb +5 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 629d91efc99ba1f54872c6c668a1ee0f86bf7d04
|
4
|
+
data.tar.gz: b2e8c929798553e24d7bf1e8fe0e81718c0b811b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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, '
|
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};")
|
@@ -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 '+
|
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.
|
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-
|
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
|